diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc b/chromium/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc new file mode 100644 index 00000000000..ad3eca49af9 --- /dev/null +++ b/chromium/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc @@ -0,0 +1,330 @@ +// 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/web/web_settings.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/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" +#include "third_party/blink/renderer/platform/wtf/time.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(SingleCachedMetadataHandler* cache_handler, + int hot_hours) { + const double hot_seconds = hot_hours * 60 * 60; + scoped_refptr<CachedMetadata> cached_metadata = + cache_handler->GetCachedMetadata( + V8CodeCache::TagForTimeStamp(cache_handler)); + if (!cached_metadata) + return false; + double time_stamp; + const int size = sizeof(time_stamp); + DCHECK_EQ(cached_metadata->size(), static_cast<unsigned long>(size)); + memcpy(&time_stamp, cached_metadata->Data(), size); + return (WTF::CurrentTime() - time_stamp) < hot_seconds; +} + +} // namespace + +v8::ScriptCompiler::CachedData* V8CodeCache::CreateCachedData( + 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 char* data = cached_metadata->Data(); + int length = cached_metadata->size(); + return new v8::ScriptCompiler::CachedData( + reinterpret_cast<const uint8_t*>(data), length, + v8::ScriptCompiler::CachedData::BufferNotOwned); +} + +std::tuple<v8::ScriptCompiler::CompileOptions, + V8CodeCache::ProduceCacheOptions, + v8::ScriptCompiler::NoCacheReason> +V8CodeCache::GetCompileOptions(V8CacheOptions cache_options, + const ScriptSourceCode& source) { + static const int kMinimalCodeLength = 1024; + static const int kHotHours = 72; + v8::ScriptCompiler::NoCacheReason no_cache_reason; + + switch (source.SourceLocationType()) { + 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; + } + + SingleCachedMetadataHandler* cache_handler = source.CacheHandler(); + if (!cache_handler) { + return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions, + ProduceCacheOptions::kNoProduceCache, + no_cache_reason); + } + + if (cache_options == kV8CacheOptionsNone) { + no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseCachingDisabled; + return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions, + ProduceCacheOptions::kNoProduceCache, + no_cache_reason); + } + + if (source.Source().length() < kMinimalCodeLength) { + no_cache_reason = v8::ScriptCompiler::kNoCacheBecauseScriptTooSmall; + return std::make_tuple(v8::ScriptCompiler::kNoCompileOptions, + ProduceCacheOptions::kNoProduceCache, + no_cache_reason); + } + + uint32_t code_cache_tag = V8CodeCache::TagForCodeCache(cache_handler); + scoped_refptr<CachedMetadata> code_cache = + cache_handler->GetCachedMetadata(code_cache_tag); + if (code_cache) { + 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 = kV8CacheOptionsCodeWithoutHeatCheck; + + switch (cache_options) { + case kV8CacheOptionsDefault: + case kV8CacheOptionsCode: + if (!IsResourceHotForCaching(cache_handler, kHotHours)) { + 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 kV8CacheOptionsCodeWithoutHeatCheck: + return std::make_tuple( + v8::ScriptCompiler::kNoCompileOptions, + ProduceCacheOptions::kProduceCodeCache, + v8::ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache); + case kV8CacheOptionsFullCodeWithoutHeatCheck: + return std::make_tuple( + v8::ScriptCompiler::kEagerCompile, + ProduceCacheOptions::kProduceCodeCache, + v8::ScriptCompiler::kNoCacheBecauseDeferredProduceCodeCache); + case kV8CacheOptionsNone: + // 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); +} + +void V8CodeCache::ProduceCache( + v8::Isolate* isolate, + v8::Local<v8::Script> script, + const ScriptSourceCode& source, + ProduceCacheOptions produce_cache_options, + v8::ScriptCompiler::CompileOptions compile_options) { + TRACE_EVENT0("v8", "v8.compile"); + RuntimeCallStatsScopedTracer rcs_scoped_tracer(isolate); + RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8); + + switch (produce_cache_options) { + case ProduceCacheOptions::kSetTimeStamp: + V8CodeCache::SetCacheTimeStamp(source.CacheHandler()); + break; + case ProduceCacheOptions::kProduceCodeCache: { + constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline"; + TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, "v8.compile", "fileName", + source.Url().GetString().Utf8()); + + std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data( + v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript())); + if (cached_data) { + const char* data = reinterpret_cast<const char*>(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.Source().length()); + DEFINE_THREAD_SAFE_STATIC_LOCAL( + CustomCountHistogram, code_cache_size_histogram, + ("V8.CodeCacheSizeRatio", 0, 10000, 50)); + code_cache_size_histogram.Count(cache_size_ratio); + } + SingleCachedMetadataHandler* cache_handler = source.CacheHandler(); + cache_handler->ClearCachedMetadata( + CachedMetadataHandler::kCacheLocally); + cache_handler->SetCachedMetadata( + V8CodeCache::TagForCodeCache(cache_handler), data, length, + CachedMetadataHandler::kSendToPlatform); + } + + TRACE_EVENT_END1( + kTraceEventCategoryGroup, "v8.compile", "data", + InspectorCompileScriptEvent::Data( + source.Url().GetString(), source.StartPosition(), + InspectorCompileScriptEvent::V8CacheResult( + InspectorCompileScriptEvent::V8CacheResult::ProduceResult( + compile_options, cached_data ? cached_data->length : 0), + base::Optional<InspectorCompileScriptEvent::V8CacheResult:: + ConsumeResult>()), + source.Streamer())); + break; + } + case ProduceCacheOptions::kNoProduceCache: + break; + } +} + +uint32_t V8CodeCache::TagForCodeCache( + SingleCachedMetadataHandler* cache_handler) { + return CacheTag(kCacheTagCode, cache_handler->Encoding()); +} + +uint32_t V8CodeCache::TagForTimeStamp( + SingleCachedMetadataHandler* cache_handler) { + return CacheTag(kCacheTagTimeStamp, cache_handler->Encoding()); +} + +// Store a timestamp to the cache as hint. +void V8CodeCache::SetCacheTimeStamp( + SingleCachedMetadataHandler* cache_handler) { + double now = WTF::CurrentTime(); + cache_handler->ClearCachedMetadata(CachedMetadataHandler::kCacheLocally); + cache_handler->SetCachedMetadata(TagForTimeStamp(cache_handler), + reinterpret_cast<char*>(&now), sizeof(now), + CachedMetadataHandler::kSendToPlatform); +} + +// 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), + v8::Integer::New(isolate, 0), // line_offset + v8::Integer::New(isolate, 0), // column_offset + v8::Boolean::New( + isolate, + opaque_mode == OpaqueMode::kNotOpaque), // is_shared_cross_origin + v8::Local<v8::Integer>(), // script_id + V8String(isolate, String("")), // source_map_url + v8::Boolean::New(isolate, + opaque_mode == OpaqueMode::kOpaque), // is_opaque + v8::False(isolate), // is_wasm + v8::False(isolate), // 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()), + reinterpret_cast<const char*>(cached_data->data), + cached_data->length); + } + } + + TRACE_EVENT_END1( + kTraceEventCategoryGroup, "v8.compile", "data", + InspectorCompileScriptEvent::Data( + file_name, TextPosition(), + InspectorCompileScriptEvent::V8CacheResult( + InspectorCompileScriptEvent::V8CacheResult::ProduceResult( + v8::ScriptCompiler::kEagerCompile, + cached_data ? cached_data->length : 0), + base::Optional< + InspectorCompileScriptEvent::V8CacheResult::ConsumeResult>()), + false)); + + return cached_metadata; +} + +STATIC_ASSERT_ENUM(WebSettings::kV8CacheOptionsDefault, kV8CacheOptionsDefault); +STATIC_ASSERT_ENUM(WebSettings::kV8CacheOptionsNone, kV8CacheOptionsNone); +STATIC_ASSERT_ENUM(WebSettings::kV8CacheOptionsCode, kV8CacheOptionsCode); +STATIC_ASSERT_ENUM(WebSettings::kV8CacheOptionsCodeWithoutHeatCheck, + kV8CacheOptionsCodeWithoutHeatCheck); +STATIC_ASSERT_ENUM(WebSettings::kV8CacheOptionsFullCodeWithoutHeatCheck, + kV8CacheOptionsFullCodeWithoutHeatCheck); + +} // namespace blink |