Skip to content

Commit

Permalink
[api] Add API for off-thread code cache deserialization
Browse files Browse the repository at this point in the history
To consume a code cache off-thread

  1. The embedder creates a CachedData object wrapping the data blob.
  2. The embedder calls ScriptCompiler::StartConsumingCodeCache with the
     CachedData, and receives a ScriptCompiler::CodeCacheConsumeTask
     which takes ownership of the CachedData.
  3. The embedder calls ScriptCompiler::CodeCacheConsumeTask::Run
     on a different thread.
  4. Once this completes, the embedded passes the completed task as an
     optional argument into Source constructor, and calls Compile as
     before.

This is roughly similar to how streaming compilation works, with the
QoL improvement that Source owns the CodeCacheConsumeTask and therefore
we can reuse the same Compile method and do the off-thread finalization
behind the scenes inside Compile.

On the v8::internal side, ScriptCompiler::CodeCacheConsumeTask wraps a
v8::internal::BackgroundDeserializeTask, which has a Run and a Finish
method. The Run creates a LocalIsolate (again, similar to
BackgroundCompileTask), calls some helpers on CodeSerializer, and stores
the pre-finalization result in a OffThreadDeserializeData structure.
This stores Persistent Handles to the off-thread initialized SFI and
a vector of Scripts needing fixing up, and it owns the PersistentHandles
object which owns those Handles. Finally, the Finish method consumes
this OffThreadDeserializeData structure, fixes up Scripts, moves the
SFI Handle into the caller HandleScope, and that's it.

Since we don't yet have the source at off-thread deserialization time,
the various code cache sanity checks are done without the source hash
when deserializing, and the Finish method re-does them now that the
source is available.

Bug: chromium:1075999
Change-Id: If1faf35ba3ef840fa4e735581d0b29c96c1d5fc8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3067322
Commit-Queue: Leszek Swirski <[email protected]>
Reviewed-by: Omer Katz <[email protected]>
Reviewed-by: Jakob Gruber <[email protected]>
Reviewed-by: Camillo Bruni <[email protected]>
Cr-Commit-Position: refs/heads/master@{#76155}
  • Loading branch information
LeszekSwirski authored and V8 LUCI CQ committed Aug 9, 2021
1 parent 71986fe commit f888f48
Show file tree
Hide file tree
Showing 12 changed files with 658 additions and 174 deletions.
68 changes: 46 additions & 22 deletions include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ template<typename T> class PropertyCallbackInfo;
template<typename T> class ReturnValue;

namespace internal {
class BackgroundDeserializeTask;
class BasicTracedReferenceExtractor;
class ExternalString;
class FunctionCallbackArguments;
Expand Down Expand Up @@ -1814,6 +1815,8 @@ enum class ScriptType { kClassic, kModule };
*/
class V8_EXPORT ScriptCompiler {
public:
class ConsumeCodeCacheTask;

/**
* Compilation data that the embedder can cache and pass back to speed up
* future compilations. The data is produced if the CompilerOptions passed to
Expand Down Expand Up @@ -1857,12 +1860,15 @@ class V8_EXPORT ScriptCompiler {
*/
class Source {
public:
// Source takes ownership of CachedData.
// Source takes ownership of both CachedData and CodeCacheConsumeTask.
V8_INLINE Source(Local<String> source_string, const ScriptOrigin& origin,
CachedData* cached_data = nullptr);
V8_INLINE explicit Source(Local<String> source_string,
CachedData* cached_data = nullptr);
V8_INLINE ~Source();
CachedData* cached_data = nullptr,
ConsumeCodeCacheTask* consume_cache_task = nullptr);
// Source takes ownership of both CachedData and CodeCacheConsumeTask.
V8_INLINE explicit Source(
Local<String> source_string, CachedData* cached_data = nullptr,
ConsumeCodeCacheTask* consume_cache_task = nullptr);
V8_INLINE ~Source() = default;

// Ownership of the CachedData or its buffers is *not* transferred to the
// caller. The CachedData object is alive as long as the Source object is
Expand All @@ -1871,10 +1877,6 @@ class V8_EXPORT ScriptCompiler {

V8_INLINE const ScriptOriginOptions& GetResourceOptions() const;

// Prevent copying.
Source(const Source&) = delete;
Source& operator=(const Source&) = delete;

private:
friend class ScriptCompiler;

Expand All @@ -1891,7 +1893,8 @@ class V8_EXPORT ScriptCompiler {
// Cached data from previous compilation (if a kConsume*Cache flag is
// set), or hold newly generated cache data (kProduce*Cache flags) are
// set when calling a compile method.
CachedData* cached_data;
std::unique_ptr<CachedData> cached_data;
std::unique_ptr<ConsumeCodeCacheTask> consume_cache_task;
};

/**
Expand Down Expand Up @@ -1988,6 +1991,26 @@ class V8_EXPORT ScriptCompiler {
internal::ScriptStreamingData* data_;
};

/**
* A task which the embedder must run on a background thread to
* consume a V8 code cache. Returned by
* ScriptCompiler::StarConsumingCodeCache.
*/
class V8_EXPORT ConsumeCodeCacheTask final {
public:
~ConsumeCodeCacheTask();

void Run();

private:
friend class ScriptCompiler;

explicit ConsumeCodeCacheTask(
std::unique_ptr<internal::BackgroundDeserializeTask> impl);

std::unique_ptr<internal::BackgroundDeserializeTask> impl_;
};

enum CompileOptions {
kNoCompileOptions = 0,
kConsumeCodeCache,
Expand Down Expand Up @@ -2069,6 +2092,9 @@ class V8_EXPORT ScriptCompiler {
Isolate* isolate, StreamedSource* source,
ScriptType type = ScriptType::kClassic);

static ConsumeCodeCacheTask* StartConsumingCodeCache(
Isolate* isolate, std::unique_ptr<CachedData> source);

/**
* Compiles a streamed script (bound to current context).
*
Expand Down Expand Up @@ -11585,29 +11611,27 @@ int ScriptOrigin::ScriptId() const { return script_id_; }
Local<Value> ScriptOrigin::SourceMapUrl() const { return source_map_url_; }

ScriptCompiler::Source::Source(Local<String> string, const ScriptOrigin& origin,
CachedData* data)
CachedData* data,
ConsumeCodeCacheTask* consume_cache_task)
: source_string(string),
resource_name(origin.ResourceName()),
resource_line_offset(origin.LineOffset()),
resource_column_offset(origin.ColumnOffset()),
resource_options(origin.Options()),
source_map_url(origin.SourceMapUrl()),
host_defined_options(origin.HostDefinedOptions()),
cached_data(data) {}

ScriptCompiler::Source::Source(Local<String> string,
CachedData* data)
: source_string(string), cached_data(data) {}


ScriptCompiler::Source::~Source() {
delete cached_data;
}
cached_data(data),
consume_cache_task(consume_cache_task) {}

ScriptCompiler::Source::Source(Local<String> string, CachedData* data,
ConsumeCodeCacheTask* consume_cache_task)
: source_string(string),
cached_data(data),
consume_cache_task(consume_cache_task) {}

const ScriptCompiler::CachedData* ScriptCompiler::Source::GetCachedData()
const {
return cached_data;
return cached_data.get();
}

const ScriptOriginOptions& ScriptCompiler::Source::GetResourceOptions() const {
Expand Down
62 changes: 47 additions & 15 deletions src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2397,15 +2397,31 @@ MaybeLocal<UnboundScript> ScriptCompiler::CompileUnboundInternal(
CompileUnbound, MaybeLocal<UnboundScript>(),
InternalEscapableScope);

i::ScriptData* script_data = nullptr;
i::Handle<i::String> str = Utils::OpenHandle(*(source->source_string));

std::unique_ptr<i::AlignedCachedData> cached_data;
if (options == kConsumeCodeCache) {
DCHECK(source->cached_data);
// ScriptData takes care of pointer-aligning the data.
script_data = new i::ScriptData(source->cached_data->data,
source->cached_data->length);
if (source->consume_cache_task) {
// If there's a cache consume task, finish it
i::MaybeHandle<i::SharedFunctionInfo> maybe_function_info =
source->consume_cache_task->impl_->Finish(isolate, str,
source->resource_options);
i::Handle<i::SharedFunctionInfo> result;
if (maybe_function_info.ToHandle(&result)) {
RETURN_ESCAPED(ToApiHandle<UnboundScript>(result));
}
// If the above failed, then we must have rejected the cache. Continue
// with normal compilation, disabling the code cache consumption.
source->cached_data->rejected = true;
options = kNoCompileOptions;
} else {
DCHECK(source->cached_data);
// AlignedCachedData takes care of pointer-aligning the data.
cached_data.reset(new i::AlignedCachedData(source->cached_data->data,
source->cached_data->length));
}
}

i::Handle<i::String> str = Utils::OpenHandle(*(source->source_string));
i::Handle<i::SharedFunctionInfo> result;
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompileScript");
i::ScriptDetails script_details = GetScriptDetails(
Expand All @@ -2414,12 +2430,11 @@ MaybeLocal<UnboundScript> ScriptCompiler::CompileUnboundInternal(
source->host_defined_options, source->resource_options);
i::MaybeHandle<i::SharedFunctionInfo> maybe_function_info =
i::Compiler::GetSharedFunctionInfoForScript(
isolate, str, script_details, nullptr, script_data, options,
isolate, str, script_details, nullptr, cached_data.get(), options,
no_cache_reason, i::NOT_NATIVES_CODE);
if (options == kConsumeCodeCache) {
source->cached_data->rejected = script_data->rejected();
source->cached_data->rejected = cached_data->rejected();
}
delete script_data;
has_pending_exception = !maybe_function_info.ToHandle(&result);
RETURN_ON_FAILED_EXECUTION(UnboundScript);
RETURN_ESCAPED(ToApiHandle<UnboundScript>(result));
Expand Down Expand Up @@ -2541,24 +2556,23 @@ MaybeLocal<Function> ScriptCompiler::CompileFunctionInContext(
source->resource_column_offset, source->source_map_url,
source->host_defined_options, source->resource_options);

i::ScriptData* script_data = nullptr;
std::unique_ptr<i::AlignedCachedData> cached_data;
if (options == kConsumeCodeCache) {
DCHECK(source->cached_data);
// ScriptData takes care of pointer-aligning the data.
script_data = new i::ScriptData(source->cached_data->data,
source->cached_data->length);
cached_data.reset(new i::AlignedCachedData(source->cached_data->data,
source->cached_data->length));
}

i::Handle<i::JSFunction> scoped_result;
has_pending_exception =
!i::Compiler::GetWrappedFunction(
Utils::OpenHandle(*source->source_string), arguments_list, context,
script_details, script_data, options, no_cache_reason)
script_details, cached_data.get(), options, no_cache_reason)
.ToHandle(&scoped_result);
if (options == kConsumeCodeCache) {
source->cached_data->rejected = script_data->rejected();
source->cached_data->rejected = cached_data->rejected();
}
delete script_data;
RETURN_ON_FAILED_EXECUTION(Function);
result = handle_scope.Escape(Utils::CallableToLocal(scoped_result));
}
Expand Down Expand Up @@ -2597,6 +2611,24 @@ ScriptCompiler::ScriptStreamingTask* ScriptCompiler::StartStreaming(
return new ScriptCompiler::ScriptStreamingTask(data);
}

ScriptCompiler::ConsumeCodeCacheTask::ConsumeCodeCacheTask(
std::unique_ptr<i::BackgroundDeserializeTask> impl)
: impl_(std::move(impl)) {}

ScriptCompiler::ConsumeCodeCacheTask::~ConsumeCodeCacheTask() = default;

void ScriptCompiler::ConsumeCodeCacheTask::Run() { impl_->Run(); }

ScriptCompiler::ConsumeCodeCacheTask* ScriptCompiler::StartConsumingCodeCache(
Isolate* v8_isolate, std::unique_ptr<CachedData> cached_data) {
if (!i::FLAG_concurrent_cache_deserialization) return nullptr;
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ASSERT_NO_SCRIPT_NO_EXCEPTION(isolate);
return new ScriptCompiler::ConsumeCodeCacheTask(
std::make_unique<i::BackgroundDeserializeTask>(isolate,
std::move(cached_data)));
}

namespace {
i::MaybeHandle<i::SharedFunctionInfo> CompileStreamedSource(
i::Isolate* isolate, ScriptCompiler::StreamedSource* v8_source,
Expand Down
37 changes: 35 additions & 2 deletions src/codegen/compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,37 @@ Handle<Script> BackgroundCompileTask::GetScript(Isolate* isolate) {
return handle(*script_, isolate);
}

BackgroundDeserializeTask::BackgroundDeserializeTask(
Isolate* isolate, std::unique_ptr<ScriptCompiler::CachedData> cached_data)
: isolate_for_local_isolate_(isolate),
cached_data_(cached_data->data, cached_data->length) {
// 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() {
LocalIsolate isolate(isolate_for_local_isolate_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&isolate);
LocalHandleScope handle_scope(&isolate);

Handle<SharedFunctionInfo> inner_result;
off_thread_data_ =
CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_);
}

MaybeHandle<SharedFunctionInfo> BackgroundDeserializeTask::Finish(
Isolate* isolate, Handle<String> source,
ScriptOriginOptions origin_options) {
return CodeSerializer::FinishOffThreadDeserialize(
isolate, std::move(off_thread_data_), &cached_data_, source,
origin_options);
}

// ----------------------------------------------------------------------------
// Implementation of Compiler

Expand Down Expand Up @@ -2805,7 +2836,8 @@ MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
Isolate* isolate, Handle<String> source,
const ScriptDetails& script_details, v8::Extension* extension,
ScriptData* cached_data, ScriptCompiler::CompileOptions compile_options,
AlignedCachedData* cached_data,
ScriptCompiler::CompileOptions compile_options,
ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives) {
ScriptCompileTimerScope compile_timer(isolate, no_cache_reason);

Expand Down Expand Up @@ -2908,7 +2940,8 @@ MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
MaybeHandle<JSFunction> Compiler::GetWrappedFunction(
Handle<String> source, Handle<FixedArray> arguments,
Handle<Context> context, const ScriptDetails& script_details,
ScriptData* cached_data, v8::ScriptCompiler::CompileOptions compile_options,
AlignedCachedData* cached_data,
v8::ScriptCompiler::CompileOptions compile_options,
v8::ScriptCompiler::NoCacheReason no_cache_reason) {
Isolate* isolate = context->GetIsolate();
ScriptCompileTimerScope compile_timer(isolate, no_cache_reason);
Expand Down
25 changes: 22 additions & 3 deletions src/codegen/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
#include "src/objects/debug-objects.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/pending-compilation-error-handler.h"
#include "src/snapshot/code-serializer.h"
#include "src/utils/allocation.h"
#include "src/zone/zone.h"

namespace v8 {
namespace internal {

// Forward declarations.
class AlignedCachedData;
class AstRawString;
class BackgroundCompileTask;
class IsCompiledScope;
Expand All @@ -35,7 +37,6 @@ class OptimizedCompilationJob;
class ParseInfo;
class Parser;
class RuntimeCallStats;
class ScriptData;
class TimedHistogram;
class UnoptimizedCompilationInfo;
class UnoptimizedCompilationJob;
Expand Down Expand Up @@ -136,7 +137,7 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic {
V8_WARN_UNUSED_RESULT static MaybeHandle<JSFunction> GetWrappedFunction(
Handle<String> source, Handle<FixedArray> arguments,
Handle<Context> context, const ScriptDetails& script_details,
ScriptData* cached_data,
AlignedCachedData* cached_data,
v8::ScriptCompiler::CompileOptions compile_options,
v8::ScriptCompiler::NoCacheReason no_cache_reason);

Expand All @@ -161,7 +162,8 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic {
static MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScript(
Isolate* isolate, Handle<String> source,
const ScriptDetails& script_details, v8::Extension* extension,
ScriptData* cached_data, ScriptCompiler::CompileOptions compile_options,
AlignedCachedData* cached_data,
ScriptCompiler::CompileOptions compile_options,
ScriptCompiler::NoCacheReason no_cache_reason,
NativesFlag is_natives_code);

Expand Down Expand Up @@ -558,6 +560,23 @@ struct ScriptStreamingData {
std::unique_ptr<BackgroundCompileTask> task;
};

class V8_EXPORT_PRIVATE BackgroundDeserializeTask {
public:
BackgroundDeserializeTask(Isolate* isolate,
std::unique_ptr<ScriptCompiler::CachedData> data);

void Run();

MaybeHandle<SharedFunctionInfo> Finish(Isolate* isolate,
Handle<String> source,
ScriptOriginOptions origin_options);

private:
Isolate* isolate_for_local_isolate_;
AlignedCachedData cached_data_;
CodeSerializer::OffThreadDeserializeData off_thread_data_;
};

} // namespace internal
} // namespace v8

Expand Down
4 changes: 2 additions & 2 deletions src/debug/debug-interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,10 @@ MaybeLocal<UnboundScript> CompileInspectorScript(Isolate* v8_isolate,
i::Handle<i::String> str = Utils::OpenHandle(*source);
i::Handle<i::SharedFunctionInfo> result;
{
i::ScriptData* script_data = nullptr;
i::AlignedCachedData* cached_data = nullptr;
i::MaybeHandle<i::SharedFunctionInfo> maybe_function_info =
i::Compiler::GetSharedFunctionInfoForScript(
isolate, str, i::ScriptDetails(), nullptr, script_data,
isolate, str, i::ScriptDetails(), nullptr, cached_data,
ScriptCompiler::kNoCompileOptions,
ScriptCompiler::kNoCacheBecauseInspector,
i::FLAG_expose_inspector_scripts ? i::NOT_NATIVES_CODE
Expand Down
2 changes: 2 additions & 0 deletions src/flags/flag-definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,8 @@ DEFINE_BOOL(stress_background_compile, false,
DEFINE_BOOL(
finalize_streaming_on_background, true,
"perform the script streaming finalization on the background thread")
DEFINE_BOOL(concurrent_cache_deserialization, true,
"enable deserializing code caches on background")
// TODO(leszeks): Parallel compile tasks currently don't support off-thread
// finalization.
DEFINE_NEG_IMPLICATION(parallel_compile_tasks, finalize_streaming_on_background)
Expand Down
1 change: 1 addition & 0 deletions src/heap/local-heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class V8_EXPORT_PRIVATE LocalHeap {
std::unique_ptr<PersistentHandles> persistent_handles);
std::unique_ptr<PersistentHandles> DetachPersistentHandles();
#ifdef DEBUG
bool HasPersistentHandles() { return !!persistent_handles_; }
bool ContainsPersistentHandle(Address* location);
bool ContainsLocalHandle(Address* location);
bool IsHandleDereferenceAllowed();
Expand Down
Loading

0 comments on commit f888f48

Please sign in to comment.