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