blob: 0f9959905b6ed988441f6b463a91ac909703e939 [file] [log] [blame]
// Copyright 2018 The Chromium 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 "third_party/blink/renderer/bindings/core/v8/v8_code_cache.h"
#include "base/optional.h"
#include "build/build_config.h"
#include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/bindings/core/v8/module_record.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
namespace blink {
namespace {
enum CacheTagKind { kCacheTagCode = 0, kCacheTagTimeStamp = 1, kCacheTagLast };
static const int kCacheTagKindSize = 1;
static_assert((1 << kCacheTagKindSize) >= kCacheTagLast,
"CacheTagLast must be large enough");
uint32_t CacheTag(CacheTagKind kind, const String& encoding) {
static uint32_t v8_cache_data_version =
v8::ScriptCompiler::CachedDataVersionTag() << kCacheTagKindSize;
// A script can be (successfully) interpreted with different encodings,
// depending on the page it appears in. The cache doesn't know anything
// about encodings, but the cached data is specific to one encoding. If we
// later load the script from the cache and interpret it with a different
// encoding, the cached data is not valid for that encoding.
return (v8_cache_data_version | kind) +
(encoding.IsNull() ? 0 : StringHash::GetHash(encoding));
}
// Check previously stored timestamp.
bool IsResourceHotForCaching(const SingleCachedMetadataHandler* cache_handler) {
static constexpr base::TimeDelta kHotHours = base::TimeDelta::FromHours(72);
scoped_refptr<CachedMetadata> cached_metadata =
cache_handler->GetCachedMetadata(
V8CodeCache::TagForTimeStamp(cache_handler));
if (!cached_metadata)
return false;
uint64_t time_stamp_ms;
const uint32_t size = sizeof(time_stamp_ms);
DCHECK_EQ(cached_metadata->size(), size);
memcpy(&time_stamp_ms, cached_metadata->Data(), size);
base::TimeTicks time_stamp =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(time_stamp_ms);
return (base::TimeTicks::Now() - time_stamp) < kHotHours;
}
} // namespace
bool V8CodeCache::HasCodeCache(
const SingleCachedMetadataHandler* cache_handler) {
if (!cache_handler)
return false;
uint32_t code_cache_tag = V8CodeCache::TagForCodeCache(cache_handler);
return cache_handler->GetCachedMetadata(code_cache_tag).get();
}
v8::ScriptCompiler::CachedData* V8CodeCache::CreateCachedData(
const SingleCachedMetadataHandler* cache_handler) {
DCHECK(cache_handler);
uint32_t code_cache_tag = V8CodeCache::TagForCodeCache(cache_handler);
scoped_refptr<CachedMetadata> cached_metadata =
cache_handler->GetCachedMetadata(code_cache_tag);
DCHECK(cached_metadata);
const uint8_t* data = cached_metadata->Data();
int length = cached_metadata->size();
return new v8::ScriptCompiler::CachedData(
data, length, v8::ScriptCompiler::CachedData::BufferNotOwned);
}
std::tuple<v8::ScriptCompiler::CompileOptions,
V8CodeCache::ProduceCacheOptions,
v8::ScriptCompiler::NoCacheReason>
V8CodeCache::GetCompileOptions(mojom::blink::V8CacheOptions cache_options,
const ScriptSourceCode& source) {
return GetCompileOptions(cache_options, source.CacheHandler(),
source.Source().length(),
source.SourceLocationType());
}
std::tuple<v8::ScriptCompiler::CompileOptions,
V8CodeCache::ProduceCacheOptions,
v8::ScriptCompiler::NoCacheReason>
V8CodeCache::GetCompileOptions(mojom::blink::V8CacheOptions cache_options,
const SingleCachedMetadataHandler* cache_handler,
size_t source_text_length,
ScriptSourceLocationType source_location_type) {
static const int kMinimalCodeLength = 1024;
v8::ScriptCompiler::NoCacheReason no_cache_reason;
switch (source_location_type) {
case ScriptSourceLocationType::kInline:
no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseInlineScript;
break;
case ScriptSourceLocationType::kInlineInsideDocumentWrite:
no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseInDocumentWrite;
break;
case ScriptSourceLocationType::kExternalFile:
no_cache_reason =
v8::ScriptCompiler::kNoCacheBecauseResourceWithNoCacheHandler;
break;
// TODO(leszeks): Possibly differentiate between the other kinds of script
// origin also.
default:
no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseNoResource;
break;
}
if (!cache_handler) {
return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kNoProduceCache,
no_cache_reason);
}
if (cache_options == mojom::blink::V8CacheOptions::kNone) {
no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseCachingDisabled;
return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kNoProduceCache,
no_cache_reason);
}
if (source_text_length < kMinimalCodeLength) {
no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseScriptTooSmall;
return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kNoProduceCache,
no_cache_reason);
}
if (HasCodeCache(cache_handler)) {
return std::make_tuple(v8::ScriptCompiler::kConsumeCodeCache,
ProduceCacheOptions::kNoProduceCache,
no_cache_reason);
}
// If the resource is served from CacheStorage, generate the V8 code cache in
// the first load.
if (cache_handler->IsServedFromCacheStorage())
cache_options = mojom::blink::V8CacheOptions::kCodeWithoutHeatCheck;
switch (cache_options) {
case mojom::blink::V8CacheOptions::kDefault:
case mojom::blink::V8CacheOptions::kCode:
if (!IsResourceHotForCaching(cache_handler)) {
return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kSetTimeStamp,
v8::ScriptCompiler::kNoCacheBecauseCacheTooCold);
}
return std::make_tuple(
v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kProduceCodeCache,
v8::ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache);
case mojom::blink::V8CacheOptions::kCodeWithoutHeatCheck:
return std::make_tuple(
v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kProduceCodeCache,
v8::ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache);
case mojom::blink::V8CacheOptions::kFullCodeWithoutHeatCheck:
return std::make_tuple(
v8::ScriptCompiler::kEagerCompile,
ProduceCacheOptions::kProduceCodeCache,
v8::ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache);
case mojom::blink::V8CacheOptions::kNone:
// Shouldn't happen, as this is handled above.
// Case is here so that compiler can check all cases are handled.
NOTREACHED();
break;
}
// All switch branches should return and we should never get here.
// But some compilers aren't sure, hence this default.
NOTREACHED();
return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions,
ProduceCacheOptions::kNoProduceCache,
v8::ScriptCompiler::kNoCacheNoReason);
}
template <typename UnboundScript>
static void ProduceCacheInternal(
v8::Isolate* isolate,
UnboundScript unbound_script,
SingleCachedMetadataHandler* cache_handler,
size_t source_text_length,
const KURL& source_url,
const TextPosition& source_start_position,
bool is_streamed,
const char* trace_name,
V8CodeCache::ProduceCacheOptions produce_cache_options,
ScriptStreamer::NotStreamingReason not_streaming_reason) {
TRACE_EVENT0("v8", trace_name);
RuntimeCallStatsScopedTracer rcs_scoped_tracer(isolate);
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
switch (produce_cache_options) {
case V8CodeCache::ProduceCacheOptions::kSetTimeStamp:
V8CodeCache::SetCacheTimeStamp(cache_handler);
break;
case V8CodeCache::ProduceCacheOptions::kProduceCodeCache: {
// TODO(crbug.com/938269): Investigate why this can be empty here.
if (unbound_script.IsEmpty())
break;
constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline";
TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, trace_name, "fileName",
source_url.GetString().Utf8());
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data(
v8::ScriptCompiler::CreateCodeCache(unbound_script));
if (cached_data) {
const uint8_t* data = cached_data->data;
int length = cached_data->length;
if (length > 1024) {
// Omit histogram samples for small cache data to avoid outliers.
int cache_size_ratio =
static_cast<int>(100.0 * length / source_text_length);
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, code_cache_size_histogram,
("V8.CodeCacheSizeRatio", 0, 10000, 50));
code_cache_size_histogram.Count(cache_size_ratio);
}
cache_handler->ClearCachedMetadata(
CachedMetadataHandler::kClearLocally);
cache_handler->SetCachedMetadata(
V8CodeCache::TagForCodeCache(cache_handler), data, length);
}
TRACE_EVENT_END1(
kTraceEventCategoryGroup, trace_name, "data",
inspector_compile_script_event::Data(
source_url.GetString(), source_start_position,
inspector_compile_script_event::V8CacheResult(
inspector_compile_script_event::V8CacheResult::ProduceResult(
cached_data ? cached_data->length : 0),
base::Optional<inspector_compile_script_event::V8CacheResult::
ConsumeResult>()),
is_streamed, not_streaming_reason));
break;
}
case V8CodeCache::ProduceCacheOptions::kNoProduceCache:
break;
}
}
void V8CodeCache::ProduceCache(v8::Isolate* isolate,
v8::Local<v8::Script> script,
const ScriptSourceCode& source,
ProduceCacheOptions produce_cache_options) {
ProduceCacheInternal(isolate, script->GetUnboundScript(),
source.CacheHandler(), source.Source().length(),
source.Url(), source.StartPosition(), source.Streamer(),
"v8.compile", produce_cache_options,
source.NotStreamingReason());
}
void V8CodeCache::ProduceCache(v8::Isolate* isolate,
ModuleRecordProduceCacheData* produce_cache_data,
size_t source_text_length,
const KURL& source_url,
const TextPosition& source_start_position) {
ProduceCacheInternal(isolate, produce_cache_data->UnboundScript(isolate),
produce_cache_data->CacheHandler(), source_text_length,
source_url, source_start_position, false,
"v8.compileModule",
produce_cache_data->GetProduceCacheOptions(),
ScriptStreamer::NotStreamingReason::kModuleScript);
}
uint32_t V8CodeCache::TagForCodeCache(
const SingleCachedMetadataHandler* cache_handler) {
return CacheTag(kCacheTagCode, cache_handler->Encoding());
}
uint32_t V8CodeCache::TagForTimeStamp(
const SingleCachedMetadataHandler* cache_handler) {
return CacheTag(kCacheTagTimeStamp, cache_handler->Encoding());
}
// Store a timestamp to the cache as hint.
void V8CodeCache::SetCacheTimeStamp(
SingleCachedMetadataHandler* cache_handler) {
uint64_t now_ms = base::TimeTicks::Now().since_origin().InMilliseconds();
cache_handler->ClearCachedMetadata(CachedMetadataHandler::kClearLocally);
cache_handler->SetCachedMetadata(TagForTimeStamp(cache_handler),
reinterpret_cast<uint8_t*>(&now_ms),
sizeof(now_ms));
}
// static
scoped_refptr<CachedMetadata> V8CodeCache::GenerateFullCodeCache(
ScriptState* script_state,
const String& script_string,
const String& file_name,
const WTF::TextEncoding& encoding,
OpaqueMode opaque_mode) {
constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline";
TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, "v8.compile", "fileName",
file_name.Utf8());
ScriptState::Scope scope(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
// v8::TryCatch is needed to suppress all exceptions thrown during the code
// cache generation.
v8::TryCatch block(isolate);
ReferrerScriptInfo referrer_info;
v8::ScriptOrigin origin(
V8String(isolate, file_name),
0, // line_offset
0, // column_offset
opaque_mode == OpaqueMode::kNotOpaque, // is_shared_cross_origin
-1, // script_id
V8String(isolate, String("")), // source_map_url
opaque_mode == OpaqueMode::kOpaque, // is_opaque
false, // is_wasm
false, // is_module
referrer_info.ToV8HostDefinedOptions(isolate));
v8::Local<v8::String> code(V8String(isolate, script_string));
v8::ScriptCompiler::Source source(code, origin);
scoped_refptr<CachedMetadata> cached_metadata;
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
v8::Local<v8::UnboundScript> unbound_script;
// When failed to compile the script with syntax error, the exceptions is
// suppressed by the v8::TryCatch, and returns null.
if (v8::ScriptCompiler::CompileUnboundScript(
isolate, &source, v8::ScriptCompiler::kEagerCompile)
.ToLocal(&unbound_script)) {
cached_data.reset(v8::ScriptCompiler::CreateCodeCache(unbound_script));
if (cached_data && cached_data->length) {
cached_metadata =
CachedMetadata::Create(CacheTag(kCacheTagCode, encoding.GetName()),
cached_data->data, cached_data->length);
}
}
TRACE_EVENT_END1(
kTraceEventCategoryGroup, "v8.compile", "data",
inspector_compile_script_event::Data(
file_name, TextPosition(),
inspector_compile_script_event::V8CacheResult(
inspector_compile_script_event::V8CacheResult::ProduceResult(
cached_data ? cached_data->length : 0),
base::Optional<inspector_compile_script_event::V8CacheResult::
ConsumeResult>()),
false, ScriptStreamer::NotStreamingReason::kHasCodeCache));
return cached_metadata;
}
} // namespace blink