• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 the V8 project 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 "src/snapshot/embedded/embedded-data.h"
6 
7 #include "src/codegen/assembler-inl.h"
8 #include "src/codegen/callable.h"
9 #include "src/codegen/interface-descriptors-inl.h"
10 #include "src/objects/objects-inl.h"
11 #include "src/snapshot/embedded/embedded-data-inl.h"
12 #include "src/snapshot/snapshot-utils.h"
13 #include "src/snapshot/snapshot.h"
14 #include "v8-internal.h"
15 
16 namespace v8 {
17 namespace internal {
18 
19 namespace {
20 
TryLookupCode(const EmbeddedData & d,Address address)21 Builtin TryLookupCode(const EmbeddedData& d, Address address) {
22   if (!d.IsInCodeRange(address)) return Builtin::kNoBuiltinId;
23 
24   if (address < d.InstructionStartOfBuiltin(static_cast<Builtin>(0))) {
25     return Builtin::kNoBuiltinId;
26   }
27 
28   // Note: Addresses within the padding section between builtins (i.e. within
29   // start + size <= address < start + padded_size) are interpreted as belonging
30   // to the preceding builtin.
31 
32   int l = 0, r = Builtins::kBuiltinCount;
33   while (l < r) {
34     const int mid = (l + r) / 2;
35     const Builtin builtin = Builtins::FromInt(mid);
36     Address start = d.InstructionStartOfBuiltin(builtin);
37     Address end = start + d.PaddedInstructionSizeOfBuiltin(builtin);
38 
39     if (address < start) {
40       r = mid;
41     } else if (address >= end) {
42       l = mid + 1;
43     } else {
44       return builtin;
45     }
46   }
47 
48   UNREACHABLE();
49 }
50 
51 }  // namespace
52 
53 // static
PcIsOffHeap(Isolate * isolate,Address pc)54 bool OffHeapInstructionStream::PcIsOffHeap(Isolate* isolate, Address pc) {
55   // Mksnapshot calls this while the embedded blob is not available yet.
56   if (isolate->embedded_blob_code() == nullptr) return false;
57   DCHECK_NOT_NULL(Isolate::CurrentEmbeddedBlobCode());
58 
59   if (EmbeddedData::FromBlob(isolate).IsInCodeRange(pc)) return true;
60   return isolate->is_short_builtin_calls_enabled() &&
61          EmbeddedData::FromBlob().IsInCodeRange(pc);
62 }
63 
64 // static
TryGetAddressForHashing(Isolate * isolate,Address address,uint32_t * hashable_address)65 bool OffHeapInstructionStream::TryGetAddressForHashing(
66     Isolate* isolate, Address address, uint32_t* hashable_address) {
67   // Mksnapshot calls this while the embedded blob is not available yet.
68   if (isolate->embedded_blob_code() == nullptr) return false;
69   DCHECK_NOT_NULL(Isolate::CurrentEmbeddedBlobCode());
70 
71   EmbeddedData d = EmbeddedData::FromBlob(isolate);
72   if (d.IsInCodeRange(address)) {
73     *hashable_address = d.AddressForHashing(address);
74     return true;
75   }
76 
77   if (isolate->is_short_builtin_calls_enabled()) {
78     d = EmbeddedData::FromBlob();
79     if (d.IsInCodeRange(address)) {
80       *hashable_address = d.AddressForHashing(address);
81       return true;
82     }
83   }
84   return false;
85 }
86 
87 // static
TryLookupCode(Isolate * isolate,Address address)88 Builtin OffHeapInstructionStream::TryLookupCode(Isolate* isolate,
89                                                 Address address) {
90   // Mksnapshot calls this while the embedded blob is not available yet.
91   if (isolate->embedded_blob_code() == nullptr) return Builtin::kNoBuiltinId;
92   DCHECK_NOT_NULL(Isolate::CurrentEmbeddedBlobCode());
93 
94   Builtin builtin = i::TryLookupCode(EmbeddedData::FromBlob(isolate), address);
95 
96   if (isolate->is_short_builtin_calls_enabled() &&
97       !Builtins::IsBuiltinId(builtin)) {
98     builtin = i::TryLookupCode(EmbeddedData::FromBlob(), address);
99   }
100 
101 #ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
102   if (V8_SHORT_BUILTIN_CALLS_BOOL && !Builtins::IsBuiltinId(builtin)) {
103     // When shared pointer compression cage is enabled and it has the embedded
104     // code blob copy then it could have been used regardless of whether the
105     // isolate uses it or knows about it or not (see
106     // Code::OffHeapInstructionStart()).
107     // So, this blob has to be checked too.
108     CodeRange* code_range = CodeRange::GetProcessWideCodeRange().get();
109     if (code_range && code_range->embedded_blob_code_copy() != nullptr) {
110       builtin = i::TryLookupCode(EmbeddedData::FromBlob(code_range), address);
111     }
112   }
113 #endif
114   return builtin;
115 }
116 
117 // static
CreateOffHeapOffHeapInstructionStream(Isolate * isolate,uint8_t ** code,uint32_t * code_size,uint8_t ** data,uint32_t * data_size)118 void OffHeapInstructionStream::CreateOffHeapOffHeapInstructionStream(
119     Isolate* isolate, uint8_t** code, uint32_t* code_size, uint8_t** data,
120     uint32_t* data_size) {
121   // Create the embedded blob from scratch using the current Isolate's heap.
122   EmbeddedData d = EmbeddedData::FromIsolate(isolate);
123 
124   // Allocate the backing store that will contain the embedded blob in this
125   // Isolate. The backing store is on the native heap, *not* on V8's garbage-
126   // collected heap.
127   v8::PageAllocator* page_allocator = v8::internal::GetPlatformPageAllocator();
128   const uint32_t alignment =
129       static_cast<uint32_t>(page_allocator->AllocatePageSize());
130 
131   void* const requested_allocation_code_address =
132       AlignedAddress(isolate->heap()->GetRandomMmapAddr(), alignment);
133   const uint32_t allocation_code_size = RoundUp(d.code_size(), alignment);
134   uint8_t* allocated_code_bytes = static_cast<uint8_t*>(AllocatePages(
135       page_allocator, requested_allocation_code_address, allocation_code_size,
136       alignment, PageAllocator::kReadWrite));
137   CHECK_NOT_NULL(allocated_code_bytes);
138 
139   void* const requested_allocation_data_address =
140       AlignedAddress(isolate->heap()->GetRandomMmapAddr(), alignment);
141   const uint32_t allocation_data_size = RoundUp(d.data_size(), alignment);
142   uint8_t* allocated_data_bytes = static_cast<uint8_t*>(AllocatePages(
143       page_allocator, requested_allocation_data_address, allocation_data_size,
144       alignment, PageAllocator::kReadWrite));
145   CHECK_NOT_NULL(allocated_data_bytes);
146 
147   // Copy the embedded blob into the newly allocated backing store. Switch
148   // permissions to read-execute since builtin code is immutable from now on
149   // and must be executable in case any JS execution is triggered.
150   //
151   // Once this backing store is set as the current_embedded_blob, V8 cannot tell
152   // the difference between a 'real' embedded build (where the blob is embedded
153   // in the binary) and what we are currently setting up here (where the blob is
154   // on the native heap).
155   std::memcpy(allocated_code_bytes, d.code(), d.code_size());
156   if (FLAG_experimental_flush_embedded_blob_icache) {
157     FlushInstructionCache(allocated_code_bytes, d.code_size());
158   }
159   CHECK(SetPermissions(page_allocator, allocated_code_bytes,
160                        allocation_code_size, PageAllocator::kReadExecute));
161 
162   std::memcpy(allocated_data_bytes, d.data(), d.data_size());
163   CHECK(SetPermissions(page_allocator, allocated_data_bytes,
164                        allocation_data_size, PageAllocator::kRead));
165 
166   *code = allocated_code_bytes;
167   *code_size = d.code_size();
168   *data = allocated_data_bytes;
169   *data_size = d.data_size();
170 
171   d.Dispose();
172 }
173 
174 // static
FreeOffHeapOffHeapInstructionStream(uint8_t * code,uint32_t code_size,uint8_t * data,uint32_t data_size)175 void OffHeapInstructionStream::FreeOffHeapOffHeapInstructionStream(
176     uint8_t* code, uint32_t code_size, uint8_t* data, uint32_t data_size) {
177   v8::PageAllocator* page_allocator = v8::internal::GetPlatformPageAllocator();
178   const uint32_t page_size =
179       static_cast<uint32_t>(page_allocator->AllocatePageSize());
180   FreePages(page_allocator, code, RoundUp(code_size, page_size));
181   FreePages(page_allocator, data, RoundUp(data_size, page_size));
182 }
183 
184 namespace {
185 
BuiltinAliasesOffHeapTrampolineRegister(Isolate * isolate,Code code)186 bool BuiltinAliasesOffHeapTrampolineRegister(Isolate* isolate, Code code) {
187   DCHECK(Builtins::IsIsolateIndependent(code.builtin_id()));
188   switch (Builtins::KindOf(code.builtin_id())) {
189     case Builtins::CPP:
190     case Builtins::TFC:
191     case Builtins::TFH:
192     case Builtins::TFJ:
193     case Builtins::TFS:
194       break;
195 
196     // Bytecode handlers will only ever be used by the interpreter and so there
197     // will never be a need to use trampolines with them.
198     case Builtins::BCH:
199     case Builtins::ASM:
200       // TODO(jgruber): Extend checks to remaining kinds.
201       return false;
202   }
203 
204   STATIC_ASSERT(CallInterfaceDescriptor::ContextRegister() !=
205                 kOffHeapTrampolineRegister);
206 
207   Callable callable = Builtins::CallableFor(isolate, code.builtin_id());
208   CallInterfaceDescriptor descriptor = callable.descriptor();
209 
210   for (int i = 0; i < descriptor.GetRegisterParameterCount(); i++) {
211     Register reg = descriptor.GetRegisterParameter(i);
212     if (reg == kOffHeapTrampolineRegister) return true;
213   }
214 
215   return false;
216 }
217 
FinalizeEmbeddedCodeTargets(Isolate * isolate,EmbeddedData * blob)218 void FinalizeEmbeddedCodeTargets(Isolate* isolate, EmbeddedData* blob) {
219   static const int kRelocMask =
220       RelocInfo::ModeMask(RelocInfo::CODE_TARGET) |
221       RelocInfo::ModeMask(RelocInfo::RELATIVE_CODE_TARGET);
222 
223   STATIC_ASSERT(Builtins::kAllBuiltinsAreIsolateIndependent);
224   for (Builtin builtin = Builtins::kFirst; builtin <= Builtins::kLast;
225        ++builtin) {
226     Code code = FromCodeT(isolate->builtins()->code(builtin));
227     RelocIterator on_heap_it(code, kRelocMask);
228     RelocIterator off_heap_it(blob, code, kRelocMask);
229 
230 #if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM64) || \
231     defined(V8_TARGET_ARCH_ARM) || defined(V8_TARGET_ARCH_MIPS) ||  \
232     defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_S390) || \
233     defined(V8_TARGET_ARCH_RISCV64) || defined(V8_TARGET_ARCH_LOONG64)
234     // On these platforms we emit relative builtin-to-builtin
235     // jumps for isolate independent builtins in the snapshot. This fixes up the
236     // relative jumps to the right offsets in the snapshot.
237     // See also: Code::IsIsolateIndependent.
238     while (!on_heap_it.done()) {
239       DCHECK(!off_heap_it.done());
240 
241       RelocInfo* rinfo = on_heap_it.rinfo();
242       DCHECK_EQ(rinfo->rmode(), off_heap_it.rinfo()->rmode());
243       Code target = Code::GetCodeFromTargetAddress(rinfo->target_address());
244       CHECK(Builtins::IsIsolateIndependentBuiltin(target));
245 
246       // Do not emit write-barrier for off-heap writes.
247       off_heap_it.rinfo()->set_target_address(
248           blob->InstructionStartOfBuiltin(target.builtin_id()),
249           SKIP_WRITE_BARRIER);
250 
251       on_heap_it.next();
252       off_heap_it.next();
253     }
254     DCHECK(off_heap_it.done());
255 #else
256     // Architectures other than x64 and arm/arm64 do not use pc-relative calls
257     // and thus must not contain embedded code targets. Instead, we use an
258     // indirection through the root register.
259     CHECK(on_heap_it.done());
260     CHECK(off_heap_it.done());
261 #endif
262   }
263 }
264 
265 }  // namespace
266 
267 // static
FromIsolate(Isolate * isolate)268 EmbeddedData EmbeddedData::FromIsolate(Isolate* isolate) {
269   Builtins* builtins = isolate->builtins();
270 
271   // Store instruction stream lengths and offsets.
272   std::vector<struct LayoutDescription> layout_descriptions(kTableSize);
273 
274   bool saw_unsafe_builtin = false;
275   uint32_t raw_code_size = 0;
276   uint32_t raw_data_size = 0;
277   STATIC_ASSERT(Builtins::kAllBuiltinsAreIsolateIndependent);
278   for (Builtin builtin = Builtins::kFirst; builtin <= Builtins::kLast;
279        ++builtin) {
280     Code code = FromCodeT(builtins->code(builtin));
281 
282     // Sanity-check that the given builtin is isolate-independent and does not
283     // use the trampoline register in its calling convention.
284     if (!code.IsIsolateIndependent(isolate)) {
285       saw_unsafe_builtin = true;
286       fprintf(stderr, "%s is not isolate-independent.\n",
287               Builtins::name(builtin));
288     }
289     if (BuiltinAliasesOffHeapTrampolineRegister(isolate, code)) {
290       saw_unsafe_builtin = true;
291       fprintf(stderr, "%s aliases the off-heap trampoline register.\n",
292               Builtins::name(builtin));
293     }
294 
295     uint32_t instruction_size =
296         static_cast<uint32_t>(code.raw_instruction_size());
297     uint32_t metadata_size = static_cast<uint32_t>(code.raw_metadata_size());
298 
299     DCHECK_EQ(0, raw_code_size % kCodeAlignment);
300     {
301       const int builtin_index = static_cast<int>(builtin);
302       struct LayoutDescription& layout_desc =
303           layout_descriptions[builtin_index];
304       layout_desc.instruction_offset = raw_code_size;
305       layout_desc.instruction_length = instruction_size;
306       layout_desc.metadata_offset = raw_data_size;
307       layout_desc.metadata_length = metadata_size;
308 
309       layout_desc.handler_table_offset =
310           raw_data_size + static_cast<uint32_t>(code.handler_table_offset());
311 #if V8_EMBEDDED_CONSTANT_POOL
312       layout_desc.constant_pool_offset =
313           raw_data_size + static_cast<uint32_t>(code.constant_pool_offset());
314 #endif
315       layout_desc.code_comments_offset_offset =
316           raw_data_size + static_cast<uint32_t>(code.code_comments_offset());
317       layout_desc.unwinding_info_offset_offset =
318           raw_data_size + static_cast<uint32_t>(code.unwinding_info_offset());
319     }
320     // Align the start of each section.
321     raw_code_size += PadAndAlignCode(instruction_size);
322     raw_data_size += PadAndAlignData(metadata_size);
323   }
324   CHECK_WITH_MSG(
325       !saw_unsafe_builtin,
326       "One or more builtins marked as isolate-independent either contains "
327       "isolate-dependent code or aliases the off-heap trampoline register. "
328       "If in doubt, ask jgruber@");
329 
330   // Allocate space for the code section, value-initialized to 0.
331   STATIC_ASSERT(RawCodeOffset() == 0);
332   const uint32_t blob_code_size = RawCodeOffset() + raw_code_size;
333   uint8_t* const blob_code = new uint8_t[blob_code_size]();
334 
335   // Allocate space for the data section, value-initialized to 0.
336   STATIC_ASSERT(IsAligned(FixedDataSize(), Code::kMetadataAlignment));
337   const uint32_t blob_data_size = FixedDataSize() + raw_data_size;
338   uint8_t* const blob_data = new uint8_t[blob_data_size]();
339 
340   // Initially zap the entire blob, effectively padding the alignment area
341   // between two builtins with int3's (on x64/ia32).
342   ZapCode(reinterpret_cast<Address>(blob_code), blob_code_size);
343 
344   // Hash relevant parts of the Isolate's heap and store the result.
345   {
346     STATIC_ASSERT(IsolateHashSize() == kSizetSize);
347     const size_t hash = isolate->HashIsolateForEmbeddedBlob();
348     std::memcpy(blob_data + IsolateHashOffset(), &hash, IsolateHashSize());
349   }
350 
351   // Write the layout_descriptions tables.
352   DCHECK_EQ(LayoutDescriptionTableSize(),
353             sizeof(layout_descriptions[0]) * layout_descriptions.size());
354   std::memcpy(blob_data + LayoutDescriptionTableOffset(),
355               layout_descriptions.data(), LayoutDescriptionTableSize());
356 
357   // .. and the variable-size data section.
358   uint8_t* const raw_metadata_start = blob_data + RawMetadataOffset();
359   STATIC_ASSERT(Builtins::kAllBuiltinsAreIsolateIndependent);
360   for (Builtin builtin = Builtins::kFirst; builtin <= Builtins::kLast;
361        ++builtin) {
362     Code code = FromCodeT(builtins->code(builtin));
363     uint32_t offset =
364         layout_descriptions[static_cast<int>(builtin)].metadata_offset;
365     uint8_t* dst = raw_metadata_start + offset;
366     DCHECK_LE(RawMetadataOffset() + offset + code.raw_metadata_size(),
367               blob_data_size);
368     std::memcpy(dst, reinterpret_cast<uint8_t*>(code.raw_metadata_start()),
369                 code.raw_metadata_size());
370   }
371 
372   // .. and the variable-size code section.
373   uint8_t* const raw_code_start = blob_code + RawCodeOffset();
374   STATIC_ASSERT(Builtins::kAllBuiltinsAreIsolateIndependent);
375   for (Builtin builtin = Builtins::kFirst; builtin <= Builtins::kLast;
376        ++builtin) {
377     Code code = FromCodeT(builtins->code(builtin));
378     uint32_t offset =
379         layout_descriptions[static_cast<int>(builtin)].instruction_offset;
380     uint8_t* dst = raw_code_start + offset;
381     DCHECK_LE(RawCodeOffset() + offset + code.raw_instruction_size(),
382               blob_code_size);
383     std::memcpy(dst, reinterpret_cast<uint8_t*>(code.raw_instruction_start()),
384                 code.raw_instruction_size());
385   }
386 
387   EmbeddedData d(blob_code, blob_code_size, blob_data, blob_data_size);
388 
389   // Fix up call targets that point to other embedded builtins.
390   FinalizeEmbeddedCodeTargets(isolate, &d);
391 
392   // Hash the blob and store the result.
393   {
394     STATIC_ASSERT(EmbeddedBlobDataHashSize() == kSizetSize);
395     const size_t data_hash = d.CreateEmbeddedBlobDataHash();
396     std::memcpy(blob_data + EmbeddedBlobDataHashOffset(), &data_hash,
397                 EmbeddedBlobDataHashSize());
398 
399     STATIC_ASSERT(EmbeddedBlobCodeHashSize() == kSizetSize);
400     const size_t code_hash = d.CreateEmbeddedBlobCodeHash();
401     std::memcpy(blob_data + EmbeddedBlobCodeHashOffset(), &code_hash,
402                 EmbeddedBlobCodeHashSize());
403 
404     DCHECK_EQ(data_hash, d.CreateEmbeddedBlobDataHash());
405     DCHECK_EQ(data_hash, d.EmbeddedBlobDataHash());
406     DCHECK_EQ(code_hash, d.CreateEmbeddedBlobCodeHash());
407     DCHECK_EQ(code_hash, d.EmbeddedBlobCodeHash());
408   }
409 
410   if (FLAG_serialization_statistics) d.PrintStatistics();
411 
412   return d;
413 }
414 
CreateEmbeddedBlobDataHash() const415 size_t EmbeddedData::CreateEmbeddedBlobDataHash() const {
416   STATIC_ASSERT(EmbeddedBlobDataHashOffset() == 0);
417   STATIC_ASSERT(EmbeddedBlobCodeHashOffset() == EmbeddedBlobDataHashSize());
418   STATIC_ASSERT(IsolateHashOffset() ==
419                 EmbeddedBlobCodeHashOffset() + EmbeddedBlobCodeHashSize());
420   static constexpr uint32_t kFirstHashedDataOffset = IsolateHashOffset();
421   // Hash the entire data section except the embedded blob hash fields
422   // themselves.
423   base::Vector<const byte> payload(data_ + kFirstHashedDataOffset,
424                                    data_size_ - kFirstHashedDataOffset);
425   return Checksum(payload);
426 }
427 
CreateEmbeddedBlobCodeHash() const428 size_t EmbeddedData::CreateEmbeddedBlobCodeHash() const {
429   CHECK(FLAG_text_is_readable);
430   base::Vector<const byte> payload(code_, code_size_);
431   return Checksum(payload);
432 }
433 
PrintStatistics() const434 void EmbeddedData::PrintStatistics() const {
435   DCHECK(FLAG_serialization_statistics);
436 
437   constexpr int kCount = Builtins::kBuiltinCount;
438   int sizes[kCount];
439   STATIC_ASSERT(Builtins::kAllBuiltinsAreIsolateIndependent);
440   for (int i = 0; i < kCount; i++) {
441     sizes[i] = InstructionSizeOfBuiltin(Builtins::FromInt(i));
442   }
443 
444   // Sort for percentiles.
445   std::sort(&sizes[0], &sizes[kCount]);
446 
447   const int k50th = kCount * 0.5;
448   const int k75th = kCount * 0.75;
449   const int k90th = kCount * 0.90;
450   const int k99th = kCount * 0.99;
451 
452   PrintF("EmbeddedData:\n");
453   PrintF("  Total size:                  %d\n",
454          static_cast<int>(code_size() + data_size()));
455   PrintF("  Data size:                   %d\n", static_cast<int>(data_size()));
456   PrintF("  Code size:                   %d\n", static_cast<int>(code_size()));
457   PrintF("  Instruction size (50th percentile): %d\n", sizes[k50th]);
458   PrintF("  Instruction size (75th percentile): %d\n", sizes[k75th]);
459   PrintF("  Instruction size (90th percentile): %d\n", sizes[k90th]);
460   PrintF("  Instruction size (99th percentile): %d\n", sizes[k99th]);
461   PrintF("\n");
462 }
463 
464 }  // namespace internal
465 }  // namespace v8
466