runtime/ic-test.cpp (1,747 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "ic.h" #include "gtest/gtest.h" #include "attributedict.h" #include "dict-builtins.h" #include "str-builtins.h" #include "test-utils.h" #include "type-builtins.h" namespace py { namespace testing { using IcTest = RuntimeFixture; TEST_F( IcTest, icLookupMonomorphicWithEmptyCacheReturnsErrorNotFoundAndSetIsFoundToFalse) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(2 * kIcPointersPerEntry)); caches.fill(NoneType::object()); bool is_found; EXPECT_TRUE(icLookupMonomorphic(*caches, 1, LayoutId::kSmallInt, &is_found) .isErrorNotFound()); EXPECT_FALSE(is_found); } TEST_F(IcTest, IcLookupBinaryOpReturnsErrorNotFound) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(kIcPointersPerEntry)); caches.fill(NoneType::object()); BinaryOpFlags flags; EXPECT_TRUE(icLookupBinaryOp(*caches, 0, LayoutId::kSmallInt, LayoutId::kSmallInt, &flags) .isErrorNotFound()); } TEST_F(IcTest, IcLookupGlobalVar) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(2)); caches.fill(NoneType::object()); ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); caches.atPut(0, *cache); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches, 0)), 99)); EXPECT_TRUE(icLookupGlobalVar(*caches, 1).isNoneType()); } TEST_F(IcTest, IcUpdateAttrSetsMonomorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(1 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object value(&scope, runtime_->newInt(88)); Object name(&scope, Str::empty()); Function dependent(&scope, newEmptyFunction()); EXPECT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, value, name, dependent), ICState::kMonomorphic); bool is_found; EXPECT_EQ(icLookupMonomorphic(*caches, 0, LayoutId::kSmallInt, &is_found), *value); } TEST_F(IcTest, IcUpdateAttrUpdatesExistingMonomorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(1 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object value(&scope, runtime_->newInt(88)); Object name(&scope, Str::empty()); Function dependent(&scope, newEmptyFunction()); ASSERT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, value, name, dependent), ICState::kMonomorphic); bool is_found; EXPECT_EQ(icLookupMonomorphic(*caches, 0, LayoutId::kSmallInt, &is_found), *value); EXPECT_TRUE(is_found); Object new_value(&scope, runtime_->newInt(99)); EXPECT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, new_value, name, dependent), ICState::kMonomorphic); EXPECT_EQ(icLookupMonomorphic(*caches, 0, LayoutId::kSmallInt, &is_found), *new_value); EXPECT_TRUE(is_found); } TEST_F(IcTest, IcUpdateAttrSetsPolymorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(1 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object int_value(&scope, runtime_->newInt(88)); Object str_value(&scope, runtime_->newInt(99)); Object name(&scope, Str::empty()); Function dependent(&scope, newEmptyFunction()); ASSERT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, int_value, name, dependent), ICState::kMonomorphic); EXPECT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallStr, str_value, name, dependent), ICState::kPolymorphic); bool is_found; EXPECT_EQ(icLookupPolymorphic(*caches, 0, LayoutId::kSmallInt, &is_found), *int_value); EXPECT_TRUE(is_found); EXPECT_EQ(icLookupPolymorphic(*caches, 0, LayoutId::kSmallStr, &is_found), *str_value); EXPECT_TRUE(is_found); } TEST_F(IcTest, IcUpdateAttrUpdatesPolymorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(1 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object int_value(&scope, runtime_->newInt(88)); Object str_value(&scope, runtime_->newInt(99)); Object name(&scope, Str::empty()); Function dependent(&scope, newEmptyFunction()); ASSERT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, int_value, name, dependent), ICState::kMonomorphic); ASSERT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallStr, str_value, name, dependent), ICState::kPolymorphic); bool is_found; ASSERT_EQ(icLookupPolymorphic(*caches, 0, LayoutId::kSmallInt, &is_found), *int_value); ASSERT_TRUE(is_found); ASSERT_EQ(icLookupPolymorphic(*caches, 0, LayoutId::kSmallStr, &is_found), *str_value); ASSERT_TRUE(is_found); Object new_value(&scope, runtime_->newInt(101)); EXPECT_EQ(icUpdateAttr(thread_, caches, 0, LayoutId::kSmallStr, new_value, name, dependent), ICState::kPolymorphic); EXPECT_EQ(icLookupPolymorphic(*caches, 0, LayoutId::kSmallStr, &is_found), *new_value); EXPECT_TRUE(is_found); } TEST_F(IcTest, IcUpdateAttrInsertsDependencyUpToDefiningType) { HandleScope scope(thread_); ASSERT_FALSE(runFromCStr(runtime_, R"( class A: pass class B(A): foo = "class B" class C(B): bar = "class C" c = C() )") .isError()); // Inserting dependent adds dependent to a new Placeholder in C for 'foo', and // to the existing ValueCell in B. A won't be affected since it's not visited // during MRO traversal. MutableTuple caches(&scope, runtime_->newMutableTuple(4)); caches.fill(NoneType::object()); Object c(&scope, mainModuleAt(runtime_, "c")); Object value(&scope, SmallInt::fromWord(1234)); Object foo(&scope, Runtime::internStrFromCStr(thread_, "foo")); Function dependent(&scope, newEmptyFunction()); icUpdateAttr(thread_, caches, 0, c.layoutId(), value, foo, dependent); Type type_a(&scope, mainModuleAt(runtime_, "A")); RawObject unused = NoneType::object(); EXPECT_FALSE(attributeValueCellAt(*type_a, *foo, &unused)); Type type_b(&scope, mainModuleAt(runtime_, "B")); ValueCell b_entry(&scope, typeValueCellAt(*type_b, *foo)); EXPECT_FALSE(b_entry.isPlaceholder()); WeakLink b_link(&scope, b_entry.dependencyLink()); EXPECT_EQ(b_link.referent(), dependent); EXPECT_TRUE(b_link.next().isNoneType()); Type type_c(&scope, mainModuleAt(runtime_, "C")); ValueCell c_entry(&scope, typeValueCellAt(*type_c, *foo)); EXPECT_TRUE(c_entry.isPlaceholder()); WeakLink c_link(&scope, c_entry.dependencyLink()); EXPECT_EQ(c_link.referent(), dependent); EXPECT_TRUE(c_link.next().isNoneType()); } TEST_F(IcTest, IcUpdateAttrDoesNotInsertsDependencyToSealedType) { HandleScope scope(thread_); Str instance(&scope, runtime_->newStrFromCStr("str instance")); MutableTuple caches(&scope, runtime_->newMutableTuple(4)); caches.fill(NoneType::object()); Object value(&scope, SmallInt::fromWord(1234)); Object dunder_add(&scope, runtime_->symbols()->at(ID(__add__))); Function dependent(&scope, newEmptyFunction()); icUpdateAttr(thread_, caches, 0, instance.layoutId(), value, dunder_add, dependent); Type type_str(&scope, runtime_->typeAt(LayoutId::kStr)); ValueCell dunder_add_entry(&scope, typeValueCellAt(*type_str, *dunder_add)); EXPECT_TRUE(dunder_add_entry.dependencyLink().isNoneType()); } static RawObject dependencyLinkOfTypeAttr(Thread* thread, const Type& type, const char* attribute_name) { HandleScope scope(thread); Object attribute_name_str(&scope, Runtime::internStrFromCStr(thread, attribute_name)); ValueCell value_cell(&scope, typeValueCellAt(*type, *attribute_name_str)); return value_cell.dependencyLink(); } static bool icDependentIncluded(RawObject dependent, RawObject link) { for (; !link.isNoneType(); link = WeakLink::cast(link).next()) { if (WeakLink::cast(link).referent() == dependent) { return true; } } return false; } TEST_F(IcTest, IcEvictAttr) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def __init__(self): self.foo = 4 def cache_a_foo(a): return a.foo a = A() cache_a_foo(a) class B: pass )") .isError()); HandleScope scope(thread_); Type type_a(&scope, mainModuleAt(runtime_, "A")); Function cache_a_foo(&scope, mainModuleAt(runtime_, "cache_a_foo")); MutableTuple caches(&scope, cache_a_foo.caches()); Object cached_object(&scope, mainModuleAt(runtime_, "a")); // Precondition check that the A.foo attribute lookup has been cached. ASSERT_FALSE( icLookupAttr(*caches, 1, cached_object.layoutId()).isErrorNotFound()); ASSERT_EQ(WeakLink::cast(dependencyLinkOfTypeAttr(thread_, type_a, "foo")) .referent(), *cache_a_foo); // Try evicting caches with an attribute name that is not in the cache. This // should have no effect. Type cached_type(&scope, mainModuleAt(runtime_, "A")); IcIterator it(&scope, runtime_, *cache_a_foo); Object not_cached_attr_name(&scope, Runtime::internStrFromCStr(thread_, "random")); icEvictAttr(thread_, it, cached_type, not_cached_attr_name, AttributeKind::kNotADataDescriptor, cache_a_foo); EXPECT_FALSE( icLookupAttr(*caches, 1, cached_object.layoutId()).isErrorNotFound()); EXPECT_EQ(WeakLink::cast(dependencyLinkOfTypeAttr(thread_, type_a, "foo")) .referent(), *cache_a_foo); // Try evicting instance attribute caches for a non-data descriptor // assignment. Because instance attributes have a higher priority than // non-data descriptors, nothing should be evicted. Object foo(&scope, Runtime::internStrFromCStr(thread_, "foo")); icEvictAttr(thread_, it, cached_type, foo, AttributeKind::kNotADataDescriptor, cache_a_foo); EXPECT_FALSE( icLookupAttr(*caches, 1, cached_object.layoutId()).isErrorNotFound()); EXPECT_EQ(WeakLink::cast(dependencyLinkOfTypeAttr(thread_, type_a, "foo")) .referent(), *cache_a_foo); // Try evicting caches with a type that is not being cached. This should have // no effect. Type not_cached_type(&scope, mainModuleAt(runtime_, "B")); icEvictAttr(thread_, it, not_cached_type, foo, AttributeKind::kDataDescriptor, cache_a_foo); EXPECT_FALSE( icLookupAttr(*caches, 1, cached_object.layoutId()).isErrorNotFound()); EXPECT_EQ(WeakLink::cast(dependencyLinkOfTypeAttr(thread_, type_a, "foo")) .referent(), *cache_a_foo); // An update to a type attribute whose type, Attribute name with a data // desciptor value invalidates an instance attribute cache. icEvictAttr(thread_, it, cached_type, foo, AttributeKind::kDataDescriptor, cache_a_foo); EXPECT_TRUE( icLookupAttr(*caches, 1, cached_object.layoutId()).isErrorNotFound()); // The dependency for cache_a_foo gets deleted. EXPECT_FALSE(icDependentIncluded( *cache_a_foo, dependencyLinkOfTypeAttr(thread_, type_a, "foo"))); } TEST_F(IcTest, IcEvictBinaryOpEvictsCacheForUpdateToLeftOperandType) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def __ge__(self, other): return True class B: def __le__(self, other): return True def cache_binop(a, b): return a >= b a = A() b = B() cache_binop(a, b) )") .isError()); HandleScope scope(thread_); Function cache_binop(&scope, mainModuleAt(runtime_, "cache_binop")); MutableTuple caches(&scope, cache_binop.caches()); Object left_operand(&scope, mainModuleAt(runtime_, "a")); Object right_operand(&scope, mainModuleAt(runtime_, "b")); Type left_operand_type(&scope, mainModuleAt(runtime_, "A")); BinaryOpFlags flags_out; // Precondition check that the A.__ge__ attribute lookup has been cached. ASSERT_FALSE(icLookupBinaryOp(*caches, 0, left_operand.layoutId(), right_operand.layoutId(), &flags_out) .isErrorNotFound()); IcIterator it(&scope, runtime_, *cache_binop); // An update to A.__ge__ invalidates the binop cache for a >= b. Object dunder_ge(&scope, Runtime::internStrFromCStr(thread_, "__ge__")); icEvictBinaryOp(thread_, it, left_operand_type, dunder_ge, cache_binop); EXPECT_TRUE(icLookupBinaryOp(*caches, 0, left_operand.layoutId(), right_operand.layoutId(), &flags_out) .isErrorNotFound()); } TEST_F(IcTest, IcEvictBinaryOpEvictsCacheForUpdateToRightOperand) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def __ge__(self, other): return True class B: def __le__(self, other): return True def cache_binop(a, b): return a >= b a = A() b = B() cache_binop(a, b) )") .isError()); HandleScope scope(thread_); Function cache_binop(&scope, mainModuleAt(runtime_, "cache_binop")); MutableTuple caches(&scope, cache_binop.caches()); Object left_operand(&scope, mainModuleAt(runtime_, "a")); Object right_operand(&scope, mainModuleAt(runtime_, "b")); Type right_operand_type(&scope, mainModuleAt(runtime_, "B")); BinaryOpFlags flags_out; // Precondition check that the A.__ge__ attribute lookup has been cached. ASSERT_FALSE(icLookupBinaryOp(*caches, 0, left_operand.layoutId(), right_operand.layoutId(), &flags_out) .isErrorNotFound()); IcIterator it(&scope, runtime_, *cache_binop); Object dunder_le(&scope, Runtime::internStrFromCStr(thread_, "__le__")); // An update to B.__le__ invalidates the binop cache for a >= b. icEvictBinaryOp(thread_, it, right_operand_type, dunder_le, cache_binop); EXPECT_TRUE(icLookupBinaryOp(*caches, 0, left_operand.layoutId(), right_operand.layoutId(), &flags_out) .isErrorNotFound()); } TEST_F(IcTest, IcEvictBinaryOpDoesnNotDeleteDependenciesFromCachedTypes) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def __ge__(self, other): return True class B: def __le__(self, other): return True def cache_compare_op(a, b): t0 = a >= b t1 = b <= 5 a = A() b = B() cache_compare_op(a, b) A__ge__ = A.__ge__ B__le__ = B.__le__ )") .isError()); HandleScope scope(thread_); Object a(&scope, mainModuleAt(runtime_, "a")); Object b(&scope, mainModuleAt(runtime_, "b")); Object type_a_dunder_ge(&scope, mainModuleAt(runtime_, "A__ge__")); Object type_b_dunder_le(&scope, mainModuleAt(runtime_, "B__le__")); Function cache_compare_op(&scope, mainModuleAt(runtime_, "cache_compare_op")); MutableTuple caches(&scope, cache_compare_op.caches()); BinaryOpFlags flags_out; // Ensure that A.__ge__ is cached for t0 = a >= b. ASSERT_EQ( icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flags_out), *type_a_dunder_ge); // Ensure that B.__le__ is cached for t1 = b >= 5. ASSERT_EQ(icLookupBinaryOp(*caches, 1, b.layoutId(), SmallInt::fromWord(0).layoutId(), &flags_out), *type_b_dunder_le); Type type_a(&scope, mainModuleAt(runtime_, "A")); // Ensure cache_compare_op is a dependent of A.__ge__. ASSERT_TRUE(icDependentIncluded( *cache_compare_op, dependencyLinkOfTypeAttr(thread_, type_a, "__ge__"))); Type type_b(&scope, mainModuleAt(runtime_, "B")); // Ensure cache_compare_op is a dependent of B.__le__. ASSERT_TRUE(icDependentIncluded( *cache_compare_op, dependencyLinkOfTypeAttr(thread_, type_b, "__le__"))); // Update A.__ge__ to invalidate cache for t0 = a >= b. Object dunder_ge_name(&scope, Runtime::internStrFromCStr(thread_, "__ge__")); icEvictCache(thread_, cache_compare_op, type_a, dunder_ge_name, AttributeKind::kNotADataDescriptor); // The invalidation removes dependency from cache_compare_op to A.__ge__. EXPECT_FALSE(icDependentIncluded( *cache_compare_op, dependencyLinkOfTypeAttr(thread_, type_a, "__ge__"))); // However, cache_compare_op still depends on B.__le__ since b >= 5 is cached. EXPECT_TRUE(icDependentIncluded( *cache_compare_op, dependencyLinkOfTypeAttr(thread_, type_b, "__le__"))); } TEST_F(IcTest, IcDeleteDependentInValueCellDependencyLinkDeletesDependent) { HandleScope scope(thread_); ValueCell value_cell(&scope, runtime_->newValueCell()); Object dependent0(&scope, newTupleWithNone(4)); Object dependent1(&scope, newTupleWithNone(5)); Object dependent2(&scope, newTupleWithNone(6)); Object dependent3(&scope, newTupleWithNone(7)); icInsertDependentToValueCellDependencyLink(thread_, dependent3, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent2, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent1, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent0, value_cell); // Delete the head. icDeleteDependentInValueCell(thread_, value_cell, dependent0); WeakLink link(&scope, value_cell.dependencyLink()); EXPECT_EQ(link.referent(), *dependent1); EXPECT_TRUE(link.prev().isNoneType()); EXPECT_EQ(WeakLink::cast(link.next()).referent(), *dependent2); EXPECT_EQ(WeakLink::cast(link.next()).prev(), *link); // Delete the dependent in the middle. icDeleteDependentInValueCell(thread_, value_cell, dependent2); link = value_cell.dependencyLink(); EXPECT_EQ(link.referent(), *dependent1); EXPECT_EQ(WeakLink::cast(link.next()).referent(), *dependent3); EXPECT_EQ(WeakLink::cast(link.next()).prev(), *link); // Delete the tail. icDeleteDependentInValueCell(thread_, value_cell, dependent3); link = value_cell.dependencyLink(); EXPECT_EQ(link.referent(), *dependent1); EXPECT_TRUE(link.next().isNoneType()); // Delete the last node. icDeleteDependentInValueCell(thread_, value_cell, dependent1); EXPECT_TRUE(value_cell.dependencyLink().isNoneType()); } TEST_F( IcTest, IcDeleteDependentFromCachedAttributeDeletesDependentUnderAttributeNameInMro) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def foo(self): return 1 def bar(self): return 1 def x(a): return a.foo() def y(a): return a.bar() a = A() x(a) y(a) )") .isError()); HandleScope scope(thread_); Type type_a(&scope, mainModuleAt(runtime_, "A")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object bar_name(&scope, Runtime::internStrFromCStr(thread_, "bar")); Function dependent_x(&scope, mainModuleAt(runtime_, "x")); Function dependent_y(&scope, mainModuleAt(runtime_, "y")); // A.foo -> x ValueCell foo_in_a(&scope, typeValueCellAt(*type_a, *foo_name)); ASSERT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(), *dependent_x); // A.bar -> y ValueCell bar_in_a(&scope, typeValueCellAt(*type_a, *bar_name)); ASSERT_EQ(WeakLink::cast(bar_in_a.dependencyLink()).referent(), *dependent_y); LayoutId type_a_instance_layout_id = type_a.instanceLayoutId(); // Try to delete dependent_y under name "foo". Nothing happens. icDeleteDependentFromInheritingTypes(thread_, type_a_instance_layout_id, foo_name, type_a, dependent_y); EXPECT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(), *dependent_x); EXPECT_EQ(WeakLink::cast(bar_in_a.dependencyLink()).referent(), *dependent_y); // Try to delete dependent_x under name "bar". Nothing happens. icDeleteDependentFromInheritingTypes(thread_, type_a_instance_layout_id, bar_name, type_a, dependent_x); EXPECT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(), *dependent_x); EXPECT_EQ(WeakLink::cast(bar_in_a.dependencyLink()).referent(), *dependent_y); icDeleteDependentFromInheritingTypes(thread_, type_a_instance_layout_id, foo_name, type_a, dependent_x); EXPECT_TRUE(foo_in_a.dependencyLink().isNoneType()); EXPECT_EQ(WeakLink::cast(bar_in_a.dependencyLink()).referent(), *dependent_y); icDeleteDependentFromInheritingTypes(thread_, type_a_instance_layout_id, bar_name, type_a, dependent_y); EXPECT_TRUE(foo_in_a.dependencyLink().isNoneType()); EXPECT_TRUE(bar_in_a.dependencyLink().isNoneType()); } TEST_F(IcTest, IcDeleteDependentFromCachedAttributeDeletesDependentUpToUpdatedType) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def foo(self): return 1 class B(A): def foo(self): return 2 class C(B): pass def x(c): return c.foo() c = C() x(c) a = A() x(a) )") .isError()); HandleScope scope(thread_); Type a(&scope, mainModuleAt(runtime_, "A")); Type b(&scope, mainModuleAt(runtime_, "B")); Type c(&scope, mainModuleAt(runtime_, "C")); Object dependent_x(&scope, mainModuleAt(runtime_, "x")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); // A.foo -> x ValueCell foo_in_a(&scope, typeValueCellAt(*a, *foo_name)); ASSERT_FALSE(foo_in_a.isPlaceholder()); ASSERT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(), *dependent_x); // B.foo -> x ValueCell foo_in_b(&scope, typeValueCellAt(*b, *foo_name)); ASSERT_FALSE(foo_in_b.isPlaceholder()); ASSERT_EQ(WeakLink::cast(foo_in_b.dependencyLink()).referent(), *dependent_x); // C.foo -> x // Note that this dependency is a placeholder. ValueCell foo_in_c(&scope, typeValueCellAt(*c, *foo_name)); ASSERT_TRUE(foo_in_c.isPlaceholder()); ASSERT_EQ(WeakLink::cast(foo_in_c.dependencyLink()).referent(), *dependent_x); Object c_obj(&scope, mainModuleAt(runtime_, "c")); // Delete dependent_x for an update to B.foo. icDeleteDependentFromInheritingTypes(thread_, c_obj.layoutId(), foo_name, b, dependent_x); // B.foo's update doesn't affect the cache for A.foo since the update does not // shadow a.foo where type(a) == A. EXPECT_TRUE(foo_in_c.dependencyLink().isNoneType()); EXPECT_TRUE(foo_in_b.dependencyLink().isNoneType()); // Didn't delete this since type lookup cannot reach A by successful attribute // lookup for "foo" in B. EXPECT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(), *dependent_x); } TEST_F( IcTest, IcHighestSuperTypeNotInMroOfOtherCachedTypesReturnsHighestNotCachedSuperType) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def foo(self): return 4 class B(A): pass def cache_foo(x): return x.foo a_foo = A.foo b = B() cache_foo(b) )") .isError()); HandleScope scope(thread_); Function cache_foo(&scope, mainModuleAt(runtime_, "cache_foo")); Object a_foo(&scope, mainModuleAt(runtime_, "a_foo")); Object b_obj(&scope, mainModuleAt(runtime_, "b")); Type a_type(&scope, mainModuleAt(runtime_, "A")); MutableTuple caches(&scope, cache_foo.caches()); ASSERT_EQ(icLookupAttr(*caches, 1, b_obj.layoutId()), *a_foo); // Manually delete the cache for B.foo in cache_foo. caches.atPut(1 * kIcPointersPerEntry + kIcEntryKeyOffset, NoneType::object()); caches.atPut(1 * kIcPointersPerEntry + kIcEntryValueOffset, NoneType::object()); ASSERT_TRUE(icLookupAttr(*caches, 1, b_obj.layoutId()).isErrorNotFound()); // Now cache_foo doesn't depend on neither A.foo nor B.foo, so this should // return A. Object foo(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object result(&scope, icHighestSuperTypeNotInMroOfOtherCachedTypes( thread_, b_obj.layoutId(), foo, cache_foo)); EXPECT_EQ(result, *a_type); } TEST_F(IcTest, IcIsCachedAttributeAffectedByUpdatedType) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def foo(self): return 1 class B(A): def foo(self): return 2 class C(B): pass def x(c): return c.foo() c = C() x(c) )") .isError()); HandleScope scope(thread_); Type type_a(&scope, mainModuleAt(runtime_, "A")); Type type_b(&scope, mainModuleAt(runtime_, "B")); Type type_c(&scope, mainModuleAt(runtime_, "C")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); LayoutId type_c_instance_layout_id = type_c.instanceLayoutId(); // Check if A.foo is not retrived from C.foo. EXPECT_FALSE(icIsCachedAttributeAffectedByUpdatedType( thread_, type_c_instance_layout_id, foo_name, type_a)); // Check if B.foo is retrieved from C.foo. EXPECT_TRUE(icIsCachedAttributeAffectedByUpdatedType( thread_, type_c_instance_layout_id, foo_name, type_b)); // Assign C.foo to a real value. ValueCell foo_in_c(&scope, typeValueCellAt(*type_c, *foo_name)); foo_in_c.setValue(NoneType::object()); // Check if B.foo is not retrived from C.foo from now on. EXPECT_FALSE(icIsCachedAttributeAffectedByUpdatedType( thread_, type_c_instance_layout_id, foo_name, type_b)); // Instead, C.foo is retrieved. EXPECT_TRUE(icIsCachedAttributeAffectedByUpdatedType( thread_, type_c_instance_layout_id, foo_name, type_c)); } // Create a function that maps cache index 1 to the given attribute name. static RawObject testingFunctionCachingAttributes( Thread* thread, const Object& attribute_name) { Runtime* runtime = thread->runtime(); HandleScope scope(thread); Tuple consts(&scope, runtime->emptyTuple()); Object name(&scope, Str::empty()); Tuple names(&scope, runtime->newTupleWith2(attribute_name, name)); Code code(&scope, newCodeWithBytesConstsNames(View<byte>(nullptr, 0), consts, names)); MutableBytes rewritten_bytecode(&scope, runtime->newMutableBytesUninitialized(8)); rewrittenBytecodeOpAtPut(rewritten_bytecode, 0, LOAD_ATTR_ANAMORPHIC); rewrittenBytecodeArgAtPut(rewritten_bytecode, 0, 0); rewrittenBytecodeCacheAtPut(rewritten_bytecode, 0, 1); Module module(&scope, findMainModule(runtime)); Function function(&scope, runtime->newFunctionWithCode(thread, name, code, module)); function.setRewrittenBytecode(*rewritten_bytecode); MutableTuple caches(&scope, runtime->newMutableTuple(2 * kIcPointersPerEntry)); caches.fill(NoneType::object()); function.setCaches(*caches); return *function; } TEST_F(IcTest, IcEvictCacheEvictsCachesForMatchingAttributeName) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: pass c = C() )") .isError()); HandleScope scope(thread_); Type type(&scope, mainModuleAt(runtime_, "C")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object bar_name(&scope, Runtime::internStrFromCStr(thread_, "bar")); Function dependent(&scope, testingFunctionCachingAttributes(thread_, foo_name)); // foo -> dependent. ValueCell foo(&scope, attributeValueCellAtPut(thread_, type, foo_name)); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent, foo)); // Create an attribute cache for an instance of C, under name "foo". Object instance(&scope, mainModuleAt(runtime_, "c")); MutableTuple caches(&scope, dependent.caches()); Object value(&scope, SmallInt::fromWord(1234)); Object name(&scope, Str::empty()); icUpdateAttr(thread_, caches, 1, instance.layoutId(), value, name, dependent); ASSERT_EQ(icLookupAttr(*caches, 1, instance.layoutId()), SmallInt::fromWord(1234)); // Deleting caches for "bar" doesn't affect the cache for "foo". icEvictCache(thread_, dependent, type, bar_name, AttributeKind::kDataDescriptor); EXPECT_EQ(icLookupAttr(*caches, 1, instance.layoutId()), SmallInt::fromWord(1234)); // Deleting caches for "foo". icEvictCache(thread_, dependent, type, foo_name, AttributeKind::kDataDescriptor); EXPECT_TRUE(icLookupAttr(*caches, 1, instance.layoutId()).isErrorNotFound()); } TEST_F(IcTest, IcEvictCacheEvictsCachesForInstanceOffsetOnlyWhenDataDesciptorIsTrue) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: pass c = C() )") .isError()); HandleScope scope(thread_); Type type(&scope, mainModuleAt(runtime_, "C")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Function dependent(&scope, testingFunctionCachingAttributes(thread_, foo_name)); // foo -> dependent. ValueCell foo(&scope, attributeValueCellAtPut(thread_, type, foo_name)); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent, foo)); // Create an instance offset cache for an instance of C, under name "foo". Object instance(&scope, mainModuleAt(runtime_, "c")); MutableTuple caches(&scope, dependent.caches()); Object value(&scope, SmallInt::fromWord(1234)); Object name(&scope, Str::empty()); icUpdateAttr(thread_, caches, 1, instance.layoutId(), value, name, dependent); ASSERT_EQ(icLookupAttr(*caches, 1, instance.layoutId()), SmallInt::fromWord(1234)); // An attempt to delete caches for "foo" with data_descriptor == false doesn't // affect it. icEvictCache(thread_, dependent, type, foo_name, AttributeKind::kNotADataDescriptor); EXPECT_EQ(icLookupAttr(*caches, 1, instance.layoutId()), SmallInt::fromWord(1234)); // Delete caches for "foo" with data_descriptor == true actually deletes it. icEvictCache(thread_, dependent, type, foo_name, AttributeKind::kDataDescriptor); EXPECT_TRUE(icLookupAttr(*caches, 1, instance.layoutId()).isErrorNotFound()); } TEST_F(IcTest, IcEvictCacheEvictsOnlyAffectedCaches) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def foo(self): return 1 class B(A): def foo(self): return 2 class C(B): pass a = A() b = B() c = C() )") .isError()); HandleScope scope(thread_); Type a_type(&scope, mainModuleAt(runtime_, "A")); Type b_type(&scope, mainModuleAt(runtime_, "B")); Type c_type(&scope, mainModuleAt(runtime_, "C")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Function dependent(&scope, testingFunctionCachingAttributes(thread_, foo_name)); // The following lines simulate that dependent caches a.foo, b.foo, c.foo, and // x.foo. A.foo -> dependent. ValueCell a_foo(&scope, typeValueCellAt(*a_type, *foo_name)); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent, a_foo)); // B.foo -> dependent. ValueCell b_foo(&scope, typeValueCellAt(*b_type, *foo_name)); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent, b_foo)); // C.foo -> dependent. ValueCell c_foo(&scope, attributeValueCellAtPut(thread_, c_type, foo_name)); // This is a placeholder since C.foo is resolved to B.foo. c_foo.makePlaceholder(); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent, c_foo)); // Create a cache for a.foo in dependent. Object a(&scope, mainModuleAt(runtime_, "a")); MutableTuple caches(&scope, dependent.caches()); Object value_100(&scope, SmallInt::fromWord(100)); Object name(&scope, Str::empty()); icUpdateAttr(thread_, caches, 1, a.layoutId(), value_100, name, dependent); ASSERT_EQ(icLookupAttr(*caches, 1, a.layoutId()), SmallInt::fromWord(100)); // Create a cache for b.foo in dependent. Object b(&scope, mainModuleAt(runtime_, "b")); Object value_200(&scope, SmallInt::fromWord(200)); icUpdateAttr(thread_, caches, 1, b.layoutId(), value_200, name, dependent); ASSERT_EQ(icLookupAttr(*caches, 1, b.layoutId()), SmallInt::fromWord(200)); // Create a cache for c.foo in dependent. Object c(&scope, mainModuleAt(runtime_, "c")); Object value_300(&scope, SmallInt::fromWord(300)); icUpdateAttr(thread_, caches, 1, c.layoutId(), value_300, name, dependent); ASSERT_EQ(icLookupAttr(*caches, 1, c.layoutId()), SmallInt::fromWord(300)); // Trigger invalidation by updating B.foo. icEvictCache(thread_, dependent, b_type, foo_name, AttributeKind::kDataDescriptor); // Note that only caches made for the type attribute are evincted, and // dependent is dropped from them. EXPECT_EQ(icLookupAttr(*caches, 1, a.layoutId()), SmallInt::fromWord(100)); EXPECT_EQ(WeakLink::cast(a_foo.dependencyLink()).referent(), *dependent); EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isErrorNotFound()); EXPECT_TRUE(b_foo.dependencyLink().isNoneType()); EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound()); EXPECT_TRUE(c_foo.dependencyLink().isNoneType()); // Trigger invalidation by updating A.foo. icEvictCache(thread_, dependent, a_type, foo_name, AttributeKind::kDataDescriptor); EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isErrorNotFound()); EXPECT_TRUE(a_foo.dependencyLink().isNoneType()); } TEST_F(IcTest, IcEvictCacheWithPolymorphicCacheEvictsCache) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: pass class B: pass a = A() b = B() )") .isError()); HandleScope scope(thread_); Type a_type(&scope, mainModuleAt(runtime_, "A")); Object a(&scope, mainModuleAt(runtime_, "a")); Type b_type(&scope, mainModuleAt(runtime_, "B")); Object b(&scope, mainModuleAt(runtime_, "b")); Object a_value(&scope, runtime_->newInt(88)); Object b_value(&scope, runtime_->newInt(99)); Object name(&scope, Str::empty()); Function dependent(&scope, testingFunctionCachingAttributes(thread_, name)); MutableTuple caches(&scope, dependent.caches()); ASSERT_EQ( icUpdateAttr(thread_, caches, 1, a.layoutId(), a_value, name, dependent), ICState::kMonomorphic); EXPECT_EQ( icUpdateAttr(thread_, caches, 1, b.layoutId(), b_value, name, dependent), ICState::kPolymorphic); bool is_found; EXPECT_EQ(icLookupPolymorphic(*caches, 1, a.layoutId(), &is_found), *a_value); EXPECT_TRUE(is_found); EXPECT_EQ(icLookupPolymorphic(*caches, 1, b.layoutId(), &is_found), *b_value); EXPECT_TRUE(is_found); icEvictCache(thread_, dependent, a_type, name, AttributeKind::kDataDescriptor); EXPECT_TRUE(icLookupPolymorphic(*caches, 1, a.layoutId(), &is_found) .isErrorNotFound()); EXPECT_FALSE(is_found); icEvictCache(thread_, dependent, b_type, name, AttributeKind::kDataDescriptor); EXPECT_TRUE(icLookupPolymorphic(*caches, 1, b.layoutId(), &is_found) .isErrorNotFound()); EXPECT_FALSE(is_found); } // Verify if IcInvalidateCachesForTypeAttr calls // DeleteCachesForTypeAttrInDependent with all dependents. TEST_F(IcTest, IcInvalidateCachesForTypeAttrProcessesAllDependents) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: pass c = C() )") .isError()); HandleScope scope(thread_); Type type(&scope, mainModuleAt(runtime_, "C")); Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object bar_name(&scope, Runtime::internStrFromCStr(thread_, "bar")); Function dependent0(&scope, testingFunctionCachingAttributes(thread_, foo_name)); Function dependent1(&scope, testingFunctionCachingAttributes(thread_, bar_name)); // Create a property so these value cells look like data descriptor attributes Object none(&scope, NoneType::object()); Object data_descriptor(&scope, runtime_->newProperty(none, none, none)); // foo -> dependent0. ValueCell foo(&scope, attributeValueCellAtPut(thread_, type, foo_name)); foo.setValue(*data_descriptor); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent0, foo)); // bar -> dependent1. ValueCell bar(&scope, attributeValueCellAtPut(thread_, type, bar_name)); bar.setValue(*data_descriptor); ASSERT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, dependent1, bar)); MutableTuple dependent0_caches(&scope, dependent0.caches()); Object instance(&scope, mainModuleAt(runtime_, "c")); { // Create an attribute cache for an instance of C, under name "foo" in // dependent0. Object name(&scope, Str::empty()); Object value(&scope, SmallInt::fromWord(1234)); icUpdateAttr(thread_, dependent0_caches, 1, instance.layoutId(), value, name, dependent0); ASSERT_EQ(icLookupAttr(*dependent0_caches, 1, instance.layoutId()), SmallInt::fromWord(1234)); } MutableTuple dependent1_caches(&scope, dependent1.caches()); { // Create an attribute cache for an instance of C, under name "bar" in // dependent1. Object name(&scope, Str::empty()); Object value(&scope, SmallInt::fromWord(5678)); icUpdateAttr(thread_, dependent1_caches, 1, instance.layoutId(), value, name, dependent1); ASSERT_EQ(icLookupAttr(*dependent1_caches, 1, instance.layoutId()), SmallInt::fromWord(5678)); } icInvalidateAttr(thread_, type, foo_name, foo); EXPECT_TRUE(icLookupAttr(*dependent0_caches, 1, instance.layoutId()) .isErrorNotFound()); EXPECT_EQ(icLookupAttr(*dependent1_caches, 1, instance.layoutId()), SmallInt::fromWord(5678)); icInvalidateAttr(thread_, type, bar_name, bar); EXPECT_TRUE(icLookupAttr(*dependent0_caches, 1, instance.layoutId()) .isErrorNotFound()); EXPECT_TRUE(icLookupAttr(*dependent1_caches, 1, instance.layoutId()) .isErrorNotFound()); } TEST_F(IcTest, BinarySubscrUpdateCacheWithRaisingDescriptorPropagatesException) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class Desc: def __get__(self, instance, type): raise UserWarning("foo") class C: __getitem__ = Desc() container = C() result = container[0] )"), LayoutId::kUserWarning, "foo")); } TEST_F(IcTest, IcIsAttrCachedInDependentReturnsTrueForAttrCaches) { ASSERT_FALSE(runFromCStr(runtime_, R"( class X: def foo(self): return 4 class Y(X): pass class A: def foo(self): return 4 class B(A): pass def cache_Y_foo(): return Y().foo() cache_Y_foo() )") .isError()); HandleScope scope(thread_); Type type_a(&scope, mainModuleAt(runtime_, "A")); Type type_b(&scope, mainModuleAt(runtime_, "B")); Type type_x(&scope, mainModuleAt(runtime_, "X")); Type type_y(&scope, mainModuleAt(runtime_, "Y")); Object foo(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object bar(&scope, Runtime::internStrFromCStr(thread_, "bar")); Function cache_y_foo(&scope, mainModuleAt(runtime_, "cache_Y_foo")); // Note that cache_y_foo depends both on X.foo and Y.foo since an // update to either one of them flows to Y().foo(). EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_x, foo, cache_y_foo)); EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_y, foo, cache_y_foo)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_x, bar, cache_y_foo)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_a, foo, cache_y_foo)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_b, foo, cache_y_foo)); } TEST_F(IcTest, IcIsAttrCachedInDependentReturnsTrueForBinaryOpCaches) { ASSERT_FALSE(runFromCStr(runtime_, R"( class X: def __ge__(self, other): return 5 class Y(X): pass class A: def foo(self): return 4 class B(A): pass def cache_Y_ge(): return Y() >= B() cache_Y_ge() )") .isError()); HandleScope scope(thread_); Type type_x(&scope, mainModuleAt(runtime_, "X")); Type type_y(&scope, mainModuleAt(runtime_, "Y")); Type type_a(&scope, mainModuleAt(runtime_, "A")); Type type_b(&scope, mainModuleAt(runtime_, "B")); Object dunder_ge(&scope, Runtime::internStrFromCStr(thread_, "__ge__")); Object dunder_le(&scope, Runtime::internStrFromCStr(thread_, "__le__")); Function cache_ge(&scope, mainModuleAt(runtime_, "cache_Y_ge")); // Note that cache_ge indirectly depends on X, but directly on Y since both // X.__ge__ and Y.__ge__ affect Y() >= sth. EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_x, dunder_ge, cache_ge)); EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_y, dunder_ge, cache_ge)); // Note that cache_ge indirectly depends on A, but directly on B since both // B.__le__ and C.__le__ affect sth >= B(). EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_a, dunder_le, cache_ge)); EXPECT_TRUE(icIsAttrCachedInDependent(thread_, type_b, dunder_le, cache_ge)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_x, dunder_le, cache_ge)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_y, dunder_le, cache_ge)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_a, dunder_ge, cache_ge)); EXPECT_FALSE(icIsAttrCachedInDependent(thread_, type_b, dunder_ge, cache_ge)); } TEST_F(IcTest, IcDependentIncludedWithNoneLinkReturnsFalse) { EXPECT_FALSE(icDependentIncluded(Unbound::object(), NoneType::object())); } TEST_F(IcTest, IcDependentIncludedWithDependentInChainReturnsTrue) { HandleScope scope(thread_); Object none(&scope, NoneType::object()); Object one(&scope, runtime_->newSet()); Object two(&scope, runtime_->newSet()); Object three(&scope, runtime_->newSet()); // Set up None <- link0 <-> link1 <-> link2 -> None WeakLink link0(&scope, runtime_->newWeakLink(thread_, one, none, none)); WeakLink link1(&scope, runtime_->newWeakLink(thread_, two, link0, none)); WeakLink link2(&scope, runtime_->newWeakLink(thread_, three, link1, none)); link0.setNext(*link1); link1.setNext(*link2); EXPECT_TRUE(icDependentIncluded(*one, *link0)); EXPECT_TRUE(icDependentIncluded(*two, *link0)); EXPECT_TRUE(icDependentIncluded(*three, *link0)); EXPECT_FALSE(icDependentIncluded(*one, *link1)); EXPECT_TRUE(icDependentIncluded(*two, *link1)); EXPECT_TRUE(icDependentIncluded(*three, *link1)); EXPECT_FALSE(icDependentIncluded(*one, *link2)); EXPECT_FALSE(icDependentIncluded(*two, *link2)); EXPECT_TRUE(icDependentIncluded(*three, *link2)); EXPECT_FALSE(icDependentIncluded(Unbound::object(), *link0)); EXPECT_FALSE(icDependentIncluded(Unbound::object(), *link1)); EXPECT_FALSE(icDependentIncluded(Unbound::object(), *link2)); } TEST_F(IcTest, IcEvictCacheEvictsCompareOpCaches) { ASSERT_FALSE(runFromCStr(runtime_, R"( class A: def __ge__(self, other): return True class B: pass def cache_compare_op(a, b): return a >= b a = A() b = B() A__ge__ = A.__ge__ cache_compare_op(a, b) )") .isError()); HandleScope scope(thread_); Object a(&scope, mainModuleAt(runtime_, "a")); Object b(&scope, mainModuleAt(runtime_, "b")); Object type_a_dunder_ge(&scope, mainModuleAt(runtime_, "A__ge__")); Function cache_compare_op(&scope, mainModuleAt(runtime_, "cache_compare_op")); MutableTuple caches(&scope, cache_compare_op.caches()); BinaryOpFlags flags_out; Object cached(&scope, icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flags_out)); // Precondition check that the A.__ge__ lookup has been cached. ASSERT_EQ(*cached, *type_a_dunder_ge); Type type_a(&scope, mainModuleAt(runtime_, "A")); Object dunder_ge_name(&scope, Runtime::internStrFromCStr(thread_, "__ge__")); ValueCell dunder_ge(&scope, typeValueCellAt(*type_a, *dunder_ge_name)); WeakLink dunder_ge_link(&scope, dunder_ge.dependencyLink()); // Precondition check that cache_compare_op is a dependent of A.__ge__. ASSERT_EQ(dunder_ge_link.referent(), *cache_compare_op); Type type_b(&scope, mainModuleAt(runtime_, "B")); Object dunder_le_name(&scope, Runtime::internStrFromCStr(thread_, "__le__")); ValueCell dunder_le(&scope, typeValueCellAt(*type_b, *dunder_le_name)); WeakLink dunder_le_link(&scope, dunder_le.dependencyLink()); // Precondition check that cache_compare_op is a dependent of B.__le__. ASSERT_EQ(dunder_le_link.referent(), *cache_compare_op); // Updating A.__ge__ triggers cache invalidation. icEvictCache(thread_, cache_compare_op, type_a, dunder_ge_name, AttributeKind::kNotADataDescriptor); EXPECT_TRUE( icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flags_out) .isErrorNotFound()); EXPECT_FALSE( icDependentIncluded(*cache_compare_op, dunder_ge.dependencyLink())); EXPECT_TRUE(dunder_le.dependencyLink().isNoneType()); } TEST_F(IcTest, ForIterUpdateCacheWithRaisingDescriptorDunderNextPropagatesException) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class Desc: def __get__(self, instance, type): raise UserWarning("foo") class C: def __iter__(self): return self __next__ = Desc() container = C() result = [x for x in container] )"), LayoutId::kUserWarning, "foo")); } TEST_F(IcTest, BinarySubscrUpdateCacheWithFunctionUpdatesCache) { ASSERT_FALSE(runFromCStr(runtime_, R"( class Container: def __getitem__(self, index): return index + 1 def f(c, k): return c[k] container = Container() getitem = type(container).__getitem__ result = f(container, 0) )") .isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); EXPECT_TRUE(isIntEqualsWord(*result, 1)); Object container(&scope, mainModuleAt(runtime_, "container")); Object getitem(&scope, mainModuleAt(runtime_, "getitem")); Function f(&scope, mainModuleAt(runtime_, "f")); MutableTuple caches(&scope, f.caches()); // Expect that BINARY_SUBSCR is the only cached opcode in f(). ASSERT_EQ(caches.length(), 1 * kIcPointersPerEntry); EXPECT_EQ(icLookupAttr(*caches, 0, container.layoutId()), *getitem); ASSERT_FALSE(runFromCStr(runtime_, R"( container2 = Container() result2 = f(container2, 1) )") .isError()); Object container2(&scope, mainModuleAt(runtime_, "container2")); Object result2(&scope, mainModuleAt(runtime_, "result2")); EXPECT_EQ(container2.layoutId(), container.layoutId()); EXPECT_TRUE(isIntEqualsWord(*result2, 2)); } TEST_F(IcTest, BinarySubscrUpdateCacheWithNonFunctionDoesntUpdateCache) { ASSERT_FALSE(runFromCStr(runtime_, R"( def f(c, k): return c[k] class Container: def get(self): def getitem(key): return key return getitem __getitem__ = property(get) container = Container() result = f(container, "hi") )") .isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); EXPECT_TRUE(isStrEqualsCStr(*result, "hi")); Object container(&scope, mainModuleAt(runtime_, "container")); Function f(&scope, mainModuleAt(runtime_, "f")); MutableTuple caches(&scope, f.caches()); // Expect that BINARY_SUBSCR is the only cached opcode in f(). ASSERT_EQ(caches.length(), 1 * kIcPointersPerEntry); EXPECT_TRUE(icLookupAttr(*caches, 0, container.layoutId()).isErrorNotFound()); ASSERT_FALSE(runFromCStr(runtime_, R"( container2 = Container() result2 = f(container, "hello there!") )") .isError()); Object container2(&scope, mainModuleAt(runtime_, "container2")); Object result2(&scope, mainModuleAt(runtime_, "result2")); ASSERT_EQ(container2.layoutId(), container.layoutId()); EXPECT_TRUE(isStrEqualsCStr(*result2, "hello there!")); } TEST_F(IcTest, IcUpdateBinaryOpSetsEmptyEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(2 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object value(&scope, runtime_->newStrFromCStr("this is a random value")); EXPECT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, value, kBinaryOpNone), ICState::kMonomorphic); BinaryOpFlags flags; EXPECT_EQ(icLookupBinOpMonomorphic(*caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, &flags), *value); } TEST_F(IcTest, IcUpdateBinaryOpSetsExistingMonomorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(2 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object value(&scope, runtime_->newStrFromCStr("xxx")); ASSERT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, value, kBinaryOpNone), ICState::kMonomorphic); Object new_value(&scope, runtime_->newStrFromCStr("yyy")); EXPECT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, new_value, kBinaryOpNone), ICState::kMonomorphic); BinaryOpFlags flags; EXPECT_EQ(icLookupBinOpMonomorphic(*caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, &flags), *new_value); } TEST_F(IcTest, IcUpdateBinaryOpSetsExistingPolymorphicEntry) { HandleScope scope(thread_); MutableTuple caches(&scope, runtime_->newMutableTuple(2 * kIcPointersPerEntry)); caches.fill(NoneType::object()); Object value(&scope, runtime_->newStrFromCStr("xxx")); ASSERT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, value, kBinaryOpNone), ICState::kMonomorphic); BinaryOpFlags flags; ASSERT_EQ(icLookupBinOpMonomorphic(*caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, &flags), *value); ASSERT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kSmallInt, LayoutId::kLargeInt, value, kBinaryOpNone), ICState::kPolymorphic); ASSERT_EQ(icLookupBinOpPolymorphic(*caches, 1, LayoutId::kSmallInt, LayoutId::kLargeInt, &flags), *value); Object new_value(&scope, runtime_->newStrFromCStr("yyy")); EXPECT_EQ(icUpdateBinOp(thread_, caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, new_value, kBinaryOpNone), ICState::kPolymorphic); EXPECT_EQ(icLookupBinOpPolymorphic(*caches, 1, LayoutId::kLargeInt, LayoutId::kSmallInt, &flags), *new_value); } TEST_F(IcTest, ForIterUpdateCacheWithFunctionUpdatesCache) { ASSERT_FALSE(runFromCStr(runtime_, R"( def f(container): for i in container: return i class C: def __iter__(self): return Iterator() class Iterator: def __init__(self): self.next_called = False def __next__(self): if self.next_called: raise StopIteration return 1 container = C() iterator = iter(container) iter_next = Iterator.__next__ result = f(container) )") .isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); EXPECT_TRUE(isIntEqualsWord(*result, 1)); Object iterator(&scope, mainModuleAt(runtime_, "iterator")); Object iter_next(&scope, mainModuleAt(runtime_, "iter_next")); Function f(&scope, mainModuleAt(runtime_, "f")); MutableTuple caches(&scope, f.caches()); // Expect that FOR_ITER is the only cached opcode in f(). ASSERT_EQ(caches.length(), 1 * kIcPointersPerEntry); EXPECT_EQ(icLookupAttr(*caches, 0, iterator.layoutId()), *iter_next); } TEST_F(IcTest, ForIterUpdateCacheWithNonFunctionDoesntUpdateCache) { ASSERT_FALSE(runFromCStr(runtime_, R"( def f(container): for i in container: return i class Iter: def get(self): def next(): return 123 return next __next__ = property(get) class Container: def __iter__(self): return Iter() container = Container() iterator = iter(container) result = f(container) )") .isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); EXPECT_TRUE(isIntEqualsWord(*result, 123)); Object iterator(&scope, mainModuleAt(runtime_, "iterator")); Function f(&scope, mainModuleAt(runtime_, "f")); MutableTuple caches(&scope, f.caches()); // Expect that FOR_ITER is the only cached opcode in f(). ASSERT_EQ(caches.length(), 1 * kIcPointersPerEntry); EXPECT_TRUE(icLookupAttr(*caches, 0, iterator.layoutId()).isErrorNotFound()); } static RawObject testingFunction(Thread* thread) { Runtime* runtime = thread->runtime(); HandleScope scope(thread); Object name(&scope, Str::empty()); Tuple consts(&scope, runtime->emptyTuple()); Tuple names(&scope, runtime->newTupleWith2(name, name)); Code code(&scope, newCodeWithBytesConstsNames(View<byte>(nullptr, 0), consts, names)); MutableBytes rewritten_bytecode( &scope, runtime->newMutableBytesUninitialized(4 * kCodeUnitSize)); rewrittenBytecodeOpAtPut(rewritten_bytecode, 0, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 0, 0); rewrittenBytecodeOpAtPut(rewritten_bytecode, 1, STORE_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 1, 1); rewrittenBytecodeOpAtPut(rewritten_bytecode, 2, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 2, 0); rewrittenBytecodeOpAtPut(rewritten_bytecode, 3, STORE_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 3, 1); Module module(&scope, findMainModule(runtime)); Function function(&scope, runtime->newFunctionWithCode(thread, name, code, module)); function.setRewrittenBytecode(*rewritten_bytecode); MutableTuple caches(&scope, runtime->newMutableTuple(2)); caches.fill(NoneType::object()); function.setCaches(*caches); return *function; } TEST_F(IcTest, IcInsertDependentToValueCellDependencyLinkInsertsDependentAsHead) { HandleScope scope(thread_); Function function0(&scope, testingFunction(thread_)); Function function1(&scope, testingFunction(thread_)); ValueCell cache(&scope, runtime_->newValueCell()); ASSERT_TRUE(cache.dependencyLink().isNoneType()); EXPECT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, function0, cache)); WeakLink link0(&scope, cache.dependencyLink()); EXPECT_EQ(link0.referent(), *function0); EXPECT_TRUE(link0.prev().isNoneType()); EXPECT_TRUE(link0.next().isNoneType()); EXPECT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, function1, cache)); WeakLink link1(&scope, cache.dependencyLink()); EXPECT_EQ(link1.referent(), *function1); EXPECT_TRUE(link1.prev().isNoneType()); EXPECT_EQ(link1.next(), *link0); } TEST_F( IcTest, IcInsertDependentToValueCellDependencyLinkDoesNotInsertExistingDependent) { HandleScope scope(thread_); Function function0(&scope, testingFunction(thread_)); Function function1(&scope, testingFunction(thread_)); ValueCell cache(&scope, runtime_->newValueCell()); EXPECT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, function0, cache)); EXPECT_TRUE( icInsertDependentToValueCellDependencyLink(thread_, function1, cache)); EXPECT_FALSE( icInsertDependentToValueCellDependencyLink(thread_, function0, cache)); WeakLink link(&scope, cache.dependencyLink()); EXPECT_EQ(link.referent(), *function1); EXPECT_TRUE(link.prev().isNoneType()); EXPECT_EQ(WeakLink::cast(link.next()).referent(), *function0); EXPECT_TRUE(WeakLink::cast(link.next()).next().isNoneType()); } TEST_F(IcTest, IcUpdateGlobalVarFillsCacheLineAndReplaceOpcode) { HandleScope scope(thread_); Function function(&scope, testingFunction(thread_)); MutableTuple caches(&scope, function.caches()); MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode()); ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); ValueCell another_cache(&scope, runtime_->newValueCell()); another_cache.setValue(SmallInt::fromWord(123)); icUpdateGlobalVar(thread_, function, 0, cache); EXPECT_EQ(caches.at(0), cache); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 0), LOAD_GLOBAL_CACHED); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 1), STORE_GLOBAL); icUpdateGlobalVar(thread_, function, 1, another_cache); EXPECT_EQ(caches.at(0), cache); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 0), LOAD_GLOBAL_CACHED); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 1), STORE_GLOBAL_CACHED); } TEST_F(IcTest, IcUpdateGlobalVarFillsCacheLineAndReplaceOpcodeWithExtendedArg) { HandleScope scope(thread_); Function function(&scope, testingFunction(thread_)); MutableTuple caches(&scope, function.caches()); MutableBytes rewritten_bytecode( &scope, runtime_->newMutableBytesUninitialized(4 * kCodeUnitSize)); // TODO(T45440363): Replace the argument of EXTENDED_ARG for a non-zero value. rewrittenBytecodeOpAtPut(rewritten_bytecode, 0, EXTENDED_ARG); rewrittenBytecodeArgAtPut(rewritten_bytecode, 0, 0); rewrittenBytecodeOpAtPut(rewritten_bytecode, 1, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 1, 0); rewrittenBytecodeOpAtPut(rewritten_bytecode, 2, EXTENDED_ARG); rewrittenBytecodeArgAtPut(rewritten_bytecode, 2, 0); rewrittenBytecodeOpAtPut(rewritten_bytecode, 3, STORE_GLOBAL); rewrittenBytecodeArgAtPut(rewritten_bytecode, 3, 1); function.setRewrittenBytecode(*rewritten_bytecode); ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); ValueCell another_cache(&scope, runtime_->newValueCell()); another_cache.setValue(SmallInt::fromWord(123)); icUpdateGlobalVar(thread_, function, 0, cache); EXPECT_EQ(caches.at(0), cache); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 1), LOAD_GLOBAL_CACHED); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 3), STORE_GLOBAL); icUpdateGlobalVar(thread_, function, 1, another_cache); EXPECT_EQ(caches.at(0), cache); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 1), LOAD_GLOBAL_CACHED); EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 3), STORE_GLOBAL_CACHED); } TEST_F(IcTest, IcUpdateGlobalVarCreatesDependencyLink) { HandleScope scope(thread_); Function function(&scope, testingFunction(thread_)); ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); icUpdateGlobalVar(thread_, function, 0, cache); ASSERT_TRUE(cache.dependencyLink().isWeakLink()); WeakLink link(&scope, cache.dependencyLink()); EXPECT_EQ(link.referent(), *function); EXPECT_EQ(link.prev(), NoneType::object()); EXPECT_EQ(link.next(), NoneType::object()); } TEST_F(IcTest, IcUpdateGlobalVarInsertsHeadOfDependencyLink) { HandleScope scope(thread_); Function function0(&scope, testingFunction(thread_)); Function function1(&scope, testingFunction(thread_)); // Adds cache into function0's caches first, then to function1's. ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); icUpdateGlobalVar(thread_, function0, 0, cache); icUpdateGlobalVar(thread_, function1, 0, cache); ASSERT_TRUE(cache.dependencyLink().isWeakLink()); WeakLink link(&scope, cache.dependencyLink()); EXPECT_EQ(link.referent(), *function1); EXPECT_TRUE(link.prev().isNoneType()); WeakLink next_link(&scope, link.next()); EXPECT_EQ(next_link.referent(), *function0); EXPECT_EQ(next_link.prev(), *link); EXPECT_TRUE(next_link.next().isNoneType()); } TEST_F(IcTest, IcInvalidateGlobalVarRemovesInvalidatedCacheFromReferencedFunctions) { HandleScope scope(thread_); Function function0(&scope, testingFunction(thread_)); Function function1(&scope, testingFunction(thread_)); MutableTuple caches0(&scope, function0.caches()); MutableTuple caches1(&scope, function1.caches()); // Both caches of Function0 & 1 caches the same cache value. ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); ValueCell another_cache(&scope, runtime_->newValueCell()); another_cache.setValue(SmallInt::fromWord(123)); icUpdateGlobalVar(thread_, function0, 0, cache); icUpdateGlobalVar(thread_, function0, 1, another_cache); icUpdateGlobalVar(thread_, function1, 0, another_cache); icUpdateGlobalVar(thread_, function1, 1, cache); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 0)), 99)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 1)), 123)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 0)), 123)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 1)), 99)); // Invalidating cache makes it removed from both caches, and nobody depends on // it anymore. icInvalidateGlobalVar(thread_, cache); EXPECT_TRUE(icLookupGlobalVar(*caches0, 0).isNoneType()); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 1)), 123)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 0)), 123)); EXPECT_TRUE(icLookupGlobalVar(*caches1, 1).isNoneType()); EXPECT_TRUE(cache.dependencyLink().isNoneType()); } TEST_F(IcTest, IcInvalidateGlobalVarDoNotDeferenceDeallocatedReferent) { HandleScope scope(thread_); Function function0(&scope, testingFunction(thread_)); Function function1(&scope, testingFunction(thread_)); MutableTuple caches0(&scope, function0.caches()); MutableTuple caches1(&scope, function1.caches()); // Both caches of Function0 & 1 caches the same cache value. ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); ValueCell another_cache(&scope, runtime_->newValueCell()); another_cache.setValue(SmallInt::fromWord(123)); icUpdateGlobalVar(thread_, function0, 0, cache); icUpdateGlobalVar(thread_, function0, 1, another_cache); icUpdateGlobalVar(thread_, function1, 0, another_cache); icUpdateGlobalVar(thread_, function1, 1, cache); ASSERT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 0)), 99)); ASSERT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 1)), 123)); ASSERT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 0)), 123)); ASSERT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 1)), 99)); // Simulate GCing function1. WeakLink link(&scope, cache.dependencyLink()); ASSERT_EQ(link.referent(), *function1); link.setReferent(NoneType::object()); // Invalidation cannot touch function1 anymore. icInvalidateGlobalVar(thread_, cache); EXPECT_TRUE(icLookupGlobalVar(*caches0, 0).isNoneType()); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches0, 1)), 123)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 0)), 123)); EXPECT_TRUE( isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches1, 1)), 99)); EXPECT_TRUE(cache.dependencyLink().isNoneType()); } TEST_F(IcTest, IcInvalidateGlobalVarRevertsOpCodeToOriginalOnes) { HandleScope scope(thread_); Function function(&scope, testingFunction(thread_)); MutableBytes bytecode(&scope, function.rewrittenBytecode()); ValueCell cache(&scope, runtime_->newValueCell()); cache.setValue(SmallInt::fromWord(99)); ValueCell another_cache(&scope, runtime_->newValueCell()); another_cache.setValue(SmallInt::fromWord(123)); byte original_expected[] = {LOAD_GLOBAL, 0, 0, 0, STORE_GLOBAL, 1, 0, 0, LOAD_GLOBAL, 0, 0, 0, STORE_GLOBAL, 1, 0, 0}; ASSERT_TRUE(isMutableBytesEqualsBytes(bytecode, original_expected)); icUpdateGlobalVar(thread_, function, 0, cache); byte cached_expected0[] = { LOAD_GLOBAL_CACHED, 0, 0, 0, STORE_GLOBAL, 1, 0, 0, LOAD_GLOBAL_CACHED, 0, 0, 0, STORE_GLOBAL, 1, 0, 0}; EXPECT_TRUE(isMutableBytesEqualsBytes(bytecode, cached_expected0)); icUpdateGlobalVar(thread_, function, 1, another_cache); byte cached_expected1[] = { LOAD_GLOBAL_CACHED, 0, 0, 0, STORE_GLOBAL_CACHED, 1, 0, 0, LOAD_GLOBAL_CACHED, 0, 0, 0, STORE_GLOBAL_CACHED, 1, 0, 0}; EXPECT_TRUE(isMutableBytesEqualsBytes(bytecode, cached_expected1)); icInvalidateGlobalVar(thread_, cache); // Only invalidated cache's opcode gets reverted to the original one. byte invalidated_expected[] = { LOAD_GLOBAL, 0, 0, 0, STORE_GLOBAL_CACHED, 1, 0, 0, LOAD_GLOBAL, 0, 0, 0, STORE_GLOBAL_CACHED, 1, 0, 0, }; EXPECT_TRUE(isMutableBytesEqualsBytes(bytecode, invalidated_expected)); } TEST_F(IcTest, IcIteratorIteratesOverAttrCaches) { HandleScope scope(thread_); MutableBytes bytecode( &scope, runtime_->newMutableBytesUninitialized(10 * kCodeUnitSize)); rewrittenBytecodeOpAtPut(bytecode, 0, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 0, 100); rewrittenBytecodeCacheAtPut(bytecode, 0, 0); rewrittenBytecodeOpAtPut(bytecode, 1, LOAD_ATTR_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 1, 0); rewrittenBytecodeCacheAtPut(bytecode, 1, 0); rewrittenBytecodeOpAtPut(bytecode, 2, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 2, 100); rewrittenBytecodeCacheAtPut(bytecode, 2, 0); rewrittenBytecodeOpAtPut(bytecode, 3, LOAD_METHOD_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 3, 1); rewrittenBytecodeCacheAtPut(bytecode, 3, 1); rewrittenBytecodeOpAtPut(bytecode, 4, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 4, 100); rewrittenBytecodeCacheAtPut(bytecode, 4, 0); rewrittenBytecodeOpAtPut(bytecode, 5, LOAD_ATTR_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 5, 2); rewrittenBytecodeCacheAtPut(bytecode, 5, 2); rewrittenBytecodeOpAtPut(bytecode, 6, STORE_ATTR_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 6, 3); rewrittenBytecodeCacheAtPut(bytecode, 6, 3); rewrittenBytecodeOpAtPut(bytecode, 7, FOR_ITER_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 7, -1); rewrittenBytecodeCacheAtPut(bytecode, 7, 4); rewrittenBytecodeOpAtPut(bytecode, 8, BINARY_SUBSCR_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 8, -1); rewrittenBytecodeCacheAtPut(bytecode, 8, 5); rewrittenBytecodeOpAtPut(bytecode, 9, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 9, 100); rewrittenBytecodeCacheAtPut(bytecode, 9, 0); word num_caches = 6; Object name1(&scope, Runtime::internStrFromCStr( thread_, "load_attr_cached_attr_name")); Object name2(&scope, Runtime::internStrFromCStr( thread_, "load_method_cached_attr_name")); Object name3(&scope, Runtime::internStrFromCStr( thread_, "load_attr_cached_attr_name2")); Object name4(&scope, Runtime::internStrFromCStr( thread_, "store_attr_cached_attr_name")); Tuple names(&scope, runtime_->newTupleWith4(name1, name2, name3, name4)); Object name(&scope, runtime_->newStrFromCStr("name")); Function dependent(&scope, newEmptyFunction()); Object value(&scope, NoneType::object()); MutableTuple caches( &scope, runtime_->newMutableTuple(num_caches * kIcPointersPerEntry)); caches.fill(NoneType::object()); // Caches for LOAD_ATTR_ANAMORPHIC at PC 2. value = SmallInt::fromWord(10); icUpdateAttr(thread_, caches, 0, LayoutId::kBool, value, name, dependent); value = SmallInt::fromWord(20); icUpdateAttr(thread_, caches, 0, LayoutId::kSmallInt, value, name, dependent); // Caches for LOAD_METHOD_ANAMORPHIC at PC 6. value = SmallInt::fromWord(30); icUpdateAttr(thread_, caches, 1, LayoutId::kSmallInt, value, name, dependent); // Caches are empty for LOAD_ATTR_ANAMORPHIC at PC 10. // Caches for STORE_ATTR_ANAMORPHIC at PC 12. value = SmallInt::fromWord(40); icUpdateAttr(thread_, caches, 3, LayoutId::kNoneType, value, name, dependent); // Caches for FOR_ITER_ANAMORPHIC at PC 14. value = SmallInt::fromWord(50); icUpdateAttr(thread_, caches, 4, LayoutId::kStr, value, name, dependent); // Caches for BINARY_SUBSCR_ANAMORPHIC at PC 16. value = SmallInt::fromWord(60); icUpdateAttr(thread_, caches, 5, LayoutId::kTuple, value, name, dependent); Function function(&scope, newEmptyFunction()); function.setRewrittenBytecode(*bytecode); function.setCaches(*caches); Code::cast(function.code()).setNames(*names); IcIterator it(&scope, runtime_, *function); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); Object load_attr_cached_attr_name( &scope, Runtime::internStrFromCStr(thread_, "load_attr_cached_attr_name")); EXPECT_TRUE(it.isAttrNameEqualTo(load_attr_cached_attr_name)); EXPECT_EQ(it.layoutId(), LayoutId::kBool); EXPECT_TRUE(it.isInstanceAttr()); it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); EXPECT_TRUE(it.isAttrNameEqualTo(load_attr_cached_attr_name)); EXPECT_EQ(it.layoutId(), LayoutId::kSmallInt); EXPECT_TRUE(it.isInstanceAttr()); it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); Object load_method_cached_attr_name( &scope, Runtime::internStrFromCStr(thread_, "load_method_cached_attr_name")); EXPECT_TRUE(it.isAttrNameEqualTo(load_method_cached_attr_name)); EXPECT_EQ(it.layoutId(), SmallInt::fromWord(100).layoutId()); EXPECT_TRUE(it.isInstanceAttr()); it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); Object store_attr_cached_attr_name( &scope, Runtime::internStrFromCStr(thread_, "store_attr_cached_attr_name")); EXPECT_TRUE(it.isAttrNameEqualTo(store_attr_cached_attr_name)); EXPECT_EQ(it.layoutId(), NoneType::object().layoutId()); EXPECT_TRUE(it.isInstanceAttr()); ASSERT_EQ( caches.at(3 * kIcPointersPerEntry + kIcEntryKeyOffset), SmallInt::fromWord(static_cast<word>(NoneType::object().layoutId()))); ASSERT_FALSE( caches.at(3 * kIcPointersPerEntry + kIcEntryValueOffset).isNoneType()); it.evict(); EXPECT_TRUE( caches.at(3 * kIcPointersPerEntry + kIcEntryKeyOffset).isNoneType()); EXPECT_TRUE( caches.at(3 * kIcPointersPerEntry + kIcEntryValueOffset).isNoneType()); it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); Object for_iter_cached_attr_name( &scope, Runtime::internStrFromCStr(thread_, "__next__")); EXPECT_TRUE(it.isAttrNameEqualTo(for_iter_cached_attr_name)); EXPECT_EQ(it.layoutId(), LayoutId::kStr); EXPECT_TRUE(it.isInstanceAttr()); it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isAttrCache()); EXPECT_FALSE(it.isBinaryOpCache()); Object binary_subscr_cached_attr_name( &scope, Runtime::internStrFromCStr(thread_, "__getitem__")); EXPECT_TRUE(it.isAttrNameEqualTo(binary_subscr_cached_attr_name)); EXPECT_EQ(it.layoutId(), LayoutId::kTuple); EXPECT_TRUE(it.isInstanceAttr()); it.next(); EXPECT_FALSE(it.hasNext()); } TEST_F(IcTest, IcIteratorIteratesOverBinaryOpCaches) { HandleScope scope(thread_); MutableBytes bytecode( &scope, runtime_->newMutableBytesUninitialized(4 * kCodeUnitSize)); rewrittenBytecodeOpAtPut(bytecode, 0, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 0, 100); rewrittenBytecodeCacheAtPut(bytecode, 0, 0); rewrittenBytecodeOpAtPut(bytecode, 1, COMPARE_OP_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 1, CompareOp::GE); rewrittenBytecodeCacheAtPut(bytecode, 1, 0); rewrittenBytecodeOpAtPut(bytecode, 2, BINARY_OP_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 2, static_cast<word>(Interpreter::BinaryOp::ADD)); rewrittenBytecodeCacheAtPut(bytecode, 2, 1); rewrittenBytecodeOpAtPut(bytecode, 3, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 3, 100); rewrittenBytecodeCacheAtPut(bytecode, 3, 0); word num_caches = 2; MutableTuple caches( &scope, runtime_->newMutableTuple(num_caches * kIcPointersPerEntry)); // Caches for COMPARE_OP_ANAMORPHIC at 2. word compare_op_cached_index = 0 * kIcPointersPerEntry + 0 * kIcPointersPerEntry; word compare_op_key_high_bits = static_cast<word>(SmallInt::fromWord(0).layoutId()) << Header::kLayoutIdBits | static_cast<word>(SmallStr::fromCStr("test").layoutId()); caches.atPut(compare_op_cached_index + kIcEntryKeyOffset, SmallInt::fromWord(compare_op_key_high_bits << kBitsPerByte | static_cast<word>(kBinaryOpReflected))); caches.atPut(compare_op_cached_index + kIcEntryValueOffset, SmallInt::fromWord(50)); // Caches for BINARY_OP_ANAMORPHIC at 4. word binary_op_cached_index = 1 * kIcPointersPerEntry + 0 * kIcPointersPerEntry; word binary_op_key_high_bits = static_cast<word>(SmallStr::fromCStr("").layoutId()) << Header::kLayoutIdBits | static_cast<word>(SmallInt::fromWord(0).layoutId()); caches.atPut(binary_op_cached_index + kIcEntryKeyOffset, SmallInt::fromWord(binary_op_key_high_bits << kBitsPerByte | static_cast<word>(kBinaryOpReflected))); caches.atPut(binary_op_cached_index + kIcEntryValueOffset, SmallInt::fromWord(60)); Function function(&scope, newEmptyFunction()); function.setRewrittenBytecode(*bytecode); function.setCaches(*caches); IcIterator it(&scope, runtime_, *function); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isBinaryOpCache()); EXPECT_FALSE(it.isAttrCache()); EXPECT_EQ(it.leftLayoutId(), SmallInt::fromWord(-1).layoutId()); EXPECT_EQ(it.rightLayoutId(), SmallStr::fromCStr("").layoutId()); { Object left_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__ge__")); EXPECT_EQ(left_operator_name, it.leftMethodName()); Object right_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__le__")); EXPECT_EQ(right_operator_name, it.rightMethodName()); } it.next(); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isBinaryOpCache()); EXPECT_FALSE(it.isAttrCache()); EXPECT_EQ(it.leftLayoutId(), SmallStr::fromCStr("").layoutId()); EXPECT_EQ(it.rightLayoutId(), SmallInt::fromWord(-1).layoutId()); { Object left_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__add__")); EXPECT_EQ(left_operator_name, it.leftMethodName()); Object right_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__radd__")); EXPECT_EQ(right_operator_name, it.rightMethodName()); } it.next(); EXPECT_FALSE(it.hasNext()); } TEST_F(IcTest, IcIteratorIteratesOverInplaceOpCaches) { HandleScope scope(thread_); MutableBytes bytecode( &scope, runtime_->newMutableBytesUninitialized(4 * kCodeUnitSize)); rewrittenBytecodeOpAtPut(bytecode, 0, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 0, 100); rewrittenBytecodeCacheAtPut(bytecode, 0, 0); rewrittenBytecodeOpAtPut(bytecode, 1, INPLACE_OP_ANAMORPHIC); rewrittenBytecodeArgAtPut(bytecode, 1, static_cast<word>(Interpreter::BinaryOp::MUL)); rewrittenBytecodeCacheAtPut(bytecode, 1, 0); rewrittenBytecodeOpAtPut(bytecode, 2, LOAD_GLOBAL); rewrittenBytecodeArgAtPut(bytecode, 2, 100); rewrittenBytecodeCacheAtPut(bytecode, 2, 0); word num_caches = 1; MutableTuple caches( &scope, runtime_->newMutableTuple(num_caches * kIcPointersPerEntry)); // Caches for BINARY_OP_ANAMORPHIC at 2. word inplace_op_cached_index = 0 * kIcPointersPerEntry + 0 * kIcPointersPerEntry; word inplace_op_key_high_bits = static_cast<word>(SmallStr::fromCStr("a").layoutId()) << Header::kLayoutIdBits | static_cast<word>(SmallInt::fromWord(3).layoutId()); caches.atPut(inplace_op_cached_index + kIcEntryKeyOffset, SmallInt::fromWord(inplace_op_key_high_bits << kBitsPerByte | static_cast<word>(kBinaryOpReflected))); caches.atPut(inplace_op_cached_index + kIcEntryValueOffset, SmallInt::fromWord(70)); Function function(&scope, newEmptyFunction()); function.setRewrittenBytecode(*bytecode); function.setCaches(*caches); IcIterator it(&scope, runtime_, *function); ASSERT_TRUE(it.hasNext()); ASSERT_TRUE(it.isInplaceOpCache()); EXPECT_FALSE(it.isBinaryOpCache()); EXPECT_FALSE(it.isAttrCache()); EXPECT_EQ(it.leftLayoutId(), SmallStr::fromCStr("").layoutId()); EXPECT_EQ(it.rightLayoutId(), SmallInt::fromWord(-1).layoutId()); { Object inplace_operator_name( &scope, Runtime::internStrFromCStr(thread_, "__imul__")); EXPECT_EQ(inplace_operator_name, it.inplaceMethodName()); Object left_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__mul__")); EXPECT_EQ(left_operator_name, it.leftMethodName()); Object right_operator_name(&scope, Runtime::internStrFromCStr(thread_, "__rmul__")); EXPECT_EQ(right_operator_name, it.rightMethodName()); } it.next(); EXPECT_FALSE(it.hasNext()); } TEST_F(IcTest, IcRemoveDeadWeakLinksRemoveRemovesDeadHead) { HandleScope scope(thread_); ValueCell value_cell(&scope, runtime_->newValueCell()); Object dependent1(&scope, newTupleWithNone(1)); Object dependent2(&scope, newTupleWithNone(2)); Object dependent3(&scope, newTupleWithNone(3)); icInsertDependentToValueCellDependencyLink(thread_, dependent1, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent2, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent3, value_cell); // The dependenty link looks like dependent3 -> dependent2 -> dependent1. // Kill dependent3. WeakLink head(&scope, value_cell.dependencyLink()); head.setReferent(NoneType::object()); icRemoveDeadWeakLinks(*value_cell); ASSERT_TRUE(value_cell.dependencyLink().isWeakLink()); WeakLink new_head(&scope, value_cell.dependencyLink()); EXPECT_EQ(new_head.referent(), *dependent2); EXPECT_TRUE(new_head.prev().isNoneType()); WeakLink new_next(&scope, new_head.next()); EXPECT_EQ(new_next.referent(), *dependent1); EXPECT_EQ(new_next.prev(), *new_head); EXPECT_TRUE(new_next.next().isNoneType()); } TEST_F(IcTest, IcRemoveDeadWeakLinksRemoveRemovesDeadMiddleNode) { HandleScope scope(thread_); ValueCell value_cell(&scope, runtime_->newValueCell()); Object dependent1(&scope, newTupleWithNone(1)); Object dependent2(&scope, newTupleWithNone(2)); Object dependent3(&scope, newTupleWithNone(3)); icInsertDependentToValueCellDependencyLink(thread_, dependent1, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent2, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent3, value_cell); // The dependenty link looks like dependent3 -> dependent2 -> dependent1. WeakLink head(&scope, value_cell.dependencyLink()); // Kill dependent2. WeakLink next(&scope, head.next()); next.setReferent(NoneType::object()); icRemoveDeadWeakLinks(*value_cell); ASSERT_TRUE(value_cell.dependencyLink().isWeakLink()); WeakLink new_head(&scope, value_cell.dependencyLink()); EXPECT_EQ(new_head.referent(), *dependent3); EXPECT_TRUE(new_head.prev().isNoneType()); WeakLink new_next(&scope, new_head.next()); EXPECT_EQ(new_next.referent(), *dependent1); EXPECT_EQ(new_next.prev(), *new_head); EXPECT_TRUE(new_next.next().isNoneType()); } TEST_F(IcTest, IcRemoveDeadWeakLinksRemoveRemovesDeadTailNode) { HandleScope scope(thread_); ValueCell value_cell(&scope, runtime_->newValueCell()); Object dependent1(&scope, newTupleWithNone(1)); Object dependent2(&scope, newTupleWithNone(2)); Object dependent3(&scope, newTupleWithNone(3)); icInsertDependentToValueCellDependencyLink(thread_, dependent1, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent2, value_cell); icInsertDependentToValueCellDependencyLink(thread_, dependent3, value_cell); // The dependenty link looks like dependent3 -> dependent2 -> dependent1. WeakLink head(&scope, value_cell.dependencyLink()); // Kill dependent1. WeakLink next_next(&scope, WeakLink::cast(head.next()).next()); next_next.setReferent(NoneType::object()); icRemoveDeadWeakLinks(*value_cell); ASSERT_TRUE(value_cell.dependencyLink().isWeakLink()); WeakLink new_head(&scope, value_cell.dependencyLink()); EXPECT_EQ(new_head.referent(), *dependent3); EXPECT_TRUE(new_head.prev().isNoneType()); WeakLink new_next(&scope, new_head.next()); EXPECT_EQ(new_next.referent(), *dependent2); EXPECT_EQ(new_next.prev(), *new_head); EXPECT_TRUE(new_next.next().isNoneType()); } TEST_F(IcTest, EncodeBinaryOpKeyEntryReturnsKeyAccessedByLookupBinOpMonomorphic) { HandleScope scope(thread_); SmallInt entry_key(&scope, encodeBinaryOpKey(LayoutId::kStr, LayoutId::kInt, kBinaryOpReflected)); Object entry_value(&scope, runtime_->newStrFromCStr("value")); MutableTuple caches(&scope, runtime_->newMutableTuple(kIcPointersPerEntry)); caches.fill(NoneType::object()); caches.atPut(kIcEntryKeyOffset, *entry_key); caches.atPut(kIcEntryValueOffset, *entry_value); BinaryOpFlags flags_out; Object result(&scope, icLookupBinOpMonomorphic(*caches, 0, LayoutId::kStr, LayoutId::kInt, &flags_out)); EXPECT_EQ(result, entry_value); EXPECT_TRUE(icLookupBinOpMonomorphic(*caches, 0, LayoutId::kInt, LayoutId::kStr, &flags_out) .isErrorNotFound()); } TEST_F( IcTest, IcInvalidateAttrWithDunderFunctionsUpdatesCorrespondingAttributeTypeFlags) { HandleScope scope(thread_); ASSERT_FALSE(runFromCStr(runtime_, R"( class A: pass class B(A): pass class C(B): pass class X: pass class D(X, C): pass class E(D): pass def custom_getattribute(self, name): return "bogus" object_getattribute = object.__getattribute__ )") .isError()); Type a(&scope, mainModuleAt(runtime_, "A")); Type b(&scope, mainModuleAt(runtime_, "B")); Type c(&scope, mainModuleAt(runtime_, "C")); Type d(&scope, mainModuleAt(runtime_, "D")); Type e(&scope, mainModuleAt(runtime_, "E")); Type x(&scope, mainModuleAt(runtime_, "X")); ASSERT_TRUE(a.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); ASSERT_TRUE(b.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); ASSERT_TRUE(c.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); ASSERT_TRUE(d.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); ASSERT_TRUE(e.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); ASSERT_TRUE(x.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); Object custom_getattribute(&scope, mainModuleAt(runtime_, "custom_getattribute")); typeAtPutById(thread_, c, ID(__getattribute__), custom_getattribute); EXPECT_TRUE(a.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(b.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_FALSE(c.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_FALSE(d.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_FALSE(e.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(x.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); Object object_getattribute(&scope, mainModuleAt(runtime_, "object_getattribute")); typeAtPutById(thread_, c, ID(__getattribute__), object_getattribute); EXPECT_TRUE(a.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(b.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(c.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(d.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(e.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); EXPECT_TRUE(x.hasFlag(Type::Flag::kHasObjectDunderGetattribute)); } } // namespace testing } // namespace py