this repo has no description
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