• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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/wasm/wasm-serialization.h"
6 
7 #include "src/base/platform/wrappers.h"
8 #include "src/codegen/assembler-inl.h"
9 #include "src/codegen/external-reference-table.h"
10 #include "src/objects/objects-inl.h"
11 #include "src/objects/objects.h"
12 #include "src/runtime/runtime.h"
13 #include "src/snapshot/code-serializer.h"
14 #include "src/utils/ostreams.h"
15 #include "src/utils/utils.h"
16 #include "src/utils/version.h"
17 #include "src/wasm/code-space-access.h"
18 #include "src/wasm/function-compiler.h"
19 #include "src/wasm/module-compiler.h"
20 #include "src/wasm/module-decoder.h"
21 #include "src/wasm/wasm-code-manager.h"
22 #include "src/wasm/wasm-engine.h"
23 #include "src/wasm/wasm-module.h"
24 #include "src/wasm/wasm-objects-inl.h"
25 #include "src/wasm/wasm-objects.h"
26 #include "src/wasm/wasm-result.h"
27 
28 namespace v8 {
29 namespace internal {
30 namespace wasm {
31 
32 namespace {
33 constexpr uint8_t kLazyFunction = 2;
34 constexpr uint8_t kLiftoffFunction = 3;
35 constexpr uint8_t kTurboFanFunction = 4;
36 
37 // TODO(bbudge) Try to unify the various implementations of readers and writers
38 // in Wasm, e.g. StreamProcessor and ZoneBuffer, with these.
39 class Writer {
40  public:
Writer(base::Vector<byte> buffer)41   explicit Writer(base::Vector<byte> buffer)
42       : start_(buffer.begin()), end_(buffer.end()), pos_(buffer.begin()) {}
43 
bytes_written() const44   size_t bytes_written() const { return pos_ - start_; }
current_location() const45   byte* current_location() const { return pos_; }
current_size() const46   size_t current_size() const { return end_ - pos_; }
current_buffer() const47   base::Vector<byte> current_buffer() const {
48     return {current_location(), current_size()};
49   }
50 
51   template <typename T>
Write(const T & value)52   void Write(const T& value) {
53     DCHECK_GE(current_size(), sizeof(T));
54     WriteUnalignedValue(reinterpret_cast<Address>(current_location()), value);
55     pos_ += sizeof(T);
56     if (FLAG_trace_wasm_serialization) {
57       StdoutStream{} << "wrote: " << static_cast<size_t>(value)
58                      << " sized: " << sizeof(T) << std::endl;
59     }
60   }
61 
WriteVector(const base::Vector<const byte> v)62   void WriteVector(const base::Vector<const byte> v) {
63     DCHECK_GE(current_size(), v.size());
64     if (v.size() > 0) {
65       memcpy(current_location(), v.begin(), v.size());
66       pos_ += v.size();
67     }
68     if (FLAG_trace_wasm_serialization) {
69       StdoutStream{} << "wrote vector of " << v.size() << " elements"
70                      << std::endl;
71     }
72   }
73 
Skip(size_t size)74   void Skip(size_t size) { pos_ += size; }
75 
76  private:
77   byte* const start_;
78   byte* const end_;
79   byte* pos_;
80 };
81 
82 class Reader {
83  public:
Reader(base::Vector<const byte> buffer)84   explicit Reader(base::Vector<const byte> buffer)
85       : start_(buffer.begin()), end_(buffer.end()), pos_(buffer.begin()) {}
86 
bytes_read() const87   size_t bytes_read() const { return pos_ - start_; }
current_location() const88   const byte* current_location() const { return pos_; }
current_size() const89   size_t current_size() const { return end_ - pos_; }
current_buffer() const90   base::Vector<const byte> current_buffer() const {
91     return {current_location(), current_size()};
92   }
93 
94   template <typename T>
Read()95   T Read() {
96     DCHECK_GE(current_size(), sizeof(T));
97     T value =
98         ReadUnalignedValue<T>(reinterpret_cast<Address>(current_location()));
99     pos_ += sizeof(T);
100     if (FLAG_trace_wasm_serialization) {
101       StdoutStream{} << "read: " << static_cast<size_t>(value)
102                      << " sized: " << sizeof(T) << std::endl;
103     }
104     return value;
105   }
106 
107   template <typename T>
ReadVector(size_t size)108   base::Vector<const T> ReadVector(size_t size) {
109     DCHECK_GE(current_size(), size);
110     base::Vector<const byte> bytes{pos_, size * sizeof(T)};
111     pos_ += size * sizeof(T);
112     if (FLAG_trace_wasm_serialization) {
113       StdoutStream{} << "read vector of " << size << " elements of size "
114                      << sizeof(T) << " (total size " << size * sizeof(T) << ")"
115                      << std::endl;
116     }
117     return base::Vector<const T>::cast(bytes);
118   }
119 
Skip(size_t size)120   void Skip(size_t size) { pos_ += size; }
121 
122  private:
123   const byte* const start_;
124   const byte* const end_;
125   const byte* pos_;
126 };
127 
WriteHeader(Writer * writer)128 void WriteHeader(Writer* writer) {
129   writer->Write(SerializedData::kMagicNumber);
130   writer->Write(Version::Hash());
131   writer->Write(static_cast<uint32_t>(CpuFeatures::SupportedFeatures()));
132   writer->Write(FlagList::Hash());
133   DCHECK_EQ(WasmSerializer::kHeaderSize, writer->bytes_written());
134 }
135 
136 // On Intel, call sites are encoded as a displacement. For linking and for
137 // serialization/deserialization, we want to store/retrieve a tag (the function
138 // index). On Intel, that means accessing the raw displacement.
139 // On ARM64, call sites are encoded as either a literal load or a direct branch.
140 // Other platforms simply require accessing the target address.
SetWasmCalleeTag(RelocInfo * rinfo,uint32_t tag)141 void SetWasmCalleeTag(RelocInfo* rinfo, uint32_t tag) {
142 #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
143   DCHECK(rinfo->HasTargetAddressAddress());
144   DCHECK(!RelocInfo::IsCompressedEmbeddedObject(rinfo->rmode()));
145   WriteUnalignedValue(rinfo->target_address_address(), tag);
146 #elif V8_TARGET_ARCH_ARM64
147   Instruction* instr = reinterpret_cast<Instruction*>(rinfo->pc());
148   if (instr->IsLdrLiteralX()) {
149     WriteUnalignedValue(rinfo->constant_pool_entry_address(),
150                         static_cast<Address>(tag));
151   } else {
152     DCHECK(instr->IsBranchAndLink() || instr->IsUnconditionalBranch());
153     instr->SetBranchImmTarget(
154         reinterpret_cast<Instruction*>(rinfo->pc() + tag * kInstrSize));
155   }
156 #else
157   Address addr = static_cast<Address>(tag);
158   if (rinfo->rmode() == RelocInfo::EXTERNAL_REFERENCE) {
159     rinfo->set_target_external_reference(addr, SKIP_ICACHE_FLUSH);
160   } else if (rinfo->rmode() == RelocInfo::WASM_STUB_CALL) {
161     rinfo->set_wasm_stub_call_address(addr, SKIP_ICACHE_FLUSH);
162   } else {
163     rinfo->set_target_address(addr, SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
164   }
165 #endif
166 }
167 
GetWasmCalleeTag(RelocInfo * rinfo)168 uint32_t GetWasmCalleeTag(RelocInfo* rinfo) {
169 #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
170   DCHECK(!RelocInfo::IsCompressedEmbeddedObject(rinfo->rmode()));
171   return ReadUnalignedValue<uint32_t>(rinfo->target_address_address());
172 #elif V8_TARGET_ARCH_ARM64
173   Instruction* instr = reinterpret_cast<Instruction*>(rinfo->pc());
174   if (instr->IsLdrLiteralX()) {
175     return ReadUnalignedValue<uint32_t>(rinfo->constant_pool_entry_address());
176   } else {
177     DCHECK(instr->IsBranchAndLink() || instr->IsUnconditionalBranch());
178     return static_cast<uint32_t>(instr->ImmPCOffset() / kInstrSize);
179   }
180 #else
181   Address addr;
182   if (rinfo->rmode() == RelocInfo::EXTERNAL_REFERENCE) {
183     addr = rinfo->target_external_reference();
184   } else if (rinfo->rmode() == RelocInfo::WASM_STUB_CALL) {
185     addr = rinfo->wasm_stub_call_address();
186   } else {
187     addr = rinfo->target_address();
188   }
189   return static_cast<uint32_t>(addr);
190 #endif
191 }
192 
193 constexpr size_t kHeaderSize = sizeof(size_t);  // total code size
194 
195 constexpr size_t kCodeHeaderSize = sizeof(uint8_t) +  // code kind
196                                    sizeof(int) +      // offset of constant pool
197                                    sizeof(int) +  // offset of safepoint table
198                                    sizeof(int) +  // offset of handler table
199                                    sizeof(int) +  // offset of code comments
200                                    sizeof(int) +  // unpadded binary size
201                                    sizeof(int) +  // stack slots
202                                    sizeof(int) +  // tagged parameter slots
203                                    sizeof(int) +  // code size
204                                    sizeof(int) +  // reloc size
205                                    sizeof(int) +  // source positions size
206                                    sizeof(int) +  // protected instructions size
207                                    sizeof(WasmCode::Kind) +  // code kind
208                                    sizeof(ExecutionTier);    // tier
209 
210 // A List of all isolate-independent external references. This is used to create
211 // a tag from the Address of an external reference and vice versa.
212 class ExternalReferenceList {
213  public:
214   ExternalReferenceList(const ExternalReferenceList&) = delete;
215   ExternalReferenceList& operator=(const ExternalReferenceList&) = delete;
216 
tag_from_address(Address ext_ref_address) const217   uint32_t tag_from_address(Address ext_ref_address) const {
218     auto tag_addr_less_than = [this](uint32_t tag, Address searched_addr) {
219       return external_reference_by_tag_[tag] < searched_addr;
220     };
221     auto it = std::lower_bound(std::begin(tags_ordered_by_address_),
222                                std::end(tags_ordered_by_address_),
223                                ext_ref_address, tag_addr_less_than);
224     DCHECK_NE(std::end(tags_ordered_by_address_), it);
225     uint32_t tag = *it;
226     DCHECK_EQ(address_from_tag(tag), ext_ref_address);
227     return tag;
228   }
229 
address_from_tag(uint32_t tag) const230   Address address_from_tag(uint32_t tag) const {
231     DCHECK_GT(kNumExternalReferences, tag);
232     return external_reference_by_tag_[tag];
233   }
234 
Get()235   static const ExternalReferenceList& Get() {
236     static ExternalReferenceList list;  // Lazily initialized.
237     return list;
238   }
239 
240  private:
241   // Private constructor. There will only be a single instance of this object.
ExternalReferenceList()242   ExternalReferenceList() {
243     for (uint32_t i = 0; i < kNumExternalReferences; ++i) {
244       tags_ordered_by_address_[i] = i;
245     }
246     auto addr_by_tag_less_than = [this](uint32_t a, uint32_t b) {
247       return external_reference_by_tag_[a] < external_reference_by_tag_[b];
248     };
249     std::sort(std::begin(tags_ordered_by_address_),
250               std::end(tags_ordered_by_address_), addr_by_tag_less_than);
251   }
252 
253 #define COUNT_EXTERNAL_REFERENCE(name, ...) +1
254   static constexpr uint32_t kNumExternalReferencesList =
255       EXTERNAL_REFERENCE_LIST(COUNT_EXTERNAL_REFERENCE);
256   static constexpr uint32_t kNumExternalReferencesIntrinsics =
257       FOR_EACH_INTRINSIC(COUNT_EXTERNAL_REFERENCE);
258   static constexpr uint32_t kNumExternalReferences =
259       kNumExternalReferencesList + kNumExternalReferencesIntrinsics;
260 #undef COUNT_EXTERNAL_REFERENCE
261 
262   Address external_reference_by_tag_[kNumExternalReferences] = {
263 #define EXT_REF_ADDR(name, desc) ExternalReference::name().address(),
264       EXTERNAL_REFERENCE_LIST(EXT_REF_ADDR)
265 #undef EXT_REF_ADDR
266 #define RUNTIME_ADDR(name, ...) \
267   ExternalReference::Create(Runtime::k##name).address(),
268           FOR_EACH_INTRINSIC(RUNTIME_ADDR)
269 #undef RUNTIME_ADDR
270   };
271   uint32_t tags_ordered_by_address_[kNumExternalReferences];
272 };
273 
274 static_assert(std::is_trivially_destructible<ExternalReferenceList>::value,
275               "static destructors not allowed");
276 
277 }  // namespace
278 
279 class V8_EXPORT_PRIVATE NativeModuleSerializer {
280  public:
281   NativeModuleSerializer(const NativeModule*, base::Vector<WasmCode* const>);
282   NativeModuleSerializer(const NativeModuleSerializer&) = delete;
283   NativeModuleSerializer& operator=(const NativeModuleSerializer&) = delete;
284 
285   size_t Measure() const;
286   bool Write(Writer* writer);
287 
288  private:
289   size_t MeasureCode(const WasmCode*) const;
290   void WriteHeader(Writer*, size_t total_code_size);
291   void WriteCode(const WasmCode*, Writer*);
292 
293   const NativeModule* const native_module_;
294   const base::Vector<WasmCode* const> code_table_;
295   bool write_called_ = false;
296   size_t total_written_code_ = 0;
297   int num_turbofan_functions_ = 0;
298 };
299 
NativeModuleSerializer(const NativeModule * module,base::Vector<WasmCode * const> code_table)300 NativeModuleSerializer::NativeModuleSerializer(
301     const NativeModule* module, base::Vector<WasmCode* const> code_table)
302     : native_module_(module), code_table_(code_table) {
303   DCHECK_NOT_NULL(native_module_);
304   // TODO(mtrofin): persist the export wrappers. Ideally, we'd only persist
305   // the unique ones, i.e. the cache.
306 }
307 
MeasureCode(const WasmCode * code) const308 size_t NativeModuleSerializer::MeasureCode(const WasmCode* code) const {
309   if (code == nullptr) return sizeof(uint8_t);
310   DCHECK_EQ(WasmCode::kWasmFunction, code->kind());
311   if (code->tier() != ExecutionTier::kTurbofan) {
312     return sizeof(uint8_t);
313   }
314   return kCodeHeaderSize + code->instructions().size() +
315          code->reloc_info().size() + code->source_positions().size() +
316          code->protected_instructions_data().size();
317 }
318 
Measure() const319 size_t NativeModuleSerializer::Measure() const {
320   size_t size = kHeaderSize;
321   for (WasmCode* code : code_table_) {
322     size += MeasureCode(code);
323   }
324   return size;
325 }
326 
WriteHeader(Writer * writer,size_t total_code_size)327 void NativeModuleSerializer::WriteHeader(Writer* writer,
328                                          size_t total_code_size) {
329   // TODO(eholk): We need to properly preserve the flag whether the trap
330   // handler was used or not when serializing.
331 
332   writer->Write(total_code_size);
333 }
334 
WriteCode(const WasmCode * code,Writer * writer)335 void NativeModuleSerializer::WriteCode(const WasmCode* code, Writer* writer) {
336   if (code == nullptr) {
337     writer->Write(kLazyFunction);
338     return;
339   }
340 
341   DCHECK_EQ(WasmCode::kWasmFunction, code->kind());
342   // Only serialize TurboFan code, as Liftoff code can contain breakpoints or
343   // non-relocatable constants.
344   if (code->tier() != ExecutionTier::kTurbofan) {
345     // We check if the function has been executed already. If so, we serialize
346     // it as {kLiftoffFunction} so that upon deserialization the function will
347     // get compiled with Liftoff eagerly. If the function has not been executed
348     // yet, we serialize it as {kLazyFunction}, and the function will not get
349     // compiled upon deserialization.
350     NativeModule* native_module = code->native_module();
351     uint32_t budget =
352         native_module->tiering_budget_array()[declared_function_index(
353             native_module->module(), code->index())];
354     writer->Write(budget == static_cast<uint32_t>(FLAG_wasm_tiering_budget)
355                       ? kLazyFunction
356                       : kLiftoffFunction);
357     return;
358   }
359 
360   ++num_turbofan_functions_;
361   writer->Write(kTurboFanFunction);
362   // Write the size of the entire code section, followed by the code header.
363   writer->Write(code->constant_pool_offset());
364   writer->Write(code->safepoint_table_offset());
365   writer->Write(code->handler_table_offset());
366   writer->Write(code->code_comments_offset());
367   writer->Write(code->unpadded_binary_size());
368   writer->Write(code->stack_slots());
369   writer->Write(code->raw_tagged_parameter_slots_for_serialization());
370   writer->Write(code->instructions().length());
371   writer->Write(code->reloc_info().length());
372   writer->Write(code->source_positions().length());
373   writer->Write(code->protected_instructions_data().length());
374   writer->Write(code->kind());
375   writer->Write(code->tier());
376 
377   // Get a pointer to the destination buffer, to hold relocated code.
378   byte* serialized_code_start = writer->current_buffer().begin();
379   byte* code_start = serialized_code_start;
380   size_t code_size = code->instructions().size();
381   writer->Skip(code_size);
382   // Write the reloc info, source positions, and protected code.
383   writer->WriteVector(code->reloc_info());
384   writer->WriteVector(code->source_positions());
385   writer->WriteVector(code->protected_instructions_data());
386 #if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_ARM || \
387     V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64 || V8_TARGET_ARCH_S390X || \
388     V8_TARGET_ARCH_RISCV64
389   // On platforms that don't support misaligned word stores, copy to an aligned
390   // buffer if necessary so we can relocate the serialized code.
391   std::unique_ptr<byte[]> aligned_buffer;
392   if (!IsAligned(reinterpret_cast<Address>(serialized_code_start),
393                  kSystemPointerSize)) {
394     // 'byte' does not guarantee an alignment but seems to work well enough in
395     // practice.
396     aligned_buffer.reset(new byte[code_size]);
397     code_start = aligned_buffer.get();
398   }
399 #endif
400   memcpy(code_start, code->instructions().begin(), code_size);
401   // Relocate the code.
402   int mask = RelocInfo::ModeMask(RelocInfo::WASM_CALL) |
403              RelocInfo::ModeMask(RelocInfo::WASM_STUB_CALL) |
404              RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) |
405              RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE) |
406              RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE_ENCODED);
407   RelocIterator orig_iter(code->instructions(), code->reloc_info(),
408                           code->constant_pool(), mask);
409   for (RelocIterator iter(
410            {code_start, code->instructions().size()}, code->reloc_info(),
411            reinterpret_cast<Address>(code_start) + code->constant_pool_offset(),
412            mask);
413        !iter.done(); iter.next(), orig_iter.next()) {
414     RelocInfo::Mode mode = orig_iter.rinfo()->rmode();
415     switch (mode) {
416       case RelocInfo::WASM_CALL: {
417         Address orig_target = orig_iter.rinfo()->wasm_call_address();
418         uint32_t tag =
419             native_module_->GetFunctionIndexFromJumpTableSlot(orig_target);
420         SetWasmCalleeTag(iter.rinfo(), tag);
421       } break;
422       case RelocInfo::WASM_STUB_CALL: {
423         Address target = orig_iter.rinfo()->wasm_stub_call_address();
424         uint32_t tag = native_module_->GetRuntimeStubId(target);
425         DCHECK_GT(WasmCode::kRuntimeStubCount, tag);
426         SetWasmCalleeTag(iter.rinfo(), tag);
427       } break;
428       case RelocInfo::EXTERNAL_REFERENCE: {
429         Address orig_target = orig_iter.rinfo()->target_external_reference();
430         uint32_t ext_ref_tag =
431             ExternalReferenceList::Get().tag_from_address(orig_target);
432         SetWasmCalleeTag(iter.rinfo(), ext_ref_tag);
433       } break;
434       case RelocInfo::INTERNAL_REFERENCE:
435       case RelocInfo::INTERNAL_REFERENCE_ENCODED: {
436         Address orig_target = orig_iter.rinfo()->target_internal_reference();
437         Address offset = orig_target - code->instruction_start();
438         Assembler::deserialization_set_target_internal_reference_at(
439             iter.rinfo()->pc(), offset, mode);
440       } break;
441       default:
442         UNREACHABLE();
443     }
444   }
445   // If we copied to an aligned buffer, copy code into serialized buffer.
446   if (code_start != serialized_code_start) {
447     memcpy(serialized_code_start, code_start, code_size);
448   }
449   total_written_code_ += code_size;
450 }
451 
Write(Writer * writer)452 bool NativeModuleSerializer::Write(Writer* writer) {
453   DCHECK(!write_called_);
454   write_called_ = true;
455 
456   size_t total_code_size = 0;
457   for (WasmCode* code : code_table_) {
458     if (code && code->tier() == ExecutionTier::kTurbofan) {
459       DCHECK(IsAligned(code->instructions().size(), kCodeAlignment));
460       total_code_size += code->instructions().size();
461     }
462   }
463   WriteHeader(writer, total_code_size);
464 
465   for (WasmCode* code : code_table_) {
466     WriteCode(code, writer);
467   }
468   // If not a single function was written, serialization was not successful.
469   if (num_turbofan_functions_ == 0) return false;
470 
471   // Make sure that the serialized total code size was correct.
472   CHECK_EQ(total_written_code_, total_code_size);
473 
474   return true;
475 }
476 
WasmSerializer(NativeModule * native_module)477 WasmSerializer::WasmSerializer(NativeModule* native_module)
478     : native_module_(native_module),
479       code_table_(native_module->SnapshotCodeTable()) {}
480 
GetSerializedNativeModuleSize() const481 size_t WasmSerializer::GetSerializedNativeModuleSize() const {
482   NativeModuleSerializer serializer(native_module_,
483                                     base::VectorOf(code_table_));
484   return kHeaderSize + serializer.Measure();
485 }
486 
SerializeNativeModule(base::Vector<byte> buffer) const487 bool WasmSerializer::SerializeNativeModule(base::Vector<byte> buffer) const {
488   NativeModuleSerializer serializer(native_module_,
489                                     base::VectorOf(code_table_));
490   size_t measured_size = kHeaderSize + serializer.Measure();
491   if (buffer.size() < measured_size) return false;
492 
493   Writer writer(buffer);
494   WriteHeader(&writer);
495 
496   if (!serializer.Write(&writer)) return false;
497   DCHECK_EQ(measured_size, writer.bytes_written());
498   return true;
499 }
500 
501 struct DeserializationUnit {
502   base::Vector<const byte> src_code_buffer;
503   std::unique_ptr<WasmCode> code;
504   NativeModule::JumpTablesRef jump_tables;
505 };
506 
507 class DeserializationQueue {
508  public:
Add(std::vector<DeserializationUnit> batch)509   void Add(std::vector<DeserializationUnit> batch) {
510     DCHECK(!batch.empty());
511     base::MutexGuard guard(&mutex_);
512     queue_.emplace(std::move(batch));
513   }
514 
Pop()515   std::vector<DeserializationUnit> Pop() {
516     base::MutexGuard guard(&mutex_);
517     if (queue_.empty()) return {};
518     auto batch = std::move(queue_.front());
519     queue_.pop();
520     return batch;
521   }
522 
PopAll()523   std::vector<DeserializationUnit> PopAll() {
524     base::MutexGuard guard(&mutex_);
525     if (queue_.empty()) return {};
526     auto units = std::move(queue_.front());
527     queue_.pop();
528     while (!queue_.empty()) {
529       units.insert(units.end(), std::make_move_iterator(queue_.front().begin()),
530                    std::make_move_iterator(queue_.front().end()));
531       queue_.pop();
532     }
533     return units;
534   }
535 
NumBatches() const536   size_t NumBatches() const {
537     base::MutexGuard guard(&mutex_);
538     return queue_.size();
539   }
540 
541  private:
542   mutable base::Mutex mutex_;
543   std::queue<std::vector<DeserializationUnit>> queue_;
544 };
545 
546 class V8_EXPORT_PRIVATE NativeModuleDeserializer {
547  public:
548   explicit NativeModuleDeserializer(NativeModule*);
549   NativeModuleDeserializer(const NativeModuleDeserializer&) = delete;
550   NativeModuleDeserializer& operator=(const NativeModuleDeserializer&) = delete;
551 
552   bool Read(Reader* reader);
553 
lazy_functions()554   base::Vector<const int> lazy_functions() {
555     return base::VectorOf(lazy_functions_);
556   }
557 
liftoff_functions()558   base::Vector<const int> liftoff_functions() {
559     return base::VectorOf(liftoff_functions_);
560   }
561 
562  private:
563   friend class DeserializeCodeTask;
564 
565   void ReadHeader(Reader* reader);
566   DeserializationUnit ReadCode(int fn_index, Reader* reader);
567   void CopyAndRelocate(const DeserializationUnit& unit);
568   void Publish(std::vector<DeserializationUnit> batch);
569 
570   NativeModule* const native_module_;
571 #ifdef DEBUG
572   bool read_called_ = false;
573 #endif
574 
575   // Updated in {ReadCode}.
576   size_t remaining_code_size_ = 0;
577   base::Vector<byte> current_code_space_;
578   NativeModule::JumpTablesRef current_jump_tables_;
579   std::vector<int> lazy_functions_;
580   std::vector<int> liftoff_functions_;
581 };
582 
583 class DeserializeCodeTask : public JobTask {
584  public:
DeserializeCodeTask(NativeModuleDeserializer * deserializer,DeserializationQueue * reloc_queue)585   DeserializeCodeTask(NativeModuleDeserializer* deserializer,
586                       DeserializationQueue* reloc_queue)
587       : deserializer_(deserializer), reloc_queue_(reloc_queue) {}
588 
Run(JobDelegate * delegate)589   void Run(JobDelegate* delegate) override {
590     CodeSpaceWriteScope code_space_write_scope(deserializer_->native_module_);
591     do {
592       // Repeatedly publish everything that was copied already.
593       TryPublishing(delegate);
594 
595       auto batch = reloc_queue_->Pop();
596       if (batch.empty()) break;
597       for (const auto& unit : batch) {
598         deserializer_->CopyAndRelocate(unit);
599       }
600       publish_queue_.Add(std::move(batch));
601       delegate->NotifyConcurrencyIncrease();
602     } while (!delegate->ShouldYield());
603   }
604 
GetMaxConcurrency(size_t) const605   size_t GetMaxConcurrency(size_t /* worker_count */) const override {
606     // Number of copy&reloc batches, plus 1 if there is also something to
607     // publish.
608     bool publish = publishing_.load(std::memory_order_relaxed) == false &&
609                    publish_queue_.NumBatches() > 0;
610     return reloc_queue_->NumBatches() + (publish ? 1 : 0);
611   }
612 
613  private:
TryPublishing(JobDelegate * delegate)614   void TryPublishing(JobDelegate* delegate) {
615     // Publishing is sequential, so only start publishing if no one else is.
616     if (publishing_.exchange(true, std::memory_order_relaxed)) return;
617 
618     WasmCodeRefScope code_scope;
619     while (true) {
620       bool yield = false;
621       while (!yield) {
622         auto to_publish = publish_queue_.PopAll();
623         if (to_publish.empty()) break;
624         deserializer_->Publish(std::move(to_publish));
625         yield = delegate->ShouldYield();
626       }
627       publishing_.store(false, std::memory_order_relaxed);
628       if (yield) break;
629       // After finishing publishing, check again if new work arrived in the mean
630       // time. If so, continue publishing.
631       if (publish_queue_.NumBatches() == 0) break;
632       if (publishing_.exchange(true, std::memory_order_relaxed)) break;
633       // We successfully reset {publishing_} from {false} to {true}.
634     }
635   }
636 
637   NativeModuleDeserializer* const deserializer_;
638   DeserializationQueue* const reloc_queue_;
639   DeserializationQueue publish_queue_;
640   std::atomic<bool> publishing_{false};
641 };
642 
NativeModuleDeserializer(NativeModule * native_module)643 NativeModuleDeserializer::NativeModuleDeserializer(NativeModule* native_module)
644     : native_module_(native_module) {}
645 
Read(Reader * reader)646 bool NativeModuleDeserializer::Read(Reader* reader) {
647   DCHECK(!read_called_);
648 #ifdef DEBUG
649   read_called_ = true;
650 #endif
651 
652   ReadHeader(reader);
653   uint32_t total_fns = native_module_->num_functions();
654   uint32_t first_wasm_fn = native_module_->num_imported_functions();
655 
656   WasmCodeRefScope wasm_code_ref_scope;
657 
658   DeserializationQueue reloc_queue;
659 
660   std::unique_ptr<JobHandle> job_handle = V8::GetCurrentPlatform()->PostJob(
661       TaskPriority::kUserVisible,
662       std::make_unique<DeserializeCodeTask>(this, &reloc_queue));
663 
664   // Choose a batch size such that we do not create too small batches (>=100k
665   // code bytes), but also not too many (<=100 batches).
666   constexpr size_t kMinBatchSizeInBytes = 100000;
667   size_t batch_limit =
668       std::max(kMinBatchSizeInBytes, remaining_code_size_ / 100);
669 
670   std::vector<DeserializationUnit> batch;
671   size_t batch_size = 0;
672   CodeSpaceWriteScope code_space_write_scope(native_module_);
673   for (uint32_t i = first_wasm_fn; i < total_fns; ++i) {
674     DeserializationUnit unit = ReadCode(i, reader);
675     if (!unit.code) continue;
676     batch_size += unit.code->instructions().size();
677     batch.emplace_back(std::move(unit));
678     if (batch_size >= batch_limit) {
679       reloc_queue.Add(std::move(batch));
680       DCHECK(batch.empty());
681       batch_size = 0;
682       job_handle->NotifyConcurrencyIncrease();
683     }
684   }
685 
686   // We should have read the expected amount of code now, and should have fully
687   // utilized the allocated code space.
688   DCHECK_EQ(0, remaining_code_size_);
689   DCHECK_EQ(0, current_code_space_.size());
690 
691   if (!batch.empty()) {
692     reloc_queue.Add(std::move(batch));
693     job_handle->NotifyConcurrencyIncrease();
694   }
695 
696   // Wait for all tasks to finish, while participating in their work.
697   job_handle->Join();
698 
699   return reader->current_size() == 0;
700 }
701 
ReadHeader(Reader * reader)702 void NativeModuleDeserializer::ReadHeader(Reader* reader) {
703   remaining_code_size_ = reader->Read<size_t>();
704 }
705 
ReadCode(int fn_index,Reader * reader)706 DeserializationUnit NativeModuleDeserializer::ReadCode(int fn_index,
707                                                        Reader* reader) {
708   uint8_t code_kind = reader->Read<uint8_t>();
709   if (code_kind == kLazyFunction) {
710     lazy_functions_.push_back(fn_index);
711     return {};
712   }
713   if (code_kind == kLiftoffFunction) {
714     liftoff_functions_.push_back(fn_index);
715     return {};
716   }
717 
718   int constant_pool_offset = reader->Read<int>();
719   int safepoint_table_offset = reader->Read<int>();
720   int handler_table_offset = reader->Read<int>();
721   int code_comment_offset = reader->Read<int>();
722   int unpadded_binary_size = reader->Read<int>();
723   int stack_slot_count = reader->Read<int>();
724   uint32_t tagged_parameter_slots = reader->Read<uint32_t>();
725   int code_size = reader->Read<int>();
726   int reloc_size = reader->Read<int>();
727   int source_position_size = reader->Read<int>();
728   int protected_instructions_size = reader->Read<int>();
729   WasmCode::Kind kind = reader->Read<WasmCode::Kind>();
730   ExecutionTier tier = reader->Read<ExecutionTier>();
731 
732   DCHECK(IsAligned(code_size, kCodeAlignment));
733   DCHECK_GE(remaining_code_size_, code_size);
734   if (current_code_space_.size() < static_cast<size_t>(code_size)) {
735     // Allocate the next code space. Don't allocate more than 90% of
736     // {kMaxCodeSpaceSize}, to leave some space for jump tables.
737     constexpr size_t kMaxReservation =
738         RoundUp<kCodeAlignment>(WasmCodeAllocator::kMaxCodeSpaceSize * 9 / 10);
739     size_t code_space_size = std::min(kMaxReservation, remaining_code_size_);
740     std::tie(current_code_space_, current_jump_tables_) =
741         native_module_->AllocateForDeserializedCode(code_space_size);
742     DCHECK_EQ(current_code_space_.size(), code_space_size);
743     DCHECK(current_jump_tables_.is_valid());
744   }
745 
746   DeserializationUnit unit;
747   unit.src_code_buffer = reader->ReadVector<byte>(code_size);
748   auto reloc_info = reader->ReadVector<byte>(reloc_size);
749   auto source_pos = reader->ReadVector<byte>(source_position_size);
750   auto protected_instructions =
751       reader->ReadVector<byte>(protected_instructions_size);
752 
753   base::Vector<uint8_t> instructions =
754       current_code_space_.SubVector(0, code_size);
755   current_code_space_ += code_size;
756   remaining_code_size_ -= code_size;
757 
758   unit.code = native_module_->AddDeserializedCode(
759       fn_index, instructions, stack_slot_count, tagged_parameter_slots,
760       safepoint_table_offset, handler_table_offset, constant_pool_offset,
761       code_comment_offset, unpadded_binary_size, protected_instructions,
762       reloc_info, source_pos, kind, tier);
763   unit.jump_tables = current_jump_tables_;
764   return unit;
765 }
766 
CopyAndRelocate(const DeserializationUnit & unit)767 void NativeModuleDeserializer::CopyAndRelocate(
768     const DeserializationUnit& unit) {
769   memcpy(unit.code->instructions().begin(), unit.src_code_buffer.begin(),
770          unit.src_code_buffer.size());
771 
772   // Relocate the code.
773   int mask = RelocInfo::ModeMask(RelocInfo::WASM_CALL) |
774              RelocInfo::ModeMask(RelocInfo::WASM_STUB_CALL) |
775              RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) |
776              RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE) |
777              RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE_ENCODED);
778   for (RelocIterator iter(unit.code->instructions(), unit.code->reloc_info(),
779                           unit.code->constant_pool(), mask);
780        !iter.done(); iter.next()) {
781     RelocInfo::Mode mode = iter.rinfo()->rmode();
782     switch (mode) {
783       case RelocInfo::WASM_CALL: {
784         uint32_t tag = GetWasmCalleeTag(iter.rinfo());
785         Address target =
786             native_module_->GetNearCallTargetForFunction(tag, unit.jump_tables);
787         iter.rinfo()->set_wasm_call_address(target, SKIP_ICACHE_FLUSH);
788         break;
789       }
790       case RelocInfo::WASM_STUB_CALL: {
791         uint32_t tag = GetWasmCalleeTag(iter.rinfo());
792         DCHECK_LT(tag, WasmCode::kRuntimeStubCount);
793         Address target = native_module_->GetNearRuntimeStubEntry(
794             static_cast<WasmCode::RuntimeStubId>(tag), unit.jump_tables);
795         iter.rinfo()->set_wasm_stub_call_address(target, SKIP_ICACHE_FLUSH);
796         break;
797       }
798       case RelocInfo::EXTERNAL_REFERENCE: {
799         uint32_t tag = GetWasmCalleeTag(iter.rinfo());
800         Address address = ExternalReferenceList::Get().address_from_tag(tag);
801         iter.rinfo()->set_target_external_reference(address, SKIP_ICACHE_FLUSH);
802         break;
803       }
804       case RelocInfo::INTERNAL_REFERENCE:
805       case RelocInfo::INTERNAL_REFERENCE_ENCODED: {
806         Address offset = iter.rinfo()->target_internal_reference();
807         Address target = unit.code->instruction_start() + offset;
808         Assembler::deserialization_set_target_internal_reference_at(
809             iter.rinfo()->pc(), target, mode);
810         break;
811       }
812       default:
813         UNREACHABLE();
814     }
815   }
816 
817   // Finally, flush the icache for that code.
818   FlushInstructionCache(unit.code->instructions().begin(),
819                         unit.code->instructions().size());
820 }
821 
Publish(std::vector<DeserializationUnit> batch)822 void NativeModuleDeserializer::Publish(std::vector<DeserializationUnit> batch) {
823   DCHECK(!batch.empty());
824   std::vector<std::unique_ptr<WasmCode>> codes;
825   codes.reserve(batch.size());
826   for (auto& unit : batch) {
827     codes.emplace_back(std::move(unit).code);
828   }
829   auto published_codes = native_module_->PublishCode(base::VectorOf(codes));
830   for (auto* wasm_code : published_codes) {
831     wasm_code->MaybePrint();
832     wasm_code->Validate();
833   }
834 }
835 
IsSupportedVersion(base::Vector<const byte> header)836 bool IsSupportedVersion(base::Vector<const byte> header) {
837   if (header.size() < WasmSerializer::kHeaderSize) return false;
838   byte current_version[WasmSerializer::kHeaderSize];
839   Writer writer({current_version, WasmSerializer::kHeaderSize});
840   WriteHeader(&writer);
841   return memcmp(header.begin(), current_version, WasmSerializer::kHeaderSize) ==
842          0;
843 }
844 
DeserializeNativeModule(Isolate * isolate,base::Vector<const byte> data,base::Vector<const byte> wire_bytes_vec,base::Vector<const char> source_url)845 MaybeHandle<WasmModuleObject> DeserializeNativeModule(
846     Isolate* isolate, base::Vector<const byte> data,
847     base::Vector<const byte> wire_bytes_vec,
848     base::Vector<const char> source_url) {
849   if (!IsWasmCodegenAllowed(isolate, isolate->native_context())) return {};
850   if (!IsSupportedVersion(data)) return {};
851 
852   // Make the copy of the wire bytes early, so we use the same memory for
853   // decoding, lookup in the native module cache, and insertion into the cache.
854   auto owned_wire_bytes = base::OwnedVector<uint8_t>::Of(wire_bytes_vec);
855 
856   // TODO(titzer): module features should be part of the serialization format.
857   WasmEngine* wasm_engine = GetWasmEngine();
858   WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate);
859   ModuleResult decode_result = DecodeWasmModule(
860       enabled_features, owned_wire_bytes.start(), owned_wire_bytes.end(), false,
861       i::wasm::kWasmOrigin, isolate->counters(), isolate->metrics_recorder(),
862       isolate->GetOrRegisterRecorderContextId(isolate->native_context()),
863       DecodingMethod::kDeserialize, wasm_engine->allocator());
864   if (decode_result.failed()) return {};
865   std::shared_ptr<WasmModule> module = std::move(decode_result).value();
866   CHECK_NOT_NULL(module);
867 
868   auto shared_native_module = wasm_engine->MaybeGetNativeModule(
869       module->origin, owned_wire_bytes.as_vector(), isolate);
870   if (shared_native_module == nullptr) {
871     DynamicTiering dynamic_tiering = isolate->IsWasmDynamicTieringEnabled()
872                                          ? DynamicTiering::kEnabled
873                                          : DynamicTiering::kDisabled;
874     const bool kIncludeLiftoff = dynamic_tiering == DynamicTiering::kDisabled;
875     size_t code_size_estimate =
876         wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
877             module.get(), kIncludeLiftoff, dynamic_tiering);
878     shared_native_module = wasm_engine->NewNativeModule(
879         isolate, enabled_features, std::move(module), code_size_estimate);
880     // We have to assign a compilation ID here, as it is required for a
881     // potential re-compilation, e.g. triggered by
882     // {TierDownAllModulesPerIsolate}. The value is -2 so that it is different
883     // than the compilation ID of actual compilations, and also different than
884     // the sentinel value of the CompilationState.
885     shared_native_module->compilation_state()->set_compilation_id(-2);
886     shared_native_module->SetWireBytes(std::move(owned_wire_bytes));
887 
888     NativeModuleDeserializer deserializer(shared_native_module.get());
889     Reader reader(data + WasmSerializer::kHeaderSize);
890     bool error = !deserializer.Read(&reader);
891     if (error) {
892       wasm_engine->UpdateNativeModuleCache(error, &shared_native_module,
893                                            isolate);
894       return {};
895     }
896     shared_native_module->compilation_state()->InitializeAfterDeserialization(
897         deserializer.lazy_functions(), deserializer.liftoff_functions());
898     wasm_engine->UpdateNativeModuleCache(error, &shared_native_module, isolate);
899   }
900 
901   Handle<FixedArray> export_wrappers;
902   CompileJsToWasmWrappers(isolate, shared_native_module->module(),
903                           &export_wrappers);
904 
905   Handle<Script> script =
906       wasm_engine->GetOrCreateScript(isolate, shared_native_module, source_url);
907   Handle<WasmModuleObject> module_object = WasmModuleObject::New(
908       isolate, shared_native_module, script, export_wrappers);
909 
910   // Finish the Wasm script now and make it public to the debugger.
911   isolate->debug()->OnAfterCompile(script);
912 
913   // Log the code within the generated module for profiling.
914   shared_native_module->LogWasmCodes(isolate, *script);
915 
916   return module_object;
917 }
918 
919 }  // namespace wasm
920 }  // namespace internal
921 }  // namespace v8
922