Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions runtime/ic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,24 @@ ICState icUpdateAttr(Thread* thread, const MutableTuple& caches, word cache,
}
RawMutableTuple polymorphic_cache =
MutableTuple::cast(caches.at(index + kIcEntryValueOffset));
bool found = false;
for (word j = 0; j < kIcPointersPerPolyCache; j += kIcPointersPerEntry) {
entry_key = polymorphic_cache.at(j + kIcEntryKeyOffset);
if (entry_key.isNoneType() || entry_key == key) {
polymorphic_cache.atPut(j + kIcEntryKeyOffset, key);
polymorphic_cache.atPut(j + kIcEntryValueOffset, *value);
insertDependencyForTypeLookupInMro(thread, layout_id, name, dependent);
found = true;
break;
}
}
if (!found) {
// Start over. Empty the cache.
polymorphic_cache.fill(NoneType::object());
polymorphic_cache.atPut(kIcEntryKeyOffset, key);
polymorphic_cache.atPut(kIcEntryValueOffset, *value);
insertDependencyForTypeLookupInMro(thread, layout_id, name, dependent);
}
return ICState::kPolymorphic;
}

Expand Down Expand Up @@ -727,15 +736,23 @@ ICState icUpdateBinOp(Thread* thread, const MutableTuple& caches, word cache,
}
RawMutableTuple polymorphic_cache =
MutableTuple::cast(caches.at(index + kIcEntryValueOffset));
bool found = false;
for (word j = 0; j < kIcPointersPerPolyCache; j += kIcPointersPerEntry) {
entry_key = polymorphic_cache.at(j + kIcEntryKeyOffset);
if (entry_key.isNoneType() ||
SmallInt::cast(entry_key).value() >> kBitsPerByte == key_high_bits) {
polymorphic_cache.atPut(j + kIcEntryKeyOffset, new_key);
polymorphic_cache.atPut(j + kIcEntryValueOffset, *value);
found = true;
break;
}
}
if (!found) {
// Start over. Empty the cache.
polymorphic_cache.fill(NoneType::object());
polymorphic_cache.atPut(kIcEntryKeyOffset, new_key);
polymorphic_cache.atPut(kIcEntryValueOffset, *value);
}
return ICState::kPolymorphic;
}

Expand Down
209 changes: 209 additions & 0 deletions runtime/interpreter-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6664,6 +6664,215 @@ c = C()
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_ATTR_INSTANCE_PROPERTY);
}

TEST_F(InterpreterTest, LoadAttrPolymorphicRewritesEntries) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def __init__(self):
self.foo = 400

class B(A):
pass

class C(A):
pass

class D(A):
pass

class E(A):
pass

def cache_attribute(c):
return c.foo

a = A()
b = B()
c = C()
d = D()
e = E()
)")
.isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Object d(&scope, mainModuleAt(runtime_, "d"));
Object e(&scope, mainModuleAt(runtime_, "e"));
Function cache_attribute(&scope, mainModuleAt(runtime_, "cache_attribute"));
MutableTuple caches(&scope, cache_attribute.caches());
ASSERT_EQ(caches.length(), 2 * kIcPointersPerEntry);

// Load the cache for `a'.
ASSERT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isErrorNotFound());
ASSERT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isErrorNotFound());
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isErrorNotFound());
ASSERT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isErrorNotFound());
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, a), 400));
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isErrorNotFound());

// Load the cache for `b'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, b), 400));
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isErrorNotFound());

// Load the cache for `c'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, c), 400));
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isErrorNotFound());

// Load the cache for `d'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, d), 400));
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isSmallInt());
EXPECT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isErrorNotFound());

// Empty the cache and add `e'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, e), 400));
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, b.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, d.layoutId()).isErrorNotFound());
EXPECT_TRUE(icLookupAttr(*caches, 1, e.layoutId()).isSmallInt());
}

TEST_F(InterpreterTest, BinOpPolymorphicRewritesEntries) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def __add__(self, other):
return 123

class B(A):
pass

class C(A):
pass

class D(A):
pass

class E(A):
pass

def cache_attribute(c):
return c + c

a = A()
b = B()
c = C()
d = D()
e = E()
)")
.isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Object d(&scope, mainModuleAt(runtime_, "d"));
Object e(&scope, mainModuleAt(runtime_, "e"));
Function cache_attribute(&scope, mainModuleAt(runtime_, "cache_attribute"));
MutableTuple caches(&scope, cache_attribute.caches());
ASSERT_EQ(caches.length(), kIcPointersPerEntry);
BinaryOpFlags flags;

// Load the cache for `a'.
ASSERT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isErrorNotFound());
ASSERT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isErrorNotFound());
ASSERT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isErrorNotFound());
ASSERT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isErrorNotFound());
ASSERT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isErrorNotFound());
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, a), 123));
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isErrorNotFound());

// Load the cache for `b'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, b), 123));
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isErrorNotFound());

// Load the cache for `c'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, c), 123));
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isErrorNotFound());

// Load the cache for `d'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, d), 123));
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isFunction());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isErrorNotFound());

// Empty the cache and add `e'.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, e), 123));
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, a.layoutId(), a.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, b.layoutId(), b.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, c.layoutId(), c.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, d.layoutId(), d.layoutId(), &flags)
.isErrorNotFound());
EXPECT_TRUE(icLookupBinaryOp(*caches, 0, e.layoutId(), e.layoutId(), &flags)
.isFunction());
}

TEST_F(InterpreterTest, StoreAttrCachedInsertsExecutingFunctionAsDependent) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
Expand Down
4 changes: 2 additions & 2 deletions util/probes/ic-miss.bt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
usdt:./build-debugopt/bin/python:python:InvalidateInlineCache_* {
@ic_miss[ustack()]++;
usdt:./build/bin/python:python:InvalidateInlineCache_* {
@ic_miss[probe]++;
}