InlineCache.cpp 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeLanguagePch.h"
  6. #if ENABLE_NATIVE_CODEGEN
  7. #include "JITType.h"
  8. #endif
  9. namespace Js
  10. {
  11. void InlineCache::CacheLocal(
  12. Type *const type,
  13. const PropertyId propertyId,
  14. const PropertyIndex propertyIndex,
  15. const bool isInlineSlot,
  16. Type *const typeWithoutProperty,
  17. int requiredAuxSlotCapacity,
  18. ScriptContext *const requestContext)
  19. {
  20. Assert(type);
  21. Assert(propertyId != Constants::NoProperty);
  22. Assert(propertyIndex != Constants::NoSlot);
  23. Assert(requestContext);
  24. Assert(type->GetScriptContext() == requestContext);
  25. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  26. Assert(requiredAuxSlotCapacity >= 0 && requiredAuxSlotCapacity < 0x01 << RequiredAuxSlotCapacityBitCount);
  27. // Store field and load field caches are never shared so we should never have a prototype cache morphing into an add property cache.
  28. // We may, however, have a flags cache (setter) change to add property cache.
  29. Assert(typeWithoutProperty == nullptr || !IsProto());
  30. requestContext->SetHasUsedInlineCache(true);
  31. // Add cache into a store field cache list if required, but not there yet.
  32. if (typeWithoutProperty != nullptr && invalidationListSlotPtr == nullptr)
  33. {
  34. // Note, this can throw due to OOM, so we need to do it before the inline cache is set below.
  35. requestContext->RegisterStoreFieldInlineCache(this, propertyId);
  36. }
  37. if (isInlineSlot)
  38. {
  39. u.local.type = type;
  40. u.local.typeWithoutProperty = typeWithoutProperty;
  41. }
  42. else
  43. {
  44. u.local.type = TypeWithAuxSlotTag(type);
  45. u.local.typeWithoutProperty = typeWithoutProperty ? TypeWithAuxSlotTag(typeWithoutProperty) : nullptr;
  46. }
  47. u.local.isLocal = true;
  48. u.local.slotIndex = propertyIndex;
  49. u.local.requiredAuxSlotCapacity = requiredAuxSlotCapacity;
  50. type->SetHasBeenCached();
  51. if (typeWithoutProperty)
  52. {
  53. typeWithoutProperty->SetHasBeenCached();
  54. }
  55. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  56. #if DBG_DUMP
  57. if (PHASE_VERBOSE_TRACE1(Js::InlineCachePhase))
  58. {
  59. Output::Print(_u("IC::CacheLocal, %s: "), requestContext->GetPropertyName(propertyId)->GetBuffer());
  60. Dump();
  61. Output::Print(_u("\n"));
  62. Output::Flush();
  63. }
  64. #endif
  65. }
  66. void InlineCache::CacheProto(
  67. DynamicObject *const prototypeObjectWithProperty,
  68. const PropertyId propertyId,
  69. const PropertyIndex propertyIndex,
  70. const bool isInlineSlot,
  71. const bool isMissing,
  72. Type *const type,
  73. ScriptContext *const requestContext)
  74. {
  75. Assert(prototypeObjectWithProperty);
  76. Assert(propertyId != Constants::NoProperty);
  77. Assert(propertyIndex != Constants::NoSlot);
  78. Assert(type);
  79. Assert(requestContext);
  80. Assert(prototypeObjectWithProperty->GetScriptContext() == requestContext);
  81. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  82. // This is an interesting quirk. In the browser Chakra's global object cannot be used directly as a prototype, because
  83. // the global object (referenced either as window or as this) always points to the host object. Thus, when we retrieve
  84. // a property from Chakra's global object the prototypeObjectWithProperty != info->GetInstance() and we will never cache
  85. // such property loads (see CacheOperators::CachePropertyRead). However, in jc.exe or jshost.exe the only global object
  86. // is Chakra's global object, and so prototypeObjectWithProperty == info->GetInstance() and we can cache. Hence, the
  87. // assert below is only correct when running in the browser.
  88. // Assert(prototypeObjectWithProperty != prototypeObjectWithProperty->type->GetLibrary()->GetGlobalObject());
  89. // Store field and load field caches are never shared so we should never have an add property cache morphing into a prototype cache.
  90. Assert(!IsLocal() || u.local.typeWithoutProperty == nullptr);
  91. requestContext->SetHasUsedInlineCache(true);
  92. // Add cache into a proto cache list if not there yet.
  93. if (invalidationListSlotPtr == nullptr)
  94. {
  95. // Note, this can throw due to OOM, so we need to do it before the inline cache is set below.
  96. requestContext->RegisterProtoInlineCache(this, propertyId);
  97. }
  98. u.proto.prototypeObject = prototypeObjectWithProperty;
  99. u.proto.isProto = true;
  100. u.proto.isMissing = isMissing;
  101. u.proto.slotIndex = propertyIndex;
  102. if (isInlineSlot)
  103. {
  104. u.proto.type = type;
  105. }
  106. else
  107. {
  108. u.proto.type = TypeWithAuxSlotTag(type);
  109. }
  110. prototypeObjectWithProperty->GetType()->SetHasBeenCached();
  111. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  112. Assert(u.proto.isMissing == (uint16)(u.proto.prototypeObject == requestContext->GetLibrary()->GetMissingPropertyHolder()));
  113. #if DBG_DUMP
  114. if (PHASE_VERBOSE_TRACE1(Js::InlineCachePhase))
  115. {
  116. Output::Print(_u("IC::CacheProto, %s: "), requestContext->GetPropertyName(propertyId)->GetBuffer());
  117. Dump();
  118. Output::Print(_u("\n"));
  119. Output::Flush();
  120. }
  121. #endif
  122. }
  123. // TODO (InlineCacheCleanup): When simplifying inline caches due to not sharing between loads and stores, create two
  124. // separate methods CacheSetter and CacheGetter.
  125. void InlineCache::CacheAccessor(
  126. const bool isGetter,
  127. const PropertyId propertyId,
  128. const PropertyIndex propertyIndex,
  129. const bool isInlineSlot,
  130. Type *const type,
  131. DynamicObject *const object,
  132. const bool isOnProto,
  133. ScriptContext *const requestContext)
  134. {
  135. Assert(propertyId != Constants::NoProperty);
  136. Assert(propertyIndex != Constants::NoSlot);
  137. Assert(type);
  138. Assert(object);
  139. Assert(requestContext);
  140. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  141. // It is possible that prototype is from a different scriptContext than the original instance. We don't want to cache
  142. // in this case.
  143. Assert(type->GetScriptContext() == requestContext);
  144. requestContext->SetHasUsedInlineCache(true);
  145. if (isOnProto && invalidationListSlotPtr == nullptr)
  146. {
  147. // Note, this can throw due to OOM, so we need to do it before the inline cache is set below.
  148. if (!isGetter)
  149. {
  150. // If the setter is on a prototype, this cache must be invalidated whenever proto
  151. // caches are invalidated, so we must register it here. Note that store field inline
  152. // caches are invalidated any time proto caches are invalidated.
  153. requestContext->RegisterStoreFieldInlineCache(this, propertyId);
  154. }
  155. else
  156. {
  157. requestContext->RegisterProtoInlineCache(this, propertyId);
  158. }
  159. }
  160. u.accessor.isAccessor = true;
  161. // TODO (PersistentInlineCaches): Consider removing the flag altogether and just have a bit indicating
  162. // whether the cache itself is a store field cache (isStore?).
  163. u.accessor.flags = isGetter ? InlineCacheGetterFlag : InlineCacheSetterFlag;
  164. u.accessor.isOnProto = isOnProto;
  165. u.accessor.type = isInlineSlot ? type : TypeWithAuxSlotTag(type);
  166. u.accessor.slotIndex = propertyIndex;
  167. u.accessor.object = object;
  168. type->SetHasBeenCached();
  169. DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
  170. #if DBG_DUMP
  171. if (PHASE_VERBOSE_TRACE1(Js::InlineCachePhase))
  172. {
  173. Output::Print(_u("IC::CacheAccessor, %s: "), requestContext->GetPropertyName(propertyId)->GetBuffer());
  174. Dump();
  175. Output::Print(_u("\n"));
  176. Output::Flush();
  177. }
  178. #endif
  179. }
  180. bool InlineCache::PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo) const
  181. {
  182. if (type == u.local.type)
  183. {
  184. operationInfo->cacheType = CacheType_Local;
  185. operationInfo->slotType = SlotType_Inline;
  186. return true;
  187. }
  188. if (TypeWithAuxSlotTag(type) == u.local.type)
  189. {
  190. operationInfo->cacheType = CacheType_Local;
  191. operationInfo->slotType = SlotType_Aux;
  192. return true;
  193. }
  194. if (type == u.proto.type)
  195. {
  196. operationInfo->cacheType = CacheType_Proto;
  197. operationInfo->slotType = SlotType_Inline;
  198. return true;
  199. }
  200. if (TypeWithAuxSlotTag(type) == u.proto.type)
  201. {
  202. operationInfo->cacheType = CacheType_Proto;
  203. operationInfo->slotType = SlotType_Aux;
  204. return true;
  205. }
  206. if (type == u.accessor.type)
  207. {
  208. Assert(u.accessor.flags & InlineCacheGetterFlag);
  209. operationInfo->cacheType = CacheType_Getter;
  210. operationInfo->slotType = SlotType_Inline;
  211. return true;
  212. }
  213. if (TypeWithAuxSlotTag(type) == u.accessor.type)
  214. {
  215. Assert(u.accessor.flags & InlineCacheGetterFlag);
  216. operationInfo->cacheType = CacheType_Getter;
  217. operationInfo->slotType = SlotType_Aux;
  218. return true;
  219. }
  220. return false;
  221. }
  222. bool InlineCache::PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo) const
  223. {
  224. if (oldType == u.local.typeWithoutProperty)
  225. {
  226. operationInfo->cacheType = CacheType_LocalWithoutProperty;
  227. operationInfo->slotType = SlotType_Inline;
  228. return true;
  229. }
  230. if (TypeWithAuxSlotTag(oldType) == u.local.typeWithoutProperty)
  231. {
  232. operationInfo->cacheType = CacheType_LocalWithoutProperty;
  233. operationInfo->slotType = SlotType_Aux;
  234. return true;
  235. }
  236. if (type == u.local.type)
  237. {
  238. operationInfo->cacheType = CacheType_Local;
  239. operationInfo->slotType = SlotType_Inline;
  240. return true;
  241. }
  242. if (TypeWithAuxSlotTag(type) == u.local.type)
  243. {
  244. operationInfo->cacheType = CacheType_Local;
  245. operationInfo->slotType = SlotType_Aux;
  246. return true;
  247. }
  248. if (type == u.accessor.type)
  249. {
  250. if (u.accessor.flags & InlineCacheSetterFlag)
  251. {
  252. operationInfo->cacheType = CacheType_Setter;
  253. operationInfo->slotType = SlotType_Inline;
  254. return true;
  255. }
  256. }
  257. if (TypeWithAuxSlotTag(type) == u.accessor.type)
  258. {
  259. if (u.accessor.flags & InlineCacheSetterFlag)
  260. {
  261. operationInfo->cacheType = CacheType_Setter;
  262. operationInfo->slotType = SlotType_Aux;
  263. return true;
  264. }
  265. }
  266. return false;
  267. }
  268. bool InlineCache::GetGetterSetter(Type *const type, RecyclableObject **callee)
  269. {
  270. Type *const taggedType = TypeWithAuxSlotTag(type);
  271. *callee = nullptr;
  272. if (u.accessor.flags & (InlineCacheGetterFlag | InlineCacheSetterFlag))
  273. {
  274. if (type == u.accessor.type)
  275. {
  276. *callee = RecyclableObject::FromVar(u.accessor.object->GetInlineSlot(u.accessor.slotIndex));
  277. return true;
  278. }
  279. else if (taggedType == u.accessor.type)
  280. {
  281. *callee = RecyclableObject::FromVar(u.accessor.object->GetAuxSlot(u.accessor.slotIndex));
  282. return true;
  283. }
  284. }
  285. return false;
  286. }
  287. bool InlineCache::GetCallApplyTarget(RecyclableObject* obj, RecyclableObject **callee)
  288. {
  289. Type *const type = obj->GetType();
  290. Type *const taggedType = TypeWithAuxSlotTag(type);
  291. *callee = nullptr;
  292. if (IsLocal())
  293. {
  294. if (type == u.local.type)
  295. {
  296. const Var objectAtInlineSlot = DynamicObject::FromVar(obj)->GetInlineSlot(u.local.slotIndex);
  297. if (!Js::TaggedNumber::Is(objectAtInlineSlot))
  298. {
  299. *callee = RecyclableObject::FromVar(objectAtInlineSlot);
  300. return true;
  301. }
  302. }
  303. else if (taggedType == u.local.type)
  304. {
  305. const Var objectAtAuxSlot = DynamicObject::FromVar(obj)->GetAuxSlot(u.local.slotIndex);
  306. if (!Js::TaggedNumber::Is(objectAtAuxSlot))
  307. {
  308. *callee = RecyclableObject::FromVar(DynamicObject::FromVar(obj)->GetAuxSlot(u.local.slotIndex));
  309. return true;
  310. }
  311. }
  312. return false;
  313. }
  314. else if (IsProto())
  315. {
  316. if (type == u.proto.type)
  317. {
  318. const Var objectAtInlineSlot = u.proto.prototypeObject->GetInlineSlot(u.proto.slotIndex);
  319. if (!Js::TaggedNumber::Is(objectAtInlineSlot))
  320. {
  321. *callee = RecyclableObject::FromVar(objectAtInlineSlot);
  322. return true;
  323. }
  324. }
  325. else if (taggedType == u.proto.type)
  326. {
  327. const Var objectAtAuxSlot = u.proto.prototypeObject->GetAuxSlot(u.proto.slotIndex);
  328. if (!Js::TaggedNumber::Is(objectAtAuxSlot))
  329. {
  330. *callee = RecyclableObject::FromVar(objectAtAuxSlot);
  331. return true;
  332. }
  333. }
  334. return false;
  335. }
  336. return false;
  337. }
  338. void InlineCache::Clear()
  339. {
  340. #if DBG
  341. if (!IsAll((byte*)this, sizeof(InlineCache), 0))
  342. #endif
  343. {
  344. memset(this, 0, sizeof(InlineCache));
  345. }
  346. }
  347. void InlineCache::RemoveFromInvalidationListAndClear(ThreadContext* threadContext)
  348. {
  349. if (this->RemoveFromInvalidationList())
  350. {
  351. threadContext->NotifyInlineCacheBatchUnregistered(1);
  352. }
  353. this->Clear();
  354. }
  355. InlineCache *InlineCache::Clone(Js::PropertyId propertyId, ScriptContext* scriptContext)
  356. {
  357. Assert(scriptContext);
  358. InlineCacheAllocator* allocator = scriptContext->GetInlineCacheAllocator();
  359. // Important to zero the allocated cache to be sure CopyTo doesn't see garbage
  360. // when it uses the next pointer.
  361. InlineCache* clone = AllocatorNewZ(InlineCacheAllocator, allocator, InlineCache);
  362. CopyTo(propertyId, scriptContext, clone);
  363. return clone;
  364. }
  365. bool InlineCache::TryGetFixedMethodFromCache(Js::FunctionBody* functionBody, uint cacheId, Js::JavascriptFunction** pFixedMethod)
  366. {
  367. Assert(pFixedMethod);
  368. if (IsEmpty())
  369. {
  370. return false;
  371. }
  372. Js::Type * propertyOwnerType = nullptr;
  373. bool isLocal = IsLocal();
  374. bool isProto = IsProto();
  375. if (isLocal)
  376. {
  377. propertyOwnerType = TypeWithoutAuxSlotTag(this->u.local.type);
  378. }
  379. else if (isProto)
  380. {
  381. // TODO (InlineCacheCleanup): For loads from proto, we could at least grab the value from protoObject's slot
  382. // (given by the cache) and see if its a function. Only then, does it make sense to check with the type handler.
  383. propertyOwnerType = this->u.proto.prototypeObject->GetType();
  384. }
  385. else
  386. {
  387. propertyOwnerType = this->u.accessor.object->GetType();
  388. }
  389. Assert(propertyOwnerType != nullptr);
  390. if (Js::DynamicType::Is(propertyOwnerType->GetTypeId()))
  391. {
  392. Js::DynamicTypeHandler* propertyOwnerTypeHandler = ((Js::DynamicType*)propertyOwnerType)->GetTypeHandler();
  393. Js::PropertyId propertyId = functionBody->GetPropertyIdFromCacheId(cacheId);
  394. Js::PropertyRecord const * const methodPropertyRecord = functionBody->GetScriptContext()->GetPropertyName(propertyId);
  395. Var fixedMethod = nullptr;
  396. bool isUseFixedProperty;
  397. if (isLocal || isProto)
  398. {
  399. isUseFixedProperty = propertyOwnerTypeHandler->TryUseFixedProperty(methodPropertyRecord, &fixedMethod, Js::FixedPropertyKind::FixedMethodProperty, functionBody->GetScriptContext());
  400. }
  401. else
  402. {
  403. isUseFixedProperty = propertyOwnerTypeHandler->TryUseFixedAccessor(methodPropertyRecord, &fixedMethod, Js::FixedPropertyKind::FixedAccessorProperty, this->IsGetterAccessor(), functionBody->GetScriptContext());
  404. }
  405. AssertMsg(fixedMethod == nullptr || Js::JavascriptFunction::Is(fixedMethod), "The fixed value should have been a Method !!!");
  406. *pFixedMethod = reinterpret_cast<JavascriptFunction*>(fixedMethod);
  407. return isUseFixedProperty;
  408. }
  409. return false;
  410. }
  411. void InlineCache::CopyTo(PropertyId propertyId, ScriptContext * scriptContext, InlineCache * const clone)
  412. {
  413. DebugOnly(VerifyRegistrationForInvalidation(this, scriptContext, propertyId));
  414. DebugOnly(VerifyRegistrationForInvalidation(clone, scriptContext, propertyId));
  415. Assert(clone != nullptr);
  416. // Note, the Register methods can throw due to OOM, so we need to do it before the inline cache is copied below.
  417. if (this->invalidationListSlotPtr != nullptr && clone->invalidationListSlotPtr == nullptr)
  418. {
  419. if (this->NeedsToBeRegisteredForProtoInvalidation())
  420. {
  421. scriptContext->RegisterProtoInlineCache(clone, propertyId);
  422. }
  423. else if (this->NeedsToBeRegisteredForStoreFieldInvalidation())
  424. {
  425. scriptContext->RegisterStoreFieldInlineCache(clone, propertyId);
  426. }
  427. }
  428. #if DBG
  429. // inline cache pages might been locked after ZeroAll
  430. if (memcmp(&clone->u, &this->u, sizeof(this->u)) != 0)
  431. #endif
  432. {
  433. clone->u = this->u;
  434. }
  435. DebugOnly(VerifyRegistrationForInvalidation(clone, scriptContext, propertyId));
  436. }
  437. template <bool isAccessor>
  438. bool InlineCache::HasDifferentType(const bool isProto, const Type * type, const Type * typeWithoutProperty) const
  439. {
  440. Assert(!isAccessor && !isProto || !typeWithoutProperty);
  441. if (isAccessor)
  442. {
  443. return !IsEmpty() && u.accessor.type != type && u.accessor.type != TypeWithAuxSlotTag(type);
  444. }
  445. if (isProto)
  446. {
  447. return !IsEmpty() && u.proto.type != type && u.proto.type != TypeWithAuxSlotTag(type);
  448. }
  449. // If the new type matches the cached type, the types without property must also match (unless one of them is null).
  450. Assert((u.local.typeWithoutProperty == nullptr || typeWithoutProperty == nullptr) ||
  451. ((u.local.type != type || u.local.typeWithoutProperty == typeWithoutProperty) &&
  452. (u.local.type != TypeWithAuxSlotTag(type) || u.local.typeWithoutProperty == TypeWithAuxSlotTag(typeWithoutProperty))));
  453. // Don't consider a cache polymorphic, if it differs only by the typeWithoutProperty. We can handle this case with
  454. // the monomorphic cache.
  455. return !IsEmpty() && (u.local.type != type && u.local.type != TypeWithAuxSlotTag(type));
  456. }
  457. // explicit instantiation
  458. template bool InlineCache::HasDifferentType<true>(const bool isProto, const Type * type, const Type * typeWithoutProperty) const;
  459. template bool InlineCache::HasDifferentType<false>(const bool isProto, const Type * type, const Type * typeWithoutProperty) const;
  460. bool InlineCache::NeedsToBeRegisteredForProtoInvalidation() const
  461. {
  462. return (IsProto() || IsGetterAccessorOnProto());
  463. }
  464. bool InlineCache::NeedsToBeRegisteredForStoreFieldInvalidation() const
  465. {
  466. return (IsLocal() && this->u.local.typeWithoutProperty != nullptr) || IsSetterAccessorOnProto();
  467. }
  468. #if DEBUG
  469. bool InlineCache::NeedsToBeRegisteredForInvalidation() const
  470. {
  471. int howManyInvalidationsNeeded =
  472. (int)NeedsToBeRegisteredForProtoInvalidation() +
  473. (int)NeedsToBeRegisteredForStoreFieldInvalidation();
  474. Assert(howManyInvalidationsNeeded <= 1);
  475. return howManyInvalidationsNeeded > 0;
  476. }
  477. void InlineCache::VerifyRegistrationForInvalidation(const InlineCache* cache, ScriptContext* scriptContext, Js::PropertyId propertyId)
  478. {
  479. bool needsProtoInvalidation = cache->NeedsToBeRegisteredForProtoInvalidation();
  480. bool needsStoreFieldInvalidation = cache->NeedsToBeRegisteredForStoreFieldInvalidation();
  481. int howManyInvalidationsNeeded = (int)needsProtoInvalidation + (int)needsStoreFieldInvalidation;
  482. bool hasListSlotPtr = cache->invalidationListSlotPtr != nullptr;
  483. bool isProtoRegistered = hasListSlotPtr ? scriptContext->GetThreadContext()->IsProtoInlineCacheRegistered(cache, propertyId) : false;
  484. bool isStoreFieldRegistered = hasListSlotPtr ? scriptContext->GetThreadContext()->IsStoreFieldInlineCacheRegistered(cache, propertyId) : false;
  485. int howManyRegistrations = (int)isProtoRegistered + (int)isStoreFieldRegistered;
  486. Assert(howManyInvalidationsNeeded <= 1);
  487. Assert((howManyInvalidationsNeeded == 0) || hasListSlotPtr);
  488. Assert(!needsProtoInvalidation || isProtoRegistered);
  489. Assert(!needsStoreFieldInvalidation || isStoreFieldRegistered);
  490. Assert(!hasListSlotPtr || howManyRegistrations > 0);
  491. Assert(!hasListSlotPtr || (*cache->invalidationListSlotPtr) == cache);
  492. }
  493. // Confirm inline cache miss against instance property lookup info.
  494. bool InlineCache::ConfirmCacheMiss(const Type * oldType, const PropertyValueInfo* info) const
  495. {
  496. return u.local.type != oldType
  497. && u.proto.type != oldType
  498. && (u.accessor.type != oldType || info == NULL || u.accessor.flags != info->GetFlags());
  499. }
  500. #endif
  501. #if DBG_DUMP
  502. void InlineCache::Dump()
  503. {
  504. if (this->u.local.isLocal)
  505. {
  506. Output::Print(_u("LOCAL { types: 0x%X -> 0x%X, slot = %d, list slot ptr = 0x%X }"),
  507. this->u.local.typeWithoutProperty,
  508. this->u.local.type,
  509. this->u.local.slotIndex,
  510. this->invalidationListSlotPtr
  511. );
  512. }
  513. else if (this->u.proto.isProto)
  514. {
  515. Output::Print(_u("PROTO { type = 0x%X, prototype = 0x%X, slot = %d, list slot ptr = 0x%X }"),
  516. this->u.proto.type,
  517. this->u.proto.prototypeObject,
  518. this->u.proto.slotIndex,
  519. this->invalidationListSlotPtr
  520. );
  521. }
  522. else if (this->u.accessor.isAccessor)
  523. {
  524. Output::Print(_u("FLAGS { type = 0x%X, object = 0x%X, flag = 0x%X, slot = %d, list slot ptr = 0x%X }"),
  525. this->u.accessor.type,
  526. this->u.accessor.object,
  527. this->u.accessor.slotIndex,
  528. this->u.accessor.flags,
  529. this->invalidationListSlotPtr
  530. );
  531. }
  532. else
  533. {
  534. Assert(this->u.accessor.type == 0);
  535. Assert(this->u.accessor.slotIndex == 0);
  536. Output::Print(_u("uninitialized"));
  537. }
  538. }
  539. #endif
  540. template<bool isAccessor>
  541. bool PolymorphicInlineCache::HasDifferentType(
  542. const bool isProto,
  543. const Type * type,
  544. const Type * typeWithoutProperty) const
  545. {
  546. Assert(!isAccessor && !isProto || !typeWithoutProperty);
  547. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  548. if (inlineCaches[inlineCacheIndex].HasDifferentType<isAccessor>(isProto, type, typeWithoutProperty))
  549. {
  550. return true;
  551. }
  552. if (!isAccessor && !isProto && typeWithoutProperty)
  553. {
  554. inlineCacheIndex = GetInlineCacheIndexForType(typeWithoutProperty);
  555. return inlineCaches[inlineCacheIndex].HasDifferentType<isAccessor>(isProto, type, typeWithoutProperty);
  556. }
  557. return false;
  558. }
  559. // explicit instantiation
  560. template bool PolymorphicInlineCache::HasDifferentType<true>(const bool isProto, const Type * type, const Type * typeWithoutProperty) const;
  561. template bool PolymorphicInlineCache::HasDifferentType<false>(const bool isProto, const Type * type, const Type * typeWithoutProperty) const;
  562. bool PolymorphicInlineCache::HasType_Flags(const Type * type) const
  563. {
  564. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  565. return inlineCaches[inlineCacheIndex].HasType_Flags(type);
  566. }
  567. void PolymorphicInlineCache::UpdateInlineCachesFillInfo(uint index, bool set)
  568. {
  569. Assert(index < 0x20);
  570. if (set)
  571. {
  572. this->inlineCachesFillInfo |= 1 << index;
  573. }
  574. else
  575. {
  576. this->inlineCachesFillInfo &= ~(1 << index);
  577. }
  578. }
  579. bool PolymorphicInlineCache::IsFull()
  580. {
  581. Assert(this->size <= 0x20);
  582. return this->inlineCachesFillInfo == ((1 << (this->size - 1)) << 1) - 1;
  583. }
  584. void PolymorphicInlineCache::CacheLocal(
  585. Type *const type,
  586. const PropertyId propertyId,
  587. const PropertyIndex propertyIndex,
  588. const bool isInlineSlot,
  589. Type *const typeWithoutProperty,
  590. int requiredAuxSlotCapacity,
  591. ScriptContext *const requestContext)
  592. {
  593. // Let's not waste polymorphic cache slots by caching both the type without property and type with property. If the
  594. // cache is used for both adding a property and setting the existing property, then those instances will cause both
  595. // types to be cached. Until then, caching both types proactively here can unnecessarily trash useful cached info
  596. // because the types use different slots, unlike a monomorphic inline cache.
  597. if (!typeWithoutProperty)
  598. {
  599. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  600. #if INTRUSIVE_TESTTRACE_PolymorphicInlineCache
  601. bool collision = !inlineCaches[inlineCacheIndex].IsEmpty();
  602. #endif
  603. if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase))
  604. {
  605. if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1)
  606. {
  607. if (inlineCaches[inlineCacheIndex].IsLocal())
  608. {
  609. CloneInlineCacheToEmptySlotInCollision<true, false, false>(type, inlineCacheIndex);
  610. }
  611. else if (inlineCaches[inlineCacheIndex].IsProto())
  612. {
  613. CloneInlineCacheToEmptySlotInCollision<false, true, false>(type, inlineCacheIndex);
  614. }
  615. else
  616. {
  617. CloneInlineCacheToEmptySlotInCollision<false, false, true>(type, inlineCacheIndex);
  618. }
  619. }
  620. }
  621. inlineCaches[inlineCacheIndex].CacheLocal(
  622. type, propertyId, propertyIndex, isInlineSlot, nullptr, requiredAuxSlotCapacity, requestContext);
  623. UpdateInlineCachesFillInfo(inlineCacheIndex, true /*set*/);
  624. #if DBG_DUMP
  625. if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase))
  626. {
  627. Output::Print(_u("PIC::CacheLocal, %s, %d: "), requestContext->GetPropertyName(propertyId)->GetBuffer(), inlineCacheIndex);
  628. inlineCaches[inlineCacheIndex].Dump();
  629. Output::Print(_u("\n"));
  630. Output::Flush();
  631. }
  632. #endif
  633. PHASE_PRINT_INTRUSIVE_TESTTRACE1(
  634. Js::PolymorphicInlineCachePhase,
  635. _u("TestTrace PIC: CacheLocal, 0x%x, entryIndex = %d, collision = %s, entries = %d\n"), this, inlineCacheIndex, collision ? _u("true") : _u("false"), GetEntryCount());
  636. }
  637. else
  638. {
  639. uint inlineCacheIndex = GetInlineCacheIndexForType(typeWithoutProperty);
  640. #if INTRUSIVE_TESTTRACE_PolymorphicInlineCache
  641. bool collision = !inlineCaches[inlineCacheIndex].IsEmpty();
  642. #endif
  643. inlineCaches[inlineCacheIndex].CacheLocal(
  644. type, propertyId, propertyIndex, isInlineSlot, typeWithoutProperty, requiredAuxSlotCapacity, requestContext);
  645. UpdateInlineCachesFillInfo(inlineCacheIndex, true /*set*/);
  646. #if DBG_DUMP
  647. if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase))
  648. {
  649. Output::Print(_u("PIC::CacheLocal, %s, %d: "), requestContext->GetPropertyName(propertyId)->GetBuffer(), inlineCacheIndex);
  650. inlineCaches[inlineCacheIndex].Dump();
  651. Output::Print(_u("\n"));
  652. Output::Flush();
  653. }
  654. #endif
  655. PHASE_PRINT_INTRUSIVE_TESTTRACE1(
  656. Js::PolymorphicInlineCachePhase,
  657. _u("TestTrace PIC: CacheLocal, 0x%x, entryIndex = %d, collision = %s, entries = %d\n"), this, inlineCacheIndex, collision ? _u("true") : _u("false"), GetEntryCount());
  658. }
  659. }
  660. void PolymorphicInlineCache::CacheProto(
  661. DynamicObject *const prototypeObjectWithProperty,
  662. const PropertyId propertyId,
  663. const PropertyIndex propertyIndex,
  664. const bool isInlineSlot,
  665. const bool isMissing,
  666. Type *const type,
  667. ScriptContext *const requestContext)
  668. {
  669. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  670. #if INTRUSIVE_TESTTRACE_PolymorphicInlineCache
  671. bool collision = !inlineCaches[inlineCacheIndex].IsEmpty();
  672. #endif
  673. if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase))
  674. {
  675. if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1)
  676. {
  677. if (inlineCaches[inlineCacheIndex].IsLocal())
  678. {
  679. CloneInlineCacheToEmptySlotInCollision<true, false, false>(type, inlineCacheIndex);
  680. }
  681. else if (inlineCaches[inlineCacheIndex].IsProto())
  682. {
  683. CloneInlineCacheToEmptySlotInCollision<false, true, false>(type, inlineCacheIndex);
  684. }
  685. else
  686. {
  687. CloneInlineCacheToEmptySlotInCollision<false, false, true>(type, inlineCacheIndex);
  688. }
  689. }
  690. }
  691. inlineCaches[inlineCacheIndex].CacheProto(
  692. prototypeObjectWithProperty, propertyId, propertyIndex, isInlineSlot, isMissing, type, requestContext);
  693. UpdateInlineCachesFillInfo(inlineCacheIndex, true /*set*/);
  694. #if DBG_DUMP
  695. if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase))
  696. {
  697. Output::Print(_u("PIC::CacheProto, %s, %d: "), requestContext->GetPropertyName(propertyId)->GetBuffer(), inlineCacheIndex);
  698. inlineCaches[inlineCacheIndex].Dump();
  699. Output::Print(_u("\n"));
  700. Output::Flush();
  701. }
  702. #endif
  703. PHASE_PRINT_INTRUSIVE_TESTTRACE1(
  704. Js::PolymorphicInlineCachePhase,
  705. _u("TestTrace PIC: CacheProto, 0x%x, entryIndex = %d, collision = %s, entries = %d\n"), this, inlineCacheIndex, collision ? _u("true") : _u("false"), GetEntryCount());
  706. }
  707. void PolymorphicInlineCache::CacheAccessor(
  708. const bool isGetter,
  709. const PropertyId propertyId,
  710. const PropertyIndex propertyIndex,
  711. const bool isInlineSlot,
  712. Type *const type,
  713. DynamicObject *const object,
  714. const bool isOnProto,
  715. ScriptContext *const requestContext)
  716. {
  717. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  718. #if INTRUSIVE_TESTTRACE_PolymorphicInlineCache
  719. bool collision = !inlineCaches[inlineCacheIndex].IsEmpty();
  720. #endif
  721. if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase))
  722. {
  723. if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1)
  724. {
  725. if (inlineCaches[inlineCacheIndex].IsLocal())
  726. {
  727. CloneInlineCacheToEmptySlotInCollision<true, false, false>(type, inlineCacheIndex);
  728. }
  729. else if (inlineCaches[inlineCacheIndex].IsProto())
  730. {
  731. CloneInlineCacheToEmptySlotInCollision<false, true, false>(type, inlineCacheIndex);
  732. }
  733. else
  734. {
  735. CloneInlineCacheToEmptySlotInCollision<false, false, true>(type, inlineCacheIndex);
  736. }
  737. }
  738. }
  739. inlineCaches[inlineCacheIndex].CacheAccessor(isGetter, propertyId, propertyIndex, isInlineSlot, type, object, isOnProto, requestContext);
  740. UpdateInlineCachesFillInfo(inlineCacheIndex, true /*set*/);
  741. #if DBG_DUMP
  742. if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase))
  743. {
  744. Output::Print(_u("PIC::CacheAccessor, %s, %d: "), requestContext->GetPropertyName(propertyId)->GetBuffer(), inlineCacheIndex);
  745. inlineCaches[inlineCacheIndex].Dump();
  746. Output::Print(_u("\n"));
  747. Output::Flush();
  748. }
  749. #endif
  750. PHASE_PRINT_INTRUSIVE_TESTTRACE1(
  751. Js::PolymorphicInlineCachePhase,
  752. _u("TestTrace PIC: CacheAccessor, 0x%x, entryIndex = %d, collision = %s, entries = %d\n"), this, inlineCacheIndex, collision ? _u("true") : _u("false"), GetEntryCount());
  753. }
  754. bool PolymorphicInlineCache::PretendTryGetProperty(
  755. Type *const type,
  756. PropertyCacheOperationInfo * operationInfo)
  757. {
  758. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  759. return inlineCaches[inlineCacheIndex].PretendTryGetProperty(type, operationInfo);
  760. }
  761. bool PolymorphicInlineCache::PretendTrySetProperty(
  762. Type *const type,
  763. Type *const oldType,
  764. PropertyCacheOperationInfo * operationInfo)
  765. {
  766. uint inlineCacheIndex = GetInlineCacheIndexForType(type);
  767. return inlineCaches[inlineCacheIndex].PretendTrySetProperty(type, oldType, operationInfo);
  768. }
  769. void PolymorphicInlineCache::CopyTo(PropertyId propertyId, ScriptContext* scriptContext, PolymorphicInlineCache *const clone)
  770. {
  771. Assert(clone);
  772. clone->ignoreForEquivalentObjTypeSpec = this->ignoreForEquivalentObjTypeSpec;
  773. clone->cloneForJitTimeUse = this->cloneForJitTimeUse;
  774. for (uint i = 0; i < GetSize(); ++i)
  775. {
  776. Type * type = inlineCaches[i].GetType();
  777. if (type)
  778. {
  779. uint inlineCacheIndex = clone->GetInlineCacheIndexForType(type);
  780. // When copying inline caches from one polymorphic cache to another, types are again hashed to get the corresponding indices in the new polymorphic cache.
  781. // This might lead to collision in the new cache. We need to try to resolve that collision.
  782. if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase))
  783. {
  784. if (!clone->inlineCaches[inlineCacheIndex].IsEmpty() && !clone->inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1)
  785. {
  786. if (clone->inlineCaches[inlineCacheIndex].IsLocal())
  787. {
  788. clone->CloneInlineCacheToEmptySlotInCollision<true, false, false>(type, inlineCacheIndex);
  789. }
  790. else if (clone->inlineCaches[inlineCacheIndex].IsProto())
  791. {
  792. clone->CloneInlineCacheToEmptySlotInCollision<false, true, false>(type, inlineCacheIndex);
  793. }
  794. else
  795. {
  796. clone->CloneInlineCacheToEmptySlotInCollision<false, false, true>(type, inlineCacheIndex);
  797. }
  798. }
  799. }
  800. inlineCaches[i].CopyTo(propertyId, scriptContext, &clone->inlineCaches[inlineCacheIndex]);
  801. clone->UpdateInlineCachesFillInfo(inlineCacheIndex, true /*set*/);
  802. }
  803. }
  804. }
  805. #if DBG_DUMP
  806. void PolymorphicInlineCache::Dump()
  807. {
  808. for (uint i = 0; i < size; ++i)
  809. {
  810. if (!inlineCaches[i].IsEmpty())
  811. {
  812. Output::Print(_u(" %d: "), i);
  813. inlineCaches[i].Dump();
  814. Output::Print(_u("\n"));
  815. }
  816. }
  817. }
  818. #endif
  819. FunctionBodyPolymorphicInlineCache * FunctionBodyPolymorphicInlineCache::New(uint16 size, FunctionBody * functionBody)
  820. {
  821. ScriptContext * scriptContext = functionBody->GetScriptContext();
  822. InlineCache * inlineCaches = AllocatorNewArrayZ(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), InlineCache, size);
  823. #ifdef POLY_INLINE_CACHE_SIZE_STATS
  824. scriptContext->GetInlineCacheAllocator()->LogPolyCacheAlloc(size * sizeof(InlineCache));
  825. #endif
  826. FunctionBodyPolymorphicInlineCache * polymorphicInlineCache = RecyclerNewFinalizedLeaf(scriptContext->GetRecycler(), FunctionBodyPolymorphicInlineCache, inlineCaches, size, functionBody);
  827. polymorphicInlineCache->prev = nullptr;
  828. polymorphicInlineCache->next = polymorphicInlineCache->functionBody->GetPolymorphicInlineCachesHead();
  829. if (polymorphicInlineCache->next)
  830. {
  831. polymorphicInlineCache->next->prev = polymorphicInlineCache;
  832. }
  833. polymorphicInlineCache->functionBody->SetPolymorphicInlineCachesHead(polymorphicInlineCache);
  834. return polymorphicInlineCache;
  835. }
  836. void FunctionBodyPolymorphicInlineCache::Finalize(bool isShutdown)
  837. {
  838. if (size == 0)
  839. {
  840. // Already finalized
  841. Assert(!inlineCaches && !prev && !next);
  842. return;
  843. }
  844. if (this->functionBody->GetScriptContext() == nullptr)
  845. {
  846. // If caches were made during ScriptContext initialization, and initialization failed the ScriptContext
  847. // and InlineCacheAllocator will be freed, so we don't need to do anything during finalization
  848. return;
  849. }
  850. uint unregisteredInlineCacheCount = 0;
  851. Assert(inlineCaches && size > 0);
  852. // If we're not shutting down (as in closing the script context), we need to remove our inline caches from
  853. // thread context's invalidation lists, and release memory back to the arena. During script context shutdown,
  854. // we leave everything in place, because the inline cache arena will stay alive until script context is destroyed
  855. // (as in destructor has been called) and thus the invalidation lists are safe to keep references to caches from this
  856. // script context. We will, however, zero all inline caches so that we don't have to process them on subsequent
  857. // collections, which may still happen from other script contexts.
  858. if (isShutdown)
  859. {
  860. #if DBG
  861. for (int i = 0; i < size; i++)
  862. {
  863. inlineCaches[i].Clear();
  864. }
  865. #else
  866. memset(inlineCaches, 0, size * sizeof(InlineCache));
  867. #endif
  868. }
  869. else
  870. {
  871. for (int i = 0; i < size; i++)
  872. {
  873. if (inlineCaches[i].RemoveFromInvalidationList())
  874. {
  875. unregisteredInlineCacheCount++;
  876. }
  877. }
  878. AllocatorDeleteArray(InlineCacheAllocator, this->functionBody->GetScriptContext()->GetInlineCacheAllocator(), size, inlineCaches);
  879. #ifdef POLY_INLINE_CACHE_SIZE_STATS
  880. this->functionBody->GetScriptContext()->GetInlineCacheAllocator()->LogPolyCacheFree(size * sizeof(InlineCache));
  881. #endif
  882. }
  883. // Remove this PolymorphicInlineCache from the list
  884. if (this == this->functionBody->GetPolymorphicInlineCachesHead())
  885. {
  886. Assert(!prev);
  887. if (next)
  888. {
  889. Assert(next->prev == this);
  890. next->prev = nullptr;
  891. }
  892. this->functionBody->SetPolymorphicInlineCachesHead(next);
  893. }
  894. else
  895. {
  896. if (prev)
  897. {
  898. Assert(prev->next == this);
  899. prev->next = next;
  900. }
  901. if (next)
  902. {
  903. Assert(next->prev == this);
  904. next->prev = prev;
  905. }
  906. }
  907. prev = next = nullptr;
  908. inlineCaches = nullptr;
  909. size = 0;
  910. if (unregisteredInlineCacheCount > 0)
  911. {
  912. this->functionBody->GetScriptContext()->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount);
  913. }
  914. }
  915. ScriptContextPolymorphicInlineCache * ScriptContextPolymorphicInlineCache::New(uint16 size, JavascriptLibrary* javascriptLibrary)
  916. {
  917. ScriptContext * scriptContext = javascriptLibrary->GetScriptContext();
  918. InlineCache * inlineCaches = AllocatorNewArrayZ(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), InlineCache, size);
  919. #ifdef POLY_INLINE_CACHE_SIZE_STATS
  920. scriptContext->GetInlineCacheAllocator()->LogPolyCacheAlloc(size * sizeof(InlineCache));
  921. #endif
  922. ScriptContextPolymorphicInlineCache * polymorphicInlineCache = RecyclerNewFinalized(scriptContext->GetRecycler(), ScriptContextPolymorphicInlineCache, inlineCaches, size, javascriptLibrary);
  923. return polymorphicInlineCache;
  924. }
  925. void ScriptContextPolymorphicInlineCache::Finalize(bool isShutdown)
  926. {
  927. if (size == 0)
  928. {
  929. // Already finalized
  930. Assert(!inlineCaches);
  931. return;
  932. }
  933. if (this->javascriptLibrary->scriptContext == nullptr)
  934. {
  935. // If caches were made during ScriptContext initialization, and initialization failed the ScriptContext
  936. // and InlineCacheAllocator will be freed, so we don't need to do anything during finalization
  937. return;
  938. }
  939. uint unregisteredInlineCacheCount = 0;
  940. Assert(inlineCaches && size > 0);
  941. // If we're not shutting down (as in closing the script context), we need to remove our inline caches from
  942. // thread context's invalidation lists, and release memory back to the arena. During script context shutdown,
  943. // we leave everything in place, because the inline cache arena will stay alive until script context is destroyed
  944. // (as in destructor has been called) and thus the invalidation lists are safe to keep references to caches from this
  945. // script context. We will, however, zero all inline caches so that we don't have to process them on subsequent
  946. // collections, which may still happen from other script contexts.
  947. if (isShutdown)
  948. {
  949. #if DBG
  950. for (int i = 0; i < size; i++)
  951. {
  952. inlineCaches[i].Clear();
  953. }
  954. #else
  955. memset(inlineCaches, 0, size * sizeof(InlineCache));
  956. #endif
  957. }
  958. else
  959. {
  960. for (int i = 0; i < size; i++)
  961. {
  962. if (inlineCaches[i].RemoveFromInvalidationList())
  963. {
  964. unregisteredInlineCacheCount++;
  965. }
  966. }
  967. AllocatorDeleteArray(InlineCacheAllocator, this->javascriptLibrary->scriptContext->GetInlineCacheAllocator(), size, inlineCaches);
  968. #ifdef POLY_INLINE_CACHE_SIZE_STATS
  969. this->javascriptLibrary->scriptContext->GetInlineCacheAllocator()->LogPolyCacheFree(size * sizeof(InlineCache));
  970. #endif
  971. }
  972. inlineCaches = nullptr;
  973. size = 0;
  974. if (unregisteredInlineCacheCount > 0)
  975. {
  976. this->javascriptLibrary->scriptContext->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount);
  977. }
  978. }
  979. #ifdef INLINE_CACHE_STATS
  980. void FunctionBodyPolymorphicInlineCache::PrintStats(InlineCacheData *data) const
  981. {
  982. char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  983. wchar funcName[1024];
  984. uint total = data->hits + data->misses;
  985. char16 const *propName = this->functionBody->GetScriptContext()->GetThreadContext()->GetPropertyName(data->propertyId)->GetBuffer();
  986. swprintf_s(funcName, _u("%s (%s)"), this->functionBody->GetExternalDisplayName(), this->functionBody->GetDebugNumberSet(debugStringBuffer));
  987. Output::Print(_u("%s,%s,%s,%d,%d,%f,%d,%f,%d\n"),
  988. funcName,
  989. propName,
  990. data->isGetCache ? _u("get") : _u("set"),
  991. total,
  992. data->misses,
  993. static_cast<float>(data->misses) / total,
  994. data->collisions,
  995. static_cast<float>(data->collisions) / total,
  996. GetSize()
  997. );
  998. }
  999. ScriptContext* FunctionBodyPolymorphicInlineCache::GetScriptContext() const
  1000. {
  1001. return this->functionBody->GetScriptContext();
  1002. }
  1003. void ScriptContextPolymorphicInlineCache::PrintStats(InlineCacheData *data) const
  1004. {
  1005. uint total = data->hits + data->misses;
  1006. Output::Print(_u("ScriptContext,%s,%s,%d,%d,%f,%d,%f,%d\n"),
  1007. data->isGetCache ? _u("get") : _u("set"),
  1008. total,
  1009. data->misses,
  1010. static_cast<float>(data->misses) / total,
  1011. data->collisions,
  1012. static_cast<float>(data->collisions) / total,
  1013. GetSize()
  1014. );
  1015. }
  1016. ScriptContext* ScriptContextPolymorphicInlineCache::GetScriptContext() const
  1017. {
  1018. return this->javascriptLibrary->scriptContext;
  1019. }
  1020. #endif
  1021. #if ENABLE_NATIVE_CODEGEN
  1022. EquivalentTypeSet::EquivalentTypeSet(RecyclerJITTypeHolder * types, uint16 count)
  1023. : types(types), count(count), sortedAndDuplicatesRemoved(false)
  1024. {
  1025. }
  1026. JITTypeHolder EquivalentTypeSet::GetType(uint16 index) const
  1027. {
  1028. Assert(this->types != nullptr && this->count > 0 && index < this->count);
  1029. return this->types[index];
  1030. }
  1031. JITTypeHolder EquivalentTypeSet::GetFirstType() const
  1032. {
  1033. return GetType(0);
  1034. }
  1035. bool EquivalentTypeSet::Contains(const JITTypeHolder type, uint16* pIndex)
  1036. {
  1037. if (!this->GetSortedAndDuplicatesRemoved())
  1038. {
  1039. this->SortAndRemoveDuplicates();
  1040. }
  1041. for (uint16 ti = 0; ti < this->count; ti++)
  1042. {
  1043. if (this->GetType(ti) == type)
  1044. {
  1045. if (pIndex)
  1046. {
  1047. *pIndex = ti;
  1048. }
  1049. return true;
  1050. }
  1051. }
  1052. return false;
  1053. }
  1054. bool EquivalentTypeSet::AreIdentical(EquivalentTypeSet * left, EquivalentTypeSet * right)
  1055. {
  1056. if (!left->GetSortedAndDuplicatesRemoved())
  1057. {
  1058. left->SortAndRemoveDuplicates();
  1059. }
  1060. if (!right->GetSortedAndDuplicatesRemoved())
  1061. {
  1062. right->SortAndRemoveDuplicates();
  1063. }
  1064. Assert(left->GetSortedAndDuplicatesRemoved() && right->GetSortedAndDuplicatesRemoved());
  1065. if (left->count != right->count)
  1066. {
  1067. return false;
  1068. }
  1069. // TODO: OOP JIT, optimize this (previously we had memcmp)
  1070. for (uint i = 0; i < left->count; ++i)
  1071. {
  1072. if (left->types[i] != right->types[i])
  1073. {
  1074. return false;
  1075. }
  1076. }
  1077. return true;
  1078. }
  1079. bool EquivalentTypeSet::IsSubsetOf(EquivalentTypeSet * left, EquivalentTypeSet * right)
  1080. {
  1081. if (!left->GetSortedAndDuplicatesRemoved())
  1082. {
  1083. left->SortAndRemoveDuplicates();
  1084. }
  1085. if (!right->GetSortedAndDuplicatesRemoved())
  1086. {
  1087. right->SortAndRemoveDuplicates();
  1088. }
  1089. if (left->count > right->count)
  1090. {
  1091. return false;
  1092. }
  1093. // Try to find each left type in the right set.
  1094. int j = 0;
  1095. for (int i = 0; i < left->count; i++)
  1096. {
  1097. bool found = false;
  1098. for (; j < right->count; j++)
  1099. {
  1100. if (left->types[i] < right->types[j])
  1101. {
  1102. // Didn't find the left type. Fail.
  1103. return false;
  1104. }
  1105. if (left->types[i] == right->types[j])
  1106. {
  1107. // Found the left type. Continue to the next left/right pair.
  1108. found = true;
  1109. j++;
  1110. break;
  1111. }
  1112. }
  1113. Assert(j <= right->count);
  1114. if (j == right->count && !found)
  1115. {
  1116. // Exhausted the right set without finding the current left type.
  1117. return false;
  1118. }
  1119. }
  1120. return true;
  1121. }
  1122. void EquivalentTypeSet::SortAndRemoveDuplicates()
  1123. {
  1124. uint16 oldCount = this->count;
  1125. uint16 i;
  1126. // sorting
  1127. for (i = 1; i < oldCount; i++)
  1128. {
  1129. uint16 j = i;
  1130. while (j > 0 && (this->types[j - 1] > this->types[j]))
  1131. {
  1132. JITTypeHolder tmp = this->types[j];
  1133. this->types[j] = this->types[j - 1];
  1134. this->types[j - 1] = tmp;
  1135. j--;
  1136. }
  1137. }
  1138. // removing duplicate types from the sorted set
  1139. i = 0;
  1140. for (uint16 j = 1; j < oldCount; j++)
  1141. {
  1142. if (this->types[i] != this->types[j])
  1143. {
  1144. this->types[++i] = this->types[j];
  1145. }
  1146. }
  1147. this->count = ++i;
  1148. for (i; i < oldCount; i++)
  1149. {
  1150. this->types[i] = JITTypeHolder(nullptr);
  1151. }
  1152. this->sortedAndDuplicatesRemoved = true;
  1153. }
  1154. #endif
  1155. ConstructorCache ConstructorCache::DefaultInstance;
  1156. ConstructorCache* ConstructorCache::EnsureValidInstance(ConstructorCache* currentCache, ScriptContext* scriptContext)
  1157. {
  1158. Assert(currentCache != nullptr);
  1159. ConstructorCache* newCache = currentCache;
  1160. // If the old cache has been invalidated, we need to create a new one to avoid incorrectly re-validating
  1161. // caches that may have been hard-coded in the JIT-ed code with different prototype and type. However, if
  1162. // the cache is already polymorphic, it will not be hard-coded, and hence we don't need to allocate a new
  1163. // one - in case the prototype property changes frequently.
  1164. if (ConstructorCache::IsDefault(currentCache) || (currentCache->IsInvalidated() && !currentCache->IsPolymorphic()))
  1165. {
  1166. // Review (jedmiad): I don't think we need to zero the struct, since we initialize each field.
  1167. newCache = RecyclerNew(scriptContext->GetRecycler(), ConstructorCache);
  1168. // TODO: Consider marking the cache as polymorphic only if the prototype and type actually changed. In fact,
  1169. // if they didn't change we could reuse the same cache and simply mark it as valid. Not really true. The cache
  1170. // might have been invalidated due to a property becoming read-only. In that case we can't re-validate an old
  1171. // monomorphic cache. We must allocate a new one.
  1172. newCache->content.isPolymorphic = currentCache->content.isPopulated && currentCache->content.hasPrototypeChanged;
  1173. }
  1174. // If we kept the old invalidated cache, it better be marked as polymorphic.
  1175. Assert(!newCache->IsInvalidated() || newCache->IsPolymorphic());
  1176. // If the cache was polymorphic, we shouldn't have allocated a new one.
  1177. Assert(!currentCache->IsPolymorphic() || newCache == currentCache);
  1178. return newCache;
  1179. }
  1180. void ConstructorCache::InvalidateOnPrototypeChange()
  1181. {
  1182. if (IsDefault(this))
  1183. {
  1184. Assert(this->guard.value == CtorCacheGuardValues::Invalid);
  1185. Assert(!this->content.isPopulated);
  1186. }
  1187. else if (this->guard.value == CtorCacheGuardValues::Special && this->content.skipDefaultNewObject)
  1188. {
  1189. // Do nothing. If we skip the default object, changes to the prototype property don't affect
  1190. // what we'll do during object allocation.
  1191. // Can't assert the following because we set the prototype property during library initialization.
  1192. // AssertMsg(false, "Overriding a prototype on a built-in constructor should be illegal.");
  1193. }
  1194. else
  1195. {
  1196. this->guard.value = CtorCacheGuardValues::Invalid;
  1197. this->content.hasPrototypeChanged = true;
  1198. // Make sure we don't leak the old type.
  1199. Assert(this->content.type == nullptr);
  1200. this->content.pendingType = nullptr;
  1201. Assert(this->content.pendingType == nullptr);
  1202. Assert(IsInvalidated());
  1203. }
  1204. Assert(IsConsistent());
  1205. }
  1206. #if DBG_DUMP
  1207. void ConstructorCache::Dump() const
  1208. {
  1209. Output::Print(_u("guard value or type = 0x%p, script context = 0x%p, pending type = 0x%p, slots = %d, inline slots = %d, populated = %d, polymorphic = %d, update cache = %d, update type = %d, skip default = %d, no return = %d"),
  1210. this->GetRawGuardValue(), this->GetScriptContext(), this->GetPendingType(), this->GetSlotCount(), this->GetInlineSlotCount(),
  1211. this->IsPopulated(), this->IsPolymorphic(), this->GetUpdateCacheAfterCtor(), this->GetTypeUpdatePending(),
  1212. this->GetSkipDefaultNewObject(), this->GetCtorHasNoExplicitReturnValue());
  1213. }
  1214. #endif
  1215. void IsInstInlineCache::Set(Type * instanceType, JavascriptFunction * function, JavascriptBoolean * result)
  1216. {
  1217. this->type = instanceType;
  1218. this->function = function;
  1219. this->result = result;
  1220. }
  1221. void IsInstInlineCache::Clear()
  1222. {
  1223. #if DBG
  1224. if (!IsAll((byte*)this, sizeof(IsInstInlineCache), 0))
  1225. #endif
  1226. {
  1227. memset(this, 0, sizeof(IsInstInlineCache));
  1228. }
  1229. }
  1230. void IsInstInlineCache::Unregister(ScriptContext * scriptContext)
  1231. {
  1232. scriptContext->GetThreadContext()->UnregisterIsInstInlineCache(this, this->function);
  1233. }
  1234. bool IsInstInlineCache::TryGetResult(Var instance, JavascriptFunction * function, JavascriptBoolean ** result)
  1235. {
  1236. // In order to get the result from the cache we must have a function instance.
  1237. Assert(function != NULL);
  1238. if (this->function == function &&
  1239. this->type == RecyclableObject::FromVar(instance)->GetType())
  1240. {
  1241. if (result != nullptr)
  1242. {
  1243. (*result = this->result);
  1244. }
  1245. return true;
  1246. }
  1247. else
  1248. {
  1249. return false;
  1250. }
  1251. }
  1252. void IsInstInlineCache::Cache(Type * instanceType, JavascriptFunction * function, JavascriptBoolean * result, ScriptContext * scriptContext)
  1253. {
  1254. // In order to populate the cache we must have a function instance.
  1255. Assert(function != nullptr);
  1256. // We assume the following invariant: (cache->function != nullptr) => script context is registered as having some populated instance-of inline caches and
  1257. // this cache is registered with thread context for invalidation.
  1258. if (this->function == function)
  1259. {
  1260. Assert(scriptContext->IsIsInstInlineCacheRegistered(this, function));
  1261. this->Set(instanceType, function, result);
  1262. }
  1263. else
  1264. {
  1265. if (this->function != nullptr)
  1266. {
  1267. Unregister(scriptContext);
  1268. Clear();
  1269. }
  1270. // If the cache's function is not null, the cache must have been registered already. No need to register again.
  1271. // In fact, ThreadContext::RegisterIsInstInlineCache, would assert if we tried to re-register the same cache (to enforce the invariant above).
  1272. // Review (jedmiad): What happens if we run out of memory inside RegisterIsInstInlieCache?
  1273. scriptContext->RegisterIsInstInlineCache(this, function);
  1274. this->Set(instanceType, function, result);
  1275. }
  1276. }
  1277. /* static */
  1278. uint32 IsInstInlineCache::OffsetOfFunction()
  1279. {
  1280. return offsetof(IsInstInlineCache, function);
  1281. }
  1282. /* static */
  1283. uint32 IsInstInlineCache::OffsetOfType()
  1284. {
  1285. return offsetof(IsInstInlineCache, type);
  1286. }
  1287. /* static */
  1288. uint32 IsInstInlineCache::OffsetOfResult()
  1289. {
  1290. return offsetof(IsInstInlineCache, result);
  1291. }
  1292. }