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 20namespace panda::ecmascript { 21PropertyAccessor::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 37void 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(); 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 74void 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(); 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 114void PropertyAccessor::InitSimplePropertiesEnumCache() 115{ 116 ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); 117 JSHandle<JSObject> receiverObj(receiver_); 118 ASSERT(receiverObj->GetNumberOfElements() == 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 139inline void PropertyAccessor::AccumulateKeyLength(uint32_t length) 140{ 141 keyLength_ += length; 142} 143 144inline void PropertyAccessor::AccumulateShadowKeyLength(uint32_t length) 145{ 146 shadowKeyLength_ += length; 147} 148 149JSHandle<JSTaggedValue> PropertyAccessor::GetCachedHclass() 150{ 151 return cachedHclass_; 152} 153 154uint32_t PropertyAccessor::GetActualKeyLength() const 155{ 156 return acutalKeyLength_; 157} 158 159inline void PropertyAccessor::SetActualKeyLength(uint32_t length) 160{ 161 acutalKeyLength_ = length; 162} 163 164void 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 172void 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 191JSHandle<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 219JSHandle<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 236void 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 273void 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