1 // Copyright 2020 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/heap/off-thread-factory.h"
6 
7 #include "src/ast/ast-value-factory.h"
8 #include "src/ast/ast.h"
9 #include "src/base/logging.h"
10 #include "src/common/globals.h"
11 #include "src/execution/isolate.h"
12 #include "src/handles/handles.h"
13 #include "src/heap/spaces-inl.h"
14 #include "src/heap/spaces.h"
15 #include "src/objects/fixed-array.h"
16 #include "src/objects/heap-object.h"
17 #include "src/objects/map-inl.h"
18 #include "src/objects/objects-body-descriptors-inl.h"
19 #include "src/objects/shared-function-info.h"
20 #include "src/objects/string.h"
21 #include "src/objects/visitors.h"
22 #include "src/roots/roots-inl.h"
23 #include "src/roots/roots.h"
24 #include "src/tracing/trace-event.h"
25 
26 namespace v8 {
27 namespace internal {
28 
OffThreadFactory(Isolate * isolate)29 OffThreadFactory::OffThreadFactory(Isolate* isolate)
30     : roots_(isolate), space_(isolate->heap()), lo_space_(isolate->heap()) {}
31 
32 namespace {
33 
34 class StringSlotCollectingVisitor : public ObjectVisitor {
35  public:
StringSlotCollectingVisitor(ReadOnlyRoots roots)36   explicit StringSlotCollectingVisitor(ReadOnlyRoots roots) : roots_(roots) {}
37 
VisitPointers(HeapObject host,ObjectSlot start,ObjectSlot end)38   void VisitPointers(HeapObject host, ObjectSlot start,
39                      ObjectSlot end) override {
40     for (ObjectSlot slot = start; slot != end; ++slot) {
41       Object obj = *slot;
42       if (obj.IsInternalizedString() &&
43           !ReadOnlyHeap::Contains(HeapObject::cast(obj))) {
44         string_slots.emplace_back(host.ptr(), slot.address() - host.ptr());
45       }
46     }
47   }
VisitPointers(HeapObject host,MaybeObjectSlot start,MaybeObjectSlot end)48   void VisitPointers(HeapObject host, MaybeObjectSlot start,
49                      MaybeObjectSlot end) override {
50     for (MaybeObjectSlot slot = start; slot != end; ++slot) {
51       MaybeObject maybe_obj = *slot;
52       HeapObject obj;
53       if (maybe_obj.GetHeapObjectIfStrong(&obj)) {
54         if (obj.IsInternalizedString() && !ReadOnlyHeap::Contains(obj)) {
55           string_slots.emplace_back(host.ptr(), slot.address() - host.ptr());
56         }
57       }
58     }
59   }
60 
VisitCodeTarget(Code host,RelocInfo * rinfo)61   void VisitCodeTarget(Code host, RelocInfo* rinfo) override { UNREACHABLE(); }
VisitEmbeddedPointer(Code host,RelocInfo * rinfo)62   void VisitEmbeddedPointer(Code host, RelocInfo* rinfo) override {
63     UNREACHABLE();
64   }
65 
66   std::vector<RelativeSlot> string_slots;
67 
68  private:
69   ReadOnlyRoots roots_;
70 };
71 
72 }  // namespace
73 
FinishOffThread()74 void OffThreadFactory::FinishOffThread() {
75   DCHECK(!is_finished);
76 
77   StringSlotCollectingVisitor string_slot_collector(read_only_roots());
78 
79   // First iterate all objects in the spaces to find string slots. At this point
80   // all string slots have to point to off-thread strings or read-only strings.
81   {
82     PagedSpaceObjectIterator it(&space_);
83     for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) {
84       obj.IterateBodyFast(&string_slot_collector);
85     }
86   }
87   {
88     LargeObjectSpaceObjectIterator it(&lo_space_);
89     for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) {
90       obj.IterateBodyFast(&string_slot_collector);
91     }
92   }
93 
94   string_slots_ = std::move(string_slot_collector.string_slots);
95 
96   is_finished = true;
97 }
98 
Publish(Isolate * isolate)99 void OffThreadFactory::Publish(Isolate* isolate) {
100   DCHECK(is_finished);
101 
102   HandleScope handle_scope(isolate);
103 
104   // First, handlify all the string slot holder objects, so that we can keep
105   // track of them if they move.
106   //
107   // TODO(leszeks): We might be able to create a HandleScope-compatible
108   // structure off-thread and merge it into the current handle scope all in one
109   // go (DeferredHandles maybe?).
110   std::vector<Handle<HeapObject>> heap_object_handles;
111   std::vector<Handle<Script>> script_handles;
112   {
113     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
114                  "V8.OffThreadFinalization.Publish.CollectHandles");
115     heap_object_handles.reserve(string_slots_.size());
116     for (RelativeSlot relative_slot : string_slots_) {
117       // TODO(leszeks): Group slots in the same parent object to avoid creating
118       // multiple duplicate handles.
119       heap_object_handles.push_back(handle(
120           HeapObject::cast(Object(relative_slot.object_address)), isolate));
121 
122       // De-internalize the string so that we can re-internalize it later.
123       ObjectSlot slot(relative_slot.object_address + relative_slot.slot_offset);
124       String string = String::cast(slot.Acquire_Load());
125       bool one_byte = string.IsOneByteRepresentation();
126       Map map = one_byte ? read_only_roots().one_byte_string_map()
127                          : read_only_roots().string_map();
128       string.set_map_no_write_barrier(map);
129     }
130 
131     script_handles.reserve(script_list_.size());
132     for (Script script : script_list_) {
133       script_handles.push_back(handle(script, isolate));
134     }
135   }
136 
137   // Then merge the spaces. At this point, we are allowed to point between (no
138   // longer) off-thread pages and main-thread heap pages, and objects in the
139   // previously off-thread page can move.
140   {
141     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
142                  "V8.OffThreadFinalization.Publish.Merge");
143     isolate->heap()->old_space()->MergeLocalSpace(&space_);
144     isolate->heap()->lo_space()->MergeOffThreadSpace(&lo_space_);
145   }
146 
147   // Iterate the string slots, as an offset from the holders we have handles to.
148   {
149     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
150                  "V8.OffThreadFinalization.Publish.UpdateHandles");
151     for (size_t i = 0; i < string_slots_.size(); ++i) {
152       int slot_offset = string_slots_[i].slot_offset;
153 
154       // There's currently no cases where the holder object could have been
155       // resized.
156       DCHECK_LT(slot_offset, heap_object_handles[i]->Size());
157 
158       ObjectSlot slot(heap_object_handles[i]->ptr() + slot_offset);
159 
160       String string = String::cast(slot.Acquire_Load());
161       if (string.IsThinString()) {
162         // We may have already internalized this string via another slot.
163         slot.Release_Store(ThinString::cast(string).GetUnderlying());
164       } else {
165         HandleScope handle_scope(isolate);
166 
167         Handle<String> string_handle = handle(string, isolate);
168         Handle<String> internalized_string =
169             isolate->factory()->InternalizeString(string_handle);
170 
171         // Recalculate the slot in case there was GC and the holder moved.
172         ObjectSlot slot(heap_object_handles[i]->ptr() + slot_offset);
173 
174         DCHECK(string_handle->IsThinString() ||
175                string_handle->IsInternalizedString());
176         if (*string_handle != *internalized_string) {
177           slot.Release_Store(*internalized_string);
178         }
179       }
180     }
181 
182     // Merge the recorded scripts into the isolate's script list.
183     // This for loop may seem expensive, but practically there's unlikely to be
184     // more than one script in the OffThreadFactory.
185     Handle<WeakArrayList> scripts = isolate->factory()->script_list();
186     for (Handle<Script> script_handle : script_handles) {
187       scripts = WeakArrayList::Append(isolate, scripts,
188                                       MaybeObjectHandle::Weak(script_handle));
189     }
190     isolate->heap()->SetRootScriptList(*scripts);
191   }
192 }
193 
194 // Hacky method for creating a simple object with a slot pointing to a string.
195 // TODO(leszeks): Remove once we have full FixedArray support.
StringWrapperForTest(Handle<String> string)196 Handle<FixedArray> OffThreadFactory::StringWrapperForTest(
197     Handle<String> string) {
198   HeapObject wrapper =
199       AllocateRaw(FixedArray::SizeFor(1), AllocationType::kOld);
200   wrapper.set_map_after_allocation(read_only_roots().fixed_array_map());
201   FixedArray array = FixedArray::cast(wrapper);
202   array.set_length(1);
203   array.data_start().Relaxed_Store(*string);
204   return handle(array, isolate());
205 }
206 
MakeOrFindTwoCharacterString(uint16_t c1,uint16_t c2)207 Handle<String> OffThreadFactory::MakeOrFindTwoCharacterString(uint16_t c1,
208                                                               uint16_t c2) {
209   // TODO(leszeks): Do some real caching here. Also, these strings should be
210   // internalized.
211   if ((c1 | c2) <= unibrow::Latin1::kMaxChar) {
212     Handle<SeqOneByteString> ret =
213         NewRawOneByteString(2, AllocationType::kOld).ToHandleChecked();
214     ret->SeqOneByteStringSet(0, c1);
215     ret->SeqOneByteStringSet(1, c2);
216     return ret;
217   }
218   Handle<SeqTwoByteString> ret =
219       NewRawTwoByteString(2, AllocationType::kOld).ToHandleChecked();
220   ret->SeqTwoByteStringSet(0, c1);
221   ret->SeqTwoByteStringSet(1, c2);
222   return ret;
223 }
224 
InternalizeString(const Vector<const uint8_t> & string)225 Handle<String> OffThreadFactory::InternalizeString(
226     const Vector<const uint8_t>& string) {
227   uint32_t hash = StringHasher::HashSequentialString(
228       string.begin(), string.length(), HashSeed(read_only_roots()));
229   return NewOneByteInternalizedString(string, hash);
230 }
231 
InternalizeString(const Vector<const uint16_t> & string)232 Handle<String> OffThreadFactory::InternalizeString(
233     const Vector<const uint16_t>& string) {
234   uint32_t hash = StringHasher::HashSequentialString(
235       string.begin(), string.length(), HashSeed(read_only_roots()));
236   return NewTwoByteInternalizedString(string, hash);
237 }
238 
AddToScriptList(Handle<Script> shared)239 void OffThreadFactory::AddToScriptList(Handle<Script> shared) {
240   script_list_.push_back(*shared);
241 }
242 
AllocateRaw(int size,AllocationType allocation,AllocationAlignment alignment)243 HeapObject OffThreadFactory::AllocateRaw(int size, AllocationType allocation,
244                                          AllocationAlignment alignment) {
245   DCHECK(!is_finished);
246 
247   DCHECK_EQ(allocation, AllocationType::kOld);
248   AllocationResult result;
249   if (size > kMaxRegularHeapObjectSize) {
250     result = lo_space_.AllocateRaw(size);
251   } else {
252     result = space_.AllocateRaw(size, alignment);
253   }
254   return result.ToObjectChecked();
255 }
256 
257 }  // namespace internal
258 }  // namespace v8
259