Commit f888f48e authored by Leszek Swirski's avatar Leszek Swirski Committed by V8 LUCI CQ

[api] Add API for off-thread code cache deserialization

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 <leszeks@chromium.org>
Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#76155}
parent 71986fef
......@@ -128,6 +128,7 @@ template<typename T> class PropertyCallbackInfo;
template<typename T> class ReturnValue;
namespace internal {
class BackgroundDeserializeTask;
class BasicTracedReferenceExtractor;
class ExternalString;
class FunctionCallbackArguments;
......@@ -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
......@@ -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
......@@ -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;
......@@ -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;
};
/**
......@@ -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,
......@@ -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).
*
......@@ -11585,7 +11611,8 @@ 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()),
......@@ -11593,21 +11620,18 @@ ScriptCompiler::Source::Source(Local<String> string, const ScriptOrigin& origin,
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 {
......
......@@ -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(
......@@ -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));
......@@ -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));
}
......@@ -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,
......
......@@ -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
......@@ -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);
......@@ -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);
......
......@@ -19,6 +19,7 @@
#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"
......@@ -26,6 +27,7 @@ namespace v8 {
namespace internal {
// Forward declarations.
class AlignedCachedData;
class AstRawString;
class BackgroundCompileTask;
class IsCompiledScope;
......@@ -35,7 +37,6 @@ class OptimizedCompilationJob;
class ParseInfo;
class Parser;
class RuntimeCallStats;
class ScriptData;
class TimedHistogram;
class UnoptimizedCompilationInfo;
class UnoptimizedCompilationJob;
......@@ -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);
......@@ -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);
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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();
......
This diff is collapsed.
......@@ -12,14 +12,16 @@
namespace v8 {
namespace internal {
class V8_EXPORT_PRIVATE ScriptData {
class PersistentHandles;
class V8_EXPORT_PRIVATE AlignedCachedData {
public:
ScriptData(const byte* data, int length);
~ScriptData() {
AlignedCachedData(const byte* data, int length);
~AlignedCachedData() {
if (owns_data_) DeleteArray(data_);
}
ScriptData(const ScriptData&) = delete;
ScriptData& operator=(const ScriptData&) = delete;
AlignedCachedData(const AlignedCachedData&) = delete;
AlignedCachedData& operator=(const AlignedCachedData&) = delete;
const byte* data() const { return data_; }
int length() const { return length_; }
......@@ -27,6 +29,8 @@ class V8_EXPORT_PRIVATE ScriptData {
void Reject() { rejected_ = true; }
bool HasDataOwnership() const { return owns_data_; }
void AcquireDataOwnership() {
DCHECK(!owns_data_);
owns_data_ = true;
......@@ -46,17 +50,36 @@ class V8_EXPORT_PRIVATE ScriptData {
class CodeSerializer : public Serializer {
public:
struct OffThreadDeserializeData {
private:
friend class CodeSerializer;
MaybeHandle<SharedFunctionInfo> maybe_result;
std::vector<Handle<Script>> scripts;
std::unique_ptr<PersistentHandles> persistent_handles;
};
CodeSerializer(const CodeSerializer&) = delete;
CodeSerializer& operator=(const CodeSerializer&) = delete;
V8_EXPORT_PRIVATE static ScriptCompiler::CachedData* Serialize(
Handle<SharedFunctionInfo> info);
ScriptData* SerializeSharedFunctionInfo(Handle<SharedFunctionInfo> info);
AlignedCachedData* SerializeSharedFunctionInfo(
Handle<SharedFunctionInfo> info);
V8_WARN_UNUSED_RESULT static MaybeHandle<SharedFunctionInfo> Deserialize(
Isolate* isolate, ScriptData* cached_data, Handle<String> source,
Isolate* isolate, AlignedCachedData* cached_data, Handle<String> source,
ScriptOriginOptions origin_options);
V8_WARN_UNUSED_RESULT static OffThreadDeserializeData
StartDeserializeOffThread(LocalIsolate* isolate,
AlignedCachedData* cached_data);
V8_WARN_UNUSED_RESULT static MaybeHandle<SharedFunctionInfo>
FinishOffThreadDeserialize(Isolate* isolate, OffThreadDeserializeData&& data,
AlignedCachedData* cached_data,
Handle<String> source,
ScriptOriginOptions origin_options);
uint32_t source_hash() const { return source_hash_; }
protected:
......@@ -106,16 +129,18 @@ class SerializedCodeData : public SerializedData {
static const uint32_t kHeaderSize = POINTER_SIZE_ALIGN(kUnalignedHeaderSize);
// Used when consuming.
static SerializedCodeData FromCachedData(ScriptData* cached_data,
static SerializedCodeData FromCachedData(AlignedCachedData* cached_data,
uint32_t expected_source_hash,
SanityCheckResult* rejection_result);
static SerializedCodeData FromCachedDataWithoutSource(
AlignedCachedData* cached_data, SanityCheckResult* rejection_result);
// Used when producing.
SerializedCodeData(const std::vector<byte>* payload,
const CodeSerializer* cs);
// Return ScriptData object and relinquish ownership over it to the caller.
ScriptData* GetScriptData();
AlignedCachedData* GetScriptData();
base::Vector<const byte> Payload() const;
......@@ -123,7 +148,7 @@ class SerializedCodeData : public SerializedData {
ScriptOriginOptions origin_options);
private:
explicit SerializedCodeData(ScriptData* data);
explicit SerializedCodeData(AlignedCachedData* data);
SerializedCodeData(const byte* data, int size)
: SerializedData(const_cast<byte*>(data), size) {}
......@@ -132,6 +157,7 @@ class SerializedCodeData : public SerializedData {
}
SanityCheckResult SanityCheck(uint32_t expected_source_hash) const;
SanityCheckResult SanityCheckWithoutSource() const;
};
} // namespace internal
......
......@@ -1572,7 +1572,8 @@ int CountBuiltins() {
static Handle<SharedFunctionInfo> CompileScript(
Isolate* isolate, Handle<String> source, Handle<String> name,
ScriptData* cached_data, v8::ScriptCompiler::CompileOptions options) {
AlignedCachedData* cached_data,
v8::ScriptCompiler::CompileOptions options) {
return Compiler::GetSharedFunctionInfoForScript(
isolate, source, ScriptDetails(name), nullptr, cached_data,
options, ScriptCompiler::kNoCacheNoReason, NOT_NATIVES_CODE)
......@@ -1581,7 +1582,8 @@ static Handle<SharedFunctionInfo> CompileScript(
static Handle<SharedFunctionInfo> CompileScriptAndProduceCache(
Isolate* isolate, Handle<String> source, Handle<String> name,
ScriptData** script_data, v8::ScriptCompiler::CompileOptions options) {
AlignedCachedData** out_cached_data,
v8::ScriptCompiler::CompileOptions options) {
Handle<SharedFunctionInfo> sfi =
Compiler::GetSharedFunctionInfoForScript(
isolate, source, ScriptDetails(name), nullptr, nullptr, options,
......@@ -1591,8 +1593,8 @@ static Handle<SharedFunctionInfo> CompileScriptAndProduceCache(
ScriptCompiler::CreateCodeCache(ToApiHandle<UnboundScript>(sfi)));
uint8_t* buffer = NewArray<uint8_t>(cached_data->length);
MemCopy(buffer, cached_data->data, cached_data->length);
*script_data = new i::ScriptData(buffer, cached_data->length);
(*script_data)->AcquireDataOwnership();
*out_cached_data = new i::AlignedCachedData(buffer, cached_data->length);
(*out_cached_data)->AcquireDataOwnership();
return sfi;
}
......@@ -1618,7 +1620,7 @@ TEST(CodeSerializerWithProfiler) {
CHECK(!orig_source.is_identical_to(copy_source));
CHECK(orig_source->Equals(*copy_source));
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, orig_source, Handle<String>(), &cache,
......@@ -1660,7 +1662,7 @@ void TestCodeSerializerOnePlusOneImpl(bool verify_builtins_count = true) {
CHECK(!orig_source.is_identical_to(copy_source));
CHECK(orig_source->Equals(*copy_source));
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, orig_source, Handle<String>(), &cache,
......@@ -1719,7 +1721,7 @@ TEST(CodeSerializerPromotedToCompilationCache) {
const char* source = "1 + 1";
Handle<String> src = isolate->factory()->NewStringFromAsciiChecked(source);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
CompileScriptAndProduceCache(isolate, src, src, &cache,
v8::ScriptCompiler::kNoCompileOptions);
......@@ -1830,9 +1832,9 @@ TEST(CodeSerializerInternalizedString) {
Handle<JSObject> global(isolate->context().global_object(), isolate);
i::ScriptData* script_data = nullptr;
i::AlignedCachedData* cached_data = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, orig_source, Handle<String>(), &script_data,
isolate, orig_source, Handle<String>(), &cached_data,
v8::ScriptCompiler::kNoCompileOptions);
Handle<JSFunction> orig_fun =
Factory::JSFunctionBuilder{isolate, orig, isolate->native_context()}
......@@ -1846,7 +1848,7 @@ TEST(CodeSerializerInternalizedString) {
Handle<SharedFunctionInfo> copy;
{
DisallowCompilation no_compile_expected(isolate);
copy = CompileScript(isolate, copy_source, Handle<String>(), script_data,
copy = CompileScript(isolate, copy_source, Handle<String>(), cached_data,
v8::ScriptCompiler::kConsumeCodeCache);
}
CHECK_NE(*orig, *copy);
......@@ -1865,7 +1867,7 @@ TEST(CodeSerializerInternalizedString) {
CHECK(Handle<String>::cast(copy_result)->Equals(*expected));
CHECK_EQ(builtins_count, CountBuiltins());
delete script_data;
delete cached_data;
}
TEST(CodeSerializerLargeCodeObject) {
......@@ -1889,7 +1891,7 @@ TEST(CodeSerializerLargeCodeObject) {
isolate->factory()->NewStringFromUtf8(source).ToHandleChecked();
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_str, Handle<String>(), &cache,
......@@ -1956,7 +1958,7 @@ TEST(CodeSerializerLargeCodeObjectWithIncrementalMarking) {
}
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_str, Handle<String>(), &cache,
......@@ -2021,7 +2023,7 @@ TEST(CodeSerializerLargeStrings) {
.ToHandleChecked();
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_str, Handle<String>(), &cache,
......@@ -2095,7 +2097,7 @@ TEST(CodeSerializerThreeBigStrings) {
.ToHandleChecked();
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_str, Handle<String>(), &cache,
......@@ -2214,7 +2216,7 @@ TEST(CodeSerializerExternalString) {
.ToHandleChecked();
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_string, Handle<String>(), &cache,
......@@ -2278,7 +2280,7 @@ TEST(CodeSerializerLargeExternalString) {
.ToHandleChecked();
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig = CompileScriptAndProduceCache(
isolate, source_str, Handle<String>(), &cache,
......@@ -2332,7 +2334,7 @@ TEST(CodeSerializerExternalScriptName) {
CHECK(!name->IsInternalizedString());
Handle<JSObject> global(isolate->context().global_object(), isolate);
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
Handle<SharedFunctionInfo> orig =
CompileScriptAndProduceCache(isolate, source_string, name, &cache,
......@@ -2700,11 +2702,11 @@ TEST(Regress503552) {
HandleScope scope(isolate);
Handle<String> source = isolate->factory()->NewStringFromAsciiChecked(
"function f() {} function g() {}");
ScriptData* script_data = nullptr;
AlignedCachedData* cached_data = nullptr;
Handle<SharedFunctionInfo> shared = CompileScriptAndProduceCache(
isolate, source, Handle<String>(), &script_data,
isolate, source, Handle<String>(), &cached_data,
v8::ScriptCompiler::kNoCompileOptions);
delete script_data;
delete cached_data;
heap::SimulateIncrementalMarking(isolate->heap());
......@@ -4089,7 +4091,7 @@ TEST(WeakArraySerializationInCodeCache) {
Handle<String> src = isolate->factory()
->NewStringFromUtf8(base::CStrVector(source))
.ToHandleChecked();
ScriptData* cache = nullptr;
AlignedCachedData* cache = nullptr;
CompileScriptAndProduceCache(isolate, src, src, &cache,
v8::ScriptCompiler::kNoCompileOptions);
......
......@@ -201,6 +201,7 @@ v8_source_set("unittests_sources") {
"../../testing/gmock-support.h",
"../../testing/gtest-support.h",
"api/access-check-unittest.cc",
"api/deserialize-unittest.cc",
"api/exception-unittest.cc",
"api/interceptor-unittest.cc",
"api/isolate-unittest.cc",
......
// Copyright 2021 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 "include/libplatform/libplatform.h"
#include "include/v8-platform.h"
#include "include/v8.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
class DeserializeTest : public testing::Test {
public:
class IsolateAndContextScope {
public:
explicit IsolateAndContextScope(DeserializeTest* test)
: test_(test),
isolate_wrapper_(kNoCounters),
isolate_scope_(isolate_wrapper_.isolate()),
handle_scope_(isolate_wrapper_.isolate()),
context_(Context::New(isolate_wrapper_.isolate())),
context_scope_(context_) {
CHECK_NULL(test->isolate_);
CHECK(test->context_.IsEmpty());
test->isolate_ = isolate_wrapper_.isolate();
test->context_ = context_;
}
~IsolateAndContextScope() {
test_->isolate_ = nullptr;
test_->context_ = {};
}
private:
DeserializeTest* test_;
v8::IsolateWrapper isolate_wrapper_;
v8::Isolate::Scope isolate_scope_;
v8::HandleScope handle_scope_;
v8::Local<v8::Context> context_;
v8::Context::Scope context_scope_;
};
Local<String> NewString(const char* val) {
return String::NewFromUtf8(isolate(), val).ToLocalChecked();
}
Local<Value> RunGlobalFunc(const char* name) {
Local<Value> func_val =
context()->Global()->Get(context(), NewString(name)).ToLocalChecked();
CHECK(func_val->IsFunction());
Local<Function> func = Local<Function>::Cast(func_val);
return func->Call(context(), Undefined(isolate()), 0, nullptr)
.ToLocalChecked();
}
Isolate* isolate() { return isolate_; }
v8::Local<v8::Context> context() { return context_.ToLocalChecked(); }
private:
Isolate* isolate_ = nullptr;
v8::MaybeLocal<v8::Context> context_;
};
// Check that deserialization works.
TEST_F(DeserializeTest, Deserialize) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
ScriptCompiler::Source source(source_code, cached_data.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
}
}
// Check that deserialization with a different script rejects the cache but
// still works via standard compilation.
TEST_F(DeserializeTest, DeserializeRejectsDifferentSource) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
// The source hash is based on the source length, so have to make sure that
// this is different here.
Local<String> source_code = NewString("function bar() { return 142; }");
ScriptCompiler::Source source(source_code, cached_data.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
}
}
class DeserializeThread : public base::Thread {
public:
explicit DeserializeThread(ScriptCompiler::ConsumeCodeCacheTask* task)
: Thread(base::Thread::Options("DeserializeThread")), task_(task) {}
void Run() override { task_->Run(); }
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> TakeTask() {
return std::move(task_);
}
private:
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task_;
};
// Check that off-thread deserialization works.
TEST_F(DeserializeTest, OffThreadDeserialize) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
Local<String> source_code = NewString("function foo() { return 42; }");
ScriptCompiler::Source source(source_code, cached_data.release(),
deserialize_thread.TakeTask().release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
}
}
// Check that off-thread deserialization works.
TEST_F(DeserializeTest, OffThreadDeserializeRejectsDifferentSource) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
Local<String> source_code = NewString("function bar() { return 142; }");
ScriptCompiler::Source source(source_code, cached_data.release(),
deserialize_thread.TakeTask().release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
}
}
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment