1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gin/v8_initializer.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <memory>
11
12 #include "base/check.h"
13 #include "base/debug/alias.h"
14 #include "base/debug/crash_logging.h"
15 #include "base/feature_list.h"
16 #include "base/files/file.h"
17 #include "base/files/file_path.h"
18 #include "base/files/memory_mapped_file.h"
19 #include "base/lazy_instance.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/notreached.h"
22 #include "base/path_service.h"
23 #include "base/rand_util.h"
24 #include "base/strings/sys_string_conversions.h"
25 #include "base/system/sys_info.h"
26 #include "base/threading/platform_thread.h"
27 #include "base/time/time.h"
28 #include "build/build_config.h"
29 #include "gin/gin_features.h"
30
31 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
32 #if defined(OS_ANDROID)
33 #include "base/android/apk_assets.h"
34 #elif defined(OS_MAC)
35 #include "base/mac/foundation_util.h"
36 #endif
37 #endif // V8_USE_EXTERNAL_STARTUP_DATA
38
39 namespace gin {
40
41 namespace {
42
43 // This global is never freed nor closed.
44 base::MemoryMappedFile* g_mapped_snapshot = nullptr;
45
GenerateEntropy(unsigned char * buffer,size_t amount)46 bool GenerateEntropy(unsigned char* buffer, size_t amount) {
47 base::RandBytes(buffer, amount);
48 return true;
49 }
50
GetMappedFileData(base::MemoryMappedFile * mapped_file,v8::StartupData * data)51 void GetMappedFileData(base::MemoryMappedFile* mapped_file,
52 v8::StartupData* data) {
53 if (mapped_file) {
54 data->data = reinterpret_cast<const char*>(mapped_file->data());
55 data->raw_size = static_cast<int>(mapped_file->length());
56 } else {
57 data->data = nullptr;
58 data->raw_size = 0;
59 }
60 }
61
62 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
63
64 #if defined(OS_ANDROID)
65 const char kV8ContextSnapshotFileName64[] = "v8_context_snapshot_64.bin";
66 const char kV8ContextSnapshotFileName32[] = "v8_context_snapshot_32.bin";
67 const char kSnapshotFileName64[] = "snapshot_blob_64.bin";
68 const char kSnapshotFileName32[] = "snapshot_blob_32.bin";
69
70 #if defined(__LP64__)
71 #define kV8ContextSnapshotFileName kV8ContextSnapshotFileName64
72 #define kSnapshotFileName kSnapshotFileName64
73 #else
74 #define kV8ContextSnapshotFileName kV8ContextSnapshotFileName32
75 #define kSnapshotFileName kSnapshotFileName32
76 #endif
77
78 #else // defined(OS_ANDROID)
79 #if defined(USE_V8_CONTEXT_SNAPSHOT)
80 const char kV8ContextSnapshotFileName[] = V8_CONTEXT_SNAPSHOT_FILENAME;
81 #endif
82 const char kSnapshotFileName[] = "snapshot_blob.bin";
83 #endif // defined(OS_ANDROID)
84
GetSnapshotFileName(const V8Initializer::V8SnapshotFileType file_type)85 const char* GetSnapshotFileName(
86 const V8Initializer::V8SnapshotFileType file_type) {
87 switch (file_type) {
88 case V8Initializer::V8SnapshotFileType::kDefault:
89 return kSnapshotFileName;
90 case V8Initializer::V8SnapshotFileType::kWithAdditionalContext:
91 #if defined(USE_V8_CONTEXT_SNAPSHOT)
92 return kV8ContextSnapshotFileName;
93 #else
94 NOTREACHED();
95 return nullptr;
96 #endif
97 }
98 NOTREACHED();
99 return nullptr;
100 }
101
GetV8FilePath(const char * file_name,base::FilePath * path_out)102 void GetV8FilePath(const char* file_name, base::FilePath* path_out) {
103 #if defined(OS_ANDROID)
104 // This is the path within the .apk.
105 *path_out =
106 base::FilePath(FILE_PATH_LITERAL("assets")).AppendASCII(file_name);
107 #elif defined(OS_MAC)
108 base::ScopedCFTypeRef<CFStringRef> bundle_resource(
109 base::SysUTF8ToCFStringRef(file_name));
110 *path_out = base::mac::PathForFrameworkBundleResource(bundle_resource);
111 #else
112 base::FilePath data_path;
113 bool r = base::PathService::Get(base::DIR_ASSETS, &data_path);
114 DCHECK(r);
115 *path_out = data_path.AppendASCII(file_name);
116 #endif
117 }
118
MapV8File(base::File file,base::MemoryMappedFile::Region region,base::MemoryMappedFile ** mmapped_file_out)119 bool MapV8File(base::File file,
120 base::MemoryMappedFile::Region region,
121 base::MemoryMappedFile** mmapped_file_out) {
122 DCHECK(*mmapped_file_out == NULL);
123 std::unique_ptr<base::MemoryMappedFile> mmapped_file(
124 new base::MemoryMappedFile());
125 if (mmapped_file->Initialize(std::move(file), region)) {
126 *mmapped_file_out = mmapped_file.release();
127 return true;
128 }
129 return false;
130 }
131
OpenV8File(const char * file_name,base::MemoryMappedFile::Region * region_out)132 base::File OpenV8File(const char* file_name,
133 base::MemoryMappedFile::Region* region_out) {
134 // Re-try logic here is motivated by http://crbug.com/479537
135 // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609).
136
137 // These match tools/metrics/histograms.xml
138 enum OpenV8FileResult {
139 OPENED = 0,
140 OPENED_RETRY,
141 FAILED_IN_USE,
142 FAILED_OTHER,
143 MAX_VALUE
144 };
145 base::FilePath path;
146 GetV8FilePath(file_name, &path);
147
148 #if defined(OS_ANDROID)
149 base::File file(base::android::OpenApkAsset(path.value(), region_out));
150 OpenV8FileResult result = file.IsValid() ? OpenV8FileResult::OPENED
151 : OpenV8FileResult::FAILED_OTHER;
152 #else
153 // Re-try logic here is motivated by http://crbug.com/479537
154 // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609).
155 const int kMaxOpenAttempts = 5;
156 const int kOpenRetryDelayMillis = 250;
157
158 OpenV8FileResult result = OpenV8FileResult::FAILED_IN_USE;
159 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
160 base::File file;
161 for (int attempt = 0; attempt < kMaxOpenAttempts; attempt++) {
162 file.Initialize(path, flags);
163 if (file.IsValid()) {
164 *region_out = base::MemoryMappedFile::Region::kWholeFile;
165 if (attempt == 0) {
166 result = OpenV8FileResult::OPENED;
167 break;
168 } else {
169 result = OpenV8FileResult::OPENED_RETRY;
170 break;
171 }
172 } else if (file.error_details() != base::File::FILE_ERROR_IN_USE) {
173 result = OpenV8FileResult::FAILED_OTHER;
174 break;
175 } else if (kMaxOpenAttempts - 1 != attempt) {
176 base::PlatformThread::Sleep(
177 base::TimeDelta::FromMilliseconds(kOpenRetryDelayMillis));
178 }
179 }
180 #endif // defined(OS_ANDROID)
181
182 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.OpenV8File.Result",
183 result,
184 OpenV8FileResult::MAX_VALUE);
185 return file;
186 }
187
188 enum LoadV8FileResult {
189 V8_LOAD_SUCCESS = 0,
190 V8_LOAD_FAILED_OPEN,
191 V8_LOAD_FAILED_MAP,
192 V8_LOAD_FAILED_VERIFY, // Deprecated.
193 V8_LOAD_MAX_VALUE
194 };
195
196 #endif // defined(V8_USE_EXTERNAL_STARTUP_DATA)
197
198 } // namespace
199
200 // static
Initialize(IsolateHolder::ScriptMode mode)201 void V8Initializer::Initialize(IsolateHolder::ScriptMode mode) {
202 static bool v8_is_initialized = false;
203 if (v8_is_initialized)
204 return;
205
206 v8::V8::InitializePlatform(V8Platform::Get());
207
208 if (!base::FeatureList::IsEnabled(features::kV8OptimizeJavascript)) {
209 // We avoid explicitly passing --opt if kV8OptimizeJavascript is enabled
210 // since it is the default, and doing so would override flags passed
211 // explicitly, e.g., via --js-flags=--no-opt.
212 static const char no_optimize[] = "--no-opt";
213 v8::V8::SetFlagsFromString(no_optimize, sizeof(no_optimize) - 1);
214 }
215
216 if (!base::FeatureList::IsEnabled(features::kV8FlushBytecode)) {
217 static const char no_flush_bytecode[] = "--no-flush-bytecode";
218 v8::V8::SetFlagsFromString(no_flush_bytecode,
219 sizeof(no_flush_bytecode) - 1);
220 }
221
222 if (base::FeatureList::IsEnabled(features::kV8OffThreadFinalization)) {
223 static const char finalize_streaming_on_background[] =
224 "--finalize-streaming-on-background";
225 v8::V8::SetFlagsFromString(finalize_streaming_on_background,
226 sizeof(finalize_streaming_on_background) - 1);
227 }
228
229 if (!base::FeatureList::IsEnabled(features::kV8LazyFeedbackAllocation)) {
230 static const char no_lazy_feedback_allocation[] =
231 "--no-lazy-feedback-allocation";
232 v8::V8::SetFlagsFromString(no_lazy_feedback_allocation,
233 sizeof(no_lazy_feedback_allocation) - 1);
234 }
235
236 if (base::FeatureList::IsEnabled(features::kV8ConcurrentInlining)) {
237 static const char tf_experiment_concurrent_inlining[] =
238 "--concurrent_inlining";
239 v8::V8::SetFlagsFromString(tf_experiment_concurrent_inlining,
240 sizeof(tf_experiment_concurrent_inlining) - 1);
241 }
242
243 if (base::FeatureList::IsEnabled(features::kV8PerContextMarkingWorklist)) {
244 static const char stress_per_context_marking_worklist[] =
245 "--stress-per-context-marking-worklist";
246 v8::V8::SetFlagsFromString(stress_per_context_marking_worklist,
247 sizeof(stress_per_context_marking_worklist) - 1);
248 }
249
250 if (base::FeatureList::IsEnabled(features::kV8FlushEmbeddedBlobICache)) {
251 static const char experimental_flush_embedded_blob_icache[] =
252 "--experimental-flush-embedded-blob-icache";
253 v8::V8::SetFlagsFromString(
254 experimental_flush_embedded_blob_icache,
255 sizeof(experimental_flush_embedded_blob_icache) - 1);
256 }
257
258 if (base::FeatureList::IsEnabled(features::kV8ReduceConcurrentMarkingTasks)) {
259 static const char gc_experiment_reduce_concurrent_marking_tasks[] =
260 "--gc-experiment-reduce-concurrent-marking-tasks";
261 v8::V8::SetFlagsFromString(
262 gc_experiment_reduce_concurrent_marking_tasks,
263 sizeof(gc_experiment_reduce_concurrent_marking_tasks) - 1);
264 }
265
266 if (base::FeatureList::IsEnabled(features::kV8NoReclaimUnmodifiedWrappers)) {
267 static constexpr char no_reclaim_unmodified_wrappers[] =
268 "--no-reclaim-unmodified-wrappers";
269 v8::V8::SetFlagsFromString(no_reclaim_unmodified_wrappers,
270 sizeof(no_reclaim_unmodified_wrappers) - 1);
271 }
272
273 if (!base::FeatureList::IsEnabled(features::kV8LocalHeaps)) {
274 // The --local-heaps flag is enabled by default, so we need to explicitly
275 // disable it if kV8LocalHeaps is disabled.
276 static constexpr char no_local_heaps[] = "--no-local-heaps";
277 v8::V8::SetFlagsFromString(no_local_heaps, sizeof(no_local_heaps) - 1);
278
279 // Also disable TurboFan's direct access if local heaps are not enabled.
280 static constexpr char no_direct_access[] = "--no-turbo-direct-heap-access";
281 v8::V8::SetFlagsFromString(no_direct_access, sizeof(no_direct_access) - 1);
282 }
283
284 if (!base::FeatureList::IsEnabled(features::kV8TurboDirectHeapAccess)) {
285 // The --turbo-direct-heap-access flag is enabled by default, so we need to
286 // explicitly disable it if kV8TurboDirectHeapAccess is disabled.
287 static constexpr char no_direct_access[] = "--no-turbo-direct-heap-access";
288 v8::V8::SetFlagsFromString(no_direct_access, sizeof(no_direct_access) - 1);
289 }
290
291 if (IsolateHolder::kStrictMode == mode) {
292 static const char use_strict[] = "--use_strict";
293 v8::V8::SetFlagsFromString(use_strict, sizeof(use_strict) - 1);
294 }
295
296 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
297 if (g_mapped_snapshot) {
298 v8::StartupData snapshot;
299 GetMappedFileData(g_mapped_snapshot, &snapshot);
300 v8::V8::SetSnapshotDataBlob(&snapshot);
301 }
302 #endif // V8_USE_EXTERNAL_STARTUP_DATA
303
304 v8::V8::SetEntropySource(&GenerateEntropy);
305 v8::V8::Initialize();
306
307 v8_is_initialized = true;
308 }
309
310 // static
GetV8ExternalSnapshotData(v8::StartupData * snapshot)311 void V8Initializer::GetV8ExternalSnapshotData(v8::StartupData* snapshot) {
312 GetMappedFileData(g_mapped_snapshot, snapshot);
313 }
314
315 // static
GetV8ExternalSnapshotData(const char ** snapshot_data_out,int * snapshot_size_out)316 void V8Initializer::GetV8ExternalSnapshotData(const char** snapshot_data_out,
317 int* snapshot_size_out) {
318 v8::StartupData snapshot;
319 GetV8ExternalSnapshotData(&snapshot);
320 *snapshot_data_out = snapshot.data;
321 *snapshot_size_out = snapshot.raw_size;
322 }
323
324 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
325
326 // static
LoadV8Snapshot(V8SnapshotFileType snapshot_file_type)327 void V8Initializer::LoadV8Snapshot(V8SnapshotFileType snapshot_file_type) {
328 if (g_mapped_snapshot) {
329 // TODO(crbug.com/802962): Confirm not loading different type of snapshot
330 // files in a process.
331 return;
332 }
333
334 base::MemoryMappedFile::Region file_region;
335 base::File file =
336 OpenV8File(GetSnapshotFileName(snapshot_file_type), &file_region);
337 LoadV8SnapshotFromFile(std::move(file), &file_region, snapshot_file_type);
338 }
339
340 // static
LoadV8SnapshotFromFile(base::File snapshot_file,base::MemoryMappedFile::Region * snapshot_file_region,V8SnapshotFileType snapshot_file_type)341 void V8Initializer::LoadV8SnapshotFromFile(
342 base::File snapshot_file,
343 base::MemoryMappedFile::Region* snapshot_file_region,
344 V8SnapshotFileType snapshot_file_type) {
345 if (g_mapped_snapshot)
346 return;
347
348 if (!snapshot_file.IsValid()) {
349 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result",
350 V8_LOAD_FAILED_OPEN, V8_LOAD_MAX_VALUE);
351 return;
352 }
353
354 base::MemoryMappedFile::Region region =
355 base::MemoryMappedFile::Region::kWholeFile;
356 if (snapshot_file_region) {
357 region = *snapshot_file_region;
358 }
359
360 LoadV8FileResult result = V8_LOAD_SUCCESS;
361 if (!MapV8File(std::move(snapshot_file), region, &g_mapped_snapshot))
362 result = V8_LOAD_FAILED_MAP;
363 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result", result,
364 V8_LOAD_MAX_VALUE);
365 }
366
367 #if defined(OS_ANDROID)
368 // static
GetSnapshotFilePath(bool abi_32_bit,V8SnapshotFileType snapshot_file_type)369 base::FilePath V8Initializer::GetSnapshotFilePath(
370 bool abi_32_bit,
371 V8SnapshotFileType snapshot_file_type) {
372 base::FilePath path;
373 const char* filename = nullptr;
374 switch (snapshot_file_type) {
375 case V8Initializer::V8SnapshotFileType::kDefault:
376 filename = abi_32_bit ? kSnapshotFileName32 : kSnapshotFileName64;
377 break;
378 case V8Initializer::V8SnapshotFileType::kWithAdditionalContext:
379 filename = abi_32_bit ? kV8ContextSnapshotFileName32
380 : kV8ContextSnapshotFileName64;
381 break;
382 }
383 CHECK(filename);
384
385 GetV8FilePath(filename, &path);
386 return path;
387 }
388 #endif // defined(OS_ANDROID)
389 #endif // defined(V8_USE_EXTERNAL_STARTUP_DATA)
390
391 } // namespace gin
392