/src/hermes/lib/VM/HiddenClass.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #define DEBUG_TYPE "class" |
9 | | #include "hermes/VM/HiddenClass.h" |
10 | | |
11 | | #include "hermes/VM/ArrayStorage.h" |
12 | | #include "hermes/VM/GCPointer-inline.h" |
13 | | #include "hermes/VM/JSArray.h" |
14 | | #include "hermes/VM/JSObject.h" |
15 | | #include "hermes/VM/Operations.h" |
16 | | #include "hermes/VM/StringView.h" |
17 | | |
18 | | #include "llvh/Support/Debug.h" |
19 | | #pragma GCC diagnostic push |
20 | | |
21 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
22 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
23 | | #endif |
24 | | using llvh::dbgs; |
25 | | |
26 | | namespace hermes { |
27 | | namespace vm { |
28 | | |
29 | | namespace detail { |
30 | | |
31 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
32 | 0 | void TransitionMap::snapshotAddNodes(GC &gc, HeapSnapshot &snap) { |
33 | 0 | if (!isLarge()) { |
34 | 0 | return; |
35 | 0 | } |
36 | | // Make one node that is the sum of the sizes of the WeakValueMap and the |
37 | | // llvh::DenseMap to which it points. |
38 | | // This is based on the assumption that the WeakValueMap uniquely owns that |
39 | | // DenseMap. |
40 | 0 | snap.beginNode(); |
41 | 0 | snap.endNode( |
42 | 0 | HeapSnapshot::NodeType::Native, |
43 | 0 | "WeakValueMap", |
44 | 0 | gc.getNativeID(large()), |
45 | 0 | getMemorySize(), |
46 | 0 | 0); |
47 | 0 | } |
48 | | |
49 | 0 | void TransitionMap::snapshotAddEdges(GC &gc, HeapSnapshot &snap) { |
50 | 0 | uint32_t edge_index = 0; |
51 | 0 | forEachEntry([&snap, &gc, &edge_index]( |
52 | 0 | const Transition &, const WeakRef<HiddenClass> &value) { |
53 | | // Filter out empty refs from adding edges. |
54 | 0 | if (value.isValid()) { |
55 | 0 | snap.addNamedEdge( |
56 | 0 | HeapSnapshot::EdgeType::Weak, |
57 | 0 | std::to_string(edge_index++), |
58 | 0 | gc.getObjectID(value.getNoBarrierUnsafe(gc.getPointerBase()))); |
59 | 0 | } |
60 | 0 | }); |
61 | |
|
62 | 0 | if (!isLarge()) { |
63 | 0 | return; |
64 | 0 | } |
65 | 0 | snap.addNamedEdge( |
66 | 0 | HeapSnapshot::EdgeType::Internal, |
67 | 0 | "transitionMap", |
68 | 0 | gc.getNativeID(large())); |
69 | 0 | } |
70 | | |
71 | 66.0k | void TransitionMap::snapshotUntrackMemory(GC &gc) { |
72 | | // Untrack the memory ID in case one was created. |
73 | 66.0k | if (isLarge()) { |
74 | 1.47k | gc.getIDTracker().untrackNative(large()); |
75 | 1.47k | } |
76 | 66.0k | } |
77 | | #endif |
78 | | |
79 | 0 | size_t TransitionMap::getMemorySize() const { |
80 | | // Inline slot is not counted here (it counts as part of the HiddenClass). |
81 | 0 | return isLarge() ? sizeof(*large()) + large()->getMemorySize() : 0; |
82 | 0 | } |
83 | | |
84 | 1.47k | void TransitionMap::uncleanMakeLarge(Runtime &runtime) { |
85 | 1.47k | assert(!isClean() && "must not still be clean"); |
86 | 1.47k | assert(!isLarge() && "must not yet be large"); |
87 | 1.47k | auto large = new WeakValueMap<Transition, HiddenClass>(); |
88 | | // Move any valid entry into the allocated map. |
89 | 1.47k | if (auto value = smallValue().get(runtime)) |
90 | 1.47k | large->insertNew(runtime, smallKey_, runtime.makeHandle(value)); |
91 | 1.47k | smallValue().releaseSlot(); |
92 | 1.47k | u.large_ = large; |
93 | 1.47k | smallKey_.symbolID = SymbolID::deleted(); |
94 | 1.47k | assert(isLarge()); |
95 | 1.47k | } |
96 | | |
97 | | } // namespace detail |
98 | | |
99 | | const VTable HiddenClass::vt{ |
100 | | CellKind::HiddenClassKind, |
101 | | cellSize<HiddenClass>(), |
102 | | _finalizeImpl, |
103 | | _mallocSizeImpl, |
104 | | nullptr |
105 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
106 | | , |
107 | | VTable::HeapSnapshotMetadata{ |
108 | | HeapSnapshot::NodeType::Object, |
109 | | HiddenClass::_snapshotNameImpl, |
110 | | HiddenClass::_snapshotAddEdgesImpl, |
111 | | HiddenClass::_snapshotAddNodesImpl, |
112 | | nullptr} |
113 | | #endif |
114 | | }; |
115 | | |
116 | 1 | void HiddenClassBuildMeta(const GCCell *cell, Metadata::Builder &mb) { |
117 | 1 | const auto *self = static_cast<const HiddenClass *>(cell); |
118 | 1 | mb.setVTable(&HiddenClass::vt); |
119 | 1 | mb.addField("symbol", &self->symbolID_); |
120 | 1 | mb.addField("parent", &self->parent_); |
121 | 1 | mb.addField("propertyMap", &self->propertyMap_); |
122 | 1 | mb.addField("forInCache", &self->forInCache_); |
123 | 1 | } |
124 | | |
125 | 66.0k | void HiddenClass::_finalizeImpl(GCCell *cell, GC &gc) { |
126 | 66.0k | auto *self = vmcast<HiddenClass>(cell); |
127 | 66.0k | #ifdef HERMES_MEMORY_INSTRUMENTATION |
128 | 66.0k | self->transitionMap_.snapshotUntrackMemory(gc); |
129 | 66.0k | #endif |
130 | 66.0k | self->~HiddenClass(); |
131 | 66.0k | } |
132 | | |
133 | 0 | size_t HiddenClass::_mallocSizeImpl(GCCell *cell) { |
134 | 0 | auto *self = vmcast<HiddenClass>(cell); |
135 | 0 | return self->transitionMap_.getMemorySize(); |
136 | 0 | } |
137 | | |
138 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
139 | 0 | std::string HiddenClass::_snapshotNameImpl(GCCell *cell, GC &gc) { |
140 | 0 | auto *const self = vmcast<HiddenClass>(cell); |
141 | 0 | std::string name{cell->getVT()->snapshotMetaData.defaultNameForNode(self)}; |
142 | 0 | if (self->isDictionary()) { |
143 | 0 | return name + "(Dictionary)"; |
144 | 0 | } |
145 | 0 | return name; |
146 | 0 | } |
147 | | |
148 | | void HiddenClass::_snapshotAddEdgesImpl( |
149 | | GCCell *cell, |
150 | | GC &gc, |
151 | 0 | HeapSnapshot &snap) { |
152 | 0 | auto *const self = vmcast<HiddenClass>(cell); |
153 | 0 | self->transitionMap_.snapshotAddEdges(gc, snap); |
154 | 0 | } |
155 | | |
156 | | void HiddenClass::_snapshotAddNodesImpl( |
157 | | GCCell *cell, |
158 | | GC &gc, |
159 | 0 | HeapSnapshot &snap) { |
160 | 0 | auto *const self = vmcast<HiddenClass>(cell); |
161 | 0 | self->transitionMap_.snapshotAddNodes(gc, snap); |
162 | 0 | } |
163 | | #endif |
164 | | |
165 | 113 | CallResult<HermesValue> HiddenClass::createRoot(Runtime &runtime) { |
166 | 113 | return create( |
167 | 113 | runtime, |
168 | 113 | ClassFlags{}, |
169 | 113 | Runtime::makeNullHandle<HiddenClass>(), |
170 | 113 | SymbolID{}, |
171 | 113 | PropertyFlags{}, |
172 | 113 | 0); |
173 | 113 | } |
174 | | |
175 | | CallResult<HermesValue> HiddenClass::create( |
176 | | Runtime &runtime, |
177 | | ClassFlags flags, |
178 | | Handle<HiddenClass> parent, |
179 | | SymbolID symbolID, |
180 | | PropertyFlags propertyFlags, |
181 | 66.0k | unsigned numProperties) { |
182 | 66.0k | assert( |
183 | 66.0k | (flags.dictionaryMode || numProperties == 0 || *parent) && |
184 | 66.0k | "non-empty non-dictionary orphan"); |
185 | 66.0k | auto *obj = |
186 | 66.0k | runtime.makeAFixed<HiddenClass, HasFinalizer::Yes, LongLived::Yes>( |
187 | 66.0k | runtime, flags, parent, symbolID, propertyFlags, numProperties); |
188 | 66.0k | return HermesValue::encodeObjectValue(obj); |
189 | 66.0k | } |
190 | | |
191 | | Handle<HiddenClass> HiddenClass::copyToNewDictionary( |
192 | | Handle<HiddenClass> selfHandle, |
193 | | Runtime &runtime, |
194 | 137 | bool noCache) { |
195 | 137 | assert( |
196 | 137 | !selfHandle->isDictionaryNoCache() && "class already in no-cache mode"); |
197 | | |
198 | 137 | auto newFlags = selfHandle->flags_; |
199 | 137 | newFlags.dictionaryMode = true; |
200 | | // If the requested, transition to no-cache mode. |
201 | 137 | if (noCache) { |
202 | 12 | newFlags.dictionaryNoCacheMode = true; |
203 | 12 | } |
204 | | |
205 | | /// Allocate a new class without a parent. |
206 | 137 | auto newClassHandle = |
207 | 137 | runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure( |
208 | 137 | HiddenClass::create( |
209 | 137 | runtime, |
210 | 137 | newFlags, |
211 | 137 | Runtime::makeNullHandle<HiddenClass>(), |
212 | 137 | SymbolID{}, |
213 | 137 | PropertyFlags{}, |
214 | 137 | selfHandle->numProperties_))); |
215 | | |
216 | | // Optionally allocate the property map and move it to the new class. |
217 | 137 | if (LLVM_UNLIKELY(!selfHandle->propertyMap_)) |
218 | 0 | initializeMissingPropertyMap(selfHandle, runtime); |
219 | | |
220 | 137 | newClassHandle->propertyMap_.set( |
221 | 137 | runtime, selfHandle->propertyMap_, runtime.getHeap()); |
222 | 137 | selfHandle->propertyMap_.setNull(runtime.getHeap()); |
223 | | |
224 | 137 | LLVM_DEBUG( |
225 | 137 | dbgs() << "Converted Class:" << selfHandle->getDebugAllocationId() |
226 | 137 | << " to dictionary Class:" |
227 | 137 | << newClassHandle->getDebugAllocationId() << "\n"); |
228 | | |
229 | 137 | return newClassHandle; |
230 | 137 | } |
231 | | |
232 | | void HiddenClass::forEachPropertyNoAlloc( |
233 | | HiddenClass *self, |
234 | | PointerBase &base, |
235 | 0 | std::function<void(SymbolID, NamedPropertyDescriptor)> callback) { |
236 | 0 | std::vector<std::pair<SymbolID, NamedPropertyDescriptor>> properties; |
237 | 0 | HiddenClass *curr = self; |
238 | 0 | while (curr && !curr->propertyMap_) { |
239 | | // Skip invalid symbols stored in the hidden class chain, as well as |
240 | | // flag-only transitions. |
241 | 0 | if (curr->symbolID_.isValid() && !curr->propertyFlags_.flagsTransition) { |
242 | 0 | properties.emplace_back( |
243 | 0 | curr->symbolID_, |
244 | 0 | NamedPropertyDescriptor{ |
245 | 0 | curr->propertyFlags_, curr->numProperties_ - 1}); |
246 | 0 | } |
247 | 0 | curr = curr->parent_.get(base); |
248 | 0 | } |
249 | | |
250 | | // Either we reached the root hidden class and never found a property |
251 | | // map, or we found a property map somewhere in the HiddenClass chain. |
252 | 0 | if (curr) { |
253 | 0 | assert( |
254 | 0 | curr->propertyMap_ && |
255 | 0 | "If it's not the root class, it must have a property map"); |
256 | | // The DPM exists, and it can be iterated over to find some properties. |
257 | 0 | DictPropertyMap::forEachPropertyNoAlloc( |
258 | 0 | curr->propertyMap_.getNonNull(base), callback); |
259 | 0 | } |
260 | | // Add any iterated properties at the end. |
261 | | // Since we moved backwards through HiddenClasses, the properties are in |
262 | | // reverse order. Iterate backwards through properties to get the original |
263 | | // order. |
264 | 0 | for (auto it = properties.rbegin(); it != properties.rend(); ++it) { |
265 | 0 | callback(it->first, it->second); |
266 | 0 | } |
267 | 0 | } |
268 | | |
269 | | OptValue<HiddenClass::PropertyPos> HiddenClass::findProperty( |
270 | | PseudoHandle<HiddenClass> self, |
271 | | Runtime &runtime, |
272 | | SymbolID name, |
273 | | PropertyFlags expectedFlags, |
274 | 4.65M | NamedPropertyDescriptor &desc) { |
275 | | // Lazily create the property map. |
276 | 4.65M | if (LLVM_UNLIKELY(!self->propertyMap_)) { |
277 | | // If expectedFlags is valid, we can check if there is an outgoing |
278 | | // transition with name and the flags. The presence of such a transition |
279 | | // indicates that this is a new property and we don't have to build the map |
280 | | // in order to look for it (since we wouldn't find it anyway). |
281 | 235k | if (expectedFlags.isValid()) { |
282 | 2.71k | Transition t{name, expectedFlags}; |
283 | 2.71k | if (self->transitionMap_.containsKey(t, runtime.getHeap())) { |
284 | 2.26k | LLVM_DEBUG( |
285 | 2.26k | dbgs() |
286 | 2.26k | << "Property " << runtime.formatSymbolID(name) |
287 | 2.26k | << " NOT FOUND in Class:" << self->getDebugAllocationId() |
288 | 2.26k | << " due to existing transition to Class:" |
289 | 2.26k | << self->transitionMap_.lookup(runtime, t)->getDebugAllocationId() |
290 | 2.26k | << "\n"); |
291 | 2.26k | return llvh::None; |
292 | 2.26k | } |
293 | 2.71k | } |
294 | | |
295 | 233k | auto selfHandle = runtime.makeHandle(std::move(self)); |
296 | 233k | initializeMissingPropertyMap(selfHandle, runtime); |
297 | 233k | self = selfHandle; |
298 | 233k | } |
299 | | |
300 | 4.65M | auto *propMap = self->propertyMap_.getNonNull(runtime); |
301 | 4.65M | { |
302 | | // propMap is a raw pointer. We assume that find does no allocation. |
303 | 4.65M | NoAllocScope noAlloc(runtime); |
304 | 4.65M | auto found = DictPropertyMap::find(propMap, name); |
305 | 4.65M | if (!found) |
306 | 2.61M | return llvh::None; |
307 | | // Technically, the last use of propMap occurs before the call here, so |
308 | | // it would be legal for the call to allocate. If that were ever the case, |
309 | | // we would move "found" out of scope, and terminate the NoAllocScope here. |
310 | 2.03M | desc = DictPropertyMap::getDescriptorPair(propMap, *found)->second; |
311 | 2.03M | return *found; |
312 | 4.65M | } |
313 | 4.65M | } |
314 | | |
315 | | llvh::Optional<NamedPropertyDescriptor> HiddenClass::findPropertyNoAlloc( |
316 | | HiddenClass *self, |
317 | | PointerBase &base, |
318 | 0 | SymbolID name) { |
319 | 0 | for (HiddenClass *curr = self; curr; curr = curr->parent_.get(base)) { |
320 | 0 | if (curr->propertyMap_) { |
321 | | // If a property map exists, just search this hidden class |
322 | 0 | auto found = |
323 | 0 | DictPropertyMap::find(curr->propertyMap_.getNonNull(base), name); |
324 | 0 | if (found) { |
325 | 0 | return DictPropertyMap::getDescriptorPair( |
326 | 0 | curr->propertyMap_.getNonNull(base), *found) |
327 | 0 | ->second; |
328 | 0 | } |
329 | 0 | } |
330 | | // Else, no property map exists. Check the current hidden class before |
331 | | // moving up. |
332 | 0 | if (curr->symbolID_ == name) { |
333 | 0 | return NamedPropertyDescriptor{ |
334 | 0 | curr->propertyFlags_, curr->numProperties_ - 1}; |
335 | 0 | } |
336 | 0 | } |
337 | | // Reached the root hidden class without finding a property map or the |
338 | | // matching symbol, this property doesn't exist. |
339 | 0 | return llvh::None; |
340 | 0 | } |
341 | | |
342 | | bool HiddenClass::debugIsPropertyDefined( |
343 | | HiddenClass *self, |
344 | | PointerBase &base, |
345 | 120k | SymbolID name) { |
346 | 755k | do { |
347 | | // If we happen to have a property map, use it. |
348 | 755k | if (self->propertyMap_) |
349 | 3.53k | return DictPropertyMap::find(self->propertyMap_.getNonNull(base), name) |
350 | 3.53k | .hasValue(); |
351 | | // Is the property defined in this class? |
352 | 752k | if (self->symbolID_ == name) |
353 | 0 | return true; |
354 | 752k | self = self->parent_.get(base); |
355 | 752k | } while (self); |
356 | 117k | return false; |
357 | 120k | } |
358 | | |
359 | | Handle<HiddenClass> HiddenClass::deleteProperty( |
360 | | Handle<HiddenClass> selfHandle, |
361 | | Runtime &runtime, |
362 | 0 | PropertyPos pos) { |
363 | | // We convert to dictionary if we're not yet a dictionary |
364 | | // (transition to a cacheable dictionary), or if we are, but not yet |
365 | | // in no-cache mode (transition to no-cache mode). |
366 | 0 | auto newHandle = LLVM_UNLIKELY(!selfHandle->isDictionaryNoCache()) |
367 | 0 | ? copyToNewDictionary(selfHandle, runtime, selfHandle->isDictionary()) |
368 | 0 | : selfHandle; |
369 | |
|
370 | 0 | --newHandle->numProperties_; |
371 | |
|
372 | 0 | DictPropertyMap::erase(newHandle->propertyMap_.get(runtime), runtime, pos); |
373 | |
|
374 | 0 | LLVM_DEBUG( |
375 | 0 | dbgs() << "Deleting from Class:" << selfHandle->getDebugAllocationId() |
376 | 0 | << " produces Class:" << newHandle->getDebugAllocationId() |
377 | 0 | << "\n"); |
378 | |
|
379 | 0 | return newHandle; |
380 | 0 | } |
381 | | |
382 | | CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::addProperty( |
383 | | Handle<HiddenClass> selfHandle, |
384 | | Runtime &runtime, |
385 | | SymbolID name, |
386 | 846k | PropertyFlags propertyFlags) { |
387 | 846k | assert(propertyFlags.isValid() && "propertyFlags must be valid"); |
388 | | |
389 | 846k | if (LLVM_UNLIKELY(selfHandle->isDictionary())) { |
390 | 208k | auto isIndexLike = |
391 | 208k | toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name)) |
392 | 208k | .hasValue(); |
393 | 208k | selfHandle->flags_ = |
394 | 208k | computeFlags(selfHandle->flags_, propertyFlags, isIndexLike); |
395 | | |
396 | | // Allocate a new slot. |
397 | | // TODO: this changes the property map, so if we want to support OOM |
398 | | // handling in the future, and the following operation fails, we would have |
399 | | // to somehow be able to undo it, or use an approach where we peek the slot |
400 | | // but not consume until we are sure (which is less efficient, but more |
401 | | // robust). T31555339. |
402 | 208k | SlotIndex newSlot = DictPropertyMap::allocatePropertySlot( |
403 | 208k | selfHandle->propertyMap_.get(runtime), runtime); |
404 | | |
405 | 208k | if (LLVM_UNLIKELY( |
406 | 208k | addToPropertyMap( |
407 | 208k | selfHandle, |
408 | 208k | runtime, |
409 | 208k | name, |
410 | 208k | NamedPropertyDescriptor(propertyFlags, newSlot)) == |
411 | 208k | ExecutionStatus::EXCEPTION)) { |
412 | 0 | return ExecutionStatus::EXCEPTION; |
413 | 0 | } |
414 | | |
415 | 208k | ++selfHandle->numProperties_; |
416 | 208k | return std::make_pair(selfHandle, newSlot); |
417 | 208k | } |
418 | | |
419 | | // Do we already have a transition for that property+flags pair? |
420 | 637k | auto existingChild = |
421 | 637k | selfHandle->transitionMap_.lookup(runtime, {name, propertyFlags}); |
422 | 637k | if (LLVM_LIKELY(existingChild)) { |
423 | 571k | auto childHandle = runtime.makeHandle(existingChild); |
424 | | // If the child doesn't have a property map, but we do, update our map and |
425 | | // move it to the child. |
426 | 571k | if (!childHandle->propertyMap_ && selfHandle->propertyMap_) { |
427 | 226k | LLVM_DEBUG( |
428 | 226k | dbgs() << "Adding property " << runtime.formatSymbolID(name) |
429 | 226k | << " to Class:" << selfHandle->getDebugAllocationId() |
430 | 226k | << " transitions Map to existing Class:" |
431 | 226k | << childHandle->getDebugAllocationId() << "\n"); |
432 | | |
433 | 226k | if (LLVM_UNLIKELY( |
434 | 226k | addToPropertyMap( |
435 | 226k | selfHandle, |
436 | 226k | runtime, |
437 | 226k | name, |
438 | 226k | NamedPropertyDescriptor( |
439 | 226k | propertyFlags, selfHandle->numProperties_)) == |
440 | 226k | ExecutionStatus::EXCEPTION)) { |
441 | 0 | return ExecutionStatus::EXCEPTION; |
442 | 0 | } |
443 | 226k | childHandle->propertyMap_.set( |
444 | 226k | runtime, selfHandle->propertyMap_, runtime.getHeap()); |
445 | 345k | } else { |
446 | 345k | LLVM_DEBUG( |
447 | 345k | dbgs() << "Adding property " << runtime.formatSymbolID(name) |
448 | 345k | << " to Class:" << selfHandle->getDebugAllocationId() |
449 | 345k | << " transitions to existing Class:" |
450 | 345k | << childHandle->getDebugAllocationId() << "\n"); |
451 | 345k | } |
452 | | |
453 | | // In any case, clear our own map. |
454 | 571k | selfHandle->propertyMap_.setNull(runtime.getHeap()); |
455 | | |
456 | 571k | return std::make_pair(childHandle, selfHandle->numProperties_); |
457 | 571k | } |
458 | | |
459 | | // Do we need to convert to dictionary? |
460 | 65.7k | if (LLVM_UNLIKELY(selfHandle->numProperties_ == kDictionaryThreshold)) { |
461 | | // Do it. |
462 | 125 | auto childHandle = copyToNewDictionary(selfHandle, runtime); |
463 | | |
464 | 125 | auto isIndexLike = |
465 | 125 | toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name)) |
466 | 125 | .hasValue(); |
467 | 125 | childHandle->flags_ = |
468 | 125 | computeFlags(childHandle->flags_, propertyFlags, isIndexLike); |
469 | | |
470 | | // Add the property to the child. |
471 | 125 | if (LLVM_UNLIKELY( |
472 | 125 | addToPropertyMap( |
473 | 125 | childHandle, |
474 | 125 | runtime, |
475 | 125 | name, |
476 | 125 | NamedPropertyDescriptor( |
477 | 125 | propertyFlags, childHandle->numProperties_)) == |
478 | 125 | ExecutionStatus::EXCEPTION)) { |
479 | 0 | return ExecutionStatus::EXCEPTION; |
480 | 0 | } |
481 | 125 | return std::make_pair(childHandle, childHandle->numProperties_++); |
482 | 125 | } |
483 | | |
484 | 65.6k | auto isIndexLike = |
485 | 65.6k | toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name)) |
486 | 65.6k | .hasValue(); |
487 | 65.6k | auto newFlags = computeFlags(selfHandle->flags_, propertyFlags, isIndexLike); |
488 | | |
489 | | // Allocate the child. |
490 | 65.6k | auto childHandle = |
491 | 65.6k | runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure( |
492 | 65.6k | HiddenClass::create( |
493 | 65.6k | runtime, |
494 | 65.6k | newFlags, |
495 | 65.6k | selfHandle, |
496 | 65.6k | name, |
497 | 65.6k | propertyFlags, |
498 | 65.6k | selfHandle->numProperties_ + 1))); |
499 | | |
500 | | // Add it to the transition table. |
501 | 65.6k | auto inserted = selfHandle->transitionMap_.insertNew( |
502 | 65.6k | runtime, Transition(name, propertyFlags), childHandle); |
503 | 65.6k | (void)inserted; |
504 | 65.6k | assert( |
505 | 65.6k | inserted && |
506 | 65.6k | "transition already exists when adding a new property to hidden class"); |
507 | | |
508 | 65.6k | if (selfHandle->propertyMap_) { |
509 | 62.9k | assert( |
510 | 62.9k | !DictPropertyMap::find(selfHandle->propertyMap_.get(runtime), name) && |
511 | 62.9k | "Adding an existing property to hidden class"); |
512 | | |
513 | 62.9k | LLVM_DEBUG( |
514 | 62.9k | dbgs() << "Adding property " << runtime.formatSymbolID(name) |
515 | 62.9k | << " to Class:" << selfHandle->getDebugAllocationId() |
516 | 62.9k | << " transitions Map to new Class:" |
517 | 62.9k | << childHandle->getDebugAllocationId() << "\n"); |
518 | | |
519 | | // Move the map to the child class. |
520 | 62.9k | childHandle->propertyMap_.set( |
521 | 62.9k | runtime, selfHandle->propertyMap_, runtime.getHeap()); |
522 | 62.9k | selfHandle->propertyMap_.setNull(runtime.getHeap()); |
523 | | |
524 | 62.9k | if (LLVM_UNLIKELY( |
525 | 62.9k | addToPropertyMap( |
526 | 62.9k | childHandle, |
527 | 62.9k | runtime, |
528 | 62.9k | name, |
529 | 62.9k | NamedPropertyDescriptor( |
530 | 62.9k | propertyFlags, selfHandle->numProperties_)) == |
531 | 62.9k | ExecutionStatus::EXCEPTION)) { |
532 | 0 | return ExecutionStatus::EXCEPTION; |
533 | 0 | } |
534 | 62.9k | } else { |
535 | 2.60k | LLVM_DEBUG( |
536 | 2.60k | dbgs() << "Adding property " << runtime.formatSymbolID(name) |
537 | 2.60k | << " to Class:" << selfHandle->getDebugAllocationId() |
538 | 2.60k | << " transitions to new Class:" |
539 | 2.60k | << childHandle->getDebugAllocationId() << "\n"); |
540 | 2.60k | } |
541 | | |
542 | 65.6k | return std::make_pair(childHandle, selfHandle->numProperties_); |
543 | 65.6k | } |
544 | | |
545 | | Handle<HiddenClass> HiddenClass::updateProperty( |
546 | | Handle<HiddenClass> selfHandle, |
547 | | Runtime &runtime, |
548 | | PropertyPos pos, |
549 | 399 | PropertyFlags newFlags) { |
550 | 399 | assert(newFlags.isValid() && "newFlags must be valid"); |
551 | | |
552 | | // In dictionary mode we simply update our map (which must exist). |
553 | 399 | if (LLVM_UNLIKELY(selfHandle->isDictionary())) { |
554 | 12 | assert( |
555 | 12 | selfHandle->propertyMap_ && |
556 | 12 | "propertyMap must exist in dictionary mode"); |
557 | 12 | selfHandle->flags_ = computeFlags(selfHandle->flags_, newFlags, false); |
558 | 12 | DictPropertyMap::getDescriptorPair( |
559 | 12 | selfHandle->propertyMap_.getNonNull(runtime), pos) |
560 | 12 | ->second.flags = newFlags; |
561 | | // If it's still cacheable, make it non-cacheable. |
562 | 12 | if (!selfHandle->isDictionaryNoCache()) { |
563 | 12 | selfHandle = copyToNewDictionary(selfHandle, runtime, /*noCache*/ true); |
564 | 12 | } |
565 | 12 | return selfHandle; |
566 | 12 | } |
567 | | |
568 | 399 | assert( |
569 | 387 | selfHandle->propertyMap_ && "propertyMap must exist in updateProperty()"); |
570 | | |
571 | 387 | auto *descPair = DictPropertyMap::getDescriptorPair( |
572 | 387 | selfHandle->propertyMap_.get(runtime), pos); |
573 | | // If the property flags didn't change, do nothing. |
574 | 387 | if (descPair->second.flags == newFlags) |
575 | 0 | return selfHandle; |
576 | | |
577 | 387 | auto name = descPair->first; |
578 | | |
579 | | // The transition flags must indicate that it is a "flags transition". |
580 | 387 | PropertyFlags transitionFlags = newFlags; |
581 | 387 | transitionFlags.flagsTransition = 1; |
582 | | |
583 | | // Do we already have a transition for that property+flags pair? |
584 | 387 | auto existingChild = |
585 | 387 | selfHandle->transitionMap_.lookup(runtime, {name, transitionFlags}); |
586 | 387 | if (LLVM_LIKELY(existingChild)) { |
587 | | // If the child doesn't have a property map, but we do, update our map and |
588 | | // move it to the child. |
589 | 143 | if (!existingChild->propertyMap_) { |
590 | 113 | LLVM_DEBUG( |
591 | 113 | dbgs() << "Updating property " << runtime.formatSymbolID(name) |
592 | 113 | << " in Class:" << selfHandle->getDebugAllocationId() |
593 | 113 | << " transitions Map to existing Class:" |
594 | 113 | << existingChild->getDebugAllocationId() << "\n"); |
595 | | |
596 | 113 | descPair->second.flags = newFlags; |
597 | 113 | existingChild->propertyMap_.set( |
598 | 113 | runtime, selfHandle->propertyMap_, runtime.getHeap()); |
599 | 113 | } else { |
600 | 30 | LLVM_DEBUG( |
601 | 30 | dbgs() << "Updating property " << runtime.formatSymbolID(name) |
602 | 30 | << " in Class:" << selfHandle->getDebugAllocationId() |
603 | 30 | << " transitions to existing Class:" |
604 | 30 | << existingChild->getDebugAllocationId() << "\n"); |
605 | 30 | } |
606 | | |
607 | | // In any case, clear our own map. |
608 | 143 | selfHandle->propertyMap_.setNull(runtime.getHeap()); |
609 | | |
610 | 143 | return runtime.makeHandle(existingChild); |
611 | 143 | } |
612 | | |
613 | | // We are updating the existing property and adding a transition to a new |
614 | | // hidden class. |
615 | 244 | descPair->second.flags = newFlags; |
616 | | |
617 | | // Allocate the child. |
618 | 244 | auto childHandle = |
619 | 244 | runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure( |
620 | 244 | HiddenClass::create( |
621 | 244 | runtime, |
622 | 244 | computeFlags(selfHandle->flags_, newFlags, false), |
623 | 244 | selfHandle, |
624 | 244 | name, |
625 | 244 | transitionFlags, |
626 | 244 | selfHandle->numProperties_))); |
627 | | |
628 | | // Add it to the transition table. |
629 | 244 | auto inserted = selfHandle->transitionMap_.insertNew( |
630 | 244 | runtime, Transition(name, transitionFlags), childHandle); |
631 | 244 | (void)inserted; |
632 | 244 | assert( |
633 | 244 | inserted && |
634 | 244 | "transition already exists when updating a property in hidden class"); |
635 | | |
636 | 244 | LLVM_DEBUG( |
637 | 244 | dbgs() << "Updating property " << runtime.formatSymbolID(name) |
638 | 244 | << " in Class:" << selfHandle->getDebugAllocationId() |
639 | 244 | << " transitions Map to new Class:" |
640 | 244 | << childHandle->getDebugAllocationId() << "\n"); |
641 | | |
642 | | // Move the updated map to the child class. |
643 | 244 | childHandle->propertyMap_.set( |
644 | 244 | runtime, selfHandle->propertyMap_, runtime.getHeap()); |
645 | 244 | selfHandle->propertyMap_.setNull(runtime.getHeap()); |
646 | | |
647 | 244 | return childHandle; |
648 | 244 | } |
649 | | |
650 | | Handle<HiddenClass> HiddenClass::makeAllNonConfigurable( |
651 | | Handle<HiddenClass> selfHandle, |
652 | 0 | Runtime &runtime) { |
653 | 0 | if (!selfHandle->propertyMap_) |
654 | 0 | initializeMissingPropertyMap(selfHandle, runtime); |
655 | |
|
656 | 0 | LLVM_DEBUG( |
657 | 0 | dbgs() << "Class:" << selfHandle->getDebugAllocationId() |
658 | 0 | << " making all non-configurable\n"); |
659 | | |
660 | | // Keep a handle to our initial map. The order of properties in it will |
661 | | // remain the same as long as we are only doing property updates. |
662 | 0 | auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_); |
663 | |
|
664 | 0 | MutableHandle<HiddenClass> curHandle{runtime, *selfHandle}; |
665 | | |
666 | | // TODO: this can be made much more efficient at the expense of moving some |
667 | | // logic from updateOwnProperty() here. |
668 | 0 | DictPropertyMap::forEachProperty( |
669 | 0 | mapHandle, |
670 | 0 | runtime, |
671 | 0 | [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) { |
672 | 0 | if (!desc.flags.configurable) |
673 | 0 | return; |
674 | 0 | PropertyFlags newFlags = desc.flags; |
675 | 0 | newFlags.configurable = 0; |
676 | |
|
677 | 0 | assert( |
678 | 0 | curHandle->propertyMap_ && |
679 | 0 | "propertyMap must exist after updateOwnProperty()"); |
680 | | |
681 | 0 | auto found = |
682 | 0 | DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id); |
683 | 0 | assert(found && "property not found during enumeration"); |
684 | 0 | curHandle = *updateProperty(curHandle, runtime, *found, newFlags); |
685 | 0 | }); |
686 | 0 | return std::move(curHandle); |
687 | 0 | } |
688 | | |
689 | | Handle<HiddenClass> HiddenClass::makeAllReadOnly( |
690 | | Handle<HiddenClass> selfHandle, |
691 | 0 | Runtime &runtime) { |
692 | 0 | if (!selfHandle->propertyMap_) |
693 | 0 | initializeMissingPropertyMap(selfHandle, runtime); |
694 | |
|
695 | 0 | LLVM_DEBUG( |
696 | 0 | dbgs() << "Class:" << selfHandle->getDebugAllocationId() |
697 | 0 | << " making all read-only\n"); |
698 | | |
699 | | // Keep a handle to our initial map. The order of properties in it will |
700 | | // remain the same as long as we are only doing property updates. |
701 | 0 | auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_); |
702 | |
|
703 | 0 | MutableHandle<HiddenClass> curHandle{runtime, *selfHandle}; |
704 | | |
705 | | // TODO: this can be made much more efficient at the expense of moving some |
706 | | // logic from updateOwnProperty() here. |
707 | 0 | DictPropertyMap::forEachProperty( |
708 | 0 | mapHandle, |
709 | 0 | runtime, |
710 | 0 | [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) { |
711 | 0 | PropertyFlags newFlags = desc.flags; |
712 | 0 | if (!newFlags.accessor) { |
713 | 0 | newFlags.writable = 0; |
714 | 0 | newFlags.configurable = 0; |
715 | 0 | } else { |
716 | 0 | newFlags.configurable = 0; |
717 | 0 | } |
718 | 0 | if (desc.flags == newFlags) |
719 | 0 | return; |
720 | | |
721 | 0 | assert( |
722 | 0 | curHandle->propertyMap_ && |
723 | 0 | "propertyMap must exist after updateOwnProperty()"); |
724 | | |
725 | 0 | auto found = |
726 | 0 | DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id); |
727 | 0 | assert(found && "property not found during enumeration"); |
728 | 0 | curHandle = *updateProperty(curHandle, runtime, *found, newFlags); |
729 | 0 | }); |
730 | |
|
731 | 0 | return std::move(curHandle); |
732 | 0 | } |
733 | | |
734 | | Handle<HiddenClass> HiddenClass::updatePropertyFlagsWithoutTransitions( |
735 | | Handle<HiddenClass> selfHandle, |
736 | | Runtime &runtime, |
737 | | PropertyFlags flagsToClear, |
738 | | PropertyFlags flagsToSet, |
739 | 0 | OptValue<llvh::ArrayRef<SymbolID>> props) { |
740 | | // Result must be in dictionary mode, since it's a non-empty orphan. |
741 | 0 | MutableHandle<HiddenClass> classHandle{runtime}; |
742 | 0 | if (selfHandle->isDictionary()) { |
743 | 0 | classHandle = *selfHandle; |
744 | 0 | } else { |
745 | 0 | classHandle = *copyToNewDictionary(selfHandle, runtime); |
746 | 0 | } |
747 | |
|
748 | 0 | auto mapHandle = |
749 | 0 | runtime.makeHandle<DictPropertyMap>(classHandle->propertyMap_); |
750 | |
|
751 | 0 | auto changeFlags = [&flagsToClear, |
752 | 0 | &flagsToSet](NamedPropertyDescriptor &desc) { |
753 | 0 | desc.flags.changeFlags(flagsToClear, flagsToSet); |
754 | 0 | }; |
755 | | |
756 | | // If we have the subset of properties to update, only update them; otherwise, |
757 | | // update all properties. |
758 | 0 | if (props) { |
759 | | // Iterate over the properties that exist on the property map. |
760 | 0 | for (auto id : *props) { |
761 | 0 | auto pos = DictPropertyMap::find(*mapHandle, id); |
762 | 0 | if (!pos) { |
763 | 0 | continue; |
764 | 0 | } |
765 | 0 | auto descPair = DictPropertyMap::getDescriptorPair(*mapHandle, *pos); |
766 | 0 | changeFlags(descPair->second); |
767 | 0 | } |
768 | 0 | } else { |
769 | 0 | DictPropertyMap::forEachMutablePropertyDescriptor( |
770 | 0 | mapHandle, runtime, changeFlags); |
771 | 0 | } |
772 | 0 | classHandle->flags_ = computeFlags(classHandle->flags_, flagsToSet, false); |
773 | |
|
774 | 0 | return std::move(classHandle); |
775 | 0 | } |
776 | | |
777 | | CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::reserveSlot( |
778 | | Handle<HiddenClass> selfHandle, |
779 | 791 | Runtime &runtime) { |
780 | 791 | assert( |
781 | 791 | !selfHandle->isDictionary() && |
782 | 791 | "Reserved slots can only be added in class mode"); |
783 | 791 | SlotIndex index = selfHandle->numProperties_; |
784 | 791 | assert( |
785 | 791 | index < InternalProperty::NumAnonymousInternalProperties && |
786 | 791 | "Reserved slot index is too large"); |
787 | | |
788 | 791 | return HiddenClass::addProperty( |
789 | 791 | selfHandle, |
790 | 791 | runtime, |
791 | 791 | InternalProperty::getSymbolID(index), |
792 | 791 | PropertyFlags{}); |
793 | 791 | } |
794 | | |
795 | | bool HiddenClass::areAllNonConfigurable( |
796 | | Handle<HiddenClass> selfHandle, |
797 | 0 | Runtime &runtime) { |
798 | 0 | return forEachPropertyWhile( |
799 | 0 | selfHandle, |
800 | 0 | runtime, |
801 | 0 | [](Runtime &, SymbolID, NamedPropertyDescriptor desc) { |
802 | 0 | return !desc.flags.configurable; |
803 | 0 | }); |
804 | 0 | } |
805 | | |
806 | | bool HiddenClass::areAllReadOnly( |
807 | | Handle<HiddenClass> selfHandle, |
808 | 0 | Runtime &runtime) { |
809 | 0 | return forEachPropertyWhile( |
810 | 0 | selfHandle, |
811 | 0 | runtime, |
812 | 0 | [](Runtime &, SymbolID, NamedPropertyDescriptor desc) { |
813 | 0 | if (!desc.flags.accessor && desc.flags.writable) |
814 | 0 | return false; |
815 | 0 | return !desc.flags.configurable; |
816 | 0 | }); |
817 | 0 | } |
818 | | |
819 | | ExecutionStatus HiddenClass::addToPropertyMap( |
820 | | Handle<HiddenClass> selfHandle, |
821 | | Runtime &runtime, |
822 | | SymbolID name, |
823 | 498k | NamedPropertyDescriptor desc) { |
824 | 498k | assert(selfHandle->propertyMap_ && "the property map must be initialized"); |
825 | | |
826 | | // Add the new field to the property map. |
827 | 498k | MutableHandle<DictPropertyMap> updatedMap{ |
828 | 498k | runtime, selfHandle->propertyMap_.get(runtime)}; |
829 | | |
830 | 498k | if (LLVM_UNLIKELY( |
831 | 498k | DictPropertyMap::add(updatedMap, runtime, name, desc) == |
832 | 498k | ExecutionStatus::EXCEPTION)) { |
833 | 0 | return ExecutionStatus::EXCEPTION; |
834 | 0 | } |
835 | | |
836 | 498k | selfHandle->propertyMap_.setNonNull(runtime, *updatedMap, runtime.getHeap()); |
837 | 498k | return ExecutionStatus::RETURNED; |
838 | 498k | } |
839 | | |
840 | | void HiddenClass::initializeMissingPropertyMap( |
841 | | Handle<HiddenClass> selfHandle, |
842 | 233k | Runtime &runtime) { |
843 | 233k | assert(!selfHandle->propertyMap_ && "property map is already initialized"); |
844 | | |
845 | | // Check whether we can steal our parent's map. If we can, we only need |
846 | | // to add or update a single property. |
847 | 233k | if (selfHandle->parent_ && |
848 | 7.80k | selfHandle->parent_.getNonNull(runtime)->propertyMap_) |
849 | 0 | return stealPropertyMapFromParent(selfHandle, runtime); |
850 | | |
851 | 233k | LLVM_DEBUG( |
852 | 233k | dbgs() << "Class:" << selfHandle->getDebugAllocationId() |
853 | 233k | << " allocating new map\n"); |
854 | | |
855 | | // First collect all entries in reverse order. This avoids recursion. |
856 | 233k | using MapEntry = std::pair<SymbolID, PropertyFlags>; |
857 | 233k | llvh::SmallVector<MapEntry, 4> entries; |
858 | 233k | entries.reserve(selfHandle->numProperties_); |
859 | 233k | { |
860 | | // Walk chain of parents using raw pointers. |
861 | 233k | NoAllocScope _(runtime); |
862 | 280k | for (auto *cur = *selfHandle; cur->numProperties_ > 0; |
863 | 233k | cur = cur->parent_.getNonNull(runtime)) { |
864 | 46.6k | auto tmpFlags = cur->propertyFlags_; |
865 | 46.6k | tmpFlags.flagsTransition = 0; |
866 | 46.6k | entries.emplace_back(cur->symbolID_, tmpFlags); |
867 | 46.6k | } |
868 | 233k | } |
869 | | |
870 | 233k | assert( |
871 | 233k | entries.size() <= DictPropertyMap::getMaxCapacity() && |
872 | 233k | "There shouldn't ever be this many properties"); |
873 | | // Allocate the map with the correct size. |
874 | 233k | auto res = DictPropertyMap::create( |
875 | 233k | runtime, |
876 | 233k | std::max( |
877 | 233k | (DictPropertyMap::size_type)entries.size(), |
878 | 233k | toRValue(DictPropertyMap::DEFAULT_CAPACITY))); |
879 | 233k | assert( |
880 | 233k | res != ExecutionStatus::EXCEPTION && |
881 | 233k | "Since the entries would fit, there shouldn't be an exception"); |
882 | 233k | MutableHandle<DictPropertyMap> mapHandle{runtime, res->get()}; |
883 | | |
884 | | // Add the collected entries in reverse order. Note that there could be |
885 | | // duplicates. |
886 | 233k | SlotIndex slotIndex = 0; |
887 | 280k | for (auto it = entries.rbegin(), e = entries.rend(); it != e; ++it) { |
888 | 46.6k | auto inserted = DictPropertyMap::findOrAdd(mapHandle, runtime, it->first); |
889 | 46.6k | assert( |
890 | 46.6k | inserted != ExecutionStatus::EXCEPTION && |
891 | 46.6k | "Space was already reserved, this couldn't have grown"); |
892 | | |
893 | 46.6k | inserted->first->flags = it->second; |
894 | | // If it is a new property, allocate the next slot. |
895 | 46.6k | if (LLVM_LIKELY(inserted->second)) |
896 | 46.6k | inserted->first->slot = slotIndex++; |
897 | 46.6k | } |
898 | | |
899 | 233k | selfHandle->propertyMap_.setNonNull(runtime, *mapHandle, runtime.getHeap()); |
900 | 233k | } |
901 | | |
902 | | void HiddenClass::stealPropertyMapFromParent( |
903 | | Handle<HiddenClass> selfHandle, |
904 | 0 | Runtime &runtime) { |
905 | | // Most of this method uses raw pointers. |
906 | 0 | NoAllocScope noAlloc(runtime); |
907 | 0 | auto *self = *selfHandle; |
908 | 0 | assert( |
909 | 0 | self->parent_ && self->parent_.getNonNull(runtime)->propertyMap_ && |
910 | 0 | !self->propertyMap_ && |
911 | 0 | "stealPropertyMapFromParent() must be called with a valid parent with a property map"); |
912 | | |
913 | 0 | LLVM_DEBUG( |
914 | 0 | dbgs() << "Class:" << self->getDebugAllocationId() |
915 | 0 | << " stealing map from parent Class:" |
916 | 0 | << self->parent_.getNonNull(runtime)->getDebugAllocationId() |
917 | 0 | << "\n"); |
918 | | |
919 | | // Success! Just steal our parent's map and add our own property. |
920 | 0 | self->propertyMap_.set( |
921 | 0 | runtime, |
922 | 0 | self->parent_.getNonNull(runtime)->propertyMap_, |
923 | 0 | runtime.getHeap()); |
924 | 0 | self->parent_.getNonNull(runtime)->propertyMap_.setNull(runtime.getHeap()); |
925 | | |
926 | | // Does our class add a new property? |
927 | 0 | if (LLVM_LIKELY(!self->propertyFlags_.flagsTransition)) { |
928 | | // This is a new property that we must now add. |
929 | 0 | assert( |
930 | 0 | self->numProperties_ - 1 == |
931 | 0 | self->propertyMap_.getNonNull(runtime)->size() && |
932 | 0 | "propertyMap->size() must match HiddenClass::numProperties-1 in " |
933 | 0 | "new prop transition"); |
934 | | |
935 | | // Create a descriptor for our property. |
936 | 0 | NamedPropertyDescriptor desc{ |
937 | 0 | self->propertyFlags_, self->numProperties_ - 1}; |
938 | | // Return to handle mode to add the property. |
939 | 0 | noAlloc.release(); |
940 | 0 | addToPropertyMap(selfHandle, runtime, selfHandle->symbolID_, desc); |
941 | 0 | return; |
942 | 0 | } |
943 | | // Our class is updating the flags of an existing property. So we need |
944 | | // to find it and update it. |
945 | | |
946 | 0 | assert( |
947 | 0 | self->numProperties_ == self->propertyMap_.getNonNull(runtime)->size() && |
948 | 0 | "propertyMap->size() must match HiddenClass::numProperties in " |
949 | 0 | "flag update transition"); |
950 | | |
951 | 0 | auto pos = |
952 | 0 | DictPropertyMap::find(self->propertyMap_.get(runtime), self->symbolID_); |
953 | 0 | assert(pos && "property must exist in flag update transition"); |
954 | 0 | auto tmpFlags = self->propertyFlags_; |
955 | 0 | tmpFlags.flagsTransition = 0; |
956 | 0 | DictPropertyMap::getDescriptorPair(self->propertyMap_.get(runtime), *pos) |
957 | 0 | ->second.flags = tmpFlags; |
958 | 0 | } |
959 | | |
960 | | } // namespace vm |
961 | | } // namespace hermes |
962 | | |
963 | | #undef DEBUG_TYPE |