| // Copyright 2012 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/codegen/compiler.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| |
| #include "src/api/api-inl.h" |
| #include "src/asmjs/asm-js.h" |
| #include "src/ast/prettyprinter.h" |
| #include "src/ast/scopes.h" |
| #include "src/base/logging.h" |
| #include "src/base/platform/time.h" |
| #include "src/baseline/baseline.h" |
| #include "src/codegen/assembler-inl.h" |
| #include "src/codegen/compilation-cache.h" |
| #include "src/codegen/optimized-compilation-info.h" |
| #include "src/codegen/pending-optimization-table.h" |
| #include "src/codegen/script-details.h" |
| #include "src/codegen/unoptimized-compilation-info.h" |
| #include "src/common/assert-scope.h" |
| #include "src/common/globals.h" |
| #include "src/common/message-template.h" |
| #include "src/compiler-dispatcher/lazy-compile-dispatcher.h" |
| #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" |
| #include "src/compiler/turbofan.h" |
| #include "src/debug/debug.h" |
| #include "src/debug/liveedit.h" |
| #include "src/diagnostics/code-tracer.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/execution/isolate-inl.h" |
| #include "src/execution/isolate.h" |
| #include "src/execution/local-isolate.h" |
| #include "src/execution/vm-state-inl.h" |
| #include "src/flags/flags.h" |
| #include "src/handles/global-handles-inl.h" |
| #include "src/handles/handles.h" |
| #include "src/handles/maybe-handles.h" |
| #include "src/handles/persistent-handles.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/heap/local-factory-inl.h" |
| #include "src/heap/local-heap-inl.h" |
| #include "src/heap/parked-scope-inl.h" |
| #include "src/heap/visit-object.h" |
| #include "src/init/bootstrapper.h" |
| #include "src/interpreter/interpreter.h" |
| #include "src/logging/counters-scopes.h" |
| #include "src/logging/log-inl.h" |
| #include "src/logging/runtime-call-stats-scope.h" |
| #include "src/objects/feedback-cell-inl.h" |
| #include "src/objects/js-function-inl.h" |
| #include "src/objects/js-function.h" |
| #include "src/objects/map.h" |
| #include "src/objects/object-list-macros.h" |
| #include "src/objects/objects-body-descriptors-inl.h" |
| #include "src/objects/shared-function-info.h" |
| #include "src/objects/string.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/parsing/parser.h" |
| #include "src/parsing/parsing.h" |
| #include "src/parsing/pending-compilation-error-handler.h" |
| #include "src/parsing/scanner-character-streams.h" |
| #include "src/snapshot/code-serializer.h" |
| #include "src/tracing/traced-value.h" |
| #include "src/utils/ostreams.h" |
| #include "src/zone/zone-list-inl.h" // crbug.com/v8/8816 |
| |
| #ifdef V8_ENABLE_MAGLEV |
| #include "src/maglev/maglev-concurrent-dispatcher.h" |
| #include "src/maglev/maglev.h" |
| #endif // V8_ENABLE_MAGLEV |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| constexpr bool IsOSR(BytecodeOffset osr_offset) { return !osr_offset.IsNone(); } |
| |
| class CompilerTracer : public AllStatic { |
| public: |
| static void TraceStartBaselineCompile( |
| Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) { |
| if (!v8_flags.trace_baseline) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "compiling method", shared, CodeKind::BASELINE); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceStartMaglevCompile(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| bool osr, ConcurrencyMode mode) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "compiling method", function, CodeKind::MAGLEV); |
| if (osr) PrintF(scope.file(), " OSR"); |
| PrintF(scope.file(), ", mode: %s", ToString(mode)); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TracePrepareJob(Isolate* isolate, OptimizedCompilationInfo* info, |
| ConcurrencyMode mode) { |
| if (!v8_flags.trace_opt || !info->IsOptimizing()) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "compiling method", info); |
| if (info->is_osr()) PrintF(scope.file(), " OSR"); |
| PrintF(scope.file(), ", mode: %s", ToString(mode)); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceOptimizeOSRStarted(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, |
| ConcurrencyMode mode) { |
| if (!v8_flags.trace_osr) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF( |
| scope.file(), |
| "[OSR - compilation started. function: %s, osr offset: %d, mode: %s]\n", |
| function->DebugNameCStr().get(), osr_offset.ToInt(), ToString(mode)); |
| } |
| |
| static void TraceOptimizeOSRFinished(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset) { |
| if (!v8_flags.trace_osr) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF(scope.file(), |
| "[OSR - compilation finished. function: %s, osr offset: %d]\n", |
| function->DebugNameCStr().get(), osr_offset.ToInt()); |
| } |
| |
| static void TraceOptimizeOSRAvailable(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, |
| ConcurrencyMode mode) { |
| if (!v8_flags.trace_osr) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF(scope.file(), |
| "[OSR - available (compilation completed or cache hit). function: " |
| "%s, osr offset: %d, mode: %s]\n", |
| function->DebugNameCStr().get(), osr_offset.ToInt(), ToString(mode)); |
| } |
| |
| static void TraceOptimizeOSRUnavailable(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, |
| ConcurrencyMode mode) { |
| if (!v8_flags.trace_osr) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF(scope.file(), |
| "[OSR - unavailable (failed or in progress). function: %s, osr " |
| "offset: %d, mode: %s]\n", |
| function->DebugNameCStr().get(), osr_offset.ToInt(), ToString(mode)); |
| } |
| |
| static void TraceFinishTurbofanCompile(Isolate* isolate, |
| OptimizedCompilationInfo* info, |
| double ms_creategraph, |
| double ms_optimize, |
| double ms_codegen) { |
| DCHECK(v8_flags.trace_opt); |
| DCHECK(info->IsOptimizing()); |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "completed compiling", info); |
| if (info->is_osr()) PrintF(scope.file(), " OSR"); |
| PrintF(scope.file(), " - took %0.3f, %0.3f, %0.3f ms", ms_creategraph, |
| ms_optimize, ms_codegen); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceFinishBaselineCompile( |
| Isolate* isolate, DirectHandle<SharedFunctionInfo> shared, |
| double ms_timetaken) { |
| if (!v8_flags.trace_baseline) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "completed compiling", shared, CodeKind::BASELINE); |
| PrintF(scope.file(), " - took %0.3f ms", ms_timetaken); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceFinishMaglevCompile(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| bool osr, double ms_prepare, |
| double ms_execute, double ms_finalize) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "completed compiling", function, CodeKind::MAGLEV); |
| if (osr) PrintF(scope.file(), " OSR"); |
| PrintF(scope.file(), " - took %0.3f, %0.3f, %0.3f ms", ms_prepare, |
| ms_execute, ms_finalize); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceAbortedMaglevCompile(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BailoutReason bailout_reason) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "aborted compiling", function, CodeKind::MAGLEV); |
| PrintF(scope.file(), " because: %s", GetBailoutReason(bailout_reason)); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceCompletedJob(Isolate* isolate, |
| OptimizedCompilationInfo* info) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "completed optimizing", info); |
| if (info->is_osr()) PrintF(scope.file(), " OSR"); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceAbortedJob(Isolate* isolate, OptimizedCompilationInfo* info, |
| double ms_prepare, double ms_execute, |
| double ms_finalize) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "aborted optimizing", info); |
| if (info->is_osr()) PrintF(scope.file(), " OSR"); |
| PrintF(scope.file(), " because: %s", |
| GetBailoutReason(info->bailout_reason())); |
| PrintF(scope.file(), " - took %0.3f, %0.3f, %0.3f ms", ms_prepare, |
| ms_execute, ms_finalize); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceOptimizedCodeCacheHit(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, |
| CodeKind code_kind) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "found optimized code for", function, code_kind); |
| if (IsOSR(osr_offset)) { |
| PrintF(scope.file(), " at OSR bytecode offset %d", osr_offset.ToInt()); |
| } |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceOptimizeForAlwaysOpt(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| CodeKind code_kind) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintTracePrefix(scope, "optimizing", function, code_kind); |
| PrintF(scope.file(), " because --always-turbofan"); |
| PrintTraceSuffix(scope); |
| } |
| |
| static void TraceMarkForAlwaysOpt(Isolate* isolate, |
| DirectHandle<JSFunction> function) { |
| if (!v8_flags.trace_opt) return; |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF(scope.file(), "[marking "); |
| ShortPrint(*function, scope.file()); |
| PrintF(scope.file(), |
| " for optimized recompilation because --always-turbofan"); |
| PrintF(scope.file(), "]\n"); |
| } |
| |
| private: |
| static void PrintTracePrefix(const CodeTracer::Scope& scope, |
| const char* header, |
| OptimizedCompilationInfo* info) { |
| PrintTracePrefix(scope, header, info->closure(), info->code_kind()); |
| } |
| |
| static void PrintTracePrefix(const CodeTracer::Scope& scope, |
| const char* header, |
| DirectHandle<JSFunction> function, |
| CodeKind code_kind) { |
| PrintF(scope.file(), "[%s ", header); |
| ShortPrint(*function, scope.file()); |
| PrintF(scope.file(), " (target %s)", CodeKindToString(code_kind)); |
| } |
| |
| static void PrintTracePrefix(const CodeTracer::Scope& scope, |
| const char* header, |
| DirectHandle<SharedFunctionInfo> shared, |
| CodeKind code_kind) { |
| PrintF(scope.file(), "[%s ", header); |
| ShortPrint(*shared, scope.file()); |
| PrintF(scope.file(), " (target %s)", CodeKindToString(code_kind)); |
| } |
| |
| static void PrintTraceSuffix(const CodeTracer::Scope& scope) { |
| PrintF(scope.file(), "]\n"); |
| } |
| }; |
| |
| } // namespace |
| |
| // static |
| void Compiler::LogFunctionCompilation(Isolate* isolate, |
| LogEventListener::CodeTag code_type, |
| DirectHandle<Script> script, |
| Handle<SharedFunctionInfo> shared, |
| DirectHandle<FeedbackVector> vector, |
| Handle<AbstractCode> abstract_code, |
| CodeKind kind, double time_taken_ms) { |
| DCHECK_NE(*abstract_code, |
| Cast<AbstractCode>(*BUILTIN_CODE(isolate, CompileLazy))); |
| |
| // Log the code generation. If source information is available include |
| // script name and line number. Check explicitly whether logging is |
| // enabled as finding the line number is not free. |
| if (!isolate->IsLoggingCodeCreation()) return; |
| |
| Script::PositionInfo info; |
| Script::GetPositionInfo(script, shared->StartPosition(), &info); |
| int line_num = info.line + 1; |
| int column_num = info.column + 1; |
| Handle<String> script_name(IsString(script->name()) |
| ? Cast<String>(script->name()) |
| : ReadOnlyRoots(isolate).empty_string(), |
| isolate); |
| LogEventListener::CodeTag log_tag = |
| V8FileLogger::ToNativeByScript(code_type, *script); |
| PROFILE(isolate, CodeCreateEvent(log_tag, abstract_code, shared, script_name, |
| line_num, column_num)); |
| if (!vector.is_null()) { |
| LOG(isolate, FeedbackVectorEvent(*vector, *abstract_code)); |
| } |
| if (!v8_flags.log_function_events) return; |
| |
| std::string name; |
| switch (kind) { |
| case CodeKind::INTERPRETED_FUNCTION: |
| name = "interpreter"; |
| break; |
| case CodeKind::BASELINE: |
| name = "baseline"; |
| break; |
| case CodeKind::MAGLEV: |
| name = "maglev"; |
| break; |
| case CodeKind::TURBOFAN_JS: |
| name = "turbofan"; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| switch (code_type) { |
| case LogEventListener::CodeTag::kEval: |
| name += "-eval"; |
| break; |
| case LogEventListener::CodeTag::kScript: |
| case LogEventListener::CodeTag::kFunction: |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| DirectHandle<String> debug_name = |
| SharedFunctionInfo::DebugName(isolate, shared); |
| DisallowGarbageCollection no_gc; |
| LOG(isolate, FunctionEvent(name.c_str(), script->id(), time_taken_ms, |
| shared->StartPosition(), shared->EndPosition(), |
| *debug_name)); |
| } |
| |
| namespace { |
| |
| ScriptOriginOptions OriginOptionsForEval( |
| Tagged<Object> script, ParsingWhileDebugging parsing_while_debugging) { |
| bool is_shared_cross_origin = |
| parsing_while_debugging == ParsingWhileDebugging::kYes; |
| bool is_opaque = false; |
| if (IsScript(script)) { |
| auto script_origin_options = Cast<Script>(script)->origin_options(); |
| if (script_origin_options.IsSharedCrossOrigin()) { |
| is_shared_cross_origin = true; |
| } |
| if (script_origin_options.IsOpaque()) { |
| is_opaque = true; |
| } |
| } |
| return ScriptOriginOptions(is_shared_cross_origin, is_opaque); |
| } |
| |
| } // namespace |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of UnoptimizedCompilationJob |
| |
| CompilationJob::Status UnoptimizedCompilationJob::ExecuteJob() { |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToExecute); |
| base::ScopedTimer t(v8_flags.log_function_events ? &time_taken_to_execute_ |
| : nullptr); |
| return UpdateState(ExecuteJobImpl(), State::kReadyToFinalize); |
| } |
| |
| CompilationJob::Status UnoptimizedCompilationJob::FinalizeJob( |
| DirectHandle<SharedFunctionInfo> shared_info, Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| DisallowCodeDependencyChange no_dependency_change; |
| DisallowJavascriptExecution no_js(isolate); |
| |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToFinalize); |
| base::ScopedTimer t(v8_flags.log_function_events ? &time_taken_to_finalize_ |
| : nullptr); |
| return UpdateState(FinalizeJobImpl(shared_info, isolate), State::kSucceeded); |
| } |
| |
| CompilationJob::Status UnoptimizedCompilationJob::FinalizeJob( |
| DirectHandle<SharedFunctionInfo> shared_info, LocalIsolate* isolate) { |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToFinalize); |
| base::ScopedTimer t(v8_flags.log_function_events ? &time_taken_to_finalize_ |
| : nullptr); |
| return UpdateState(FinalizeJobImpl(shared_info, isolate), State::kSucceeded); |
| } |
| |
| namespace { |
| void LogUnoptimizedCompilation(Isolate* isolate, |
| Handle<SharedFunctionInfo> shared, |
| LogEventListener::CodeTag code_type, |
| base::TimeDelta time_taken_to_execute, |
| base::TimeDelta time_taken_to_finalize) { |
| Handle<AbstractCode> abstract_code; |
| if (shared->HasBytecodeArray()) { |
| abstract_code = |
| handle(Cast<AbstractCode>(shared->GetBytecodeArray(isolate)), isolate); |
| } else { |
| #if V8_ENABLE_WEBASSEMBLY |
| DCHECK(shared->HasAsmWasmData()); |
| abstract_code = Cast<AbstractCode>(BUILTIN_CODE(isolate, InstantiateAsmJs)); |
| #else |
| UNREACHABLE(); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } |
| |
| double time_taken_ms = time_taken_to_execute.InMillisecondsF() + |
| time_taken_to_finalize.InMillisecondsF(); |
| |
| DirectHandle<Script> script(Cast<Script>(shared->script()), isolate); |
| Compiler::LogFunctionCompilation( |
| isolate, code_type, script, shared, DirectHandle<FeedbackVector>(), |
| abstract_code, CodeKind::INTERPRETED_FUNCTION, time_taken_ms); |
| } |
| |
| } // namespace |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of OptimizedCompilationJob |
| |
| CompilationJob::Status OptimizedCompilationJob::PrepareJob(Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| DisallowJavascriptExecution no_js(isolate); |
| |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToPrepare); |
| base::ScopedTimer t(&time_taken_to_prepare_); |
| return UpdateState(PrepareJobImpl(isolate), State::kReadyToExecute); |
| } |
| |
| CompilationJob::Status OptimizedCompilationJob::ExecuteJob( |
| RuntimeCallStats* stats, LocalIsolate* local_isolate) { |
| DCHECK_IMPLIES(local_isolate && !local_isolate->is_main_thread(), |
| local_isolate->heap()->IsParked()); |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToExecute); |
| base::ScopedTimer t(&time_taken_to_execute_); |
| return UpdateState(ExecuteJobImpl(stats, local_isolate), |
| State::kReadyToFinalize); |
| } |
| |
| CompilationJob::Status OptimizedCompilationJob::FinalizeJob(Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| DisallowJavascriptExecution no_js(isolate); |
| |
| // Delegate to the underlying implementation. |
| DCHECK_EQ(state(), State::kReadyToFinalize); |
| base::ScopedTimer t(&time_taken_to_finalize_); |
| return UpdateState(FinalizeJobImpl(isolate), State::kSucceeded); |
| } |
| |
| GlobalHandleVector<Map> OptimizedCompilationJob::CollectRetainedMaps( |
| Isolate* isolate, DirectHandle<Code> code) { |
| DCHECK(code->is_optimized_code()); |
| |
| DisallowGarbageCollection no_gc; |
| GlobalHandleVector<Map> maps(isolate->heap()); |
| PtrComprCageBase cage_base(isolate); |
| int const mode_mask = RelocInfo::EmbeddedObjectModeMask(); |
| for (RelocIterator it(*code, mode_mask); !it.done(); it.next()) { |
| DCHECK(RelocInfo::IsEmbeddedObjectMode(it.rinfo()->rmode())); |
| Tagged<HeapObject> target_object = it.rinfo()->target_object(cage_base); |
| if (code->IsWeakObjectInOptimizedCode(target_object)) { |
| if (IsMap(target_object, cage_base)) { |
| maps.Push(Cast<Map>(target_object)); |
| } |
| } |
| } |
| return maps; |
| } |
| |
| void OptimizedCompilationJob::RegisterWeakObjectsInOptimizedCode( |
| Isolate* isolate, DirectHandle<NativeContext> context, |
| DirectHandle<Code> code, GlobalHandleVector<Map> maps) { |
| isolate->heap()->AddRetainedMaps(context, std::move(maps)); |
| code->set_can_have_weak_objects(true); |
| } |
| |
| namespace { |
| uint64_t GetNextTraceId() { |
| // Define a global counter for optimized compile trace ids, which |
| // counts in the top 32 bits of a uint64_t. This will be mixed into |
| // the TurbofanCompilationJob `this` pointer, which hopefully will |
| // make the ids unique enough even when the job memory is reused |
| // for future jobs. |
| static std::atomic_uint32_t next_trace_id = 0xfa5701d0; |
| return static_cast<uint64_t>(next_trace_id++) << 32; |
| } |
| } // namespace |
| |
| TurbofanCompilationJob::TurbofanCompilationJob( |
| Isolate* isolate, OptimizedCompilationInfo* compilation_info, |
| State initial_state) |
| : OptimizedCompilationJob("Turbofan", initial_state), |
| isolate_(isolate), |
| compilation_info_(compilation_info), |
| trace_id_(GetNextTraceId() ^ reinterpret_cast<uintptr_t>(this)) {} |
| |
| CompilationJob::Status TurbofanCompilationJob::RetryOptimization( |
| BailoutReason reason) { |
| DCHECK(compilation_info_->IsOptimizing()); |
| compilation_info_->RetryOptimization(reason); |
| return UpdateState(FAILED, State::kFailed); |
| } |
| |
| CompilationJob::Status TurbofanCompilationJob::AbortOptimization( |
| BailoutReason reason) { |
| DCHECK(compilation_info_->IsOptimizing()); |
| compilation_info_->AbortOptimization(reason); |
| return UpdateState(FAILED, State::kFailed); |
| } |
| |
| void TurbofanCompilationJob::RecordCompilationStats(ConcurrencyMode mode, |
| Isolate* isolate) const { |
| DCHECK(compilation_info()->IsOptimizing()); |
| DirectHandle<SharedFunctionInfo> shared = compilation_info()->shared_info(); |
| if (v8_flags.trace_opt || v8_flags.trace_opt_stats) { |
| double ms_creategraph = time_taken_to_prepare_.InMillisecondsF(); |
| double ms_optimize = time_taken_to_execute_.InMillisecondsF(); |
| double ms_codegen = time_taken_to_finalize_.InMillisecondsF(); |
| if (v8_flags.trace_opt) { |
| CompilerTracer::TraceFinishTurbofanCompile( |
| isolate, compilation_info(), ms_creategraph, ms_optimize, ms_codegen); |
| } |
| if (v8_flags.trace_opt_stats) { |
| static double compilation_time = 0.0; |
| static int compiled_functions = 0; |
| static int code_size = 0; |
| |
| compilation_time += (ms_creategraph + ms_optimize + ms_codegen); |
| compiled_functions++; |
| code_size += shared->SourceSize(); |
| PrintF( |
| "[turbofan] Compiled: %d functions with %d byte source size in " |
| "%fms.\n", |
| compiled_functions, code_size, compilation_time); |
| } |
| } |
| // Don't record samples from machines without high-resolution timers, |
| // as that can cause serious reporting issues. See the thread at |
| // http://g/chrome-metrics-team/NwwJEyL8odU/discussion for more details. |
| if (!base::TimeTicks::IsHighResolution()) return; |
| |
| int elapsed_microseconds = static_cast<int>(ElapsedTime().InMicroseconds()); |
| Counters* const counters = isolate->counters(); |
| counters->turbofan_ticks()->AddSample(static_cast<int>( |
| compilation_info()->tick_counter().CurrentTicks() / 1000)); |
| |
| if (compilation_info()->is_osr()) { |
| counters->turbofan_osr_prepare()->AddSample( |
| static_cast<int>(time_taken_to_prepare_.InMicroseconds())); |
| counters->turbofan_osr_execute()->AddSample( |
| static_cast<int>(time_taken_to_execute_.InMicroseconds())); |
| counters->turbofan_osr_finalize()->AddSample( |
| static_cast<int>(time_taken_to_finalize_.InMicroseconds())); |
| counters->turbofan_osr_total_time()->AddSample(elapsed_microseconds); |
| return; |
| } |
| |
| DCHECK(!compilation_info()->is_osr()); |
| counters->turbofan_optimize_prepare()->AddSample( |
| static_cast<int>(time_taken_to_prepare_.InMicroseconds())); |
| counters->turbofan_optimize_execute()->AddSample( |
| static_cast<int>(time_taken_to_execute_.InMicroseconds())); |
| counters->turbofan_optimize_finalize()->AddSample( |
| static_cast<int>(time_taken_to_finalize_.InMicroseconds())); |
| counters->turbofan_optimize_total_time()->AddSample(elapsed_microseconds); |
| |
| // Compute foreground / background time. |
| base::TimeDelta time_background; |
| base::TimeDelta time_foreground = |
| time_taken_to_prepare_ + time_taken_to_finalize_; |
| switch (mode) { |
| case ConcurrencyMode::kConcurrent: |
| time_background += time_taken_to_execute_; |
| counters->turbofan_optimize_concurrent_total_time()->AddSample( |
| elapsed_microseconds); |
| break; |
| case ConcurrencyMode::kSynchronous: |
| counters->turbofan_optimize_non_concurrent_total_time()->AddSample( |
| elapsed_microseconds); |
| time_foreground += time_taken_to_execute_; |
| break; |
| } |
| counters->turbofan_optimize_total_background()->AddSample( |
| static_cast<int>(time_background.InMicroseconds())); |
| counters->turbofan_optimize_total_foreground()->AddSample( |
| static_cast<int>(time_foreground.InMicroseconds())); |
| |
| if (v8_flags.profile_guided_optimization && |
| shared->cached_tiering_decision() == |
| CachedTieringDecision::kEarlyMaglev) { |
| shared->set_cached_tiering_decision(CachedTieringDecision::kEarlyTurbofan); |
| } |
| } |
| |
| void TurbofanCompilationJob::RecordFunctionCompilation( |
| LogEventListener::CodeTag code_type, Isolate* isolate) const { |
| Handle<AbstractCode> abstract_code = |
| Cast<AbstractCode>(compilation_info()->code()); |
| |
| double time_taken_ms = time_taken_to_prepare_.InMillisecondsF() + |
| time_taken_to_execute_.InMillisecondsF() + |
| time_taken_to_finalize_.InMillisecondsF(); |
| |
| DirectHandle<Script> script( |
| Cast<Script>(compilation_info()->shared_info()->script()), isolate); |
| DirectHandle<FeedbackVector> feedback_vector( |
| compilation_info()->closure()->feedback_vector(), isolate); |
| Compiler::LogFunctionCompilation( |
| isolate, code_type, script, compilation_info()->shared_info(), |
| feedback_vector, abstract_code, compilation_info()->code_kind(), |
| time_taken_ms); |
| } |
| |
| uint64_t TurbofanCompilationJob::trace_id() const { |
| // Xor together the this pointer and the optimization id, to try to make the |
| // id more unique on platforms where just the `this` pointer is likely to be |
| // reused. |
| return trace_id_; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Local helper methods that make up the compilation pipeline. |
| |
| namespace { |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| bool UseAsmWasm(FunctionLiteral* literal, bool asm_wasm_broken) { |
| // Check whether asm.js validation is enabled. |
| if (!v8_flags.validate_asm) return false; |
| |
| // Modules that have validated successfully, but were subsequently broken by |
| // invalid module instantiation attempts are off limit forever. |
| if (asm_wasm_broken) return false; |
| |
| // In stress mode we want to run the validator on everything. |
| if (v8_flags.stress_validate_asm) return true; |
| |
| // In general, we respect the "use asm" directive. |
| return literal->scope()->IsAsmModule(); |
| } |
| #endif |
| |
| } // namespace |
| |
| void Compiler::InstallInterpreterTrampolineCopy( |
| Isolate* isolate, Handle<SharedFunctionInfo> shared_info, |
| LogEventListener::CodeTag log_tag) { |
| DCHECK(isolate->interpreted_frames_native_stack()); |
| if (!IsBytecodeArray(shared_info->GetTrustedData(isolate))) { |
| DCHECK(!shared_info->HasInterpreterData(isolate)); |
| return; |
| } |
| DirectHandle<BytecodeArray> bytecode_array( |
| shared_info->GetBytecodeArray(isolate), isolate); |
| |
| Handle<Code> code = |
| Builtins::CreateInterpreterEntryTrampolineForProfiling(isolate); |
| |
| DirectHandle<InterpreterData> interpreter_data = |
| isolate->factory()->NewInterpreterData(bytecode_array, code); |
| |
| if (shared_info->HasBaselineCode()) { |
| shared_info->baseline_code(kAcquireLoad) |
| ->set_bytecode_or_interpreter_data(*interpreter_data); |
| } else { |
| // IsBytecodeArray |
| shared_info->set_interpreter_data(isolate, *interpreter_data); |
| } |
| |
| DirectHandle<Script> script(Cast<Script>(shared_info->script()), isolate); |
| Handle<AbstractCode> abstract_code = Cast<AbstractCode>(code); |
| Script::PositionInfo info; |
| Script::GetPositionInfo(script, shared_info->StartPosition(), &info); |
| int line_num = info.line + 1; |
| int column_num = info.column + 1; |
| Handle<String> script_name = |
| handle(IsString(script->name()) ? Cast<String>(script->name()) |
| : ReadOnlyRoots(isolate).empty_string(), |
| isolate); |
| PROFILE(isolate, CodeCreateEvent(log_tag, abstract_code, shared_info, |
| script_name, line_num, column_num)); |
| } |
| |
| namespace { |
| |
| template <typename IsolateT> |
| void InstallUnoptimizedCode(UnoptimizedCompilationInfo* compilation_info, |
| DirectHandle<SharedFunctionInfo> shared_info, |
| IsolateT* isolate) { |
| if (compilation_info->has_bytecode_array()) { |
| DCHECK(!shared_info->HasBytecodeArray()); // Only compiled once. |
| DCHECK(!compilation_info->has_asm_wasm_data()); |
| DCHECK(!shared_info->HasFeedbackMetadata()); |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| // If the function failed asm-wasm compilation, mark asm_wasm as broken |
| // to ensure we don't try to compile as asm-wasm. |
| if (compilation_info->literal()->scope()->IsAsmModule()) { |
| shared_info->set_is_asm_wasm_broken(true); |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| DirectHandle<FeedbackMetadata> feedback_metadata = FeedbackMetadata::New( |
| isolate, compilation_info->feedback_vector_spec()); |
| shared_info->set_feedback_metadata(*feedback_metadata, kReleaseStore); |
| |
| shared_info->set_age(0); |
| shared_info->set_bytecode_array(*compilation_info->bytecode_array()); |
| } else { |
| #if V8_ENABLE_WEBASSEMBLY |
| DCHECK(compilation_info->has_asm_wasm_data()); |
| // We should only have asm/wasm data when finalizing on the main thread. |
| DCHECK((std::is_same<IsolateT, Isolate>::value)); |
| shared_info->set_asm_wasm_data(*compilation_info->asm_wasm_data()); |
| shared_info->set_feedback_metadata( |
| ReadOnlyRoots(isolate).empty_feedback_metadata(), kReleaseStore); |
| #else |
| UNREACHABLE(); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } |
| } |
| |
| template <typename IsolateT> |
| void EnsureInfosArrayOnScript(DirectHandle<Script> script, |
| ParseInfo* parse_info, IsolateT* isolate) { |
| DCHECK(parse_info->flags().is_toplevel()); |
| if (script->infos()->length() > 0) { |
| DCHECK_EQ(script->infos()->length(), parse_info->max_info_id() + 1); |
| return; |
| } |
| DirectHandle<WeakFixedArray> infos(isolate->factory()->NewWeakFixedArray( |
| parse_info->max_info_id() + 1, AllocationType::kOld)); |
| script->set_infos(*infos); |
| } |
| |
| void UpdateSharedFunctionFlagsAfterCompilation(FunctionLiteral* literal) { |
| Tagged<SharedFunctionInfo> shared_info = *literal->shared_function_info(); |
| DCHECK_EQ(shared_info->language_mode(), literal->language_mode()); |
| |
| // These fields are all initialised in ParseInfo from the SharedFunctionInfo, |
| // and then set back on the literal after parse. Hence, they should already |
| // match. |
| DCHECK_EQ(shared_info->requires_instance_members_initializer(), |
| literal->requires_instance_members_initializer()); |
| DCHECK_EQ(shared_info->class_scope_has_private_brand(), |
| literal->class_scope_has_private_brand()); |
| DCHECK_EQ(shared_info->has_static_private_methods_or_accessors(), |
| literal->has_static_private_methods_or_accessors()); |
| |
| shared_info->set_has_duplicate_parameters( |
| literal->has_duplicate_parameters()); |
| shared_info->UpdateAndFinalizeExpectedNofPropertiesFromEstimate(literal); |
| |
| shared_info->SetScopeInfo(*literal->scope()->scope_info()); |
| } |
| |
| // Finalize a single compilation job. This function can return |
| // RETRY_ON_MAIN_THREAD if the job cannot be finalized off-thread, in which case |
| // it should be safe to call it again on the main thread with the same job. |
| template <typename IsolateT> |
| CompilationJob::Status FinalizeSingleUnoptimizedCompilationJob( |
| UnoptimizedCompilationJob* job, Handle<SharedFunctionInfo> shared_info, |
| IsolateT* isolate, |
| FinalizeUnoptimizedCompilationDataList* |
| finalize_unoptimized_compilation_data_list) { |
| UnoptimizedCompilationInfo* compilation_info = job->compilation_info(); |
| |
| CompilationJob::Status status = job->FinalizeJob(shared_info, isolate); |
| if (status == CompilationJob::SUCCEEDED) { |
| InstallUnoptimizedCode(compilation_info, shared_info, isolate); |
| |
| MaybeHandle<CoverageInfo> coverage_info; |
| if (compilation_info->has_coverage_info()) { |
| MutexGuardIfOffThread<IsolateT> mutex_guard( |
| isolate->shared_function_info_access(), isolate); |
| if (!shared_info->HasCoverageInfo( |
| isolate->GetMainThreadIsolateUnsafe())) { |
| coverage_info = compilation_info->coverage_info(); |
| } |
| } |
| |
| finalize_unoptimized_compilation_data_list->emplace_back( |
| isolate, shared_info, coverage_info, job->time_taken_to_execute(), |
| job->time_taken_to_finalize()); |
| } |
| DCHECK_IMPLIES(status == CompilationJob::RETRY_ON_MAIN_THREAD, |
| (std::is_same<IsolateT, LocalIsolate>::value)); |
| return status; |
| } |
| |
| std::unique_ptr<UnoptimizedCompilationJob> |
| ExecuteSingleUnoptimizedCompilationJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, Handle<Script> script, |
| AccountingAllocator* allocator, |
| std::vector<FunctionLiteral*>* eager_inner_literals, |
| LocalIsolate* local_isolate) { |
| #if V8_ENABLE_WEBASSEMBLY |
| if (UseAsmWasm(literal, parse_info->flags().is_asm_wasm_broken())) { |
| std::unique_ptr<UnoptimizedCompilationJob> asm_job( |
| AsmJs::NewCompilationJob(parse_info, literal, allocator)); |
| if (asm_job->ExecuteJob() == CompilationJob::SUCCEEDED) { |
| return asm_job; |
| } |
| // asm.js validation failed, fall through to standard unoptimized compile. |
| // Note: we rely on the fact that AsmJs jobs have done all validation in the |
| // PrepareJob and ExecuteJob phases and can't fail in FinalizeJob with |
| // with a validation error or another error that could be solve by falling |
| // through to standard unoptimized compile. |
| } |
| #endif |
| std::unique_ptr<UnoptimizedCompilationJob> job( |
| interpreter::Interpreter::NewCompilationJob( |
| parse_info, literal, script, allocator, eager_inner_literals, |
| local_isolate)); |
| |
| if (job->ExecuteJob() != CompilationJob::SUCCEEDED) { |
| // Compilation failed, return null. |
| return std::unique_ptr<UnoptimizedCompilationJob>(); |
| } |
| |
| return job; |
| } |
| |
| template <typename IsolateT> |
| bool IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs( |
| IsolateT* isolate, Handle<Script> script, ParseInfo* parse_info, |
| AccountingAllocator* allocator, IsCompiledScope* is_compiled_scope, |
| FinalizeUnoptimizedCompilationDataList* |
| finalize_unoptimized_compilation_data_list, |
| DeferredFinalizationJobDataList* |
| jobs_to_retry_finalization_on_main_thread) { |
| DeclarationScope::AllocateScopeInfos(parse_info, script, isolate); |
| |
| std::vector<FunctionLiteral*> functions_to_compile; |
| functions_to_compile.push_back(parse_info->literal()); |
| |
| bool compilation_succeeded = true; |
| while (!functions_to_compile.empty()) { |
| FunctionLiteral* literal = functions_to_compile.back(); |
| functions_to_compile.pop_back(); |
| Handle<SharedFunctionInfo> shared_info = literal->shared_function_info(); |
| // It's possible that compilation of an outer function overflowed the stack, |
| // so a literal we'd like to compile won't have its SFI yet. Skip compiling |
| // the inner function in that case. |
| if (shared_info.is_null()) continue; |
| if (shared_info->is_compiled()) continue; |
| |
| std::unique_ptr<UnoptimizedCompilationJob> job = |
| ExecuteSingleUnoptimizedCompilationJob(parse_info, literal, script, |
| allocator, &functions_to_compile, |
| isolate->AsLocalIsolate()); |
| |
| if (!job) { |
| // Compilation failed presumably because of stack overflow, make sure |
| // the shared function info contains uncompiled data for the next |
| // compilation attempts. |
| if (!shared_info->HasUncompiledData()) { |
| SharedFunctionInfo::CreateAndSetUncompiledData(isolate, literal); |
| } |
| compilation_succeeded = false; |
| // Proceed finalizing other functions in case they don't have uncompiled |
| // data. |
| continue; |
| } |
| |
| UpdateSharedFunctionFlagsAfterCompilation(literal); |
| |
| auto finalization_status = FinalizeSingleUnoptimizedCompilationJob( |
| job.get(), shared_info, isolate, |
| finalize_unoptimized_compilation_data_list); |
| |
| switch (finalization_status) { |
| case CompilationJob::SUCCEEDED: |
| if (literal == parse_info->literal()) { |
| // Ensure that the top level function is retained. |
| *is_compiled_scope = shared_info->is_compiled_scope(isolate); |
| DCHECK(is_compiled_scope->is_compiled()); |
| } |
| break; |
| |
| case CompilationJob::FAILED: |
| compilation_succeeded = false; |
| // Proceed finalizing other functions in case they don't have uncompiled |
| // data. |
| continue; |
| |
| case CompilationJob::RETRY_ON_MAIN_THREAD: |
| // This should not happen on the main thread. |
| DCHECK((!std::is_same<IsolateT, Isolate>::value)); |
| DCHECK_NOT_NULL(jobs_to_retry_finalization_on_main_thread); |
| |
| // Clear the literal and ParseInfo to prevent further attempts to |
| // access them. |
| job->compilation_info()->ClearLiteral(); |
| job->ClearParseInfo(); |
| jobs_to_retry_finalization_on_main_thread->emplace_back( |
| isolate, shared_info, std::move(job)); |
| break; |
| } |
| } |
| |
| // Report any warnings generated during compilation. |
| if (parse_info->pending_error_handler()->has_pending_warnings()) { |
| parse_info->pending_error_handler()->PrepareWarnings(isolate); |
| } |
| |
| return compilation_succeeded; |
| } |
| |
| bool FinalizeDeferredUnoptimizedCompilationJobs( |
| Isolate* isolate, DirectHandle<Script> script, |
| DeferredFinalizationJobDataList* deferred_jobs, |
| PendingCompilationErrorHandler* pending_error_handler, |
| FinalizeUnoptimizedCompilationDataList* |
| finalize_unoptimized_compilation_data_list) { |
| DCHECK(AllowCompilation::IsAllowed(isolate)); |
| |
| if (deferred_jobs->empty()) return true; |
| |
| // TODO(rmcilroy): Clear native context in debug once AsmJS generates doesn't |
| // rely on accessing native context during finalization. |
| |
| // Finalize the deferred compilation jobs. |
| for (auto&& job : *deferred_jobs) { |
| Handle<SharedFunctionInfo> shared_info = job.function_handle(); |
| if (FinalizeSingleUnoptimizedCompilationJob( |
| job.job(), shared_info, isolate, |
| finalize_unoptimized_compilation_data_list) != |
| CompilationJob::SUCCEEDED) { |
| return false; |
| } |
| } |
| |
| // Report any warnings generated during deferred finalization. |
| if (pending_error_handler->has_pending_warnings()) { |
| pending_error_handler->PrepareWarnings(isolate); |
| } |
| |
| return true; |
| } |
| |
| // A wrapper to access the optimized code cache slots on the feedback vector. |
| class OptimizedCodeCache : public AllStatic { |
| public: |
| static V8_WARN_UNUSED_RESULT MaybeHandle<Code> Get( |
| Isolate* isolate, DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, CodeKind code_kind) { |
| DCHECK_IMPLIES(V8_ENABLE_LEAPTIERING_BOOL, IsOSR(osr_offset)); |
| if (!CodeKindIsStoredInOptimizedCodeCache(code_kind)) return {}; |
| if (!function->has_feedback_vector()) return {}; |
| |
| DisallowGarbageCollection no_gc; |
| Tagged<SharedFunctionInfo> shared = function->shared(); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileGetFromOptimizedCodeMap); |
| |
| Tagged<Code> code; |
| Tagged<FeedbackVector> feedback_vector = function->feedback_vector(); |
| if (IsOSR(osr_offset)) { |
| Handle<BytecodeArray> bytecode(shared->GetBytecodeArray(isolate), |
| isolate); |
| interpreter::BytecodeArrayIterator it(bytecode, osr_offset.ToInt()); |
| // Bytecode may be different, so make sure we're at a valid OSR entry. |
| SBXCHECK(it.CurrentBytecodeIsValidOSREntry()); |
| std::optional<Tagged<Code>> maybe_code = |
| feedback_vector->GetOptimizedOsrCode(isolate, it.GetSlotOperand(2)); |
| if (maybe_code.has_value()) code = maybe_code.value(); |
| } else { |
| #ifdef V8_ENABLE_LEAPTIERING |
| UNREACHABLE(); |
| #else |
| feedback_vector->EvictOptimizedCodeMarkedForDeoptimization( |
| isolate, shared, "OptimizedCodeCache::Get"); |
| code = feedback_vector->optimized_code(isolate); |
| #endif // V8_ENABLE_LEAPTIERING |
| } |
| |
| // Normal tierup should never request a code-kind we already have. In case |
| // of OSR it can happen that we OSR from ignition to turbofan. This is |
| // explicitly allowed here by reusing any larger-kinded than requested |
| // code. |
| DCHECK_IMPLIES(!code.is_null() && code->kind() > code_kind, |
| IsOSR(osr_offset)); |
| if (code.is_null() || code->kind() < code_kind) return {}; |
| |
| DCHECK(!code->marked_for_deoptimization()); |
| DCHECK(shared->is_compiled()); |
| DCHECK(CodeKindIsStoredInOptimizedCodeCache(code->kind())); |
| DCHECK_IMPLIES(IsOSR(osr_offset), CodeKindCanOSR(code->kind())); |
| |
| CompilerTracer::TraceOptimizedCodeCacheHit(isolate, function, osr_offset, |
| code_kind); |
| return handle(code, isolate); |
| } |
| |
| static void Insert(Isolate* isolate, Tagged<JSFunction> function, |
| BytecodeOffset osr_offset, Tagged<Code> code, |
| bool is_function_context_specializing) { |
| DCHECK_IMPLIES(V8_ENABLE_LEAPTIERING_BOOL, IsOSR(osr_offset)); |
| const CodeKind kind = code->kind(); |
| if (!CodeKindIsStoredInOptimizedCodeCache(kind)) return; |
| |
| Tagged<FeedbackVector> feedback_vector = function->feedback_vector(); |
| |
| if (IsOSR(osr_offset)) { |
| DCHECK(CodeKindCanOSR(kind)); |
| DCHECK(!is_function_context_specializing); |
| Tagged<SharedFunctionInfo> shared = function->shared(); |
| Handle<BytecodeArray> bytecode(shared->GetBytecodeArray(isolate), |
| isolate); |
| interpreter::BytecodeArrayIterator it(bytecode, osr_offset.ToInt()); |
| // Bytecode may be different, so make sure we're at a valid OSR entry. |
| SBXCHECK(it.CurrentBytecodeIsValidOSREntry()); |
| feedback_vector->SetOptimizedOsrCode(isolate, it.GetSlotOperand(2), code); |
| return; |
| } |
| |
| #ifdef V8_ENABLE_LEAPTIERING |
| UNREACHABLE(); |
| #else |
| DCHECK(!IsOSR(osr_offset)); |
| |
| if (is_function_context_specializing) { |
| // Function context specialization folds-in the function context, so no |
| // sharing can occur. Make sure the optimized code cache is cleared. |
| // Only do so if the specialized code's kind matches the cached code kind. |
| if (feedback_vector->has_optimized_code() && |
| feedback_vector->optimized_code(isolate)->kind() == code->kind()) { |
| feedback_vector->ClearOptimizedCode(); |
| } |
| return; |
| } |
| |
| function->shared()->set_function_context_independent_compiled(true); |
| feedback_vector->SetOptimizedCode(isolate, code); |
| #endif // V8_ENABLE_LEAPTIERING |
| } |
| }; |
| |
| // Runs PrepareJob in the proper compilation scopes. Handles will be allocated |
| // in a persistent handle scope that is detached and handed off to the |
| // {compilation_info} after PrepareJob. |
| bool PrepareJobWithHandleScope(OptimizedCompilationJob* job, Isolate* isolate, |
| OptimizedCompilationInfo* compilation_info, |
| ConcurrencyMode mode) { |
| CompilationHandleScope compilation(isolate, compilation_info); |
| CompilerTracer::TracePrepareJob(isolate, compilation_info, mode); |
| compilation_info->ReopenAndCanonicalizeHandlesInNewScope(isolate); |
| return job->PrepareJob(isolate) == CompilationJob::SUCCEEDED; |
| } |
| |
| bool CompileTurbofan_NotConcurrent(Isolate* isolate, |
| TurbofanCompilationJob* job) { |
| OptimizedCompilationInfo* const compilation_info = job->compilation_info(); |
| DCHECK_EQ(compilation_info->code_kind(), CodeKind::TURBOFAN_JS); |
| |
| TimerEventScope<TimerEventRecompileSynchronous> timer(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kOptimizeNonConcurrent); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.OptimizeNonConcurrent"); |
| |
| if (!PrepareJobWithHandleScope(job, isolate, compilation_info, |
| ConcurrencyMode::kSynchronous)) { |
| CompilerTracer::TraceAbortedJob(isolate, compilation_info, |
| job->prepare_in_ms(), job->execute_in_ms(), |
| job->finalize_in_ms()); |
| return false; |
| } |
| |
| if (job->ExecuteJob(isolate->counters()->runtime_call_stats(), |
| isolate->main_thread_local_isolate())) { |
| CompilerTracer::TraceAbortedJob(isolate, compilation_info, |
| job->prepare_in_ms(), job->execute_in_ms(), |
| job->finalize_in_ms()); |
| return false; |
| } |
| |
| if (job->FinalizeJob(isolate) != CompilationJob::SUCCEEDED) { |
| CompilerTracer::TraceAbortedJob(isolate, compilation_info, |
| job->prepare_in_ms(), job->execute_in_ms(), |
| job->finalize_in_ms()); |
| return false; |
| } |
| |
| // Success! |
| job->RecordCompilationStats(ConcurrencyMode::kSynchronous, isolate); |
| DCHECK(!isolate->has_exception()); |
| if (!V8_ENABLE_LEAPTIERING_BOOL || job->compilation_info()->is_osr()) { |
| OptimizedCodeCache::Insert( |
| isolate, *compilation_info->closure(), compilation_info->osr_offset(), |
| *compilation_info->code(), |
| compilation_info->function_context_specializing()); |
| } |
| job->RecordFunctionCompilation(LogEventListener::CodeTag::kFunction, isolate); |
| return true; |
| } |
| |
| bool CompileTurbofan_Concurrent(Isolate* isolate, |
| std::unique_ptr<TurbofanCompilationJob> job) { |
| OptimizedCompilationInfo* const compilation_info = job->compilation_info(); |
| DCHECK_EQ(compilation_info->code_kind(), CodeKind::TURBOFAN_JS); |
| DirectHandle<JSFunction> function = compilation_info->closure(); |
| |
| if (!isolate->optimizing_compile_dispatcher()->IsQueueAvailable()) { |
| if (v8_flags.trace_concurrent_recompilation) { |
| PrintF(" ** Compilation queue full, will retry optimizing "); |
| ShortPrint(*function); |
| PrintF(" later.\n"); |
| } |
| return false; |
| } |
| |
| if (isolate->heap()->HighMemoryPressure()) { |
| if (v8_flags.trace_concurrent_recompilation) { |
| PrintF(" ** High memory pressure, will retry optimizing "); |
| ShortPrint(*function); |
| PrintF(" later.\n"); |
| } |
| return false; |
| } |
| |
| TimerEventScope<TimerEventRecompileSynchronous> timer(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kOptimizeConcurrentPrepare); |
| TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.OptimizeConcurrentPrepare", job->trace_id(), |
| TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| if (!PrepareJobWithHandleScope(job.get(), isolate, compilation_info, |
| ConcurrencyMode::kConcurrent)) { |
| return false; |
| } |
| |
| if (V8_LIKELY(!compilation_info->discard_result_for_testing())) { |
| function->SetTieringInProgress(true, compilation_info->osr_offset()); |
| } |
| |
| // The background recompile will own this job. |
| if (!isolate->optimizing_compile_dispatcher()->TryQueueForOptimization(job)) { |
| function->SetTieringInProgress(false, compilation_info->osr_offset()); |
| |
| if (v8_flags.trace_concurrent_recompilation) { |
| PrintF(" ** Compilation queue full, will retry optimizing "); |
| ShortPrint(*function); |
| PrintF(" later.\n"); |
| } |
| return false; |
| } |
| |
| if (v8_flags.trace_concurrent_recompilation) { |
| PrintF(" ** Queued "); |
| ShortPrint(*function); |
| PrintF(" for concurrent optimization.\n"); |
| } |
| |
| DCHECK(compilation_info->shared_info()->HasBytecodeArray()); |
| return true; |
| } |
| |
| enum class CompileResultBehavior { |
| // Default behavior, i.e. install the result, insert into caches, etc. |
| kDefault, |
| // Used only for stress testing. The compilation result should be discarded. |
| kDiscardForTesting, |
| }; |
| |
| bool ShouldOptimize(CodeKind code_kind, |
| DirectHandle<SharedFunctionInfo> shared) { |
| DCHECK(CodeKindIsOptimizedJSFunction(code_kind)); |
| switch (code_kind) { |
| case CodeKind::TURBOFAN_JS: |
| return v8_flags.turbofan && shared->PassesFilter(v8_flags.turbo_filter); |
| case CodeKind::MAGLEV: |
| return maglev::IsMaglevEnabled() && |
| shared->PassesFilter(v8_flags.maglev_filter); |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| MaybeHandle<Code> CompileTurbofan(Isolate* isolate, Handle<JSFunction> function, |
| DirectHandle<SharedFunctionInfo> shared, |
| ConcurrencyMode mode, |
| BytecodeOffset osr_offset, |
| CompileResultBehavior result_behavior) { |
| VMState<COMPILER> state(isolate); |
| TimerEventScope<TimerEventOptimizeCode> optimize_code_timer(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kOptimizeCode); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.OptimizeCode"); |
| |
| DCHECK(!isolate->has_exception()); |
| PostponeInterruptsScope postpone(isolate); |
| const compiler::IsScriptAvailable has_script = |
| IsScript(shared->script()) ? compiler::IsScriptAvailable::kYes |
| : compiler::IsScriptAvailable::kNo; |
| // BUG(5946): This DCHECK is necessary to make certain that we won't |
| // tolerate the lack of a script without bytecode. |
| DCHECK_IMPLIES(has_script == compiler::IsScriptAvailable::kNo, |
| shared->HasBytecodeArray()); |
| std::unique_ptr<TurbofanCompilationJob> job( |
| compiler::NewCompilationJob(isolate, function, has_script, osr_offset)); |
| |
| if (result_behavior == CompileResultBehavior::kDiscardForTesting) { |
| job->compilation_info()->set_discard_result_for_testing(); |
| } |
| |
| if (IsOSR(osr_offset)) { |
| isolate->CountUsage(v8::Isolate::kTurboFanOsrCompileStarted); |
| } |
| |
| // Prepare the job and launch concurrent compilation, or compile now. |
| if (IsConcurrent(mode)) { |
| if (CompileTurbofan_Concurrent(isolate, std::move(job))) return {}; |
| } else { |
| DCHECK(IsSynchronous(mode)); |
| if (CompileTurbofan_NotConcurrent(isolate, job.get())) { |
| return job->compilation_info()->code(); |
| } |
| } |
| |
| if (isolate->has_exception()) isolate->clear_exception(); |
| return {}; |
| } |
| |
| #ifdef V8_ENABLE_MAGLEV |
| // TODO(v8:7700): Record maglev compilations better. |
| void RecordMaglevFunctionCompilation(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| Handle<AbstractCode> code) { |
| PtrComprCageBase cage_base(isolate); |
| Handle<SharedFunctionInfo> shared(function->shared(cage_base), isolate); |
| DirectHandle<Script> script(Cast<Script>(shared->script(cage_base)), isolate); |
| DirectHandle<FeedbackVector> feedback_vector( |
| function->feedback_vector(cage_base), isolate); |
| |
| // Optimistic estimate. |
| double time_taken_ms = 0; |
| |
| Compiler::LogFunctionCompilation( |
| isolate, LogEventListener::CodeTag::kFunction, script, shared, |
| feedback_vector, code, code->kind(cage_base), time_taken_ms); |
| } |
| #endif // V8_ENABLE_MAGLEV |
| |
| MaybeHandle<Code> CompileMaglev(Isolate* isolate, Handle<JSFunction> function, |
| ConcurrencyMode mode, BytecodeOffset osr_offset, |
| CompileResultBehavior result_behavior) { |
| #ifdef V8_ENABLE_MAGLEV |
| DCHECK(maglev::IsMaglevEnabled()); |
| CHECK(result_behavior == CompileResultBehavior::kDefault); |
| |
| // TODO(v8:7700): Tracing, see CompileTurbofan. |
| |
| DCHECK(!isolate->has_exception()); |
| PostponeInterruptsScope postpone(isolate); |
| |
| // TODO(v8:7700): See everything in CompileTurbofan_Concurrent. |
| // - Tracing, |
| // - timers, |
| // - aborts on memory pressure, |
| // ... |
| |
| // Prepare the job. |
| auto job = maglev::MaglevCompilationJob::New(isolate, function, osr_offset); |
| |
| if (IsConcurrent(mode) && |
| !isolate->maglev_concurrent_dispatcher()->is_enabled()) { |
| mode = ConcurrencyMode::kSynchronous; |
| } |
| |
| { |
| TRACE_EVENT_WITH_FLOW0( |
| TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| IsSynchronous(mode) ? "V8.MaglevPrepare" : "V8.MaglevConcurrentPrepare", |
| job->trace_id(), TRACE_EVENT_FLAG_FLOW_OUT); |
| CompilerTracer::TraceStartMaglevCompile(isolate, function, job->is_osr(), |
| mode); |
| CompilationJob::Status status = job->PrepareJob(isolate); |
| CHECK_EQ(status, CompilationJob::SUCCEEDED); // TODO(v8:7700): Use status. |
| } |
| |
| if (IsSynchronous(mode)) { |
| CompilationJob::Status status = |
| job->ExecuteJob(isolate->counters()->runtime_call_stats(), |
| isolate->main_thread_local_isolate()); |
| if (status == CompilationJob::FAILED) { |
| return {}; |
| } |
| CHECK_EQ(status, CompilationJob::SUCCEEDED); |
| |
| Compiler::FinalizeMaglevCompilationJob(job.get(), isolate); |
| |
| return job->code(); |
| } |
| |
| DCHECK(IsConcurrent(mode)); |
| |
| // Enqueue it. |
| isolate->maglev_concurrent_dispatcher()->EnqueueJob(std::move(job)); |
| |
| // Remember that the function is currently being processed. |
| function->SetTieringInProgress(true, osr_offset); |
| function->SetInterruptBudget(isolate, BudgetModification::kRaise, |
| CodeKind::MAGLEV); |
| |
| return {}; |
| #else // V8_ENABLE_MAGLEV |
| UNREACHABLE(); |
| #endif // V8_ENABLE_MAGLEV |
| } |
| |
| MaybeHandle<Code> GetOrCompileOptimized( |
| Isolate* isolate, DirectHandle<JSFunction> function, ConcurrencyMode mode, |
| CodeKind code_kind, BytecodeOffset osr_offset = BytecodeOffset::None(), |
| CompileResultBehavior result_behavior = CompileResultBehavior::kDefault) { |
| DCHECK(CodeKindIsOptimizedJSFunction(code_kind)); |
| |
| DirectHandle<SharedFunctionInfo> shared(function->shared(), isolate); |
| |
| // Reset the OSR urgency. If we enter a function OSR should not be triggered. |
| // If we are in fact in a loop we should avoid triggering this compilation |
| // request on every iteration and thereby skipping other interrupts. |
| function->feedback_vector()->reset_osr_urgency(); |
| |
| // Clear the optimization marker on the function so that we don't try to |
| // re-optimize. |
| if (!IsOSR(osr_offset)) { |
| function->ResetTieringRequests(); |
| // Always reset the OSR urgency to ensure we reset it on function entry. |
| int invocation_count = |
| function->feedback_vector()->invocation_count(kRelaxedLoad); |
| if (!(V8_UNLIKELY(v8_flags.testing_d8_test_runner || |
| v8_flags.allow_natives_syntax) && |
| ManualOptimizationTable::IsMarkedForManualOptimization(isolate, |
| *function)) && |
| invocation_count < v8_flags.minimum_invocations_before_optimization) { |
| function->feedback_vector()->set_invocation_count(invocation_count + 1, |
| kRelaxedStore); |
| return {}; |
| } |
| } |
| |
| // TODO(v8:7700): Distinguish between Maglev and Turbofan. |
| if (shared->optimization_disabled() && |
| shared->disabled_optimization_reason() == BailoutReason::kNeverOptimize) { |
| return {}; |
| } |
| |
| // Do not optimize when debugger needs to hook into every call. |
| if (isolate->debug()->needs_check_on_function_call()) { |
| return {}; |
| } |
| |
| // Do not optimize if we need to be able to set break points. |
| if (shared->HasBreakInfo(isolate)) return {}; |
| |
| // Do not optimize if optimization is disabled or function doesn't pass |
| // turbo_filter. |
| if (!ShouldOptimize(code_kind, shared)) return {}; |
| |
| if (!V8_ENABLE_LEAPTIERING_BOOL || IsOSR(osr_offset)) { |
| Handle<Code> cached_code; |
| if (OptimizedCodeCache::Get(isolate, function, osr_offset, code_kind) |
| .ToHandle(&cached_code)) { |
| DCHECK_IMPLIES(!IsOSR(osr_offset), cached_code->kind() <= code_kind); |
| return cached_code; |
| } |
| |
| if (IsOSR(osr_offset)) { |
| // One OSR job per function at a time. |
| if (function->osr_tiering_in_progress()) { |
| return {}; |
| } |
| } |
| } |
| |
| DCHECK(shared->is_compiled()); |
| |
| if (code_kind == CodeKind::TURBOFAN_JS) { |
| return CompileTurbofan(isolate, indirect_handle(function, isolate), shared, |
| mode, osr_offset, result_behavior); |
| } else { |
| DCHECK_EQ(code_kind, CodeKind::MAGLEV); |
| return CompileMaglev(isolate, indirect_handle(function, isolate), mode, |
| osr_offset, result_behavior); |
| } |
| } |
| |
| // When --stress-concurrent-inlining is enabled, spawn concurrent jobs in |
| // addition to non-concurrent compiles to increase coverage in mjsunit tests |
| // (where most interesting compiles are non-concurrent). The result of the |
| // compilation is thrown out. |
| void SpawnDuplicateConcurrentJobForStressTesting( |
| Isolate* isolate, DirectHandle<JSFunction> function, ConcurrencyMode mode, |
| CodeKind code_kind) { |
| // TODO(v8:7700): Support Maglev. |
| if (code_kind == CodeKind::MAGLEV) return; |
| |
| if (function->ActiveTierIsTurbofan(isolate)) return; |
| |
| DCHECK(v8_flags.stress_concurrent_inlining && |
| isolate->concurrent_recompilation_enabled() && IsSynchronous(mode) && |
| isolate->node_observer() == nullptr); |
| CompileResultBehavior result_behavior = |
| v8_flags.stress_concurrent_inlining_attach_code |
| ? CompileResultBehavior::kDefault |
| : CompileResultBehavior::kDiscardForTesting; |
| USE(GetOrCompileOptimized(isolate, function, ConcurrencyMode::kConcurrent, |
| code_kind, BytecodeOffset::None(), |
| result_behavior)); |
| } |
| |
| bool FailAndClearException(Isolate* isolate) { |
| isolate->clear_internal_exception(); |
| return false; |
| } |
| |
| template <typename IsolateT> |
| bool PrepareException(IsolateT* isolate, ParseInfo* parse_info) { |
| if (parse_info->pending_error_handler()->has_pending_error()) { |
| parse_info->pending_error_handler()->PrepareErrors( |
| isolate, parse_info->ast_value_factory()); |
| } |
| return false; |
| } |
| |
| bool FailWithPreparedException( |
| Isolate* isolate, Handle<Script> script, |
| const PendingCompilationErrorHandler* pending_error_handler, |
| Compiler::ClearExceptionFlag flag = Compiler::KEEP_EXCEPTION) { |
| if (flag == Compiler::CLEAR_EXCEPTION) { |
| return FailAndClearException(isolate); |
| } |
| |
| if (!isolate->has_exception()) { |
| if (pending_error_handler->has_pending_error()) { |
| pending_error_handler->ReportErrors(isolate, script); |
| } else { |
| isolate->StackOverflow(); |
| } |
| } |
| return false; |
| } |
| |
| bool FailWithException(Isolate* isolate, Handle<Script> script, |
| ParseInfo* parse_info, |
| Compiler::ClearExceptionFlag flag) { |
| PrepareException(isolate, parse_info); |
| return FailWithPreparedException(isolate, script, |
| parse_info->pending_error_handler(), flag); |
| } |
| |
| void FinalizeUnoptimizedCompilation( |
| Isolate* isolate, Handle<Script> script, |
| const UnoptimizedCompileFlags& flags, |
| const UnoptimizedCompileState* compile_state, |
| const FinalizeUnoptimizedCompilationDataList& |
| finalize_unoptimized_compilation_data_list) { |
| if (compile_state->pending_error_handler()->has_pending_warnings()) { |
| compile_state->pending_error_handler()->ReportWarnings(isolate, script); |
| } |
| |
| bool need_source_positions = |
| v8_flags.stress_lazy_source_positions || |
| (!flags.collect_source_positions() && isolate->NeedsSourcePositions()); |
| |
| for (const auto& finalize_data : finalize_unoptimized_compilation_data_list) { |
| Handle<SharedFunctionInfo> shared_info = finalize_data.function_handle(); |
| // It's unlikely, but possible, that the bytecode was flushed between being |
| // allocated and now, so guard against that case, and against it being |
| // flushed in the middle of this loop. |
| IsCompiledScope is_compiled_scope(*shared_info, isolate); |
| if (!is_compiled_scope.is_compiled()) continue; |
| |
| if (need_source_positions) { |
| SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, shared_info); |
| } |
| LogEventListener::CodeTag log_tag; |
| if (shared_info->is_toplevel()) { |
| log_tag = flags.is_eval() ? LogEventListener::CodeTag::kEval |
| : LogEventListener::CodeTag::kScript; |
| } else { |
| log_tag = LogEventListener::CodeTag::kFunction; |
| } |
| log_tag = V8FileLogger::ToNativeByScript(log_tag, *script); |
| if (isolate->interpreted_frames_native_stack() && |
| isolate->logger()->is_listening_to_code_events()) { |
| Compiler::InstallInterpreterTrampolineCopy(isolate, shared_info, log_tag); |
| } |
| DirectHandle<CoverageInfo> coverage_info; |
| if (finalize_data.coverage_info().ToHandle(&coverage_info)) { |
| isolate->debug()->InstallCoverageInfo(shared_info, coverage_info); |
| } |
| |
| LogUnoptimizedCompilation(isolate, shared_info, log_tag, |
| finalize_data.time_taken_to_execute(), |
| finalize_data.time_taken_to_finalize()); |
| } |
| } |
| |
| void FinalizeUnoptimizedScriptCompilation( |
| Isolate* isolate, Handle<Script> script, |
| const UnoptimizedCompileFlags& flags, |
| const UnoptimizedCompileState* compile_state, |
| const FinalizeUnoptimizedCompilationDataList& |
| finalize_unoptimized_compilation_data_list) { |
| FinalizeUnoptimizedCompilation(isolate, script, flags, compile_state, |
| finalize_unoptimized_compilation_data_list); |
| |
| script->set_compilation_state(Script::CompilationState::kCompiled); |
| DCHECK_IMPLIES(isolate->NeedsSourcePositions(), script->has_line_ends()); |
| } |
| |
| void CompileAllWithBaseline(Isolate* isolate, |
| const FinalizeUnoptimizedCompilationDataList& |
| finalize_unoptimized_compilation_data_list) { |
| for (const auto& finalize_data : finalize_unoptimized_compilation_data_list) { |
| Handle<SharedFunctionInfo> shared_info = finalize_data.function_handle(); |
| IsCompiledScope is_compiled_scope(*shared_info, isolate); |
| if (!is_compiled_scope.is_compiled()) continue; |
| if (!CanCompileWithBaseline(isolate, *shared_info)) continue; |
| Compiler::CompileSharedWithBaseline( |
| isolate, shared_info, Compiler::CLEAR_EXCEPTION, &is_compiled_scope); |
| } |
| } |
| |
| // Create shared function info for top level and shared function infos array for |
| // inner functions. |
| template <typename IsolateT> |
| Handle<SharedFunctionInfo> CreateTopLevelSharedFunctionInfo( |
| ParseInfo* parse_info, DirectHandle<Script> script, IsolateT* isolate) { |
| EnsureInfosArrayOnScript(script, parse_info, isolate); |
| DCHECK_EQ(kNoSourcePosition, |
| parse_info->literal()->function_token_position()); |
| return isolate->factory()->NewSharedFunctionInfoForLiteral( |
| parse_info->literal(), script, true); |
| } |
| |
| Handle<SharedFunctionInfo> GetOrCreateTopLevelSharedFunctionInfo( |
| ParseInfo* parse_info, DirectHandle<Script> script, Isolate* isolate, |
| IsCompiledScope* is_compiled_scope) { |
| EnsureInfosArrayOnScript(script, parse_info, isolate); |
| MaybeHandle<SharedFunctionInfo> maybe_shared = |
| Script::FindSharedFunctionInfo(script, isolate, parse_info->literal()); |
| if (Handle<SharedFunctionInfo> shared; maybe_shared.ToHandle(&shared)) { |
| DCHECK_EQ(shared->function_literal_id(), |
| parse_info->literal()->function_literal_id()); |
| *is_compiled_scope = shared->is_compiled_scope(isolate); |
| return shared; |
| } |
| return CreateTopLevelSharedFunctionInfo(parse_info, script, isolate); |
| } |
| |
| MaybeHandle<SharedFunctionInfo> CompileToplevel( |
| ParseInfo* parse_info, Handle<Script> script, |
| MaybeDirectHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate, |
| IsCompiledScope* is_compiled_scope) { |
| TimerEventScope<TimerEventCompileCode> top_level_timer(isolate); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompileCode"); |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| |
| PostponeInterruptsScope postpone(isolate); |
| DCHECK(!isolate->native_context().is_null()); |
| RCS_SCOPE(isolate, parse_info->flags().is_eval() |
| ? RuntimeCallCounterId::kCompileEval |
| : RuntimeCallCounterId::kCompileScript); |
| VMState<BYTECODE_COMPILER> state(isolate); |
| if (parse_info->literal() == nullptr && |
| !parsing::ParseProgram(parse_info, script, maybe_outer_scope_info, |
| isolate, parsing::ReportStatisticsMode::kYes)) { |
| FailWithException(isolate, script, parse_info, |
| Compiler::ClearExceptionFlag::KEEP_EXCEPTION); |
| return MaybeHandle<SharedFunctionInfo>(); |
| } |
| // Measure how long it takes to do the compilation; only take the |
| // rest of the function into account to avoid overlap with the |
| // parsing statistics. |
| NestedTimedHistogram* rate = parse_info->flags().is_eval() |
| ? isolate->counters()->compile_eval() |
| : isolate->counters()->compile(); |
| NestedTimedHistogramScope timer(rate); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| parse_info->flags().is_eval() ? "V8.CompileEval" : "V8.Compile"); |
| |
| // Create the SharedFunctionInfo and add it to the script's list. |
| Handle<SharedFunctionInfo> shared_info = |
| GetOrCreateTopLevelSharedFunctionInfo(parse_info, script, isolate, |
| is_compiled_scope); |
| |
| FinalizeUnoptimizedCompilationDataList |
| finalize_unoptimized_compilation_data_list; |
| |
| // Prepare and execute compilation of the outer-most function. |
| if (!IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs( |
| isolate, script, parse_info, isolate->allocator(), is_compiled_scope, |
| &finalize_unoptimized_compilation_data_list, nullptr)) { |
| FailWithException(isolate, script, parse_info, |
| Compiler::ClearExceptionFlag::KEEP_EXCEPTION); |
| return MaybeHandle<SharedFunctionInfo>(); |
| } |
| |
| // Character stream shouldn't be used again. |
| parse_info->ResetCharacterStream(); |
| |
| FinalizeUnoptimizedScriptCompilation( |
| isolate, script, parse_info->flags(), parse_info->state(), |
| finalize_unoptimized_compilation_data_list); |
| |
| if (v8_flags.always_sparkplug) { |
| CompileAllWithBaseline(isolate, finalize_unoptimized_compilation_data_list); |
| } |
| |
| return shared_info; |
| } |
| |
| #ifdef V8_RUNTIME_CALL_STATS |
| RuntimeCallCounterId RuntimeCallCounterIdForCompile(ParseInfo* parse_info) { |
| if (parse_info->flags().is_toplevel()) { |
| if (parse_info->flags().is_eval()) { |
| return RuntimeCallCounterId::kCompileEval; |
| } |
| return RuntimeCallCounterId::kCompileScript; |
| } |
| return RuntimeCallCounterId::kCompileFunction; |
| } |
| #endif // V8_RUNTIME_CALL_STATS |
| |
| } // namespace |
| |
| CompilationHandleScope::~CompilationHandleScope() { |
| info_->set_persistent_handles(persistent_.Detach()); |
| } |
| |
| FinalizeUnoptimizedCompilationData::FinalizeUnoptimizedCompilationData( |
| LocalIsolate* isolate, Handle<SharedFunctionInfo> function_handle, |
| MaybeHandle<CoverageInfo> coverage_info, |
| base::TimeDelta time_taken_to_execute, |
| base::TimeDelta time_taken_to_finalize) |
| : time_taken_to_execute_(time_taken_to_execute), |
| time_taken_to_finalize_(time_taken_to_finalize), |
| function_handle_(isolate->heap()->NewPersistentHandle(function_handle)), |
| coverage_info_(isolate->heap()->NewPersistentMaybeHandle(coverage_info)) { |
| } |
| |
| DeferredFinalizationJobData::DeferredFinalizationJobData( |
| LocalIsolate* isolate, Handle<SharedFunctionInfo> function_handle, |
| std::unique_ptr<UnoptimizedCompilationJob> job) |
| : function_handle_(isolate->heap()->NewPersistentHandle(function_handle)), |
| job_(std::move(job)) {} |
| |
| BackgroundCompileTask::BackgroundCompileTask( |
| ScriptStreamingData* streamed_data, Isolate* isolate, ScriptType type, |
| ScriptCompiler::CompileOptions options, |
| ScriptCompiler::CompilationDetails* compilation_details, |
| CompileHintCallback compile_hint_callback, void* compile_hint_callback_data) |
| : isolate_for_local_isolate_(isolate), |
| flags_(UnoptimizedCompileFlags::ForToplevelCompile( |
| isolate, true, construct_language_mode(v8_flags.use_strict), |
| REPLMode::kNo, type, |
| (options & ScriptCompiler::CompileOptions::kEagerCompile) == 0 && |
| v8_flags.lazy_streaming)), |
| character_stream_(ScannerStream::For(streamed_data->source_stream.get(), |
| streamed_data->encoding)), |
| stack_size_(v8_flags.stack_size), |
| worker_thread_runtime_call_stats_( |
| isolate->counters()->worker_thread_runtime_call_stats()), |
| timer_(isolate->counters()->compile_script_on_background()), |
| compilation_details_(compilation_details), |
| start_position_(0), |
| end_position_(0), |
| function_literal_id_(kFunctionLiteralIdTopLevel), |
| compile_hint_callback_(compile_hint_callback), |
| compile_hint_callback_data_(compile_hint_callback_data) { |
| if (options & ScriptCompiler::CompileOptions::kProduceCompileHints) { |
| flags_.set_produce_compile_hints(true); |
| } |
| DCHECK(is_streaming_compilation()); |
| if (options & ScriptCompiler::kConsumeCompileHints) { |
| DCHECK_NOT_NULL(compile_hint_callback); |
| DCHECK_NOT_NULL(compile_hint_callback_data); |
| } else { |
| DCHECK_NULL(compile_hint_callback); |
| DCHECK_NULL(compile_hint_callback_data); |
| } |
| flags_.set_compile_hints_magic_enabled( |
| options & |
| ScriptCompiler::CompileOptions::kFollowCompileHintsMagicComment); |
| } |
| |
| BackgroundCompileTask::BackgroundCompileTask( |
| Isolate* isolate, Handle<SharedFunctionInfo> shared_info, |
| std::unique_ptr<Utf16CharacterStream> character_stream, |
| WorkerThreadRuntimeCallStats* worker_thread_runtime_stats, |
| TimedHistogram* timer, int max_stack_size) |
| : isolate_for_local_isolate_(isolate), |
| // TODO(leszeks): Create this from parent compile flags, to avoid |
| // accessing the Isolate. |
| flags_( |
| UnoptimizedCompileFlags::ForFunctionCompile(isolate, *shared_info)), |
| character_stream_(std::move(character_stream)), |
| stack_size_(max_stack_size), |
| worker_thread_runtime_call_stats_(worker_thread_runtime_stats), |
| timer_(timer), |
| compilation_details_(nullptr), |
| start_position_(shared_info->StartPosition()), |
| end_position_(shared_info->EndPosition()), |
| function_literal_id_(shared_info->function_literal_id()) { |
| DCHECK(!shared_info->is_toplevel()); |
| DCHECK(!is_streaming_compilation()); |
| |
| character_stream_->Seek(start_position_); |
| |
| // Get the script out of the outer ParseInfo and turn it into a persistent |
| // handle we can transfer to the background thread. |
| persistent_handles_ = std::make_unique<PersistentHandles>(isolate); |
| input_shared_info_ = persistent_handles_->NewHandle(shared_info); |
| } |
| |
| BackgroundCompileTask::~BackgroundCompileTask() = default; |
| |
| void SetScriptFieldsFromDetails(Isolate* isolate, Tagged<Script> script, |
| const ScriptDetails& script_details, |
| DisallowGarbageCollection* no_gc) { |
| Handle<Object> script_name; |
| if (script_details.name_obj.ToHandle(&script_name)) { |
| script->set_name(*script_name); |
| script->set_line_offset(script_details.line_offset); |
| script->set_column_offset(script_details.column_offset); |
| } |
| // The API can provide a source map URL, but a source map URL could also have |
| // been inferred by the parser from a magic comment. The API source map URL |
| // takes precedence (as long as it is a non-empty string). |
| Handle<Object> source_map_url; |
| if (script_details.source_map_url.ToHandle(&source_map_url) && |
| IsString(*source_map_url) && |
| Cast<String>(*source_map_url)->length() > 0) { |
| script->set_source_mapping_url(*source_map_url); |
| } |
| Handle<Object> host_defined_options; |
| if (script_details.host_defined_options.ToHandle(&host_defined_options)) { |
| // TODO(cbruni, chromium:1244145): Remove once migrated to the context. |
| if (IsFixedArray(*host_defined_options)) { |
| script->set_host_defined_options(Cast<FixedArray>(*host_defined_options)); |
| } |
| } |
| } |
| |
| namespace { |
| |
| #ifdef ENABLE_SLOW_DCHECKS |
| |
| // A class which traverses the object graph for a newly compiled Script and |
| // ensures that it contains pointers to Scripts, ScopeInfos and |
| // SharedFunctionInfos only at the expected locations. Any failure in this |
| // visitor indicates a case that is probably not handled correctly in |
| // BackgroundMergeTask. |
| class MergeAssumptionChecker final : public ObjectVisitor { |
| public: |
| explicit MergeAssumptionChecker(LocalIsolate* isolate) |
| : isolate_(isolate), cage_base_(isolate->cage_base()) {} |
| |
| void IterateObjects(Tagged<HeapObject> start) { |
| QueueVisit(start, kNormalObject); |
| while (to_visit_.size() > 0) { |
| std::pair<Tagged<HeapObject>, ObjectKind> pair = to_visit_.top(); |
| to_visit_.pop(); |
| Tagged<HeapObject> current = pair.first; |
| // The Script's infos list and the constant pools for all |
| // BytecodeArrays are expected to contain pointers to SharedFunctionInfos. |
| // However, the type of those objects (FixedArray or WeakFixedArray) |
| // doesn't have enough information to indicate their usage, so we enqueue |
| // those objects here rather than during VisitPointers. |
| if (IsScript(current)) { |
| Tagged<Script> script = Cast<Script>(current); |
| Tagged<HeapObject> infos = script->infos(); |
| QueueVisit(infos, kScriptInfosList); |
| // Avoid visiting eval_from_shared_or_wrapped_arguments. This field |
| // points to data outside the new Script, and doesn't need to be merged. |
| Tagged<HeapObject> eval_from_shared_or_wrapped_arguments; |
| if (script->eval_from_shared_or_wrapped_arguments() |
| .GetHeapObjectIfStrong( |
| &eval_from_shared_or_wrapped_arguments)) { |
| visited_.insert(eval_from_shared_or_wrapped_arguments); |
| } |
| } else if (IsBytecodeArray(current)) { |
| Tagged<HeapObject> constants = |
| Cast<BytecodeArray>(current)->constant_pool(); |
| QueueVisit(constants, kConstantPool); |
| } |
| current_object_kind_ = pair.second; |
| i::VisitObjectBody(isolate_, current, this); |
| QueueVisit(current->map(), kNormalObject); |
| } |
| } |
| |
| // ObjectVisitor implementation: |
| void VisitPointers(Tagged<HeapObject> host, ObjectSlot start, |
| ObjectSlot end) override { |
| MaybeObjectSlot maybe_start(start); |
| MaybeObjectSlot maybe_end(end); |
| VisitPointers(host, maybe_start, maybe_end); |
| } |
| void VisitPointers(Tagged<HeapObject> host, MaybeObjectSlot start, |
| MaybeObjectSlot end) override { |
| for (MaybeObjectSlot current = start; current != end; ++current) { |
| Tagged<MaybeObject> maybe_obj = current.load(cage_base_); |
| Tagged<HeapObject> obj; |
| bool is_weak = maybe_obj.IsWeak(); |
| if (maybe_obj.GetHeapObject(&obj)) { |
| if (IsSharedFunctionInfo(obj)) { |
| CHECK((current_object_kind_ == kConstantPool && !is_weak) || |
| (current_object_kind_ == kScriptInfosList && is_weak) || |
| (IsScript(host) && |
| current.address() == |
| host.address() + |
| Script::kEvalFromSharedOrWrappedArgumentsOffset)); |
| } else if (IsScopeInfo(obj)) { |
| CHECK((current_object_kind_ == kConstantPool && !is_weak) || |
| (current_object_kind_ == kNormalObject && !is_weak) || |
| (current_object_kind_ == kScriptInfosList && is_weak)); |
| } else if (IsScript(obj)) { |
| CHECK(IsSharedFunctionInfo(host) && |
| current == MaybeObjectSlot(host.address() + |
| SharedFunctionInfo::kScriptOffset)); |
| } else if (IsFixedArray(obj) && current_object_kind_ == kConstantPool) { |
| // Constant pools can contain nested fixed arrays, which in turn can |
| // point to SFIs. |
| QueueVisit(obj, kConstantPool); |
| } |
| |
| QueueVisit(obj, kNormalObject); |
| } |
| } |
| } |
| |
| // The object graph for a newly compiled Script shouldn't yet contain any |
| // Code. If any of these functions are called, then that would indicate that |
| // the graph was not disjoint from the rest of the heap as expected. |
| void VisitInstructionStreamPointer(Tagged<Code> host, |
| InstructionStreamSlot slot) override { |
| UNREACHABLE(); |
| } |
| void VisitCodeTarget(Tagged<InstructionStream> host, |
| RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| void VisitEmbeddedPointer(Tagged<InstructionStream> host, |
| RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| |
| private: |
| enum ObjectKind { |
| kNormalObject, |
| kConstantPool, |
| kScriptInfosList, |
| }; |
| |
| // If the object hasn't yet been added to the worklist, add it. Subsequent |
| // calls with the same object have no effect, even if kind is different. |
| void QueueVisit(Tagged<HeapObject> obj, ObjectKind kind) { |
| if (visited_.insert(obj).second) { |
| to_visit_.push(std::make_pair(obj, kind)); |
| } |
| } |
| |
| DisallowGarbageCollection no_gc_; |
| |
| LocalIsolate* isolate_; |
| PtrComprCageBase cage_base_; |
| std::stack<std::pair<Tagged<HeapObject>, ObjectKind>> to_visit_; |
| |
| // Objects that are either in to_visit_ or done being visited. It is safe to |
| // use HeapObject directly here because GC is disallowed while running this |
| // visitor. |
| std::unordered_set<Tagged<HeapObject>, Object::Hasher> visited_; |
| |
| ObjectKind current_object_kind_ = kNormalObject; |
| }; |
| |
| #endif // ENABLE_SLOW_DCHECKS |
| |
| } // namespace |
| |
| bool BackgroundCompileTask::is_streaming_compilation() const { |
| return function_literal_id_ == kFunctionLiteralIdTopLevel; |
| } |
| |
| void BackgroundCompileTask::Run() { |
| DCHECK_NE(ThreadId::Current(), isolate_for_local_isolate_->thread_id()); |
| LocalIsolate isolate(isolate_for_local_isolate_, ThreadKind::kBackground); |
| UnparkedScope unparked_scope(&isolate); |
| LocalHandleScope handle_scope(&isolate); |
| |
| ReusableUnoptimizedCompileState reusable_state(&isolate); |
| |
| Run(&isolate, &reusable_state); |
| } |
| |
| void BackgroundCompileTask::RunOnMainThread(Isolate* isolate) { |
| LocalHandleScope handle_scope(isolate->main_thread_local_isolate()); |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| Run(isolate->main_thread_local_isolate(), &reusable_state); |
| } |
| |
| void BackgroundCompileTask::Run( |
| LocalIsolate* isolate, ReusableUnoptimizedCompileState* reusable_state) { |
| TimedHistogramScope timer( |
| timer_, nullptr, |
| compilation_details_ |
| ? &compilation_details_->background_time_in_microseconds |
| : nullptr); |
| |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "BackgroundCompileTask::Run"); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileCompileTask, |
| RuntimeCallStats::CounterMode::kThreadSpecific); |
| |
| bool toplevel_script_compilation = flags_.is_toplevel(); |
| |
| ParseInfo info(isolate, flags_, &compile_state_, reusable_state, |
| GetCurrentStackPosition() - stack_size_ * KB); |
| info.set_character_stream(std::move(character_stream_)); |
| info.SetCompileHintCallbackAndData(compile_hint_callback_, |
| compile_hint_callback_data_); |
| if (is_streaming_compilation()) info.set_is_streaming_compilation(); |
| |
| if (toplevel_script_compilation) { |
| DCHECK_NULL(persistent_handles_); |
| DCHECK(input_shared_info_.is_null()); |
| |
| // We don't have the script source, origin, or details yet, so use default |
| // values for them. These will be fixed up during the main-thread merge. |
| Handle<Script> script = info.CreateScript( |
| isolate, isolate->factory()->empty_string(), kNullMaybeHandle, |
| ScriptOriginOptions(false, false, false, info.flags().is_module())); |
| script_ = isolate->heap()->NewPersistentHandle(script); |
| } else { |
| DCHECK_NOT_NULL(persistent_handles_); |
| isolate->heap()->AttachPersistentHandles(std::move(persistent_handles_)); |
| DirectHandle<SharedFunctionInfo> shared_info = |
| input_shared_info_.ToHandleChecked(); |
| script_ = isolate->heap()->NewPersistentHandle( |
| Cast<Script>(shared_info->script())); |
| info.CheckFlagsForFunctionFromScript(*script_); |
| |
| { |
| SharedStringAccessGuardIfNeeded access_guard(isolate); |
| info.set_function_name(info.ast_value_factory()->GetString( |
| shared_info->Name(), access_guard)); |
| } |
| |
| // Get preparsed scope data from the function literal. |
| if (shared_info->HasUncompiledDataWithPreparseData()) { |
| info.set_consumed_preparse_data(ConsumedPreparseData::For( |
| isolate, |
| handle(shared_info->uncompiled_data_with_preparse_data(isolate) |
| ->preparse_data(isolate), |
| isolate))); |
| } |
| } |
| |
| // Update the character stream's runtime call stats. |
| info.character_stream()->set_runtime_call_stats(info.runtime_call_stats()); |
| |
| Parser parser(isolate, &info); |
| if (flags().is_toplevel()) { |
| parser.InitializeEmptyScopeChain(&info); |
| } else { |
| // TODO(leszeks): Consider keeping Scope zones alive between compile tasks |
| // and passing the Scope for the FunctionLiteral through here directly |
| // without copying/deserializing. |
| DirectHandle<SharedFunctionInfo> shared_info = |
| input_shared_info_.ToHandleChecked(); |
| MaybeDirectHandle<ScopeInfo> maybe_outer_scope_info; |
| if (shared_info->HasOuterScopeInfo()) { |
| maybe_outer_scope_info = |
| direct_handle(shared_info->GetOuterScopeInfo(), isolate); |
| } |
| parser.DeserializeScopeChain( |
| isolate, &info, maybe_outer_scope_info, |
| Scope::DeserializationMode::kIncludingVariables); |
| } |
| |
| parser.ParseOnBackground(isolate, &info, script_, start_position_, |
| end_position_, function_literal_id_); |
| parser.UpdateStatistics(script_, &use_counts_, &total_preparse_skipped_); |
| |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CompileCodeBackground"); |
| RCS_SCOPE(isolate, RuntimeCallCounterIdForCompile(&info), |
| RuntimeCallStats::CounterMode::kThreadSpecific); |
| |
| MaybeHandle<SharedFunctionInfo> maybe_result; |
| if (info.literal() != nullptr) { |
| if (toplevel_script_compilation) { |
| CreateTopLevelSharedFunctionInfo(&info, script_, isolate); |
| } else { |
| // Clone into a placeholder SFI for storing the results. |
| info.literal()->set_shared_function_info( |
| isolate->factory()->CloneSharedFunctionInfo( |
| input_shared_info_.ToHandleChecked())); |
| } |
| |
| if (IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs( |
| isolate, script_, &info, reusable_state->allocator(), |
| &is_compiled_scope_, &finalize_unoptimized_compilation_data_, |
| &jobs_to_retry_finalization_on_main_thread_)) { |
| maybe_result = info.literal()->shared_function_info(); |
| } |
| } |
| |
| if (maybe_result.is_null()) { |
| PrepareException(isolate, &info); |
| } else if (v8_flags.enable_slow_asserts) { |
| #ifdef ENABLE_SLOW_DCHECKS |
| MergeAssumptionChecker checker(isolate); |
| checker.IterateObjects(*maybe_result.ToHandleChecked()); |
| #endif |
| } |
| |
| outer_function_sfi_ = isolate->heap()->NewPersistentMaybeHandle(maybe_result); |
| DCHECK(isolate->heap()->ContainsPersistentHandle(script_.location())); |
| persistent_handles_ = isolate->heap()->DetachPersistentHandles(); |
| } |
| |
| // A class which traverses the constant pools of newly compiled |
| // SharedFunctionInfos and updates any pointers which need updating. |
| class ConstantPoolPointerForwarder { |
| public: |
| explicit ConstantPoolPointerForwarder(PtrComprCageBase cage_base, |
| LocalHeap* local_heap, |
| DirectHandle<Script> old_script) |
| : cage_base_(cage_base), |
| local_heap_(local_heap), |
| old_script_(old_script) {} |
| |
| void AddBytecodeArray(Tagged<BytecodeArray> bytecode_array) { |
| CHECK(IsBytecodeArray(bytecode_array)); |
| bytecode_arrays_to_update_.emplace_back(bytecode_array, local_heap_); |
| } |
| |
| void RecordScopeInfos(Tagged<MaybeObject> maybe_old_info) { |
| RecordScopeInfos(maybe_old_info.GetHeapObjectAssumeWeak()); |
| } |
| |
| // Record all scope infos relevant for a shared function info or scope info |
| // (recorded for eval). |
| void RecordScopeInfos(Tagged<HeapObject> info) { |
| if (!v8_flags.reuse_scope_infos) return; |
| Tagged<ScopeInfo> scope_info; |
| if (Is<SharedFunctionInfo>(info)) { |
| Tagged<SharedFunctionInfo> old_sfi = Cast<SharedFunctionInfo>(info); |
| // Also record context-having own scope infos for SFIs. |
| if (!old_sfi->scope_info()->IsEmpty() && |
| old_sfi->scope_info()->HasContext()) { |
| scope_info = old_sfi->scope_info(); |
| } else if (old_sfi->HasOuterScopeInfo()) { |
| scope_info = old_sfi->GetOuterScopeInfo(); |
| } else { |
| return; |
| } |
| } else { |
| scope_info = Cast<ScopeInfo>(info); |
| } |
| |
| while (true) { |
| auto it = scope_infos_to_update_.find(scope_info->UniqueIdInScript()); |
| if (it != scope_infos_to_update_.end()) { |
| // Once we find an already recorded scope info, it need to match the one |
| // on the chain. |
| if (V8_UNLIKELY(*it->second != scope_info)) { |
| info->Print(); |
| (*it->second)->Print(); |
| scope_info->Print(); |
| UNREACHABLE(); |
| } |
| return; |
| } |
| scope_infos_to_update_[scope_info->UniqueIdInScript()] = |
| handle(scope_info, local_heap_); |
| if (!scope_info->HasOuterScopeInfo()) break; |
| scope_info = scope_info->OuterScopeInfo(); |
| } |
| } |
| |
| // Runs the update after the setup functions above specified the work to do. |
| void IterateAndForwardPointers() { |
| DCHECK(HasAnythingToForward()); |
| for (DirectHandle<BytecodeArray> entry : bytecode_arrays_to_update_) { |
| local_heap_->Safepoint(); |
| DisallowGarbageCollection no_gc; |
| IterateConstantPool(entry->constant_pool()); |
| } |
| } |
| |
| void set_has_shared_function_info_to_forward() { |
| has_shared_function_info_to_forward_ = true; |
| } |
| |
| bool HasAnythingToForward() const { |
| return has_shared_function_info_to_forward_ || |
| !scope_infos_to_update_.empty(); |
| } |
| |
| // Find an own scope info for the sfi based on the UniqueIdInScript that the |
| // own scope info would have. This works even if the SFI doesn't yet have a |
| // scope info attached by computing UniqueIdInScript from the SFI position. |
| // |
| // This should only directly be used for SFIs that already existed on the |
| // script. Their outer scope info will already be correct. |
| bool InstallOwnScopeInfo(Tagged<SharedFunctionInfo> sfi) { |
| if (!v8_flags.reuse_scope_infos) return false; |
| auto it = scope_infos_to_update_.find(sfi->UniqueIdInScript()); |
| if (it == scope_infos_to_update_.end()) return false; |
| sfi->SetScopeInfo(*it->second); |
| return true; |
| } |
| |
| // Either replace the own scope info of the sfi, or the first outer scope info |
| // that was recorded. |
| // |
| // This has to be used for all newly created SFIs since their outer scope info |
| // also may need to be reattached. |
| void UpdateScopeInfo(Tagged<SharedFunctionInfo> sfi) { |
| if (!v8_flags.reuse_scope_infos) return; |
| if (InstallOwnScopeInfo(sfi)) return; |
| if (!sfi->HasOuterScopeInfo()) return; |
| |
| Tagged<ScopeInfo> parent = |
| sfi->scope_info()->IsEmpty() ? Tagged<ScopeInfo>() : sfi->scope_info(); |
| Tagged<ScopeInfo> outer_info = sfi->GetOuterScopeInfo(); |
| |
| auto it = scope_infos_to_update_.find(outer_info->UniqueIdInScript()); |
| while (it == scope_infos_to_update_.end()) { |
| if (!outer_info->HasOuterScopeInfo()) return; |
| parent = outer_info; |
| outer_info = outer_info->OuterScopeInfo(); |
| it = scope_infos_to_update_.find(outer_info->UniqueIdInScript()); |
| } |
| if (outer_info == *it->second) return; |
| |
| VerifyScopeInfo(outer_info, *it->second); |
| |
| if (parent.is_null()) { |
| sfi->set_raw_outer_scope_info_or_feedback_metadata(*it->second); |
| } else { |
| parent->set_outer_scope_info(*it->second); |
| } |
| } |
| |
| private: |
| void VerifyScopeInfo(Tagged<ScopeInfo> scope_info, |
| Tagged<ScopeInfo> replacement) { |
| CHECK_EQ(replacement->EndPosition(), scope_info->EndPosition()); |
| CHECK_EQ(replacement->scope_type(), scope_info->scope_type()); |
| CHECK_EQ(replacement->ContextLength(), scope_info->ContextLength()); |
| } |
| template <typename TArray> |
| void IterateConstantPoolEntry(Tagged<TArray> constant_pool, int i) { |
| Tagged<Object> obj = constant_pool->get(i); |
| if (IsSmi(obj)) return; |
| Tagged<HeapObject> heap_obj = Cast<HeapObject>(obj); |
| if (IsFixedArray(heap_obj, cage_base_)) { |
| // Constant pools can have nested fixed arrays, but such relationships |
| // are acyclic and never more than a few layers deep, so recursion is |
| // fine here. |
| IterateConstantPoolNestedArray(Cast<FixedArray>(heap_obj)); |
| } else if (has_shared_function_info_to_forward_ && |
| IsSharedFunctionInfo(heap_obj, cage_base_)) { |
| VisitSharedFunctionInfo(constant_pool, i, |
| Cast<SharedFunctionInfo>(heap_obj)); |
| } else if (!scope_infos_to_update_.empty() && |
| IsScopeInfo(heap_obj, cage_base_)) { |
| VisitScopeInfo(constant_pool, i, Cast<ScopeInfo>(heap_obj)); |
| } |
| } |
| |
| template <typename TArray> |
| void VisitSharedFunctionInfo(Tagged<TArray> constant_pool, int i, |
| Tagged<SharedFunctionInfo> sfi) { |
| Tagged<MaybeObject> maybe_old_sfi = |
| old_script_->infos()->get(sfi->function_literal_id()); |
| if (maybe_old_sfi.IsWeak()) { |
| constant_pool->set( |
| i, Cast<SharedFunctionInfo>(maybe_old_sfi.GetHeapObjectAssumeWeak())); |
| } |
| } |
| |
| template <typename TArray> |
| void VisitScopeInfo(Tagged<TArray> constant_pool, int i, |
| Tagged<ScopeInfo> scope_info) { |
| auto it = scope_infos_to_update_.find(scope_info->UniqueIdInScript()); |
| // Try to replace the scope info itself with an already existing version. |
| if (it != scope_infos_to_update_.end()) { |
| if (scope_info != *it->second) { |
| VerifyScopeInfo(scope_info, *it->second); |
| constant_pool->set(i, *it->second); |
| } |
| } else if (scope_info->HasOuterScopeInfo()) { |
| // If we didn't find a match, but we have an outer scope info, try to |
| // replace the outer scope info with an already existing outer scope |
| // info. We only need to look at the direct outer scope info since we'll |
| // process all scope infos that are created by this compilation task. |
| Tagged<ScopeInfo> outer = scope_info->OuterScopeInfo(); |
| it = scope_infos_to_update_.find(outer->UniqueIdInScript()); |
| if (it != scope_infos_to_update_.end() && outer != *it->second) { |
| VerifyScopeInfo(outer, *it->second); |
| scope_info->set_outer_scope_info(*it->second); |
| } |
| } |
| } |
| |
| void IterateConstantPool(Tagged<TrustedFixedArray> constant_pool) { |
| for (int i = 0, length = constant_pool->length(); i < length; ++i) { |
| IterateConstantPoolEntry(constant_pool, i); |
| } |
| } |
| |
| void IterateConstantPoolNestedArray(Tagged<FixedArray> nested_array) { |
| for (int i = 0, length = nested_array->length(); i < length; ++i) { |
| IterateConstantPoolEntry(nested_array, i); |
| } |
| } |
| |
| PtrComprCageBase cage_base_; |
| LocalHeap* local_heap_; |
| DirectHandle<Script> old_script_; |
| std::vector<IndirectHandle<BytecodeArray>> bytecode_arrays_to_update_; |
| |
| // Indicates whether we have any shared function info to forward. |
| bool has_shared_function_info_to_forward_ = false; |
| std::unordered_map<int, IndirectHandle<ScopeInfo>> scope_infos_to_update_; |
| }; |
| |
| void BackgroundMergeTask::SetUpOnMainThread(Isolate* isolate, |
| Handle<String> source_text, |
| const ScriptDetails& script_details, |
| LanguageMode language_mode) { |
| DCHECK_EQ(state_, kNotStarted); |
| |
| HandleScope handle_scope(isolate); |
| |
| CompilationCacheScript::LookupResult lookup_result = |
| isolate->compilation_cache()->LookupScript(source_text, script_details, |
| language_mode); |
| DirectHandle<Script> script; |
| if (!lookup_result.script().ToHandle(&script)) { |
| state_ = kDone; |
| return; |
| } |
| |
| if (lookup_result.is_compiled_scope().is_compiled()) { |
| // There already exists a compiled top-level SFI, so the main thread will |
| // discard the background serialization results and use the top-level SFI |
| // from the cache, assuming the top-level SFI is still compiled by then. |
| // Thus, there is no need to keep the Script pointer for background merging. |
| // Do nothing in this case. |
| state_ = kDone; |
| } else { |
| DCHECK(lookup_result.toplevel_sfi().is_null()); |
| // A background merge is required. |
| SetUpOnMainThread(isolate, script); |
| } |
| } |
| |
| namespace { |
| void VerifyCodeMerge(Isolate* isolate, DirectHandle<Script> script) { |
| if (!v8_flags.reuse_scope_infos) return; |
| // Check that: |
| // * There aren't any duplicate scope info. Every scope/context should |
| // correspond to at most one scope info. |
| // * All published SFIs refer to the old script (i.e. we chose new vs old |
| // correctly, and updated new SFIs where needed). |
| // * All constant pool SFI entries point to an SFI referring to the old |
| // script (i.e. references were updated correctly). |
| std::unordered_map<int, Tagged<ScopeInfo>> scope_infos; |
| for (int info_idx = 0; info_idx < script->infos()->length(); info_idx++) { |
| Tagged<ScopeInfo> scope_info; |
| if (!script->infos()->get(info_idx).IsWeak()) continue; |
| Tagged<HeapObject> info = |
| script->infos()->get(info_idx).GetHeapObjectAssumeWeak(); |
| if (Is<SharedFunctionInfo>(info)) { |
| Tagged<SharedFunctionInfo> sfi = Cast<SharedFunctionInfo>(info); |
| CHECK_EQ(sfi->script(), *script); |
| |
| if (sfi->HasBytecodeArray()) { |
| Tagged<BytecodeArray> bytecode = sfi->GetBytecodeArray(isolate); |
| Tagged<TrustedFixedArray> constant_pool = bytecode->constant_pool(); |
| for (int constant_idx = 0; constant_idx < constant_pool->length(); |
| ++constant_idx) { |
| Tagged<Object> entry = constant_pool->get(constant_idx); |
| if (Is<SharedFunctionInfo>(entry)) { |
| Tagged<SharedFunctionInfo> inner_sfi = |
| Cast<SharedFunctionInfo>(entry); |
| int id = inner_sfi->function_literal_id(); |
| CHECK_EQ(MakeWeak(inner_sfi), script->infos()->get(id)); |
| CHECK_EQ(inner_sfi->script(), *script); |
| } |
| } |
| } |
| |
| if (!sfi->scope_info()->IsEmpty()) { |
| scope_info = sfi->scope_info(); |
| } else if (sfi->HasOuterScopeInfo()) { |
| scope_info = sfi->GetOuterScopeInfo(); |
| } else { |
| continue; |
| } |
| } else { |
| scope_info = Cast<ScopeInfo>(info); |
| } |
| while (true) { |
| auto it = scope_infos.find(scope_info->UniqueIdInScript()); |
| if (it != scope_infos.end()) { |
| if (*it->second != scope_info) { |
| isolate->PushParamsAndDie(reinterpret_cast<void*>(it->second->ptr()), |
| reinterpret_cast<void*>(scope_info.ptr())); |
| UNREACHABLE(); |
| } |
| break; |
| } |
| scope_infos[scope_info->UniqueIdInScript()] = scope_info; |
| if (!scope_info->HasOuterScopeInfo()) break; |
| scope_info = scope_info->OuterScopeInfo(); |
| } |
| } |
| } |
| } // namespace |
| |
| void BackgroundMergeTask::SetUpOnMainThread( |
| Isolate* isolate, DirectHandle<Script> cached_script) { |
| // Any data sent to the background thread will need to be a persistent handle. |
| #ifdef DEBUG |
| VerifyCodeMerge(isolate, cached_script); |
| #else |
| if (v8_flags.verify_code_merge) { |
| VerifyCodeMerge(isolate, cached_script); |
| } |
| #endif |
| |
| persistent_handles_ = std::make_unique<PersistentHandles>(isolate); |
| state_ = kPendingBackgroundWork; |
| cached_script_ = persistent_handles_->NewHandle(*cached_script); |
| } |
| |
| static bool force_gc_during_next_merge_for_testing_ = false; |
| |
| void BackgroundMergeTask::ForceGCDuringNextMergeForTesting() { |
| force_gc_during_next_merge_for_testing_ = true; |
| } |
| |
| void BackgroundMergeTask::BeginMergeInBackground( |
| LocalIsolate* isolate, DirectHandle<Script> new_script) { |
| DCHECK_EQ(state_, kPendingBackgroundWork); |
| |
| LocalHeap* local_heap = isolate->heap(); |
| local_heap->AttachPersistentHandles(std::move(persistent_handles_)); |
| LocalHandleScope handle_scope(local_heap); |
| DirectHandle<Script> old_script = cached_script_.ToHandleChecked(); |
| ConstantPoolPointerForwarder forwarder(isolate, local_heap, old_script); |
| |
| { |
| DisallowGarbageCollection no_gc; |
| Tagged<MaybeObject> maybe_old_toplevel_sfi = |
| old_script->infos()->get(kFunctionLiteralIdTopLevel); |
| if (maybe_old_toplevel_sfi.IsWeak()) { |
| Tagged<SharedFunctionInfo> old_toplevel_sfi = Cast<SharedFunctionInfo>( |
| maybe_old_toplevel_sfi.GetHeapObjectAssumeWeak()); |
| toplevel_sfi_from_cached_script_ = |
| local_heap->NewPersistentHandle(old_toplevel_sfi); |
| } |
| } |
| |
| // Iterate the SFI lists on both Scripts to set up the forwarding table and |
| // follow-up worklists for the main thread. |
| CHECK_EQ(old_script->infos()->length(), new_script->infos()->length()); |
| for (int i = 0; i < old_script->infos()->length(); ++i) { |
| DisallowGarbageCollection no_gc; |
| Tagged<MaybeObject> maybe_new_sfi = new_script->infos()->get(i); |
| Tagged<MaybeObject> maybe_old_info = old_script->infos()->get(i); |
| // We might have scope infos in the table if it's deserialized from a code |
| // cache. |
| if (maybe_new_sfi.IsWeak() && |
| Is<SharedFunctionInfo>(maybe_new_sfi.GetHeapObjectAssumeWeak())) { |
| Tagged<SharedFunctionInfo> new_sfi = |
| Cast<SharedFunctionInfo>(maybe_new_sfi.GetHeapObjectAssumeWeak()); |
| if (maybe_old_info.IsWeak()) { |
| forwarder.set_has_shared_function_info_to_forward(); |
| // The old script and the new script both have SharedFunctionInfos for |
| // this function literal. |
| Tagged<SharedFunctionInfo> old_sfi = |
| Cast<SharedFunctionInfo>(maybe_old_info.GetHeapObjectAssumeWeak()); |
| // Make sure to allocate a persistent handle to the old sfi whether or |
| // not it or the new sfi have bytecode -- this is necessary to keep the |
| // old sfi reference in the old script list alive, so that pointers to |
| // the new sfi are redirected to the old sfi. |
| Handle<SharedFunctionInfo> old_sfi_handle = |
| local_heap->NewPersistentHandle(old_sfi); |
| if (old_sfi->HasBytecodeArray()) { |
| // Reset the old SFI's bytecode age so that it won't likely get |
| // flushed right away. This operation might be racing against |
| // concurrent modification by another thread, but such a race is not |
| // catastrophic. |
| old_sfi->set_age(0); |
| } else if (new_sfi->HasBytecodeArray()) { |
| // Also push the old_sfi to make sure it stays alive / isn't replaced. |
| new_compiled_data_for_cached_sfis_.push_back( |
| {old_sfi_handle, local_heap->NewPersistentHandle(new_sfi)}); |
| if (old_sfi->HasOuterScopeInfo()) { |
| new_sfi->scope_info()->set_outer_scope_info( |
| old_sfi->GetOuterScopeInfo()); |
| } |
| forwarder.AddBytecodeArray(new_sfi->GetBytecodeArray(isolate)); |
| } |
| } else { |
| // The old script didn't have a SharedFunctionInfo for this function |
| // literal, so it can use the new SharedFunctionInfo. |
| new_sfi->set_script(*old_script, kReleaseStore); |
| used_new_sfis_.push_back(local_heap->NewPersistentHandle(new_sfi)); |
| if (new_sfi->HasBytecodeArray()) { |
| forwarder.AddBytecodeArray(new_sfi->GetBytecodeArray(isolate)); |
| } |
| } |
| } |
| |
| if (maybe_old_info.IsWeak()) { |
| forwarder.RecordScopeInfos(maybe_old_info); |
| // If the old script has a SFI, point to it from the new script to |
| // indicate we've already seen it and we'll reuse it if necessary (if |
| // newly compiled bytecode points to it). |
| new_script->infos()->set(i, maybe_old_info); |
| } |
| } |
| |
| // Since we are walking the script infos weak list both when figuring out |
| // which SFIs to merge above, and actually merging them below, make sure that |
| // a GC here which clears any dead weak refs or flushes any bytecode doesn't |
| // break anything. |
| if (V8_UNLIKELY(force_gc_during_next_merge_for_testing_)) { |
| // This GC is only synchronous on the main thread at the moment. |
| DCHECK(isolate->is_main_thread()); |
| local_heap->AsHeap()->CollectAllAvailableGarbage( |
| GarbageCollectionReason::kTesting); |
| } |
| |
| if (forwarder.HasAnythingToForward()) { |
| for (DirectHandle<SharedFunctionInfo> new_sfi : used_new_sfis_) { |
| forwarder.UpdateScopeInfo(*new_sfi); |
| } |
| for (const auto& new_compiled_data : new_compiled_data_for_cached_sfis_) { |
| // It's possible that new_compiled_data.cached_sfi had |
| // scope_info()->IsEmpty() while an inner function has scope info if the |
| // cached_sfi was recreated when an outer function was recompiled. If so, |
| // new_compiled_data.new_sfi does not have a reused scope info yet, and |
| // we'll have found it when we visited the inner function. Try to pick it |
| // up here. |
| forwarder.InstallOwnScopeInfo(*new_compiled_data.new_sfi); |
| } |
| forwarder.IterateAndForwardPointers(); |
| } |
| persistent_handles_ = local_heap->DetachPersistentHandles(); |
| state_ = kPendingForegroundWork; |
| } |
| |
| Handle<SharedFunctionInfo> BackgroundMergeTask::CompleteMergeInForeground( |
| Isolate* isolate, DirectHandle<Script> new_script) { |
| DCHECK_EQ(state_, kPendingForegroundWork); |
| |
| HandleScope handle_scope(isolate); |
| DirectHandle<Script> old_script = cached_script_.ToHandleChecked(); |
| ConstantPoolPointerForwarder forwarder( |
| isolate, isolate->main_thread_local_heap(), old_script); |
| |
| for (const auto& new_compiled_data : new_compiled_data_for_cached_sfis_) { |
| Tagged<SharedFunctionInfo> sfi = *new_compiled_data.cached_sfi; |
| if (!sfi->is_compiled() && new_compiled_data.new_sfi->is_compiled()) { |
| // Updating existing DebugInfos is not supported, but we don't expect |
| // uncompiled SharedFunctionInfos to contain DebugInfos. |
| DCHECK(!new_compiled_data.cached_sfi->HasDebugInfo(isolate)); |
| // The goal here is to copy every field except script from |
| // new_sfi to cached_sfi. The safest way to do so (including a DCHECK that |
| // no fields were skipped) is to first copy the script from |
| // cached_sfi to new_sfi, and then copy every field using CopyFrom. |
| new_compiled_data.new_sfi->set_script(sfi->script(kAcquireLoad), |
| kReleaseStore); |
| sfi->CopyFrom(*new_compiled_data.new_sfi, isolate); |
| } |
| } |
| |
| for (int i = 0; i < old_script->infos()->length(); ++i) { |
| Tagged<MaybeObject> maybe_old_info = old_script->infos()->get(i); |
| Tagged<MaybeObject> maybe_new_info = new_script->infos()->get(i); |
| if (maybe_new_info == maybe_old_info) continue; |
| DisallowGarbageCollection no_gc; |
| if (maybe_old_info.IsWeak()) { |
| // The old script's SFI didn't exist during the background work, but does |
| // now. This means a re-merge is necessary. Potential references to the |
| // new script's SFI need to be updated to point to the cached script's SFI |
| // instead. The cached script's SFI's outer scope infos need to be used by |
| // the new script's outer SFIs. |
| if (Is<SharedFunctionInfo>(maybe_old_info.GetHeapObjectAssumeWeak())) { |
| forwarder.set_has_shared_function_info_to_forward(); |
| } |
| forwarder.RecordScopeInfos(maybe_old_info); |
| } else { |
| old_script->infos()->set(i, maybe_new_info); |
| } |
| } |
| |
| // Most of the time, the background merge was sufficient. However, if there |
| // are any new pointers that need forwarding, a new traversal of the constant |
| // pools is required. |
| if (forwarder.HasAnythingToForward()) { |
| for (DirectHandle<SharedFunctionInfo> new_sfi : used_new_sfis_) { |
| forwarder.UpdateScopeInfo(*new_sfi); |
| if (new_sfi->HasBytecodeArray(isolate)) { |
| forwarder.AddBytecodeArray(new_sfi->GetBytecodeArray(isolate)); |
| } |
| } |
| for (const auto& new_compiled_data : new_compiled_data_for_cached_sfis_) { |
| // It's possible that cached_sfi wasn't compiled, but an inner function |
| // existed that didn't exist when be background merged. In that case, pick |
| // up the relevant scope infos. |
| Tagged<SharedFunctionInfo> sfi = *new_compiled_data.cached_sfi; |
| forwarder.InstallOwnScopeInfo(sfi); |
| if (new_compiled_data.cached_sfi->HasBytecodeArray(isolate)) { |
| forwarder.AddBytecodeArray( |
| new_compiled_data.cached_sfi->GetBytecodeArray(isolate)); |
| } |
| } |
| forwarder.IterateAndForwardPointers(); |
| } |
| |
| Tagged<MaybeObject> maybe_toplevel_sfi = |
| old_script->infos()->get(kFunctionLiteralIdTopLevel); |
| CHECK(maybe_toplevel_sfi.IsWeak()); |
| Handle<SharedFunctionInfo> result = handle( |
| Cast<SharedFunctionInfo>(maybe_toplevel_sfi.GetHeapObjectAssumeWeak()), |
| isolate); |
| |
| state_ = kDone; |
| |
| if (isolate->NeedsSourcePositions()) { |
| Script::InitLineEnds(isolate, new_script); |
| SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, result); |
| } |
| |
| #ifdef DEBUG |
| VerifyCodeMerge(isolate, old_script); |
| #else |
| if (v8_flags.verify_code_merge) { |
| VerifyCodeMerge(isolate, old_script); |
| } |
| #endif |
| |
| return handle_scope.CloseAndEscape(result); |
| } |
| |
| MaybeHandle<SharedFunctionInfo> BackgroundCompileTask::FinalizeScript( |
| Isolate* isolate, DirectHandle<String> source, |
| const ScriptDetails& script_details, |
| MaybeDirectHandle<Script> maybe_cached_script) { |
| ScriptOriginOptions origin_options = script_details.origin_options; |
| |
| DCHECK(flags_.is_toplevel()); |
| DCHECK_EQ(flags_.is_module(), origin_options.IsModule()); |
| |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result; |
| Handle<Script> script = script_; |
| |
| // We might not have been able to finalize all jobs on the background |
| // thread (e.g. asm.js jobs), so finalize those deferred jobs now. |
| if (FinalizeDeferredUnoptimizedCompilationJobs( |
| isolate, script, &jobs_to_retry_finalization_on_main_thread_, |
| compile_state_.pending_error_handler(), |
| &finalize_unoptimized_compilation_data_)) { |
| maybe_result = outer_function_sfi_; |
| } |
| |
| if (DirectHandle<Script> cached_script; |
| maybe_cached_script.ToHandle(&cached_script) && !maybe_result.is_null()) { |
| BackgroundMergeTask merge; |
| merge.SetUpOnMainThread(isolate, cached_script); |
| CHECK(merge.HasPendingBackgroundWork()); |
| merge.BeginMergeInBackground(isolate->AsLocalIsolate(), script); |
| CHECK(merge.HasPendingForegroundWork()); |
| DirectHandle<SharedFunctionInfo> result = |
| merge.CompleteMergeInForeground(isolate, script); |
| maybe_result = result; |
| script = handle(Cast<Script>(result->script()), isolate); |
| DCHECK(Object::StrictEquals(script->source(), *source)); |
| DCHECK(isolate->factory()->script_list()->Contains(MakeWeak(*script))); |
| } else { |
| Script::SetSource(isolate, script, source); |
| script->set_origin_options(origin_options); |
| |
| // The one post-hoc fix-up: Add the script to the script list. |
| DirectHandle<WeakArrayList> scripts = isolate->factory()->script_list(); |
| scripts = WeakArrayList::Append(isolate, scripts, |
| MaybeObjectDirectHandle::Weak(script)); |
| isolate->heap()->SetRootScriptList(*scripts); |
| |
| // Set the script fields after finalization, to keep this path the same |
| // between main-thread and off-thread finalization. |
| { |
| DisallowGarbageCollection no_gc; |
| SetScriptFieldsFromDetails(isolate, *script, script_details, &no_gc); |
| LOG(isolate, ScriptDetails(*script)); |
| } |
| } |
| |
| ReportStatistics(isolate); |
| |
| DirectHandle<SharedFunctionInfo> result; |
| if (!maybe_result.ToHandle(&result)) { |
| FailWithPreparedException(isolate, script, |
| compile_state_.pending_error_handler()); |
| return kNullMaybeHandle; |
| } |
| |
| FinalizeUnoptimizedScriptCompilation(isolate, script, flags_, &compile_state_, |
| finalize_unoptimized_compilation_data_); |
| |
| return handle(*result, isolate); |
| } |
| |
| bool BackgroundCompileTask::FinalizeFunction( |
| Isolate* isolate, Compiler::ClearExceptionFlag flag) { |
| DCHECK(!flags_.is_toplevel()); |
| |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result; |
| DirectHandle<SharedFunctionInfo> input_shared_info = |
| input_shared_info_.ToHandleChecked(); |
| |
| // The UncompiledData on the input SharedFunctionInfo will have a pointer to |
| // the LazyCompileDispatcher Job that launched this task, which will now be |
| // considered complete, so clear that regardless of whether the finalize |
| // succeeds or not. |
| input_shared_info->ClearUncompiledDataJobPointer(isolate); |
| |
| // We might not have been able to finalize all jobs on the background |
| // thread (e.g. asm.js jobs), so finalize those deferred jobs now. |
| if (FinalizeDeferredUnoptimizedCompilationJobs( |
| isolate, script_, &jobs_to_retry_finalization_on_main_thread_, |
| compile_state_.pending_error_handler(), |
| &finalize_unoptimized_compilation_data_)) { |
| maybe_result = outer_function_sfi_; |
| } |
| |
| ReportStatistics(isolate); |
| |
| DirectHandle<SharedFunctionInfo> result; |
| if (!maybe_result.ToHandle(&result)) { |
| FailWithPreparedException(isolate, script_, |
| compile_state_.pending_error_handler(), flag); |
| return false; |
| } |
| |
| FinalizeUnoptimizedCompilation(isolate, script_, flags_, &compile_state_, |
| finalize_unoptimized_compilation_data_); |
| |
| // Move the compiled data from the placeholder SFI back to the real SFI. |
| input_shared_info->CopyFrom(*result, isolate); |
| |
| return true; |
| } |
| |
| void BackgroundCompileTask::AbortFunction() { |
| // The UncompiledData on the input SharedFunctionInfo will have a pointer to |
| // the LazyCompileDispatcher Job that launched this task, which is about to be |
| // deleted, so clear that to avoid the SharedFunctionInfo from pointing to |
| // deallocated memory. |
| input_shared_info_.ToHandleChecked()->ClearUncompiledDataJobPointer( |
| isolate_for_local_isolate_); |
| } |
| |
| void BackgroundCompileTask::ReportStatistics(Isolate* isolate) { |
| // Update use-counts. |
| for (auto feature : use_counts_) { |
| isolate->CountUsage(feature); |
| } |
| } |
| |
| BackgroundDeserializeTask::BackgroundDeserializeTask( |
| Isolate* isolate, std::unique_ptr<ScriptCompiler::CachedData> cached_data) |
| : isolate_for_local_isolate_(isolate), |
| cached_data_(cached_data->data, cached_data->length), |
| timer_(isolate->counters()->deserialize_script_on_background()) { |
| // If the passed in cached data has ownership of the buffer, move it to the |
| // task. |
| if (cached_data->buffer_policy == ScriptCompiler::CachedData::BufferOwned && |
| !cached_data_.HasDataOwnership()) { |
| cached_data->buffer_policy = ScriptCompiler::CachedData::BufferNotOwned; |
| cached_data_.AcquireDataOwnership(); |
| } |
| } |
| |
| void BackgroundDeserializeTask::Run() { |
| TimedHistogramScope timer(timer_, nullptr, &background_time_in_microseconds_); |
| LocalIsolate isolate(isolate_for_local_isolate_, ThreadKind::kBackground); |
| UnparkedScope unparked_scope(&isolate); |
| LocalHandleScope handle_scope(&isolate); |
| |
| DirectHandle<SharedFunctionInfo> inner_result; |
| off_thread_data_ = |
| CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_); |
| if (v8_flags.enable_slow_asserts && off_thread_data_.HasResult()) { |
| #ifdef ENABLE_SLOW_DCHECKS |
| MergeAssumptionChecker checker(&isolate); |
| checker.IterateObjects(*off_thread_data_.GetOnlyScript(isolate.heap())); |
| #endif |
| } |
| } |
| |
| void BackgroundDeserializeTask::SourceTextAvailable( |
| Isolate* isolate, Handle<String> source_text, |
| const ScriptDetails& script_details) { |
| DCHECK_EQ(isolate, isolate_for_local_isolate_); |
| LanguageMode language_mode = construct_language_mode(v8_flags.use_strict); |
| background_merge_task_.SetUpOnMainThread(isolate, source_text, script_details, |
| language_mode); |
| } |
| |
| bool BackgroundDeserializeTask::ShouldMergeWithExistingScript() const { |
| DCHECK(v8_flags.merge_background_deserialized_script_with_compilation_cache); |
| return background_merge_task_.HasPendingBackgroundWork() && |
| off_thread_data_.HasResult(); |
| } |
| |
| void BackgroundDeserializeTask::MergeWithExistingScript() { |
| DCHECK(ShouldMergeWithExistingScript()); |
| |
| LocalIsolate isolate(isolate_for_local_isolate_, ThreadKind::kBackground); |
| UnparkedScope unparked_scope(&isolate); |
| LocalHandleScope handle_scope(isolate.heap()); |
| |
| background_merge_task_.BeginMergeInBackground( |
| &isolate, off_thread_data_.GetOnlyScript(isolate.heap())); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> BackgroundDeserializeTask::Finish( |
| Isolate* isolate, DirectHandle<String> source, |
| const ScriptDetails& script_details) { |
| return CodeSerializer::FinishOffThreadDeserialize( |
| isolate, std::move(off_thread_data_), &cached_data_, source, |
| script_details, &background_merge_task_); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of Compiler |
| |
| // static |
| bool Compiler::CollectSourcePositions( |
| Isolate* isolate, DirectHandle<SharedFunctionInfo> shared_info) { |
| DCHECK(shared_info->is_compiled()); |
| DCHECK(shared_info->HasBytecodeArray()); |
| DCHECK(!shared_info->GetBytecodeArray(isolate)->HasSourcePositionTable()); |
| |
| // Source position collection should be context independent. |
| NullContextScope null_context_scope(isolate); |
| |
| // Collecting source positions requires allocating a new source position |
| // table. |
| DCHECK(AllowHeapAllocation::IsAllowed()); |
| |
| Handle<BytecodeArray> bytecode = |
| handle(shared_info->GetBytecodeArray(isolate), isolate); |
| |
| // TODO(v8:8510): Push the CLEAR_EXCEPTION flag or something like it down into |
| // the parser so it aborts without setting an exception, which then |
| // gets thrown. This would avoid the situation where potentially we'd reparse |
| // several times (running out of stack each time) before hitting this limit. |
| if (GetCurrentStackPosition() < isolate->stack_guard()->real_climit()) { |
| // Stack is already exhausted. |
| bytecode->SetSourcePositionsFailedToCollect(); |
| return false; |
| } |
| |
| // Unfinalized scripts don't yet have the proper source string attached and |
| // thus can't be reparsed. |
| if (Cast<Script>(shared_info->script())->IsMaybeUnfinalized(isolate)) { |
| bytecode->SetSourcePositionsFailedToCollect(); |
| return false; |
| } |
| |
| DCHECK(AllowCompilation::IsAllowed(isolate)); |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| |
| DCHECK(!isolate->has_exception()); |
| VMState<BYTECODE_COMPILER> state(isolate); |
| PostponeInterruptsScope postpone(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileCollectSourcePositions); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CollectSourcePositions"); |
| NestedTimedHistogramScope timer( |
| isolate->counters()->collect_source_positions()); |
| |
| // Set up parse info. |
| UnoptimizedCompileFlags flags = |
| UnoptimizedCompileFlags::ForFunctionCompile(isolate, *shared_info); |
| flags.set_collect_source_positions(true); |
| flags.set_is_reparse(true); |
| // Prevent parallel tasks from being spawned by this job. |
| flags.set_post_parallel_compile_tasks_for_eager_toplevel(false); |
| flags.set_post_parallel_compile_tasks_for_lazy(false); |
| |
| UnoptimizedCompileState compile_state; |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); |
| |
| // Parse and update ParseInfo with the results. Don't update parsing |
| // statistics since we've already parsed the code before. |
| if (!parsing::ParseAny(&parse_info, shared_info, isolate, |
| parsing::ReportStatisticsMode::kNo)) { |
| // Parsing failed probably as a result of stack exhaustion. |
| bytecode->SetSourcePositionsFailedToCollect(); |
| return FailAndClearException(isolate); |
| } |
| |
| // Character stream shouldn't be used again. |
| parse_info.ResetCharacterStream(); |
| |
| // Generate the unoptimized bytecode. |
| // TODO(v8:8510): Consider forcing preparsing of inner functions to avoid |
| // wasting time fully parsing them when they won't ever be used. |
| std::unique_ptr<UnoptimizedCompilationJob> job; |
| { |
| job = interpreter::Interpreter::NewSourcePositionCollectionJob( |
| &parse_info, parse_info.literal(), bytecode, isolate->allocator(), |
| isolate->main_thread_local_isolate()); |
| |
| if (!job || job->ExecuteJob() != CompilationJob::SUCCEEDED || |
| job->FinalizeJob(shared_info, isolate) != CompilationJob::SUCCEEDED) { |
| // Recompiling failed probably as a result of stack exhaustion. |
| bytecode->SetSourcePositionsFailedToCollect(); |
| return FailAndClearException(isolate); |
| } |
| } |
| |
| DCHECK(job->compilation_info()->flags().collect_source_positions()); |
| |
| // If debugging, make sure that instrumented bytecode has the source position |
| // table set on it as well. |
| if (std::optional<Tagged<DebugInfo>> debug_info = |
| shared_info->TryGetDebugInfo(isolate)) { |
| if (debug_info.value()->HasInstrumentedBytecodeArray()) { |
| Tagged<TrustedByteArray> source_position_table = |
| job->compilation_info()->bytecode_array()->SourcePositionTable(); |
| shared_info->GetActiveBytecodeArray(isolate)->set_source_position_table( |
| source_position_table, kReleaseStore); |
| } |
| } |
| |
| DCHECK(!isolate->has_exception()); |
| DCHECK(shared_info->is_compiled_scope(isolate).is_compiled()); |
| return true; |
| } |
| |
| // static |
| bool Compiler::Compile(Isolate* isolate, Handle<SharedFunctionInfo> shared_info, |
| ClearExceptionFlag flag, |
| IsCompiledScope* is_compiled_scope, |
| CreateSourcePositions create_source_positions_flag) { |
| // We should never reach here if the function is already compiled. |
| DCHECK(!shared_info->is_compiled()); |
| DCHECK(!is_compiled_scope->is_compiled()); |
| DCHECK(AllowCompilation::IsAllowed(isolate)); |
| DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| DCHECK(!isolate->has_exception()); |
| DCHECK(!shared_info->HasBytecodeArray()); |
| |
| VMState<BYTECODE_COMPILER> state(isolate); |
| PostponeInterruptsScope postpone(isolate); |
| TimerEventScope<TimerEventCompileCode> compile_timer(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileFunction); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompileCode"); |
| AggregatedHistogramTimerScope timer(isolate->counters()->compile_lazy()); |
| |
| Handle<Script> script(Cast<Script>(shared_info->script()), isolate); |
| |
| // Set up parse info. |
| UnoptimizedCompileFlags flags = |
| UnoptimizedCompileFlags::ForFunctionCompile(isolate, *shared_info); |
| if (create_source_positions_flag == CreateSourcePositions::kYes) { |
| flags.set_collect_source_positions(true); |
| } |
| |
| UnoptimizedCompileState compile_state; |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); |
| |
| // Check if the compiler dispatcher has shared_info enqueued for compile. |
| LazyCompileDispatcher* dispatcher = isolate->lazy_compile_dispatcher(); |
| if (dispatcher && dispatcher->IsEnqueued(shared_info)) { |
| if (!dispatcher->FinishNow(shared_info)) { |
| return FailWithException(isolate, script, &parse_info, flag); |
| } |
| *is_compiled_scope = shared_info->is_compiled_scope(isolate); |
| DCHECK(is_compiled_scope->is_compiled()); |
| return true; |
| } |
| |
| if (shared_info->HasUncompiledDataWithPreparseData()) { |
| parse_info.set_consumed_preparse_data(ConsumedPreparseData::For( |
| isolate, handle(shared_info->uncompiled_data_with_preparse_data(isolate) |
| ->preparse_data(), |
| isolate))); |
| } |
| |
| // Parse and update ParseInfo with the results. |
| if (!parsing::ParseAny(&parse_info, shared_info, isolate, |
| parsing::ReportStatisticsMode::kYes)) { |
| return FailWithException(isolate, script, &parse_info, flag); |
| } |
| parse_info.literal()->set_shared_function_info(shared_info); |
| |
| // Generate the unoptimized bytecode or asm-js data. |
| FinalizeUnoptimizedCompilationDataList |
| finalize_unoptimized_compilation_data_list; |
| |
| if (!IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs( |
| isolate, script, &parse_info, isolate->allocator(), is_compiled_scope, |
| &finalize_unoptimized_compilation_data_list, nullptr)) { |
| return FailWithException(isolate, script, &parse_info, flag); |
| } |
| |
| FinalizeUnoptimizedCompilation(isolate, script, flags, &compile_state, |
| finalize_unoptimized_compilation_data_list); |
| |
| if (v8_flags.always_sparkplug) { |
| CompileAllWithBaseline(isolate, finalize_unoptimized_compilation_data_list); |
| } |
| |
| if (script->produce_compile_hints()) { |
| // Log lazy function compilation. |
| DirectHandle<ArrayList> list; |
| if (IsUndefined(script->compiled_lazy_function_positions())) { |
| constexpr int kInitialLazyFunctionPositionListSize = 100; |
| list = ArrayList::New(isolate, kInitialLazyFunctionPositionListSize); |
| } else { |
| list = direct_handle( |
| Cast<ArrayList>(script->compiled_lazy_function_positions()), isolate); |
| } |
| list = ArrayList::Add(isolate, list, |
| Smi::FromInt(shared_info->StartPosition())); |
| script->set_compiled_lazy_function_positions(*list); |
| } |
| |
| DCHECK(!isolate->has_exception()); |
| DCHECK(is_compiled_scope->is_compiled()); |
| return true; |
| } |
| |
| // static |
| bool Compiler::Compile(Isolate* isolate, DirectHandle<JSFunction> function, |
| ClearExceptionFlag flag, |
| IsCompiledScope* is_compiled_scope) { |
| // We should never reach here if the function is already compiled or |
| // optimized. |
| DCHECK(!function->is_compiled(isolate)); |
| DCHECK_IMPLIES(function->has_feedback_vector() && |
| function->IsTieringRequestedOrInProgress(), |
| function->shared()->is_compiled()); |
| DCHECK_IMPLIES(function->HasAvailableOptimizedCode(isolate), |
| function->shared()->is_compiled()); |
| |
| // Reset the JSFunction if we are recompiling due to the bytecode having been |
| // flushed. |
| function->ResetIfCodeFlushed(isolate); |
| |
| Handle<SharedFunctionInfo> shared_info(function->shared(), isolate); |
| |
| // Ensure shared function info is compiled. |
| *is_compiled_scope = shared_info->is_compiled_scope(isolate); |
| if (!is_compiled_scope->is_compiled() && |
| !Compile(isolate, shared_info, flag, is_compiled_scope)) { |
| return false; |
| } |
| |
| DCHECK(is_compiled_scope->is_compiled()); |
| DirectHandle<Code> code(shared_info->GetCode(isolate), isolate); |
| |
| // Initialize the feedback cell for this JSFunction and reset the interrupt |
| // budget for feedback vector allocation even if there is a closure feedback |
| // cell array. We are re-compiling when we have a closure feedback cell array |
| // which means we are compiling after a bytecode flush. |
| // TODO(verwaest/mythria): Investigate if allocating feedback vector |
| // immediately after a flush would be better. |
| JSFunction::InitializeFeedbackCell(function, is_compiled_scope, true); |
| function->ResetTieringRequests(); |
| |
| function->UpdateCode(*code); |
| |
| // Optimize now if --always-turbofan is enabled. |
| #if V8_ENABLE_WEBASSEMBLY |
| if (v8_flags.always_turbofan && !function->shared()->HasAsmWasmData()) { |
| #else |
| if (v8_flags.always_turbofan) { |
| #endif // V8_ENABLE_WEBASSEMBLY |
| DCHECK(!function->tiering_in_progress()); |
| CompilerTracer::TraceOptimizeForAlwaysOpt(isolate, function, |
| CodeKindForTopTier()); |
| |
| const CodeKind code_kind = CodeKindForTopTier(); |
| const ConcurrencyMode concurrency_mode = ConcurrencyMode::kSynchronous; |
| |
| if (v8_flags.stress_concurrent_inlining && |
| isolate->concurrent_recompilation_enabled() && |
| isolate->node_observer() == nullptr) { |
| SpawnDuplicateConcurrentJobForStressTesting(isolate, function, |
| concurrency_mode, code_kind); |
| } |
| |
| DirectHandle<Code> maybe_code; |
| if (GetOrCompileOptimized(isolate, function, concurrency_mode, code_kind) |
| .ToHandle(&maybe_code)) { |
| code = maybe_code; |
| function->UpdateOptimizedCode(isolate, *code); |
| } |
| } |
| |
| // Install a feedback vector if necessary. |
| if (code->kind() == CodeKind::BASELINE) { |
| JSFunction::EnsureFeedbackVector(isolate, function, is_compiled_scope); |
| } |
| |
| // Check postconditions on success. |
| DCHECK(!isolate->has_exception()); |
| DCHECK(function->shared()->is_compiled()); |
| DCHECK(function->is_compiled(isolate)); |
| return true; |
| } |
| |
| // static |
| bool Compiler::CompileSharedWithBaseline(Isolate* isolate, |
| Handle<SharedFunctionInfo> shared, |
| Compiler::ClearExceptionFlag flag, |
| IsCompiledScope* is_compiled_scope) { |
| // We shouldn't be passing uncompiled functions into this function. |
| DCHECK(is_compiled_scope->is_compiled()); |
| |
| // Early return for already baseline-compiled functions. |
| if (shared->HasBaselineCode()) return true; |
| |
| // Check if we actually can compile with baseline. |
| if (!CanCompileWithBaseline(isolate, *shared)) return false; |
| |
| StackLimitCheck check(isolate); |
| if (check.JsHasOverflowed(kStackSpaceRequiredForCompilation * KB)) { |
| if (flag == Compiler::KEEP_EXCEPTION) { |
| isolate->StackOverflow(); |
| } |
| return false; |
| } |
| |
| CompilerTracer::TraceStartBaselineCompile(isolate, shared); |
| Handle<Code> code; |
| base::TimeDelta time_taken; |
| { |
| base::ScopedTimer timer( |
| v8_flags.trace_baseline || v8_flags.log_function_events ? &time_taken |
| : nullptr); |
| if (!GenerateBaselineCode(isolate, shared).ToHandle(&code)) { |
| // TODO(leszeks): This can only fail because of an OOM. Do we want to |
| // report these somehow, or silently ignore them? |
| return false; |
| } |
| shared->set_baseline_code(*code, kReleaseStore); |
| shared->set_age(0); |
| } |
| double time_taken_ms = time_taken.InMillisecondsF(); |
| |
| CompilerTracer::TraceFinishBaselineCompile(isolate, shared, time_taken_ms); |
| |
| if (IsScript(shared->script())) { |
| LogFunctionCompilation( |
| isolate, LogEventListener::CodeTag::kFunction, |
| direct_handle(Cast<Script>(shared->script()), isolate), shared, |
| DirectHandle<FeedbackVector>(), Cast<AbstractCode>(code), |
| CodeKind::BASELINE, time_taken_ms); |
| } |
| return true; |
| } |
| |
| // static |
| bool Compiler::CompileBaseline(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| ClearExceptionFlag flag, |
| IsCompiledScope* is_compiled_scope) { |
| Handle<SharedFunctionInfo> shared(function->shared(isolate), isolate); |
| if (!CompileSharedWithBaseline(isolate, shared, flag, is_compiled_scope)) { |
| return false; |
| } |
| |
| // Baseline code needs a feedback vector. |
| JSFunction::EnsureFeedbackVector(isolate, function, is_compiled_scope); |
| |
| Tagged<Code> baseline_code = shared->baseline_code(kAcquireLoad); |
| DCHECK_EQ(baseline_code->kind(), CodeKind::BASELINE); |
| function->UpdateCodeKeepTieringRequests(baseline_code); |
| return true; |
| } |
| |
| // static |
| MaybeHandle<SharedFunctionInfo> Compiler::CompileToplevel( |
| ParseInfo* parse_info, Handle<Script> script, Isolate* isolate, |
| IsCompiledScope* is_compiled_scope) { |
| return v8::internal::CompileToplevel(parse_info, script, kNullMaybeHandle, |
| isolate, is_compiled_scope); |
| } |
| |
| // static |
| bool Compiler::FinalizeBackgroundCompileTask(BackgroundCompileTask* task, |
| Isolate* isolate, |
| ClearExceptionFlag flag) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.FinalizeBackgroundCompileTask"); |
| RCS_SCOPE(isolate, |
| RuntimeCallCounterId::kCompileFinalizeBackgroundCompileTask); |
| |
| HandleScope scope(isolate); |
| |
| if (!task->FinalizeFunction(isolate, flag)) return false; |
| |
| DCHECK(!isolate->has_exception()); |
| return true; |
| } |
| |
| // static |
| void Compiler::CompileOptimized(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| ConcurrencyMode mode, CodeKind code_kind) { |
| DCHECK(CodeKindIsOptimizedJSFunction(code_kind)); |
| DCHECK(AllowCompilation::IsAllowed(isolate)); |
| |
| if (v8_flags.stress_concurrent_inlining && |
| isolate->concurrent_recompilation_enabled() && IsSynchronous(mode) && |
| isolate->node_observer() == nullptr) { |
| SpawnDuplicateConcurrentJobForStressTesting(isolate, function, mode, |
| code_kind); |
| } |
| |
| #ifdef DEBUG |
| if (V8_ENABLE_LEAPTIERING_BOOL && mode == ConcurrencyMode::kConcurrent) { |
| DCHECK_IMPLIES(code_kind == CodeKind::MAGLEV, |
| !function->ActiveTierIsMaglev(isolate)); |
| DCHECK_IMPLIES(code_kind == CodeKind::TURBOFAN_JS, |
| !function->ActiveTierIsTurbofan(isolate)); |
| } |
| bool tiering_was_in_progress = function->tiering_in_progress(); |
| DCHECK_IMPLIES(tiering_was_in_progress, mode != ConcurrencyMode::kConcurrent); |
| #endif // DEBUG |
| |
| DirectHandle<Code> code; |
| if (GetOrCompileOptimized(isolate, function, mode, code_kind) |
| .ToHandle(&code)) { |
| function->UpdateOptimizedCode(isolate, *code); |
| DCHECK_IMPLIES(v8_flags.log_function_events, |
| function->IsLoggingRequested(isolate)); |
| } else { |
| #ifdef V8_ENABLE_LEAPTIERING |
| // We can get here from CompileLazy when we have requested optimized code |
| // which isn't yet ready. Without Leaptiering, we'll already have set the |
| // function's code to the bytecode/baseline code on the SFI. However, in the |
| // leaptiering case, we potentially need to do this now. |
| if (!function->is_compiled(isolate)) { |
| function->UpdateCodeKeepTieringRequests( |
| function->shared()->GetCode(isolate)); |
| } |
| #endif // V8_ENABLE_LEAPTIERING |
| } |
| |
| #ifdef DEBUG |
| DCHECK(!isolate->has_exception()); |
| DCHECK(function->is_compiled(isolate)); |
| DCHECK(function->shared()->HasBytecodeArray()); |
| |
| DCHECK_IMPLIES(function->IsTieringRequestedOrInProgress() && |
| !function->IsLoggingRequested(isolate), |
| function->tiering_in_progress()); |
| if (!v8_flags.always_turbofan) { |
| // Before a maglev optimization job is started we might have to compile |
| // bytecode. This can trigger a turbofan compilation if always_turbofan is |
| // set. Therefore we need to skip this dcheck in that case. |
| DCHECK_IMPLIES(!tiering_was_in_progress && function->tiering_in_progress(), |
| function->ChecksTieringState(isolate)); |
| } |
| DCHECK_IMPLIES(!tiering_was_in_progress && function->tiering_in_progress(), |
| IsConcurrent(mode)); |
| #endif // DEBUG |
| } |
| |
| // static |
| MaybeDirectHandle<SharedFunctionInfo> Compiler::CompileForLiveEdit( |
| ParseInfo* parse_info, Handle<Script> script, |
| MaybeDirectHandle<ScopeInfo> outer_scope_info, Isolate* isolate) { |
| IsCompiledScope is_compiled_scope; |
| return v8::internal::CompileToplevel(parse_info, script, outer_scope_info, |
| isolate, &is_compiled_scope); |
| } |
| |
| // static |
| MaybeDirectHandle<JSFunction> Compiler::GetFunctionFromEval( |
| DirectHandle<String> source, DirectHandle<SharedFunctionInfo> outer_info, |
| DirectHandle<Context> context, LanguageMode language_mode, |
| ParseRestriction restriction, int parameters_end_pos, int eval_position, |
| ParsingWhileDebugging parsing_while_debugging) { |
| Isolate* isolate = context->GetIsolate(); |
| |
| // The cache lookup key needs to be aware of the separation between the |
| // parameters and the body to prevent this valid invocation: |
| // Function("", "function anonymous(\n/**/) {\n}"); |
| // from adding an entry that falsely approves this invalid invocation: |
| // Function("\n/**/) {\nfunction anonymous(", "}"); |
| // The actual eval_position for indirect eval and CreateDynamicFunction |
| // is unused (just 0), which means it's an available field to use to indicate |
| // this separation. But to make sure we're not causing other false hits, we |
| // negate the scope position. |
| int eval_cache_position = eval_position; |
| if (restriction == ONLY_SINGLE_FUNCTION_LITERAL && |
| parameters_end_pos != kNoSourcePosition) { |
| // use the parameters_end_pos as the eval_position in the eval cache. |
| DCHECK_EQ(eval_position, kNoSourcePosition); |
| eval_cache_position = -parameters_end_pos; |
| } |
| CompilationCache* compilation_cache = isolate->compilation_cache(); |
| InfoCellPair eval_result = compilation_cache->LookupEval( |
| source, outer_info, context, language_mode, eval_cache_position); |
| DirectHandle<FeedbackCell> feedback_cell; |
| if (eval_result.has_feedback_cell()) { |
| feedback_cell = direct_handle(eval_result.feedback_cell(), isolate); |
| } |
| |
| DirectHandle<SharedFunctionInfo> shared_info; |
| Handle<Script> script; |
| IsCompiledScope is_compiled_scope; |
| bool allow_eval_cache; |
| if (eval_result.has_shared()) { |
| shared_info = |
| DirectHandle<SharedFunctionInfo>(eval_result.shared(), isolate); |
| script = Handle<Script>(Cast<Script>(shared_info->script()), isolate); |
| is_compiled_scope = shared_info->is_compiled_scope(isolate); |
| allow_eval_cache = true; |
| } else { |
| UnoptimizedCompileFlags flags = UnoptimizedCompileFlags::ForToplevelCompile( |
| isolate, true, language_mode, REPLMode::kNo, ScriptType::kClassic, |
| v8_flags.lazy_eval); |
| flags.set_is_eval(true); |
| flags.set_parsing_while_debugging(parsing_while_debugging); |
| DCHECK(!flags.is_module()); |
| flags.set_parse_restriction(restriction); |
| |
| UnoptimizedCompileState compile_state; |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); |
| parse_info.set_parameters_end_pos(parameters_end_pos); |
| |
| MaybeDirectHandle<ScopeInfo> maybe_outer_scope_info; |
| if (!IsNativeContext(*context)) { |
| maybe_outer_scope_info = direct_handle(context->scope_info(), isolate); |
| } |
| script = parse_info.CreateScript( |
| isolate, source, kNullMaybeHandle, |
| OriginOptionsForEval(outer_info->script(), parsing_while_debugging)); |
| script->set_eval_from_shared(*outer_info); |
| if (eval_position == kNoSourcePosition) { |
| // If the position is missing, attempt to get the code offset by |
| // walking the stack. Do not translate the code offset into source |
| // position, but store it as negative value for lazy translation. |
| DebuggableStackFrameIterator it(isolate); |
| if (!it.done() && it.is_javascript()) { |
| FrameSummary summary = it.GetTopValidFrame(); |
| script->set_eval_from_shared( |
| summary.AsJavaScript().function()->shared()); |
| script->set_origin_options( |
| OriginOptionsForEval(*summary.script(), parsing_while_debugging)); |
| eval_position = -summary.code_offset(); |
| } else { |
| eval_position = 0; |
| } |
| } |
| script->set_eval_from_position(eval_position); |
| |
| if (!v8::internal::CompileToplevel(&parse_info, script, |
| maybe_outer_scope_info, isolate, |
| &is_compiled_scope) |
| .ToHandle(&shared_info)) { |
| return MaybeDirectHandle<JSFunction>(); |
| } |
| allow_eval_cache = parse_info.allow_eval_cache(); |
| } |
| |
| // If caller is strict mode, the result must be in strict mode as well. |
| DCHECK(is_sloppy(language_mode) || is_strict(shared_info->language_mode())); |
| |
| DirectHandle<JSFunction> result; |
| if (eval_result.has_shared()) { |
| if (eval_result.has_feedback_cell()) { |
| result = Factory::JSFunctionBuilder{isolate, shared_info, context} |
| .set_feedback_cell(feedback_cell) |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| } else { |
| result = Factory::JSFunctionBuilder{isolate, shared_info, context} |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| // TODO(mythria): I don't think we need this here. PostInstantiation |
| // already initializes feedback cell. |
| JSFunction::InitializeFeedbackCell(result, &is_compiled_scope, true); |
| if (allow_eval_cache) { |
| // Make sure to cache this result. |
| DirectHandle<FeedbackCell> new_feedback_cell( |
| result->raw_feedback_cell(), isolate); |
| compilation_cache->PutEval(source, outer_info, context, shared_info, |
| new_feedback_cell, eval_cache_position); |
| } |
| } |
| } else { |
| result = Factory::JSFunctionBuilder{isolate, shared_info, context} |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| // TODO(mythria): I don't think we need this here. PostInstantiation |
| // already initializes feedback cell. |
| JSFunction::InitializeFeedbackCell(result, &is_compiled_scope, true); |
| if (allow_eval_cache) { |
| // Add the SharedFunctionInfo and the LiteralsArray to the eval cache if |
| // we didn't retrieve from there. |
| DirectHandle<FeedbackCell> new_feedback_cell(result->raw_feedback_cell(), |
| isolate); |
| compilation_cache->PutEval(source, outer_info, context, shared_info, |
| new_feedback_cell, eval_cache_position); |
| } |
| } |
| CHECK(is_compiled_scope.is_compiled()); |
| |
| return result; |
| } |
| |
| // Check whether embedder allows code generation in this context. |
| // (via v8::Isolate::SetModifyCodeGenerationFromStringsCallback) |
| bool ModifyCodeGenerationFromStrings(Isolate* isolate, |
| DirectHandle<NativeContext> context, |
| Handle<i::Object>* source, |
| bool is_code_like) { |
| DCHECK(isolate->modify_code_gen_callback()); |
| DCHECK(source); |
| |
| // Callback set. Run it, and use the return value as source, or block |
| // execution if it's not set. |
| VMState<EXTERNAL> state(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCodeGenerationFromStringsCallbacks); |
| ModifyCodeGenerationFromStringsResult result = |
| isolate->modify_code_gen_callback()(v8::Utils::ToLocal(context), |
| v8::Utils::ToLocal(*source), |
| is_code_like); |
| if (result.codegen_allowed && !result.modified_source.IsEmpty()) { |
| // Use the new source (which might be the same as the old source). |
| *source = |
| Utils::OpenHandle(*result.modified_source.ToLocalChecked(), false); |
| } |
| return result.codegen_allowed; |
| } |
| |
| // Run Embedder-mandated checks before generating code from a string. |
| // |
| // Returns a string to be used for compilation, or a flag that an object type |
| // was encountered that is neither a string, nor something the embedder knows |
| // how to handle. |
| // |
| // Returns: (assuming: std::tie(source, unknown_object)) |
| // - !source.is_null(): compilation allowed, source contains the source string. |
| // - unknown_object is true: compilation allowed, but we don't know how to |
| // deal with source_object. |
| // - source.is_null() && !unknown_object: compilation should be blocked. |
| // |
| // - !source_is_null() and unknown_object can't be true at the same time. |
| |
| // static |
| std::pair<MaybeDirectHandle<String>, bool> |
| Compiler::ValidateDynamicCompilationSource(Isolate* isolate, |
| DirectHandle<NativeContext> context, |
| Handle<i::Object> original_source, |
| bool is_code_like) { |
| // Check if the context unconditionally allows code gen from strings. |
| // allow_code_gen_from_strings can be many things, so we'll always check |
| // against the 'false' literal, so that e.g. undefined and 'true' are treated |
| // the same. |
| if (!IsFalse(context->allow_code_gen_from_strings(), isolate) && |
| IsString(*original_source)) { |
| return {Cast<String>(original_source), false}; |
| } |
| |
| // Check if the context wants to block or modify this source object. |
| // Double-check that we really have a string now. |
| // (Let modify_code_gen_callback decide, if it's been set.) |
| if (isolate->modify_code_gen_callback()) { |
| Handle<i::Object> modified_source = original_source; |
| if (!ModifyCodeGenerationFromStrings(isolate, context, &modified_source, |
| is_code_like)) { |
| return {MaybeHandle<String>(), false}; |
| } |
| if (!IsString(*modified_source)) { |
| return {MaybeHandle<String>(), true}; |
| } |
| return {Cast<String>(modified_source), false}; |
| } |
| |
| if (!IsFalse(context->allow_code_gen_from_strings(), isolate) && |
| Object::IsCodeLike(*original_source, isolate)) { |
| // Codegen is unconditionally allowed, and we're been given a CodeLike |
| // object. Stringify. |
| MaybeHandle<String> stringified_source = |
| Object::ToString(isolate, original_source); |
| return {stringified_source, stringified_source.is_null()}; |
| } |
| |
| // If unconditional codegen was disabled, and no callback defined, we block |
| // strings and allow all other objects. |
| return {MaybeHandle<String>(), !IsString(*original_source)}; |
| } |
| |
| // static |
| MaybeDirectHandle<JSFunction> Compiler::GetFunctionFromValidatedString( |
| DirectHandle<NativeContext> native_context, |
| MaybeDirectHandle<String> source, ParseRestriction restriction, |
| int parameters_end_pos) { |
| Isolate* const isolate = native_context->GetIsolate(); |
| |
| // Raise an EvalError if we did not receive a string. |
| if (source.is_null()) { |
| Handle<Object> error_message = |
| native_context->ErrorMessageForCodeGenerationFromStrings(); |
| THROW_NEW_ERROR(isolate, NewEvalError(MessageTemplate::kCodeGenFromStrings, |
| error_message)); |
| } |
| |
| // Compile source string in the native context. |
| int eval_position = kNoSourcePosition; |
| DirectHandle<SharedFunctionInfo> outer_info( |
| native_context->empty_function()->shared(), isolate); |
| return Compiler::GetFunctionFromEval( |
| source.ToHandleChecked(), outer_info, native_context, |
| LanguageMode::kSloppy, restriction, parameters_end_pos, eval_position); |
| } |
| |
| // static |
| MaybeDirectHandle<JSFunction> Compiler::GetFunctionFromString( |
| DirectHandle<NativeContext> context, Handle<Object> source, |
| int parameters_end_pos, bool is_code_like) { |
| Isolate* const isolate = context->GetIsolate(); |
| MaybeDirectHandle<String> validated_source = |
| ValidateDynamicCompilationSource(isolate, context, source, is_code_like) |
| .first; |
| return GetFunctionFromValidatedString(context, validated_source, |
| ONLY_SINGLE_FUNCTION_LITERAL, |
| parameters_end_pos); |
| } |
| |
| namespace { |
| |
| struct ScriptCompileTimerScope { |
| public: |
| // TODO(leszeks): There are too many blink-specific entries in this enum, |
| // figure out a way to push produce/hit-isolate-cache/consume/consume-failed |
| // back up the API and log them in blink instead. |
| enum class CacheBehaviour { |
| kProduceCodeCache, |
| kHitIsolateCacheWhenNoCache, |
| kConsumeCodeCache, |
| kConsumeCodeCacheFailed, |
| kNoCacheBecauseInlineScript, |
| kNoCacheBecauseScriptTooSmall, |
| kNoCacheBecauseCacheTooCold, |
| kNoCacheNoReason, |
| kNoCacheBecauseNoResource, |
| kNoCacheBecauseInspector, |
| kNoCacheBecauseCachingDisabled, |
| kNoCacheBecauseModule, |
| kNoCacheBecauseStreamingSource, |
| kNoCacheBecauseV8Extension, |
| kHitIsolateCacheWhenProduceCodeCache, |
| kHitIsolateCacheWhenConsumeCodeCache, |
| kNoCacheBecauseExtensionModule, |
| kNoCacheBecausePacScript, |
| kNoCacheBecauseInDocumentWrite, |
| kNoCacheBecauseResourceWithNoCacheHandler, |
| kHitIsolateCacheWhenStreamingSource, |
| kNoCacheBecauseStaticCodeCache, |
| kCount |
| }; |
| |
| ScriptCompileTimerScope( |
| Isolate* isolate, ScriptCompiler::NoCacheReason no_cache_reason, |
| ScriptCompiler::CompilationDetails* compilation_details) |
| : isolate_(isolate), |
| histogram_scope_(&compilation_details->foreground_time_in_microseconds), |
| all_scripts_histogram_scope_(isolate->counters()->compile_script()), |
| no_cache_reason_(no_cache_reason), |
| hit_isolate_cache_(false), |
| consuming_code_cache_(false), |
| consuming_code_cache_failed_(false) {} |
| |
| ~ScriptCompileTimerScope() { |
| CacheBehaviour cache_behaviour = GetCacheBehaviour(); |
| |
| Histogram* cache_behaviour_histogram = |
| isolate_->counters()->compile_script_cache_behaviour(); |
| // Sanity check that the histogram has exactly one bin per enum entry. |
| DCHECK_EQ(0, cache_behaviour_histogram->min()); |
| DCHECK_EQ(static_cast<int>(CacheBehaviour::kCount), |
| cache_behaviour_histogram->max() + 1); |
| DCHECK_EQ(static_cast<int>(CacheBehaviour::kCount), |
| cache_behaviour_histogram->num_buckets()); |
| cache_behaviour_histogram->AddSample(static_cast<int>(cache_behaviour)); |
| |
| histogram_scope_.set_histogram( |
| GetCacheBehaviourTimedHistogram(cache_behaviour)); |
| } |
| |
| void set_hit_isolate_cache() { hit_isolate_cache_ = true; } |
| |
| void set_consuming_code_cache() { consuming_code_cache_ = true; } |
| |
| void set_consuming_code_cache_failed() { |
| consuming_code_cache_failed_ = true; |
| } |
| |
| private: |
| Isolate* isolate_; |
| LazyTimedHistogramScope histogram_scope_; |
| // TODO(leszeks): This timer is the sum of the other times, consider removing |
| // it to save space. |
| NestedTimedHistogramScope all_scripts_histogram_scope_; |
| ScriptCompiler::NoCacheReason no_cache_reason_; |
| bool hit_isolate_cache_; |
| bool consuming_code_cache_; |
| bool consuming_code_cache_failed_; |
| |
| CacheBehaviour GetCacheBehaviour() { |
| if (consuming_code_cache_) { |
| if (hit_isolate_cache_) { |
| return CacheBehaviour::kHitIsolateCacheWhenConsumeCodeCache; |
| } else if (consuming_code_cache_failed_) { |
| return CacheBehaviour::kConsumeCodeCacheFailed; |
| } |
| return CacheBehaviour::kConsumeCodeCache; |
| } |
| |
| if (hit_isolate_cache_) { |
| // A roundabout way of knowing the embedder is going to produce a code |
| // cache (which is done by a separate API call later) is to check whether |
| // no_cache_reason_ is |
| // ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache. |
| if (no_cache_reason_ == |
| ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache) { |
| return CacheBehaviour::kHitIsolateCacheWhenProduceCodeCache; |
| } else if (no_cache_reason_ == |
| ScriptCompiler::kNoCacheBecauseStreamingSource) { |
| return CacheBehaviour::kHitIsolateCacheWhenStreamingSource; |
| } |
| return CacheBehaviour::kHitIsolateCacheWhenNoCache; |
| } |
| |
| switch (no_cache_reason_) { |
| case ScriptCompiler::kNoCacheBecauseInlineScript: |
| return CacheBehaviour::kNoCacheBecauseInlineScript; |
| case ScriptCompiler::kNoCacheBecauseScriptTooSmall: |
| return CacheBehaviour::kNoCacheBecauseScriptTooSmall; |
| case ScriptCompiler::kNoCacheBecauseCacheTooCold: |
| return CacheBehaviour::kNoCacheBecauseCacheTooCold; |
| case ScriptCompiler::kNoCacheNoReason: |
| return CacheBehaviour::kNoCacheNoReason; |
| case ScriptCompiler::kNoCacheBecauseNoResource: |
| return CacheBehaviour::kNoCacheBecauseNoResource; |
| case ScriptCompiler::kNoCacheBecauseInspector: |
| return CacheBehaviour::kNoCacheBecauseInspector; |
| case ScriptCompiler::kNoCacheBecauseCachingDisabled: |
| return CacheBehaviour::kNoCacheBecauseCachingDisabled; |
| case ScriptCompiler::kNoCacheBecauseModule: |
| return CacheBehaviour::kNoCacheBecauseModule; |
| case ScriptCompiler::kNoCacheBecauseStreamingSource: |
| return CacheBehaviour::kNoCacheBecauseStreamingSource; |
| case ScriptCompiler::kNoCacheBecauseV8Extension: |
| return CacheBehaviour::kNoCacheBecauseV8Extension; |
| case ScriptCompiler::kNoCacheBecauseExtensionModule: |
| return CacheBehaviour::kNoCacheBecauseExtensionModule; |
| case ScriptCompiler::kNoCacheBecausePacScript: |
| return CacheBehaviour::kNoCacheBecausePacScript; |
| case ScriptCompiler::kNoCacheBecauseInDocumentWrite: |
| return CacheBehaviour::kNoCacheBecauseInDocumentWrite; |
| case ScriptCompiler::kNoCacheBecauseResourceWithNoCacheHandler: |
| return CacheBehaviour::kNoCacheBecauseResourceWithNoCacheHandler; |
| case ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache: |
| return CacheBehaviour::kProduceCodeCache; |
| case ScriptCompiler::kNoCacheBecauseStaticCodeCache: |
| return CacheBehaviour::kNoCacheBecauseStaticCodeCache; |
| } |
| UNREACHABLE(); |
| } |
| |
| TimedHistogram* GetCacheBehaviourTimedHistogram( |
| CacheBehaviour cache_behaviour) { |
| switch (cache_behaviour) { |
| case CacheBehaviour::kProduceCodeCache: |
| // Even if we hit the isolate's compilation cache, we currently recompile |
| // when we want to produce the code cache. |
| case CacheBehaviour::kHitIsolateCacheWhenProduceCodeCache: |
| return isolate_->counters()->compile_script_with_produce_cache(); |
| case CacheBehaviour::kHitIsolateCacheWhenNoCache: |
| case CacheBehaviour::kHitIsolateCacheWhenConsumeCodeCache: |
| case CacheBehaviour::kHitIsolateCacheWhenStreamingSource: |
| return isolate_->counters()->compile_script_with_isolate_cache_hit(); |
| case CacheBehaviour::kConsumeCodeCacheFailed: |
| return isolate_->counters()->compile_script_consume_failed(); |
| case CacheBehaviour::kConsumeCodeCache: |
| return isolate_->counters()->compile_script_with_consume_cache(); |
| |
| // Note that this only counts the finalization part of streaming, the |
| // actual streaming compile is counted by BackgroundCompileTask into |
| // "compile_script_on_background". |
| case CacheBehaviour::kNoCacheBecauseStreamingSource: |
| return isolate_->counters()->compile_script_streaming_finalization(); |
| |
| case CacheBehaviour::kNoCacheBecauseInlineScript: |
| return isolate_->counters() |
| ->compile_script_no_cache_because_inline_script(); |
| case CacheBehaviour::kNoCacheBecauseScriptTooSmall: |
| return isolate_->counters() |
| ->compile_script_no_cache_because_script_too_small(); |
| case CacheBehaviour::kNoCacheBecauseCacheTooCold: |
| return isolate_->counters() |
| ->compile_script_no_cache_because_cache_too_cold(); |
| |
| // Aggregate all the other "no cache" counters into a single histogram, to |
| // save space. |
| case CacheBehaviour::kNoCacheNoReason: |
| case CacheBehaviour::kNoCacheBecauseNoResource: |
| case CacheBehaviour::kNoCacheBecauseInspector: |
| case CacheBehaviour::kNoCacheBecauseCachingDisabled: |
| // TODO(leszeks): Consider counting separately once modules are more |
| // common. |
| case CacheBehaviour::kNoCacheBecauseModule: |
| case CacheBehaviour::kNoCacheBecauseV8Extension: |
| case CacheBehaviour::kNoCacheBecauseExtensionModule: |
| case CacheBehaviour::kNoCacheBecausePacScript: |
| case CacheBehaviour::kNoCacheBecauseInDocumentWrite: |
| case CacheBehaviour::kNoCacheBecauseResourceWithNoCacheHandler: |
| case CacheBehaviour::kNoCacheBecauseStaticCodeCache: |
| return isolate_->counters()->compile_script_no_cache_other(); |
| |
| case CacheBehaviour::kCount: |
| UNREACHABLE(); |
| } |
| UNREACHABLE(); |
| } |
| }; |
| |
| Handle<Script> NewScript(Isolate* isolate, ParseInfo* parse_info, |
| DirectHandle<String> source, |
| ScriptDetails script_details, NativesFlag natives) { |
| // Create a script object describing the script to be compiled. |
| Handle<Script> script = parse_info->CreateScript( |
| isolate, source, script_details.wrapped_arguments, |
| script_details.origin_options, natives); |
| DisallowGarbageCollection no_gc; |
| SetScriptFieldsFromDetails(isolate, *script, script_details, &no_gc); |
| LOG(isolate, ScriptDetails(*script)); |
| return script; |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> CompileScriptOnMainThread( |
| const UnoptimizedCompileFlags flags, DirectHandle<String> source, |
| const ScriptDetails& script_details, NativesFlag natives, |
| v8::Extension* extension, Isolate* isolate, |
| MaybeHandle<Script> maybe_script, IsCompiledScope* is_compiled_scope, |
| CompileHintCallback compile_hint_callback = nullptr, |
| void* compile_hint_callback_data = nullptr) { |
| UnoptimizedCompileState compile_state; |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); |
| parse_info.set_extension(extension); |
| parse_info.SetCompileHintCallbackAndData(compile_hint_callback, |
| compile_hint_callback_data); |
| |
| Handle<Script> script; |
| if (!maybe_script.ToHandle(&script)) { |
| script = NewScript(isolate, &parse_info, source, script_details, natives); |
| } |
| DCHECK_EQ(parse_info.flags().is_repl_mode(), script->is_repl_mode()); |
| |
| return Compiler::CompileToplevel(&parse_info, script, isolate, |
| is_compiled_scope); |
| } |
| |
| class StressBackgroundCompileThread : public ParkingThread { |
| public: |
| StressBackgroundCompileThread(Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details) |
| : ParkingThread( |
| base::Thread::Options("StressBackgroundCompileThread", 2 * i::MB)), |
| source_(source), |
| streamed_source_(std::make_unique<SourceStream>(source, isolate), |
| v8::ScriptCompiler::StreamedSource::TWO_BYTE) { |
| ScriptType type = script_details.origin_options.IsModule() |
| ? ScriptType::kModule |
| : ScriptType::kClassic; |
| data()->task = std::make_unique<i::BackgroundCompileTask>( |
| data(), isolate, type, |
| ScriptCompiler::CompileOptions::kNoCompileOptions, |
| &streamed_source_.compilation_details()); |
| } |
| |
| void Run() override { data()->task->Run(); } |
| |
| ScriptStreamingData* data() { return streamed_source_.impl(); } |
| |
| private: |
| // Dummy external source stream which returns the whole source in one go. |
| // TODO(leszeks): Also test chunking the data. |
| class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { |
| public: |
| SourceStream(DirectHandle<String> source, Isolate* isolate) : done_(false) { |
| source_length_ = source->length(); |
| source_buffer_ = std::make_unique<uint16_t[]>(source_length_); |
| String::WriteToFlat(*source, source_buffer_.get(), 0, source_length_); |
| } |
| |
| size_t GetMoreData(const uint8_t** src) override { |
| if (done_) { |
| return 0; |
| } |
| *src = reinterpret_cast<uint8_t*>(source_buffer_.release()); |
| done_ = true; |
| |
| return source_length_ * 2; |
| } |
| |
| private: |
| uint32_t source_length_; |
| std::unique_ptr<uint16_t[]> source_buffer_; |
| bool done_; |
| }; |
| |
| Handle<String> source_; |
| v8::ScriptCompiler::StreamedSource streamed_source_; |
| }; |
| |
| bool CanBackgroundCompile(const ScriptDetails& script_details, |
| v8::Extension* extension, |
| ScriptCompiler::CompileOptions compile_options, |
| NativesFlag natives) { |
| // TODO(leszeks): Remove the module check once background compilation of |
| // modules is supported. |
| return !script_details.origin_options.IsModule() && !extension && |
| script_details.repl_mode == REPLMode::kNo && |
| (compile_options == ScriptCompiler::kNoCompileOptions) && |
| natives == NOT_NATIVES_CODE; |
| } |
| |
| bool CompilationExceptionIsRangeError(Isolate* isolate, |
| DirectHandle<Object> obj) { |
| if (!IsJSError(*obj, isolate)) return false; |
| DirectHandle<JSReceiver> js_obj = Cast<JSReceiver>(obj); |
| DirectHandle<JSReceiver> constructor; |
| if (!JSReceiver::GetConstructor(isolate, js_obj).ToHandle(&constructor)) { |
| return false; |
| } |
| return *constructor == *isolate->range_error_function(); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> |
| CompileScriptOnBothBackgroundAndMainThread(Handle<String> source, |
| const ScriptDetails& script_details, |
| Isolate* isolate, |
| IsCompiledScope* is_compiled_scope) { |
| // Start a background thread compiling the script. |
| StressBackgroundCompileThread background_compile_thread(isolate, source, |
| script_details); |
| |
| UnoptimizedCompileFlags flags_copy = |
| background_compile_thread.data()->task->flags(); |
| |
| CHECK(background_compile_thread.Start()); |
| MaybeDirectHandle<SharedFunctionInfo> main_thread_maybe_result; |
| bool main_thread_had_stack_overflow = false; |
| // In parallel, compile on the main thread to flush out any data races. |
| { |
| IsCompiledScope inner_is_compiled_scope; |
| // The background thread should also create any relevant exceptions, so we |
| // can ignore the main-thread created ones. |
| // TODO(leszeks): Maybe verify that any thrown (or unthrown) exceptions are |
| // equivalent. |
| TryCatch ignore_try_catch(reinterpret_cast<v8::Isolate*>(isolate)); |
| flags_copy.set_script_id(Script::kTemporaryScriptId); |
| main_thread_maybe_result = CompileScriptOnMainThread( |
| flags_copy, source, script_details, NOT_NATIVES_CODE, nullptr, isolate, |
| MaybeHandle<Script>(), &inner_is_compiled_scope); |
| if (main_thread_maybe_result.is_null()) { |
| // Assume all range errors are stack overflows. |
| main_thread_had_stack_overflow = CompilationExceptionIsRangeError( |
| isolate, direct_handle(isolate->exception(), isolate)); |
| isolate->clear_exception(); |
| } |
| } |
| |
| // Join with background thread and finalize compilation. |
| background_compile_thread.ParkedJoin(isolate->main_thread_local_isolate()); |
| |
| ScriptCompiler::CompilationDetails compilation_details; |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result = |
| Compiler::GetSharedFunctionInfoForStreamedScript( |
| isolate, source, script_details, background_compile_thread.data(), |
| &compilation_details); |
| |
| // Either both compiles should succeed, or both should fail. The one exception |
| // to this is that the main-thread compilation might stack overflow while the |
| // background compilation doesn't, so relax the check to include this case. |
| // TODO(leszeks): Compare the contents of the results of the two compiles. |
| if (main_thread_had_stack_overflow) { |
| CHECK(main_thread_maybe_result.is_null()); |
| } else { |
| CHECK_EQ(maybe_result.is_null(), main_thread_maybe_result.is_null()); |
| } |
| |
| DirectHandle<SharedFunctionInfo> result; |
| if (maybe_result.ToHandle(&result)) { |
| // The BackgroundCompileTask's IsCompiledScope will keep the result alive |
| // until it dies at the end of this function, after which this new |
| // IsCompiledScope can take over. |
| *is_compiled_scope = result->is_compiled_scope(isolate); |
| } |
| |
| return maybe_result; |
| } |
| |
| namespace { |
| ScriptCompiler::InMemoryCacheResult CategorizeLookupResult( |
| const CompilationCacheScript::LookupResult& lookup_result) { |
| return !lookup_result.toplevel_sfi().is_null() |
| ? ScriptCompiler::InMemoryCacheResult::kHit |
| : !lookup_result.script().is_null() |
| ? ScriptCompiler::InMemoryCacheResult::kPartial |
| : ScriptCompiler::InMemoryCacheResult::kMiss; |
| } |
| } // namespace |
| |
| MaybeDirectHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, v8::Extension* extension, |
| AlignedCachedData* cached_data, BackgroundDeserializeTask* deserialize_task, |
| v8::CompileHintCallback compile_hint_callback, |
| void* compile_hint_callback_data, |
| ScriptCompiler::CompileOptions compile_options, |
| ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| ScriptCompileTimerScope compile_timer(isolate, no_cache_reason, |
| compilation_details); |
| |
| if (compile_options & ScriptCompiler::kConsumeCodeCache) { |
| // Have to have exactly one of cached_data or deserialize_task. |
| DCHECK(cached_data || deserialize_task); |
| DCHECK(!(cached_data && deserialize_task)); |
| DCHECK_NULL(extension); |
| } else { |
| DCHECK_NULL(cached_data); |
| DCHECK_NULL(deserialize_task); |
| } |
| |
| if (compile_options & ScriptCompiler::kConsumeCompileHints) { |
| DCHECK_NOT_NULL(compile_hint_callback); |
| DCHECK_NOT_NULL(compile_hint_callback_data); |
| } else { |
| DCHECK_NULL(compile_hint_callback); |
| DCHECK_NULL(compile_hint_callback_data); |
| } |
| |
| compilation_details->background_time_in_microseconds = |
| deserialize_task ? deserialize_task->background_time_in_microseconds() |
| : 0; |
| |
| LanguageMode language_mode = construct_language_mode(v8_flags.use_strict); |
| CompilationCache* compilation_cache = isolate->compilation_cache(); |
| |
| // For extensions or REPL mode scripts neither do a compilation cache lookup, |
| // nor put the compilation result back into the cache. |
| const bool use_compilation_cache = |
| extension == nullptr && script_details.repl_mode == REPLMode::kNo; |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result; |
| MaybeHandle<Script> maybe_script; |
| IsCompiledScope is_compiled_scope; |
| if (use_compilation_cache) { |
| bool can_consume_code_cache = |
| compile_options & ScriptCompiler::kConsumeCodeCache; |
| if (can_consume_code_cache) { |
| compile_timer.set_consuming_code_cache(); |
| } |
| |
| // First check per-isolate compilation cache. |
| CompilationCacheScript::LookupResult lookup_result = |
| compilation_cache->LookupScript(source, script_details, language_mode); |
| compilation_details->in_memory_cache_result = |
| CategorizeLookupResult(lookup_result); |
| maybe_script = lookup_result.script(); |
| maybe_result = lookup_result.toplevel_sfi(); |
| is_compiled_scope = lookup_result.is_compiled_scope(); |
| if (!maybe_result.is_null()) { |
| compile_timer.set_hit_isolate_cache(); |
| } else if (can_consume_code_cache) { |
| compile_timer.set_consuming_code_cache(); |
| // Then check cached code provided by embedder. |
| NestedTimedHistogramScope timer( |
| isolate->counters()->compile_deserialize()); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CompileDeserialize"); |
| if (deserialize_task) { |
| // If there's a cache consume task, finish it. |
| maybe_result = |
| deserialize_task->Finish(isolate, source, script_details); |
| // It is possible at this point that there is a Script object for this |
| // script in the compilation cache (held in the variable maybe_script), |
| // which does not match maybe_result->script(). This could happen any of |
| // three ways: |
| // 1. The embedder didn't call MergeWithExistingScript. |
| // 2. At the time the embedder called SourceTextAvailable, there was not |
| // yet a Script in the compilation cache, but it arrived sometime |
| // later. |
| // 3. At the time the embedder called SourceTextAvailable, there was a |
| // Script available, and the new content has been merged into that |
| // Script. However, since then, the Script was replaced in the |
| // compilation cache, such as by another evaluation of the script |
| // hitting case 2, or DevTools clearing the cache. |
| // This is okay; the new Script object will replace the current Script |
| // held by the compilation cache. Both Scripts may remain in use |
| // indefinitely, causing increased memory usage, but these cases are |
| // sufficiently unlikely, and ensuring a correct merge in the third case |
| // would be non-trivial. |
| } else { |
| maybe_result = CodeSerializer::Deserialize( |
| isolate, cached_data, source, script_details, maybe_script); |
| } |
| |
| bool consuming_code_cache_succeeded = false; |
| DirectHandle<SharedFunctionInfo> result; |
| if (maybe_result.ToHandle(&result)) { |
| is_compiled_scope = result->is_compiled_scope(isolate); |
| if (is_compiled_scope.is_compiled()) { |
| consuming_code_cache_succeeded = true; |
| // Promote to per-isolate compilation cache. |
| compilation_cache->PutScript(source, language_mode, result); |
| } |
| } |
| if (!consuming_code_cache_succeeded) { |
| // Deserializer failed. Fall through to compile. |
| compile_timer.set_consuming_code_cache_failed(); |
| } |
| } |
| } |
| |
| if (maybe_result.is_null()) { |
| // No cache entry found compile the script. |
| if (v8_flags.stress_background_compile && |
| CanBackgroundCompile(script_details, extension, compile_options, |
| natives)) { |
| // If the --stress-background-compile flag is set, do the actual |
| // compilation on a background thread, and wait for its result. |
| maybe_result = CompileScriptOnBothBackgroundAndMainThread( |
| source, script_details, isolate, &is_compiled_scope); |
| } else { |
| UnoptimizedCompileFlags flags = |
| UnoptimizedCompileFlags::ForToplevelCompile( |
| isolate, natives == NOT_NATIVES_CODE, language_mode, |
| script_details.repl_mode, |
| script_details.origin_options.IsModule() ? ScriptType::kModule |
| : ScriptType::kClassic, |
| v8_flags.lazy); |
| |
| flags.set_is_eager(compile_options & ScriptCompiler::kEagerCompile); |
| flags.set_compile_hints_magic_enabled( |
| compile_options & ScriptCompiler::kFollowCompileHintsMagicComment); |
| |
| if (DirectHandle<Script> script; maybe_script.ToHandle(&script)) { |
| flags.set_script_id(script->id()); |
| } |
| |
| maybe_result = CompileScriptOnMainThread( |
| flags, source, script_details, natives, extension, isolate, |
| maybe_script, &is_compiled_scope, compile_hint_callback, |
| compile_hint_callback_data); |
| } |
| |
| // Add the result to the isolate cache. |
| DirectHandle<SharedFunctionInfo> result; |
| if (use_compilation_cache && maybe_result.ToHandle(&result)) { |
| DCHECK(is_compiled_scope.is_compiled()); |
| compilation_cache->PutScript(source, language_mode, result); |
| } else if (maybe_result.is_null() && natives != EXTENSION_CODE) { |
| isolate->ReportPendingMessages(); |
| } |
| } |
| DirectHandle<SharedFunctionInfo> result; |
| if (compile_options & ScriptCompiler::CompileOptions::kProduceCompileHints && |
| maybe_result.ToHandle(&result)) { |
| Cast<Script>(result->script())->set_produce_compile_hints(true); |
| } |
| |
| return maybe_result; |
| } |
| |
| } // namespace |
| |
| MaybeDirectHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, |
| ScriptCompiler::CompileOptions compile_options, |
| ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| return GetSharedFunctionInfoForScriptImpl( |
| isolate, source, script_details, nullptr, nullptr, nullptr, nullptr, |
| nullptr, compile_options, no_cache_reason, natives, compilation_details); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> |
| Compiler::GetSharedFunctionInfoForScriptWithExtension( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, v8::Extension* extension, |
| ScriptCompiler::CompileOptions compile_options, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| return GetSharedFunctionInfoForScriptImpl( |
| isolate, source, script_details, extension, nullptr, nullptr, nullptr, |
| nullptr, compile_options, ScriptCompiler::kNoCacheBecauseV8Extension, |
| natives, compilation_details); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> |
| Compiler::GetSharedFunctionInfoForScriptWithCachedData( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, AlignedCachedData* cached_data, |
| ScriptCompiler::CompileOptions compile_options, |
| ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| return GetSharedFunctionInfoForScriptImpl( |
| isolate, source, script_details, nullptr, cached_data, nullptr, nullptr, |
| nullptr, compile_options, no_cache_reason, natives, compilation_details); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> |
| Compiler::GetSharedFunctionInfoForScriptWithDeserializeTask( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, |
| BackgroundDeserializeTask* deserialize_task, |
| ScriptCompiler::CompileOptions compile_options, |
| ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| return GetSharedFunctionInfoForScriptImpl( |
| isolate, source, script_details, nullptr, nullptr, deserialize_task, |
| nullptr, nullptr, compile_options, no_cache_reason, natives, |
| compilation_details); |
| } |
| |
| MaybeDirectHandle<SharedFunctionInfo> |
| Compiler::GetSharedFunctionInfoForScriptWithCompileHints( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, |
| v8::CompileHintCallback compile_hint_callback, |
| void* compile_hint_callback_data, |
| ScriptCompiler::CompileOptions compile_options, |
| ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| return GetSharedFunctionInfoForScriptImpl( |
| isolate, source, script_details, nullptr, nullptr, nullptr, |
| compile_hint_callback, compile_hint_callback_data, compile_options, |
| no_cache_reason, natives, compilation_details); |
| } |
| |
| // static |
| MaybeDirectHandle<JSFunction> Compiler::GetWrappedFunction( |
| Handle<String> source, DirectHandle<Context> context, |
| const ScriptDetails& script_details, AlignedCachedData* cached_data, |
| v8::ScriptCompiler::CompileOptions compile_options, |
| v8::ScriptCompiler::NoCacheReason no_cache_reason) { |
| Isolate* isolate = context->GetIsolate(); |
| ScriptCompiler::CompilationDetails compilation_details; |
| ScriptCompileTimerScope compile_timer(isolate, no_cache_reason, |
| &compilation_details); |
| |
| if (compile_options & ScriptCompiler::kConsumeCodeCache) { |
| DCHECK(cached_data); |
| DCHECK_EQ(script_details.repl_mode, REPLMode::kNo); |
| } else { |
| DCHECK_NULL(cached_data); |
| } |
| |
| LanguageMode language_mode = construct_language_mode(v8_flags.use_strict); |
| DCHECK(!script_details.wrapped_arguments.is_null()); |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result; |
| DirectHandle<SharedFunctionInfo> result; |
| Handle<Script> script; |
| IsCompiledScope is_compiled_scope; |
| bool can_consume_code_cache = |
| compile_options & ScriptCompiler::kConsumeCodeCache; |
| CompilationCache* compilation_cache = isolate->compilation_cache(); |
| // First check per-isolate compilation cache. |
| CompilationCacheScript::LookupResult lookup_result = |
| compilation_cache->LookupScript(source, script_details, language_mode); |
| maybe_result = lookup_result.toplevel_sfi(); |
| if (maybe_result.ToHandle(&result)) { |
| is_compiled_scope = result->is_compiled_scope(isolate); |
| compile_timer.set_hit_isolate_cache(); |
| } else if (can_consume_code_cache) { |
| compile_timer.set_consuming_code_cache(); |
| // Then check cached code provided by embedder. |
| NestedTimedHistogramScope timer(isolate->counters()->compile_deserialize()); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CompileDeserialize"); |
| maybe_result = CodeSerializer::Deserialize(isolate, cached_data, source, |
| script_details); |
| bool consuming_code_cache_succeeded = false; |
| if (maybe_result.ToHandle(&result)) { |
| is_compiled_scope = result->is_compiled_scope(isolate); |
| if (is_compiled_scope.is_compiled()) { |
| consuming_code_cache_succeeded = true; |
| // Promote to per-isolate compilation cache. |
| compilation_cache->PutScript(source, language_mode, result); |
| } |
| } |
| if (!consuming_code_cache_succeeded) { |
| // Deserializer failed. Fall through to compile. |
| compile_timer.set_consuming_code_cache_failed(); |
| } |
| } |
| |
| if (maybe_result.is_null()) { |
| UnoptimizedCompileFlags flags = UnoptimizedCompileFlags::ForToplevelCompile( |
| isolate, true, language_mode, script_details.repl_mode, |
| ScriptType::kClassic, v8_flags.lazy); |
| flags.set_is_eval(true); // Use an eval scope as declaration scope. |
| flags.set_function_syntax_kind(FunctionSyntaxKind::kWrapped); |
| // TODO(delphick): Remove this and instead make the wrapped and wrapper |
| // functions fully non-lazy instead thus preventing source positions from |
| // being omitted. |
| flags.set_collect_source_positions(true); |
| flags.set_is_eager(compile_options & ScriptCompiler::kEagerCompile); |
| |
| UnoptimizedCompileState compile_state; |
| ReusableUnoptimizedCompileState reusable_state(isolate); |
| ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); |
| |
| MaybeDirectHandle<ScopeInfo> maybe_outer_scope_info; |
| if (!IsNativeContext(*context)) { |
| maybe_outer_scope_info = direct_handle(context->scope_info(), isolate); |
| } |
| script = NewScript(isolate, &parse_info, source, script_details, |
| NOT_NATIVES_CODE); |
| |
| DirectHandle<SharedFunctionInfo> top_level; |
| maybe_result = v8::internal::CompileToplevel(&parse_info, script, |
| maybe_outer_scope_info, |
| isolate, &is_compiled_scope); |
| if (maybe_result.is_null()) isolate->ReportPendingMessages(); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, top_level, maybe_result); |
| |
| SharedFunctionInfo::ScriptIterator infos(isolate, *script); |
| for (Tagged<SharedFunctionInfo> info = infos.Next(); !info.is_null(); |
| info = infos.Next()) { |
| if (info->is_wrapped()) { |
| result = direct_handle(info, isolate); |
| break; |
| } |
| } |
| DCHECK(!result.is_null()); |
| |
| is_compiled_scope = result->is_compiled_scope(isolate); |
| script = Handle<Script>(Cast<Script>(result->script()), isolate); |
| // Add the result to the isolate cache if there's no context extension. |
| if (maybe_outer_scope_info.is_null()) { |
| compilation_cache->PutScript(source, language_mode, result); |
| } |
| } |
| |
| DCHECK(is_compiled_scope.is_compiled()); |
| |
| return Factory::JSFunctionBuilder{isolate, result, context} |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| } |
| |
| // static |
| MaybeDirectHandle<SharedFunctionInfo> |
| Compiler::GetSharedFunctionInfoForStreamedScript( |
| Isolate* isolate, Handle<String> source, |
| const ScriptDetails& script_details, ScriptStreamingData* streaming_data, |
| ScriptCompiler::CompilationDetails* compilation_details) { |
| DCHECK(!script_details.origin_options.IsWasm()); |
| |
| ScriptCompileTimerScope compile_timer( |
| isolate, ScriptCompiler::kNoCacheBecauseStreamingSource, |
| compilation_details); |
| PostponeInterruptsScope postpone(isolate); |
| |
| BackgroundCompileTask* task = streaming_data->task.get(); |
| |
| MaybeDirectHandle<SharedFunctionInfo> maybe_result; |
| MaybeDirectHandle<Script> maybe_cached_script; |
| // Check if compile cache already holds the SFI, if so no need to finalize |
| // the code compiled on the background thread. |
| CompilationCache* compilation_cache = isolate->compilation_cache(); |
| { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.StreamingFinalization.CheckCache"); |
| CompilationCacheScript::LookupResult lookup_result = |
| compilation_cache->LookupScript(source, script_details, |
| task->flags().outer_language_mode()); |
| compilation_details->in_memory_cache_result = |
| CategorizeLookupResult(lookup_result); |
| |
| if (!lookup_result.toplevel_sfi().is_null()) { |
| maybe_result = lookup_result.toplevel_sfi(); |
| } |
| |
| if (!maybe_result.is_null()) { |
| compile_timer.set_hit_isolate_cache(); |
| } else { |
| maybe_cached_script = lookup_result.script(); |
| } |
| } |
| |
| if (maybe_result.is_null()) { |
| // No cache entry found, finalize compilation of the script and add it to |
| // the isolate cache. |
| RCS_SCOPE(isolate, |
| RuntimeCallCounterId::kCompilePublishBackgroundFinalization); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.OffThreadFinalization.Publish"); |
| |
| maybe_result = task->FinalizeScript(isolate, source, script_details, |
| maybe_cached_script); |
| |
| DirectHandle<SharedFunctionInfo> result; |
| if (maybe_result.ToHandle(&result)) { |
| if (task->flags().produce_compile_hints()) { |
| Cast<Script>(result->script())->set_produce_compile_hints(true); |
| } |
| |
| // Add compiled code to the isolate cache. |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.StreamingFinalization.AddToCache"); |
| compilation_cache->PutScript(source, task->flags().outer_language_mode(), |
| result); |
| } |
| } |
| |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.StreamingFinalization.Release"); |
| streaming_data->Release(); |
| return maybe_result; |
| } // namespace internal |
| |
| // static |
| template <typename IsolateT> |
| DirectHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo( |
| FunctionLiteral* literal, DirectHandle<Script> script, IsolateT* isolate) { |
| // If we're parallel compiling functions, we might already have attached a SFI |
| // to this literal. |
| if (!literal->shared_function_info().is_null()) { |
| return literal->shared_function_info(); |
| } |
| // Precondition: code has been parsed and scopes have been analyzed. |
| MaybeDirectHandle<SharedFunctionInfo> maybe_existing; |
| |
| // Find any previously allocated shared function info for the given literal. |
| maybe_existing = Script::FindSharedFunctionInfo(script, isolate, literal); |
| |
| // If we found an existing shared function info, return it. |
| DirectHandle<SharedFunctionInfo> existing; |
| if (maybe_existing.ToHandle(&existing)) { |
| // If the function has been uncompiled (bytecode flushed) it will have lost |
| // any preparsed data. If we produced preparsed data during this compile for |
| // this function, replace the uncompiled data with one that includes it. |
| if (literal->produced_preparse_data() != nullptr && |
| existing->HasUncompiledDataWithoutPreparseData()) { |
| DirectHandle<UncompiledData> existing_uncompiled_data( |
| existing->uncompiled_data(isolate), isolate); |
| DCHECK_EQ(literal->start_position(), |
| existing_uncompiled_data->start_position()); |
| DCHECK_EQ(literal->end_position(), |
| existing_uncompiled_data->end_position()); |
| // Use existing uncompiled data's inferred name as it may be more |
| // accurate than the literal we preparsed. |
| Handle<String> inferred_name = |
| handle(existing_uncompiled_data->inferred_name(), isolate); |
| Handle<PreparseData> preparse_data = |
| literal->produced_preparse_data()->Serialize(isolate); |
| DirectHandle<UncompiledData> new_uncompiled_data = |
| isolate->factory()->NewUncompiledDataWithPreparseData( |
| inferred_name, existing_uncompiled_data->start_position(), |
| existing_uncompiled_data->end_position(), preparse_data); |
| existing->set_uncompiled_data(*new_uncompiled_data); |
| } |
| return existing; |
| } |
| |
| // Allocate a shared function info object which will be compiled lazily. |
| DirectHandle<SharedFunctionInfo> result = |
| isolate->factory()->NewSharedFunctionInfoForLiteral(literal, script, |
| false); |
| return result; |
| } |
| |
| template DirectHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo( |
| FunctionLiteral* literal, DirectHandle<Script> script, Isolate* isolate); |
| template DirectHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo( |
| FunctionLiteral* literal, DirectHandle<Script> script, |
| LocalIsolate* isolate); |
| |
| // static |
| MaybeHandle<Code> Compiler::CompileOptimizedOSR( |
| Isolate* isolate, DirectHandle<JSFunction> function, |
| BytecodeOffset osr_offset, ConcurrencyMode mode, CodeKind code_kind) { |
| DCHECK(IsOSR(osr_offset)); |
| |
| if (V8_UNLIKELY(isolate->serializer_enabled())) return {}; |
| if (V8_UNLIKELY(function->shared()->optimization_disabled())) return {}; |
| |
| // TODO(chromium:1031479): Currently, OSR triggering mechanism is tied to the |
| // bytecode array. So, it might be possible to mark closure in one native |
| // context and optimize a closure from a different native context. So check if |
| // there is a feedback vector before OSRing. We don't expect this to happen |
| // often. |
| if (V8_UNLIKELY(!function->has_feedback_vector())) return {}; |
| |
| CompilerTracer::TraceOptimizeOSRStarted(isolate, function, osr_offset, mode); |
| MaybeHandle<Code> result = |
| GetOrCompileOptimized(isolate, function, mode, code_kind, osr_offset); |
| |
| if (result.is_null()) { |
| CompilerTracer::TraceOptimizeOSRUnavailable(isolate, function, osr_offset, |
| mode); |
| } else { |
| DCHECK_GE(result.ToHandleChecked()->kind(), CodeKind::MAGLEV); |
| CompilerTracer::TraceOptimizeOSRAvailable(isolate, function, osr_offset, |
| mode); |
| } |
| |
| return result; |
| } |
| |
| // static |
| void Compiler::DisposeTurbofanCompilationJob(Isolate* isolate, |
| TurbofanCompilationJob* job) { |
| TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.OptimizeConcurrentDispose", job->trace_id(), |
| TRACE_EVENT_FLAG_FLOW_IN); |
| DirectHandle<JSFunction> function = job->compilation_info()->closure(); |
| function->SetTieringInProgress(false, job->compilation_info()->osr_offset()); |
| } |
| |
| // static |
| void Compiler::FinalizeTurbofanCompilationJob(TurbofanCompilationJob* job, |
| Isolate* isolate) { |
| VMState<COMPILER> state(isolate); |
| OptimizedCompilationInfo* compilation_info = job->compilation_info(); |
| |
| TimerEventScope<TimerEventRecompileSynchronous> timer(isolate); |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kOptimizeConcurrentFinalize); |
| TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.OptimizeConcurrentFinalize", job->trace_id(), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| DirectHandle<JSFunction> function = compilation_info->closure(); |
| DirectHandle<SharedFunctionInfo> shared = compilation_info->shared_info(); |
| |
| const bool use_result = !compilation_info->discard_result_for_testing(); |
| const BytecodeOffset osr_offset = compilation_info->osr_offset(); |
| |
| DCHECK(!shared->HasBreakInfo(isolate)); |
| |
| // 1) Optimization on the concurrent thread may have failed. |
| // 2) The function may have already been optimized by OSR. Simply continue. |
| // Except when OSR already disabled optimization for some reason. |
| // 3) The code may have already been invalidated due to dependency change. |
| // 4) InstructionStream generation may have failed. |
| if (job->state() == CompilationJob::State::kReadyToFinalize) { |
| if (shared->optimization_disabled()) { |
| job->RetryOptimization(BailoutReason::kOptimizationDisabled); |
| } else if (job->FinalizeJob(isolate) == CompilationJob::SUCCEEDED) { |
| job->RecordCompilationStats(ConcurrencyMode::kConcurrent, isolate); |
| job->RecordFunctionCompilation(LogEventListener::CodeTag::kFunction, |
| isolate); |
| if (V8_LIKELY(use_result)) { |
| function->SetTieringInProgress(false, |
| job->compilation_info()->osr_offset()); |
| if (!V8_ENABLE_LEAPTIERING_BOOL || IsOSR(osr_offset)) { |
| OptimizedCodeCache::Insert( |
| isolate, *compilation_info->closure(), |
| compilation_info->osr_offset(), *compilation_info->code(), |
| compilation_info->function_context_specializing()); |
| } |
| CompilerTracer::TraceCompletedJob(isolate, compilation_info); |
| if (IsOSR(osr_offset)) { |
| CompilerTracer::TraceOptimizeOSRFinished(isolate, function, |
| osr_offset); |
| } else { |
| function->UpdateOptimizedCode(isolate, *compilation_info->code()); |
| } |
| } |
| return; |
| } |
| } |
| |
| DCHECK_EQ(job->state(), CompilationJob::State::kFailed); |
| CompilerTracer::TraceAbortedJob(isolate, compilation_info, |
| job->prepare_in_ms(), job->execute_in_ms(), |
| job->finalize_in_ms()); |
| if (V8_LIKELY(use_result)) { |
| function->SetTieringInProgress(false, |
| job->compilation_info()->osr_offset()); |
| if (!IsOSR(osr_offset)) { |
| function->UpdateCode(shared->GetCode(isolate)); |
| } |
| } |
| } |
| |
| // static |
| void Compiler::DisposeMaglevCompilationJob(maglev::MaglevCompilationJob* job, |
| Isolate* isolate) { |
| #ifdef V8_ENABLE_MAGLEV |
| DirectHandle<JSFunction> function = job->function(); |
| function->SetTieringInProgress(false, job->osr_offset()); |
| #endif // V8_ENABLE_MAGLEV |
| } |
| |
| // static |
| void Compiler::FinalizeMaglevCompilationJob(maglev::MaglevCompilationJob* job, |
| Isolate* isolate) { |
| #ifdef V8_ENABLE_MAGLEV |
| VMState<COMPILER> state(isolate); |
| |
| DirectHandle<JSFunction> function = job->function(); |
| BytecodeOffset osr_offset = job->osr_offset(); |
| function->SetTieringInProgress(false, osr_offset); |
| |
| if (function->ActiveTierIsTurbofan(isolate) && !job->is_osr()) { |
| CompilerTracer::TraceAbortedMaglevCompile( |
| isolate, function, BailoutReason::kHigherTierAvailable); |
| return; |
| } |
| // Discard code compiled for a discarded native context without finalization. |
| if (function->native_context()->global_object()->IsDetached()) { |
| return; |
| } |
| |
| const CompilationJob::Status status = job->FinalizeJob(isolate); |
| |
| // TODO(v8:7700): Use the result and check if job succeed |
| // when all the bytecodes are implemented. |
| USE(status); |
| |
| if (status == CompilationJob::SUCCEEDED) { |
| DirectHandle<SharedFunctionInfo> shared(function->shared(), isolate); |
| DCHECK(!shared->HasBreakInfo(isolate)); |
| |
| // Note the finalized InstructionStream object has already been installed on |
| // the function by MaglevCompilationJob::FinalizeJobImpl. |
| |
| Handle<Code> code = job->code().ToHandleChecked(); |
| if (!job->is_osr()) { |
| job->function()->UpdateOptimizedCode(isolate, *code); |
| } |
| |
| DCHECK(code->is_maglevved()); |
| if (!V8_ENABLE_LEAPTIERING_BOOL || IsOSR(osr_offset)) { |
| OptimizedCodeCache::Insert(isolate, *function, osr_offset, *code, |
| job->specialize_to_function_context()); |
| } |
| |
| RecordMaglevFunctionCompilation(isolate, function, |
| Cast<AbstractCode>(code)); |
| job->RecordCompilationStats(isolate); |
| if (v8_flags.profile_guided_optimization && |
| shared->cached_tiering_decision() <= |
| CachedTieringDecision::kEarlySparkplug) { |
| shared->set_cached_tiering_decision(CachedTieringDecision::kEarlyMaglev); |
| } |
| CompilerTracer::TraceFinishMaglevCompile( |
| isolate, function, job->is_osr(), job->prepare_in_ms(), |
| job->execute_in_ms(), job->finalize_in_ms()); |
| } |
| #endif |
| } |
| |
| // static |
| void Compiler::PostInstantiation(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| IsCompiledScope* is_compiled_scope) { |
| DirectHandle<SharedFunctionInfo> shared(function->shared(), isolate); |
| |
| // If code is compiled to bytecode (i.e., isn't asm.js), then allocate a |
| // feedback and check for optimized code. |
| if (is_compiled_scope->is_compiled() && shared->HasBytecodeArray()) { |
| // Don't reset budget if there is a closure feedback cell array already. We |
| // are just creating a new closure that shares the same feedback cell. |
| JSFunction::InitializeFeedbackCell(function, is_compiled_scope, false); |
| |
| #ifndef V8_ENABLE_LEAPTIERING |
| if (function->has_feedback_vector()) { |
| // Evict any deoptimized code on feedback vector. We need to do this after |
| // creating the closure, since any heap allocations could trigger a GC and |
| // deoptimized the code on the feedback vector. So check for any |
| // deoptimized code just before installing it on the function. |
| function->feedback_vector()->EvictOptimizedCodeMarkedForDeoptimization( |
| isolate, *shared, "new function from shared function info"); |
| Tagged<Code> code = function->feedback_vector()->optimized_code(isolate); |
| if (!code.is_null()) { |
| // Caching of optimized code enabled and optimized code found. |
| DCHECK(!code->marked_for_deoptimization()); |
| DCHECK(function->shared()->is_compiled()); |
| function->UpdateOptimizedCode(isolate, code); |
| } |
| } |
| #endif // !V8_ENABLE_LEAPTIERING |
| |
| if (v8_flags.always_turbofan && shared->allows_lazy_compilation() && |
| !shared->optimization_disabled() && |
| !function->HasAvailableOptimizedCode(isolate)) { |
| CompilerTracer::TraceMarkForAlwaysOpt(isolate, function); |
| JSFunction::EnsureFeedbackVector(isolate, function, is_compiled_scope); |
| function->RequestOptimization(isolate, CodeKind::TURBOFAN_JS, |
| ConcurrencyMode::kSynchronous); |
| } |
| } |
| |
| if (shared->is_toplevel() || shared->is_wrapped()) { |
| // If it's a top-level script, report compilation to the debugger. |
| DirectHandle<Script> script(Cast<Script>(shared->script()), isolate); |
| isolate->debug()->OnAfterCompile(script); |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown"), |
| "ScriptCompiled", "data", |
| AddScriptCompiledTrace(isolate, shared)); |
| bool tracing_enabled; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"), |
| &tracing_enabled); |
| if (tracing_enabled) { |
| EmitScriptSourceTextTrace(isolate, shared); |
| } |
| } |
| } |
| |
| std::unique_ptr<v8::tracing::TracedValue> Compiler::AddScriptCompiledTrace( |
| Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) { |
| DirectHandle<Script> script(Cast<Script>(shared->script()), isolate); |
| i::Tagged<i::Object> context_value = |
| isolate->native_context()->debug_context_id(); |
| int contextId = (IsSmi(context_value)) ? i::Smi::ToInt(context_value) : 0; |
| Script::InitLineEnds(isolate, script); |
| Script::PositionInfo endInfo; |
| Script::GetPositionInfo( |
| script, i::Cast<i::String>(script->source())->length(), &endInfo); |
| Script::PositionInfo startInfo; |
| Script::GetPositionInfo(script, shared->StartPosition(), &startInfo); |
| auto value = v8::tracing::TracedValue::Create(); |
| value->SetString("isolate", |
| std::to_string(reinterpret_cast<size_t>(isolate))); |
| value->SetInteger("executionContextId", contextId); |
| value->SetInteger("scriptId", script->id()); |
| value->SetInteger("startLine", startInfo.line); |
| value->SetInteger("startColumn", startInfo.column); |
| value->SetInteger("endLine", endInfo.line); |
| value->SetInteger("endColumn", endInfo.column); |
| value->SetBoolean("isModule", script->origin_options().IsModule()); |
| value->SetBoolean("hasSourceUrl", script->HasValidSource()); |
| if (script->HasValidSource() && IsString(script->GetNameOrSourceURL())) { |
| value->SetString( |
| "sourceMapUrl", |
| i::Cast<i::String>(script->GetNameOrSourceURL())->ToCString().get()); |
| } |
| if (IsString(script->name())) { |
| value->SetString("url", |
| i::Cast<i::String>(script->name())->ToCString().get()); |
| } |
| value->SetString("hash", |
| i::Script::GetScriptHash(isolate, script, |
| /* forceForInspector: */ false) |
| ->ToCString() |
| .get()); |
| return value; |
| } |
| |
| void Compiler::EmitScriptSourceTextTrace( |
| Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) { |
| DirectHandle<Script> script(Cast<Script>(shared->script()), isolate); |
| if (IsString(script->source())) { |
| Tagged<String> source = i::Cast<i::String>(script->source()); |
| auto script_id = script->id(); |
| auto isolate_string = std::to_string(reinterpret_cast<size_t>(isolate)); |
| int32_t source_length = source->length(); |
| const int32_t kSplitMaxLength = 1000000; |
| if (source_length <= kSplitMaxLength) { |
| auto value = v8::tracing::TracedValue::Create(); |
| value->SetString("isolate", isolate_string); |
| value->SetInteger("scriptId", script_id); |
| value->SetInteger("length", source_length); |
| value->SetString("sourceText", source->ToCString().get()); |
| TRACE_EVENT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"), |
| "ScriptCompiled", "data", std::move(value)); |
| } else { |
| Handle<String> handle_source(source, isolate); |
| int32_t split_count = source_length / kSplitMaxLength + 1; |
| for (int32_t i = 0; i < split_count; i++) { |
| int32_t begin = i * kSplitMaxLength; |
| int32_t end = std::min(begin + kSplitMaxLength, source_length); |
| DirectHandle<String> partial_source = |
| isolate->factory()->NewSubString(handle_source, begin, end); |
| auto split_trace_value = v8::tracing::TracedValue::Create(); |
| split_trace_value->SetInteger("splitIndex", i); |
| split_trace_value->SetInteger("splitCount", split_count); |
| split_trace_value->SetString("isolate", isolate_string); |
| split_trace_value->SetInteger("scriptId", script_id); |
| split_trace_value->SetString("sourceText", |
| partial_source->ToCString().get()); |
| TRACE_EVENT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"), |
| "LargeScriptCompiledSplits", "data", std::move(split_trace_value)); |
| } |
| } |
| } |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of ScriptStreamingData |
| |
| ScriptStreamingData::ScriptStreamingData( |
| std::unique_ptr<ScriptCompiler::ExternalSourceStream> source_stream, |
| ScriptCompiler::StreamedSource::Encoding encoding) |
| : source_stream(std::move(source_stream)), encoding(encoding) {} |
| |
| ScriptStreamingData::~ScriptStreamingData() = default; |
| |
| void ScriptStreamingData::Release() { task.reset(); } |
| |
| } // namespace internal |
| } // namespace v8 |