1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/property_accessor.h"
17
18 #include "ecmascript/js_object-inl.h"
19
20 namespace panda::ecmascript {
PropertyAccessor(JSThread * thread,JSHandle<JSTaggedValue> object)21 PropertyAccessor::PropertyAccessor(JSThread *thread, JSHandle<JSTaggedValue> object)
22 : thread_(thread),
23 receiver_(thread, object.GetTaggedValue()),
24 fastKeysArray_(thread, JSTaggedValue::Undefined()),
25 cachedHclass_(thread, JSTaggedValue::Undefined()),
26 keyLength_(0),
27 shadowKeyLength_(0),
28 onlyHasSimpleProperties_(true),
29 canUseEnumCache_(true),
30 hasSlowProperties_(false),
31 slowKeysArray_(thread, JSTaggedValue::Undefined()),
32 acutalKeyLength_(0)
33 {
34 PreLoad();
35 }
36
PreLoad()37 void PropertyAccessor::PreLoad()
38 {
39 if (receiver_->IsSlowKeysObject()) {
40 hasSlowProperties_ = true;
41 return;
42 }
43 JSHandle<JSObject> receiverObj(receiver_);
44 JSHClass *jshclass = receiverObj->GetJSHClass();
45 if (jshclass->IsDictionaryMode()) {
46 onlyHasSimpleProperties_ = false;
47 canUseEnumCache_ = false;
48 }
49 uint32_t numOfElements = receiverObj->GetNumberOfElements(thread_);
50 if (numOfElements > 0) {
51 AccumulateKeyLength(numOfElements);
52 onlyHasSimpleProperties_ = false;
53 canUseEnumCache_ = false;
54 }
55 std::pair<uint32_t, uint32_t> numOfKeys = receiverObj->GetNumberOfEnumKeys();
56 uint32_t numOfEnumKeys = numOfKeys.first;
57 if (numOfEnumKeys > 0) {
58 AccumulateKeyLength(numOfEnumKeys);
59 }
60 uint32_t numOfShadowKeys = numOfKeys.second;
61 if (numOfShadowKeys > 0) {
62 AccumulateShadowKeyLength(numOfShadowKeys);
63 }
64
65 CollectPrototypeInfo();
66 if (hasSlowProperties_ || !onlyHasSimpleProperties_) {
67 return;
68 }
69 ASSERT(canUseEnumCache_);
70 // fast path
71 InitSimplePropertiesEnumCache();
72 }
73
CollectPrototypeInfo()74 void PropertyAccessor::CollectPrototypeInfo()
75 {
76 DISALLOW_GARBAGE_COLLECTION;
77 JSTaggedValue current = JSTaggedValue::GetPrototype(thread_, receiver_);
78 RETURN_IF_ABRUPT_COMPLETION(thread_);
79 while (current.IsHeapObject()) {
80 if (current.IsSlowKeysObject()) {
81 hasSlowProperties_ = true;
82 break;
83 }
84 JSObject *currentObj = JSObject::Cast(current.GetTaggedObject());
85 uint32_t numOfCurrentElements = currentObj->GetNumberOfElements(thread_);
86 if (numOfCurrentElements > 0) {
87 AccumulateKeyLength(numOfCurrentElements);
88 onlyHasSimpleProperties_ = false;
89 canUseEnumCache_ = false;
90 }
91 std::pair<uint32_t, uint32_t> numOfKeys = currentObj->GetNumberOfEnumKeys();
92 uint32_t numOfEnumKeys = numOfKeys.first;
93 if (numOfEnumKeys > 0) {
94 AccumulateKeyLength(numOfEnumKeys);
95 onlyHasSimpleProperties_ = false;
96 }
97 uint32_t numOfShadowKeys = numOfKeys.second;
98 if (numOfShadowKeys > 0) {
99 AccumulateShadowKeyLength(numOfShadowKeys);
100 }
101 JSHClass *jshclass = currentObj->GetJSHClass();
102 if (jshclass->IsDictionaryMode()) {
103 onlyHasSimpleProperties_ = false;
104 canUseEnumCache_ = false;
105 }
106 if (onlyHasSimpleProperties_) {
107 // a fast path to check simple enum cache
108 jshclass->SetEnumCache(thread_, JSTaggedValue::Undefined());
109 }
110 current = JSObject::GetPrototype(current);
111 }
112 }
113
InitSimplePropertiesEnumCache()114 void PropertyAccessor::InitSimplePropertiesEnumCache()
115 {
116 ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
117 JSHandle<JSObject> receiverObj(receiver_);
118 ASSERT(receiverObj->GetNumberOfElements(thread_) == 0);
119 ASSERT(!receiver_->IsInSharedHeap());
120 JSMutableHandle<TaggedArray> keyArray(thread_, JSTaggedValue::Undefined());
121 if (keyLength_ == 0) {
122 keyArray.Update(factory->EmptyArray());
123 SetActualKeyLength(0);
124 } else {
125 uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
126 JSHandle<TaggedArray> newArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
127 uint32_t length = JSObject::GetAllEnumKeys(thread_, receiverObj, EnumCache::ENUM_CACHE_HEADER_SIZE, newArray);
128 SetActualKeyLength(length);
129 JSObject::SetEnumCacheKind(thread_, *newArray, EnumCacheKind::SIMPLE);
130 keyArray.Update(newArray);
131 }
132 JSObject::ClearHasDeleteProperty(receiver_);
133 JSHClass *jsHclass = receiverObj->GetJSHClass();
134 jsHclass->SetEnumCache(thread_, keyArray.GetTaggedValue());
135 fastKeysArray_.Update(keyArray.GetTaggedValue());
136 cachedHclass_.Update(JSTaggedValue(jsHclass));
137 }
138
AccumulateKeyLength(uint32_t length)139 inline void PropertyAccessor::AccumulateKeyLength(uint32_t length)
140 {
141 keyLength_ += length;
142 }
143
AccumulateShadowKeyLength(uint32_t length)144 inline void PropertyAccessor::AccumulateShadowKeyLength(uint32_t length)
145 {
146 shadowKeyLength_ += length;
147 }
148
GetCachedHclass()149 JSHandle<JSTaggedValue> PropertyAccessor::GetCachedHclass()
150 {
151 return cachedHclass_;
152 }
153
GetActualKeyLength() const154 uint32_t PropertyAccessor::GetActualKeyLength() const
155 {
156 return acutalKeyLength_;
157 }
158
SetActualKeyLength(uint32_t length)159 inline void PropertyAccessor::SetActualKeyLength(uint32_t length)
160 {
161 acutalKeyLength_ = length;
162 }
163
AddKeysEndIfNeeded(JSHandle<TaggedArray> keys)164 void PropertyAccessor::AddKeysEndIfNeeded(JSHandle<TaggedArray> keys)
165 {
166 // when has duplicated keys
167 if (acutalKeyLength_ < keyLength_) {
168 keys->Set(thread_, acutalKeyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE, JSTaggedValue::Undefined());
169 }
170 }
171
TryInitEnumCacheWithProtoChainInfo()172 void PropertyAccessor::TryInitEnumCacheWithProtoChainInfo()
173 {
174 #if ECMASCRIPT_ENABLE_IC
175 if (!canUseEnumCache_) {
176 JSObject::SetEnumCacheKind(thread_, TaggedArray::Cast(fastKeysArray_->GetTaggedObject()), EnumCacheKind::NONE);
177 return;
178 }
179 ASSERT(!receiver_->IsInSharedHeap());
180 ASSERT(!onlyHasSimpleProperties_);
181 JSHandle<JSObject> receiverObj(receiver_);
182 JSHandle<JSHClass> jsHclass(thread_, receiverObj->GetJSHClass());
183 jsHclass->SetEnumCache(thread_, fastKeysArray_.GetTaggedValue());
184 JSObject::SetEnumCacheKind(
185 thread_, TaggedArray::Cast(fastKeysArray_->GetTaggedObject()), EnumCacheKind::PROTOCHAIN);
186 cachedHclass_.Update(jsHclass);
187 JSHClass::EnableProtoChangeMarker(thread_, jsHclass);
188 #endif
189 }
190
GetKeysFast()191 JSHandle<JSTaggedValue> PropertyAccessor::GetKeysFast()
192 {
193 if (!fastKeysArray_->IsUndefined()) {
194 AddKeysEndIfNeeded(JSHandle<TaggedArray>(thread_, fastKeysArray_.GetTaggedValue()));
195 return fastKeysArray_;
196 }
197 if (hasSlowProperties_) {
198 return JSHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
199 }
200 ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
201 uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
202 JSHandle<TaggedArray> keyArray = factory->NewTaggedArray(arraySize);
203 JSHandle<TaggedQueue> shadowQueue = factory->NewTaggedQueue(shadowKeyLength_ + 1);
204 uint32_t keysNum = EnumCache::ENUM_CACHE_HEADER_SIZE;
205 JSMutableHandle<JSTaggedValue> current(thread_, receiver_);
206 while (current->IsHeapObject()) {
207 JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle<JSObject>(current), keyArray, &keysNum, shadowQueue);
208 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
209 JSObject::ClearHasDeleteProperty(current);
210 current.Update(JSObject::GetPrototype(current.GetTaggedValue()));
211 }
212 SetActualKeyLength(keysNum - EnumCache::ENUM_CACHE_HEADER_SIZE);
213 AddKeysEndIfNeeded(keyArray);
214 fastKeysArray_.Update(keyArray.GetTaggedValue());
215 TryInitEnumCacheWithProtoChainInfo();
216 return fastKeysArray_;
217 }
218
GetKeysSlow()219 JSHandle<JSTaggedValue> PropertyAccessor::GetKeysSlow()
220 {
221 std::vector<JSHandle<TaggedArray>> remainings;
222 std::vector<JSHandle<JSTaggedValue>> visited;
223 JSMutableHandle<JSTaggedValue> current(thread_, receiver_);
224 while (current->IsHeapObject()) {
225 PushRemainingKeys(JSHandle<JSObject>(current), remainings);
226 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
227 JSObject::ClearHasDeleteProperty(current);
228 visited.emplace_back(thread_, current.GetTaggedValue());
229 current.Update(JSTaggedValue::GetPrototype(thread_, current));
230 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
231 }
232 MergeRemainings(remainings, visited);
233 return JSHandle<JSTaggedValue>(thread_, slowKeysArray_.GetTaggedValue());
234 }
235
PushRemainingKeys(JSHandle<JSObject> object,std::vector<JSHandle<TaggedArray>> & remainings)236 void PropertyAccessor::PushRemainingKeys(JSHandle<JSObject> object, std::vector<JSHandle<TaggedArray>> &remainings)
237 {
238 JSMutableHandle<JSTaggedValue> value(thread_, JSTaggedValue::Undefined());
239 uint32_t remainingIndex = 0;
240 if (object->IsJSProxy()) {
241 JSHandle<TaggedArray> proxyArr = JSProxy::OwnPropertyKeys(thread_, JSHandle<JSProxy>(object));
242 RETURN_IF_ABRUPT_COMPLETION(thread_);
243 uint32_t length = proxyArr->GetLength();
244 for (uint32_t i = 0; i < length; i++) {
245 value.Update(proxyArr->Get(i));
246 PropertyDescriptor desc(thread_);
247 JSProxy::GetOwnProperty(thread_, JSHandle<JSProxy>(object), value, desc);
248 RETURN_IF_ABRUPT_COMPLETION(thread_);
249 if (!desc.IsEnumerable()) {
250 proxyArr->Set(thread_, i, JSTaggedValue::Hole());
251 } else {
252 remainingIndex++;
253 }
254 }
255 remainings.push_back(proxyArr);
256 AccumulateKeyLength(remainingIndex);
257 } else {
258 JSHandle<TaggedArray> array = JSTaggedValue::GetOwnEnumPropertyKeys(thread_, JSHandle<JSTaggedValue>(object));
259 uint32_t length = array->GetLength();
260 for (uint32_t i = 0; i < length; i++) {
261 value.Update(array->Get(i));
262 if (!value->IsString()) {
263 array->Set(thread_, i, JSTaggedValue::Hole());
264 } else {
265 remainingIndex++;
266 }
267 }
268 remainings.push_back(array);
269 AccumulateKeyLength(remainingIndex);
270 }
271 }
272
MergeRemainings(const std::vector<JSHandle<TaggedArray>> & remainings,const std::vector<JSHandle<JSTaggedValue>> & visited)273 void PropertyAccessor::MergeRemainings(const std::vector<JSHandle<TaggedArray>> &remainings,
274 const std::vector<JSHandle<JSTaggedValue>> &visited)
275 {
276 uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
277 JSHandle<TaggedArray> keyArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
278
279 JSMutableHandle<TaggedArray> remaining(thread_, JSTaggedValue::Undefined());
280 JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
281 JSMutableHandle<JSTaggedValue> objHandle(thread_, JSTaggedValue::Undefined());
282 uint32_t index = EnumCache::ENUM_CACHE_HEADER_SIZE;
283 uint32_t numberOfRemaining = remainings.size();
284 for (uint32_t i = 0; i < numberOfRemaining; i++) {
285 remaining.Update(remainings[i]);
286 uint32_t remainingSize = remaining->GetLength();
287 for (uint32_t j = 0; j < remainingSize; j++) {
288 keyHandle.Update(remaining->Get(thread_, j));
289 if (keyHandle->IsHole()) {
290 continue;
291 }
292 bool has = false;
293 for (uint32_t k = 0; k < i; k++) {
294 objHandle.Update(visited[k]);
295 PropertyDescriptor desc(thread_);
296 has = JSTaggedValue::GetOwnProperty(thread_, objHandle, keyHandle, desc);
297 RETURN_IF_ABRUPT_COMPLETION(thread_);
298 if (has) {
299 break;
300 }
301 }
302 if (!has) {
303 keyArray->Set(thread_, index, keyHandle);
304 index++;
305 }
306 }
307 }
308 SetActualKeyLength(index - EnumCache::ENUM_CACHE_HEADER_SIZE);
309 AddKeysEndIfNeeded(keyArray);
310 slowKeysArray_.Update(keyArray.GetTaggedValue());
311 JSObject::SetEnumCacheKind(thread_, *keyArray, EnumCacheKind::NONE);
312 }
313 } // namespace panda::ecmascript
314