this repo has no description
at trunk 597 lines 24 kB view raw
1/* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2// Inline caches. 3#pragma once 4 5#include "bytecode.h" 6#include "handles.h" 7#include "interpreter.h" 8#include "objects.h" 9 10namespace py { 11 12// Looks for a cache entry for an attribute with a `layout_id` key in the 13// polymorphic cache at `cache`. 14// Returns the cached value. Returns `ErrorNotFound` if none was found. 15RawObject icLookupPolymorphic(RawMutableTuple caches, word cache, 16 LayoutId layout_id, bool* is_found); 17 18// Returns the cached value in the monomorphic cache at `cache`. 19// Returns `ErrorNotFound` if none was found, and set *is_found to false. 20RawObject icLookupMonomorphic(RawMutableTuple caches, word cache, 21 LayoutId layout_id, bool* is_found); 22 23// Returns the current state of the cache at caches[cache]. 24ICState icCurrentState(RawTuple caches, word cache); 25 26// Looks for a cache entry with `left_layout_id` and `right_layout_id` as key. 27// Returns the cached value comprising of an object reference and flags. Returns 28// `ErrorNotFound` if none was found. 29RawObject icLookupBinOpPolymorphic(RawMutableTuple caches, word cache, 30 LayoutId left_layout_id, 31 LayoutId right_layout_id, 32 BinaryOpFlags* flags_out); 33 34// Same as icLookupBinaryOp, but looks at only one entry pointed by cache. 35RawObject icLookupBinOpMonomorphic(RawMutableTuple caches, word cache, 36 LayoutId left_layout_id, 37 LayoutId right_layout_id, 38 BinaryOpFlags* flags_out); 39 40// Looks for a cache entry for a global variable. 41// Returns a ValueCell in case of cache hit. 42// Returns the NoneType object otherwise. 43RawObject icLookupGlobalVar(RawMutableTuple caches, word index); 44 45// Internal only: remove deallocated nodes from cell.dependencyLink. 46void icRemoveDeadWeakLinks(RawValueCell cell); 47 48RawSmallInt encodeBinaryOpKey(LayoutId left_layout_id, LayoutId right_layout_id, 49 BinaryOpFlags flags); 50 51// Sets a cache entry for an attribute to the given `layout_id` as key and 52// `value` as value. 53ICState icUpdateAttr(Thread* thread, const MutableTuple& caches, word cache, 54 LayoutId layout_id, const Object& value, 55 const Object& name, const Function& dependent); 56 57void icUpdateDunderClass(Thread* thread, LayoutId layout_id, const Object& name, 58 const Object& dependent); 59 60bool icIsCacheEmpty(const MutableTuple& caches, word cache); 61 62void icUpdateAttrModule(Thread* thread, const MutableTuple& caches, word cache, 63 const Object& receiver, const ValueCell& value_cell, 64 const Function& dependent); 65 66void icUpdateMethodModule(Thread* thread, const MutableTuple& caches, 67 word cache, const Object& receiver, 68 const ValueCell& value_cell, 69 const Function& dependent); 70 71void icUpdateAttrType(Thread* thread, const MutableTuple& caches, word cache, 72 const Object& receiver, const Object& selector, 73 const Object& value, const Function& dependent); 74 75void icUpdateCallFunctionTypeNew(Thread* thread, const MutableTuple& caches, 76 word cache, const Object& receiver, 77 const Object& constructor, 78 const Function& dependent); 79 80// Insert dependent into dependentLink of the given value_cell. Returns true if 81// depdent didn't exist in dependencyLink, and false otherwise. 82bool icInsertDependentToValueCellDependencyLink(Thread* thread, 83 const Object& dependent, 84 const ValueCell& value_cell); 85 86// Insert dependencies for `a binary_op b` where layout_id(a) == left_layout_id, 87// layout_id(b) == right_layout_id. 88void icInsertBinaryOpDependencies(Thread* thread, const Function& dependent, 89 LayoutId left_layout_id, 90 LayoutId right_layout_id, 91 Interpreter::BinaryOp op); 92 93// Insert dependencies for `a compare_op b` where layout_id(a) == left_layout_id 94// ,layout_id(b) == right_layout_id. 95void icInsertCompareOpDependencies(Thread* thread, const Function& dependent, 96 LayoutId left_layout_id, 97 LayoutId right_layout_id, CompareOp op); 98 99// Insert dependencies for `a inplace_op b` where layout_id(a) == left_layout_id 100// ,layout_id(b) == right_layout_id, and op is an inplace operation (e.g., +=). 101void icInsertInplaceOpDependencies(Thread* thread, const Function& dependent, 102 LayoutId left_layout_id, 103 LayoutId right_layout_id, 104 Interpreter::BinaryOp op); 105 106class IcIterator; 107 108enum class AttributeKind { kDataDescriptor, kNotADataDescriptor }; 109 110// Try evicting the attribute cache entry pointed-to by `it` and its 111// dependencies to dependent. 112void icEvictAttr(Thread* thread, const IcIterator& it, const Type& updated_type, 113 const Object& updated_attr, AttributeKind attribute_kind, 114 const Function& dependent); 115 116// Delete dependent from the MRO of cached_layout_id up to the type defining 117// `attr`. 118void icDeleteDependentToDefiningType(Thread* thread, const Function& dependent, 119 LayoutId cached_layout_id, 120 const Object& attr); 121 122// icEvictBinaryOp tries evicting the binary op cache pointed-to by `it` and 123// deletes the evicted cache' dependencies. 124// 125// - Invalidation condition 126// A binop cache for a op b gets invalidated when either type(A).op gets updated 127// or type(B).rop gets updated. For example, an update to A.__ge__ invalidates 128// all caches created for a >= other or other <= a where type(a) = A. 129// 130// - Deleting dependencies 131// After a binop cache is evicted in function f, we need to delete f's 132// dependencies on type attributes referenced by that cache. 133// 134// When a binop cache gets invalidated due to either one of the operand's type 135// update, the dependencies between f and the directly updated operand't type 136// are deleted first. 137// 138// We also delete dependencies for f created from another operand's type if that 139// operand type is not used in f. 140// 141// The following code snippet shows a concrete example of such dependency 142// deletion. 143// 144// 1 def cache_binop(a, b): 145// 2 if b <= 5: 146// 3 pass 147// 4 return a >= b 148// 5 149// 6 cache_binop(A(), B()) 150// 151// After executing the code above, A.__ge__'s dependency list will contain 152// cache_binop and B.__le__ will do so too. 153// 154// When A.__ge__ = something, that created cache is evicted, and we delete the 155// dependency from A.__ge__ to cache_binop directly. Since the cache is evicted, 156// cache_binop may not depend on B.__le__ anymore. To confirm if we can delete 157// the dependency from B.__le__ to cache_binop, we scan all caches of 158// cache_binop to make sure if any of caches in cache_binop references B.__le__, 159// and only if not, we delete the dependency. 160// 161// In the example, since cache_binop still caches B.__le__ at line 2 we cannot 162// delete this dependency. If line 2 didn't exist, we could delete it. 163void icEvictBinaryOp(Thread* thread, const IcIterator& it, 164 const Type& updated_type, const Object& updated_attr, 165 const Function& dependent); 166 167// Similar to icEvictBinopCache, but handle updates to dunder functions for 168// inplace operations (e.g., iadd, imul, and etc.). 169// TODO(T54575269): Pass SymbolId for updated_attr. 170void icEvictInplaceOp(Thread* thread, const IcIterator& it, 171 const Type& updated_type, const Object& updated_attr, 172 const Function& dependent); 173 174// Delete dependent in ValueCell's dependencyLink. 175void icDeleteDependentInValueCell(Thread* thread, const ValueCell& value_cell, 176 const Object& dependent); 177 178// Delete dependencies from cached_type to new_defining_type to dependent. 179void icDeleteDependentFromInheritingTypes(Thread* thread, 180 LayoutId cached_layout_id, 181 const Object& attr_name, 182 const Type& new_defining_type, 183 const Object& dependent); 184 185// Returns the highest supertype of `cached_type` where all types between 186// `cached_type` and that supertype in `cached_type`'s MRO are not cached in 187// dependent. In case such a supertype is not found, returns ErrorNotFound. If a 188// type is returned, it's safe to delete `dependent` from all types between 189// `cached_type` and the returned type. 190RawObject icHighestSuperTypeNotInMroOfOtherCachedTypes( 191 Thread* thread, LayoutId cached_layout_id, const Object& attr_name, 192 const Function& dependent); 193 194// Returns true if a cached attribute from type cached_type is affected by 195// an update to type[attribute_name] during MRO lookups. 196// with the given mro. assuming that type[attribute_name] exists. 197// 198// Consider the following example: 199// 200// class A: 201// def foo(self): return 1 202// 203// class B(A): 204// pass 205// 206// class C(B): 207// def foo(self): return 3 208// 209// When B.foo is cached, an update to A.foo affects the cache, but not the one 210// to C.foo. 211bool icIsCachedAttributeAffectedByUpdatedType(Thread* thread, 212 LayoutId cached_layout_id, 213 const Object& attribute_name, 214 const Type& updated_type); 215 216// Returns true if updating type.attribute_name will affect any caches in 217// function in any form. For example, updating A.foo will affect any opcode 218// that caches A's subclasses' foo attributes. 219bool icIsAttrCachedInDependent(Thread* thread, const Type& type, 220 const Object& attr_name, 221 const Function& dependent); 222 223// Evict caches for attribute_name to be shadowed by an update to 224// type[attribute_name] in dependent's cache entries, and delete obsolete 225// dependencies between dependent and other type attributes in caches' MRO. 226void icEvictCache(Thread* thread, const Function& dependent, const Type& type, 227 const Object& attr_name, AttributeKind attribute_kind); 228 229// Invalidate caches to be shadowed by a type attribute update made to 230// type[attribute_name]. data_descriptor is set to true when the newly assigned 231// value to the attribute is a data descriptor. This function is expected to be 232// called after the type attribute update is already made. 233// 234// Refer to https://fb.quip.com/q568ASVbNIad for the details of this process. 235void icInvalidateAttr(Thread* thread, const Type& type, const Object& attr_name, 236 const ValueCell& value_cell); 237 238// Sets a cache entry to a `left_layout_id` and `right_layout_id` key with 239// the given `value` and `flags` as value. 240ICState icUpdateBinOp(Thread* thread, const MutableTuple& caches, word cache, 241 LayoutId left_layout_id, LayoutId right_layout_id, 242 const Object& value, BinaryOpFlags flags); 243 244// Sets a cache entry for a global variable. 245void icUpdateGlobalVar(Thread* thread, const Function& function, word index, 246 const ValueCell& value_cell); 247 248// Invalidate all of the caches entries with this global variable. 249void icInvalidateGlobalVar(Thread* thread, const ValueCell& value_cell); 250 251// Cache layout: 252// The caches for the caching opcodes of a function are joined together in a 253// tuple object. 254// 255// +-Global Variable Cache Section ------------------------------------------- 256// | 0: global variable cache 0 (for global var with name == code.names.at(0) 257// | ... 258// | k - 1: global variable cache k - 1 where k == code.names.length() 259// +-Method Cache Section ----------------------------------------------------- 260// | k: cache 0 (used by the first opcode using an inline cache) 261// | - 0: layout_id: layout id to match as SmallInt 262// | - 1: target: cached value 263// | k: cache 1 (used by the second opcode) 264// | - 0: Unbound 265// | - 1: pointer to a data sturcutre for the polymorphic cache 266// | ... 267// | k + n * kIcPointersPerEntry: cache n 268// | 269// +-------------------------------------------------------------------------- 270const int kIcPointersPerEntry = 2; 271 272const int kIcEntriesPerPolyCache = 4; 273const int kIcPointersPerPolyCache = 274 kIcEntriesPerPolyCache * kIcPointersPerEntry; 275 276const int kIcEntryKeyOffset = 0; 277const int kIcEntryValueOffset = 1; 278 279// TODO(T54277418): Use SymbolId for binop method names. 280class IcIterator { 281 public: 282 IcIterator(HandleScope* scope, Runtime* runtime, RawFunction function) 283 : runtime_(runtime), 284 bytecode_(scope, function.rewrittenBytecode()), 285 caches_(scope, function.caches()), 286 function_(scope, function), 287 names_(scope, Code::cast(function.code()).names()), 288 codeunit_index_(0), 289 cache_index_(0), 290 end_cache_index_(0), 291 polymorphic_cache_index_(-1) { 292 next(); 293 } 294 295 bool hasNext() { return cache_index_ >= 0; } 296 297 void next() { 298 if (polymorphic_cache_index_ >= 0) { 299 // We're currently iterating through a polymorphic cache. 300 polymorphic_cache_index_ = findNextFilledPolymorphicCacheIndex( 301 caches_, cache_index_, 302 polymorphic_cache_index_ + kIcPointersPerEntry); 303 if (polymorphic_cache_index_ >= 0) return; 304 } 305 306 cache_index_ = findNextFilledCacheIndex( 307 caches_, cache_index_ + kIcPointersPerEntry, end_cache_index_); 308 if (cache_index_ >= 0) { 309 return; 310 } 311 312 // Find the next caching opcode. 313 word num_opcodes = rewrittenBytecodeLength(bytecode_); 314 while (codeunit_index_ < num_opcodes) { 315 BytecodeOp op = nextBytecodeOp(bytecode_, &codeunit_index_); 316 if (!isByteCodeWithCache(op.bc)) continue; 317 bytecode_op_ = op; 318 cache_index_ = bytecode_op_.cache * kIcPointersPerEntry; 319 end_cache_index_ = cache_index_ + kIcPointersPerEntry; 320 cache_index_ = 321 findNextFilledCacheIndex(caches_, cache_index_, end_cache_index_); 322 if (cache_index_ >= 0) { 323 if (caches_.at(cache_index_ + kIcEntryKeyOffset).isUnbound()) { 324 // We found a polymorphic_cache 325 polymorphic_cache_index_ = 326 findNextFilledPolymorphicCacheIndex(caches_, cache_index_, 0); 327 if (polymorphic_cache_index_ < 0) continue; 328 } 329 return; 330 } 331 } 332 } 333 334 bool isAttrCache() const { 335 switch (bytecode_op_.bc) { 336 case BINARY_SUBSCR_ANAMORPHIC: 337 case BINARY_SUBSCR_MONOMORPHIC: 338 case BINARY_SUBSCR_POLYMORPHIC: 339 case CALL_FUNCTION_TYPE_INIT: 340 case CALL_FUNCTION_TYPE_NEW: 341 case FOR_ITER_MONOMORPHIC: 342 case FOR_ITER_POLYMORPHIC: 343 case FOR_ITER_ANAMORPHIC: 344 case LOAD_ATTR_INSTANCE: 345 case LOAD_ATTR_INSTANCE_PROPERTY: 346 case LOAD_ATTR_INSTANCE_SLOT_DESCR: 347 case LOAD_ATTR_INSTANCE_TYPE: 348 case LOAD_ATTR_INSTANCE_TYPE_BOUND_METHOD: 349 case LOAD_ATTR_INSTANCE_TYPE_DESCR: 350 case LOAD_ATTR_TYPE: 351 case LOAD_ATTR_ANAMORPHIC: 352 case LOAD_METHOD_ANAMORPHIC: 353 case LOAD_METHOD_INSTANCE_FUNCTION: 354 case LOAD_METHOD_POLYMORPHIC: 355 case LOAD_TYPE: 356 case STORE_ATTR_INSTANCE: 357 case STORE_ATTR_INSTANCE_OVERFLOW: 358 case STORE_ATTR_INSTANCE_OVERFLOW_UPDATE: 359 case STORE_ATTR_INSTANCE_UPDATE: 360 case STORE_ATTR_POLYMORPHIC: 361 case STORE_ATTR_ANAMORPHIC: 362 case STORE_SUBSCR_ANAMORPHIC: 363 return true; 364 default: 365 return false; 366 } 367 } 368 369 bool isModuleAttrCache() const { 370 return bytecode_op_.bc == LOAD_ATTR_MODULE || 371 bytecode_op_.bc == LOAD_METHOD_MODULE; 372 } 373 374 bool isBinaryOpCache() const { 375 switch (bytecode_op_.bc) { 376 case BINARY_OP_MONOMORPHIC: 377 case BINARY_OP_POLYMORPHIC: 378 case BINARY_OP_ANAMORPHIC: 379 case COMPARE_OP_MONOMORPHIC: 380 case COMPARE_OP_POLYMORPHIC: 381 case COMPARE_OP_ANAMORPHIC: 382 return true; 383 default: 384 return false; 385 } 386 } 387 388 bool isInplaceOpCache() const { 389 switch (bytecode_op_.bc) { 390 case INPLACE_OP_MONOMORPHIC: 391 case INPLACE_OP_POLYMORPHIC: 392 case INPLACE_OP_ANAMORPHIC: 393 return true; 394 default: 395 return false; 396 } 397 } 398 399 LayoutId layoutId() const { 400 DCHECK(isAttrCache(), "should be only called for attribute caches"); 401 return static_cast<LayoutId>(SmallInt::cast(key()).value()); 402 } 403 404 bool isAttrNameEqualTo(const Object& attr_name) const; 405 406 bool isInstanceAttr() const { 407 DCHECK(isAttrCache(), "should be only called for attribute caches"); 408 return value().isSmallInt(); 409 } 410 411 LayoutId leftLayoutId() const { 412 DCHECK(isBinaryOpCache() || isInplaceOpCache(), 413 "should be only called for binop or inplace-binop caches"); 414 word cache_key_value = SmallInt::cast(key()).value() >> 8; 415 return static_cast<LayoutId>(cache_key_value >> Header::kLayoutIdBits); 416 } 417 418 LayoutId rightLayoutId() const { 419 DCHECK(isBinaryOpCache() || isInplaceOpCache(), 420 "should be only called for binop or inplace-binop caches"); 421 word cache_key_value = SmallInt::cast(key()).value() >> 8; 422 return static_cast<LayoutId>(cache_key_value & 423 ((1 << Header::kLayoutIdBits) - 1)); 424 } 425 426 RawObject leftMethodName() const; 427 428 RawObject rightMethodName() const; 429 430 RawObject inplaceMethodName() const; 431 432 void evict() const { 433 if (polymorphic_cache_index_ < 0) { 434 caches_.atPut(cache_index_ + kIcEntryKeyOffset, NoneType::object()); 435 caches_.atPut(cache_index_ + kIcEntryValueOffset, NoneType::object()); 436 return; 437 } 438 RawMutableTuple polymorphic_cache = 439 MutableTuple::cast(caches_.at(cache_index_ + kIcEntryValueOffset)); 440 polymorphic_cache.atPut(polymorphic_cache_index_ + kIcEntryKeyOffset, 441 NoneType::object()); 442 polymorphic_cache.atPut(polymorphic_cache_index_ + kIcEntryValueOffset, 443 NoneType::object()); 444 } 445 446 private: 447 static word findNextFilledCacheIndex(const MutableTuple& caches, 448 word cache_index, word end_cache_index) { 449 for (; cache_index < end_cache_index; cache_index += kIcPointersPerEntry) { 450 if (!caches.at(cache_index + kIcEntryKeyOffset).isNoneType()) { 451 return cache_index; 452 } 453 } 454 return -1; 455 } 456 457 static word findNextFilledPolymorphicCacheIndex( 458 const MutableTuple& caches, word cache_index, 459 word polymorphic_cache_index) { 460 DCHECK(caches.at(cache_index + kIcEntryKeyOffset).isUnbound(), 461 "should only be called with index of polymorphic cache"); 462 463 RawMutableTuple polymorphic_cache = 464 MutableTuple::cast(caches.at(cache_index + kIcEntryValueOffset)); 465 for (; polymorphic_cache_index < kIcPointersPerPolyCache; 466 polymorphic_cache_index += kIcPointersPerEntry) { 467 if (!polymorphic_cache.at(polymorphic_cache_index + kIcEntryKeyOffset) 468 .isNoneType()) { 469 return polymorphic_cache_index; 470 } 471 } 472 return -1; 473 } 474 475 RawObject key() const { 476 if (polymorphic_cache_index_ < 0) { 477 return caches_.at(cache_index_ + kIcEntryKeyOffset); 478 } 479 RawMutableTuple polymorphic_cache = 480 MutableTuple::cast(caches_.at(cache_index_ + kIcEntryValueOffset)); 481 return polymorphic_cache.at(polymorphic_cache_index_ + kIcEntryKeyOffset); 482 } 483 484 RawObject value() const { 485 if (polymorphic_cache_index_ < 0) { 486 return caches_.at(cache_index_ + kIcEntryValueOffset); 487 } 488 RawMutableTuple polymorphic_cache = 489 MutableTuple::cast(caches_.at(cache_index_ + kIcEntryValueOffset)); 490 return polymorphic_cache.at(polymorphic_cache_index_ + kIcEntryValueOffset); 491 } 492 493 Runtime* runtime_; 494 MutableBytes bytecode_; 495 MutableTuple caches_; 496 Function function_; 497 Tuple names_; 498 499 word codeunit_index_; 500 BytecodeOp bytecode_op_; 501 word cache_index_; 502 word end_cache_index_; 503 word polymorphic_cache_index_; 504 505 DISALLOW_IMPLICIT_CONSTRUCTORS(IcIterator); 506 DISALLOW_HEAP_ALLOCATION(); 507}; 508 509inline RawObject icLookupPolymorphic(RawMutableTuple caches, word cache, 510 LayoutId layout_id, bool* is_found) { 511 word index = cache * kIcPointersPerEntry; 512 DCHECK(caches.at(index + kIcEntryKeyOffset).isUnbound(), 513 "cache.at(index) is expected to be polymorphic"); 514 RawSmallInt key = SmallInt::fromWord(static_cast<word>(layout_id)); 515 caches = MutableTuple::cast(caches.at(index + kIcEntryValueOffset)); 516 for (word j = 0; j < kIcPointersPerPolyCache; j += kIcPointersPerEntry) { 517 if (caches.at(j + kIcEntryKeyOffset) == key) { 518 *is_found = true; 519 return caches.at(j + kIcEntryValueOffset); 520 } 521 } 522 *is_found = false; 523 return Error::notFound(); 524} 525 526inline RawObject icLookupMonomorphic(RawMutableTuple caches, word cache, 527 LayoutId layout_id, bool* is_found) { 528 word index = cache * kIcPointersPerEntry; 529 DCHECK(!caches.at(index + kIcEntryKeyOffset).isUnbound(), 530 "cache.at(index) is expected to be monomorphic"); 531 RawSmallInt key = SmallInt::fromWord(static_cast<word>(layout_id)); 532 if (caches.at(index + kIcEntryKeyOffset) == key) { 533 *is_found = true; 534 return caches.at(index + kIcEntryValueOffset); 535 } 536 *is_found = false; 537 return Error::notFound(); 538} 539 540inline RawObject icLookupBinOpPolymorphic(RawMutableTuple caches, word cache, 541 LayoutId left_layout_id, 542 LayoutId right_layout_id, 543 BinaryOpFlags* flags_out) { 544 static_assert(Header::kLayoutIdBits * 2 + kBitsPerByte <= SmallInt::kBits, 545 "Two layout ids and flags overflow a SmallInt"); 546 word index = cache * kIcPointersPerEntry; 547 DCHECK(caches.at(index + kIcEntryKeyOffset).isUnbound(), 548 "cache.at(index) is expected to be polymorphic"); 549 word key_high_bits = static_cast<word>(left_layout_id) 550 << Header::kLayoutIdBits | 551 static_cast<word>(right_layout_id); 552 caches = MutableTuple::cast(caches.at(index + kIcEntryValueOffset)); 553 for (word j = 0; j < kIcPointersPerPolyCache; j += kIcPointersPerEntry) { 554 RawObject entry_key = caches.at(j + kIcEntryKeyOffset); 555 // Stop the search if we found an empty entry. 556 if (entry_key.isNoneType()) { 557 break; 558 } 559 word entry_key_value = SmallInt::cast(entry_key).value(); 560 if (entry_key_value >> kBitsPerByte == key_high_bits) { 561 *flags_out = static_cast<BinaryOpFlags>(entry_key_value & 0xff); 562 return caches.at(j + kIcEntryValueOffset); 563 } 564 } 565 return Error::notFound(); 566} 567 568inline RawObject icLookupBinOpMonomorphic(RawMutableTuple caches, word cache, 569 LayoutId left_layout_id, 570 LayoutId right_layout_id, 571 BinaryOpFlags* flags_out) { 572 static_assert(Header::kLayoutIdBits * 2 + kBitsPerByte <= SmallInt::kBits, 573 "Two layout ids and flags overflow a SmallInt"); 574 word key_high_bits = static_cast<word>(left_layout_id) 575 << Header::kLayoutIdBits | 576 static_cast<word>(right_layout_id); 577 word index = cache * kIcPointersPerEntry; 578 DCHECK(!caches.at(index + kIcEntryKeyOffset).isUnbound(), 579 "cache.at(index) is expected to be monomorphic"); 580 RawObject entry_key = caches.at(index + kIcEntryKeyOffset); 581 // Stop the search if we found an empty entry. 582 if (entry_key.isNoneType()) { 583 return Error::notFound(); 584 } 585 word entry_key_value = SmallInt::cast(entry_key).value(); 586 if (entry_key_value >> kBitsPerByte == key_high_bits) { 587 *flags_out = static_cast<BinaryOpFlags>(entry_key_value & 0xff); 588 return caches.at(index + kIcEntryValueOffset); 589 } 590 return Error::notFound(); 591} 592 593inline RawObject icLookupGlobalVar(RawMutableTuple caches, word index) { 594 return caches.at(index); 595} 596 597} // namespace py