runtime/interpreter-test.cpp (7,746 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "interpreter.h"
#include <memory>
#include "gtest/gtest.h"
#include "attributedict.h"
#include "builtins-module.h"
#include "bytecode.h"
#include "dict-builtins.h"
#include "handles.h"
#include "ic.h"
#include "list-builtins.h"
#include "module-builtins.h"
#include "modules.h"
#include "object-builtins.h"
#include "objects.h"
#include "runtime.h"
#include "str-builtins.h"
#include "test-utils.h"
#include "trampolines.h"
#include "type-builtins.h"
namespace py {
namespace testing {
using InterpreterDeathTest = RuntimeFixture;
using InterpreterTest = RuntimeFixture;
using JitTest = RuntimeFixture;
TEST_F(InterpreterTest, IsTrueBool) {
HandleScope scope(thread_);
Object true_value(&scope, Bool::trueObj());
EXPECT_EQ(Interpreter::isTrue(thread_, *true_value), Bool::trueObj());
Object false_object(&scope, Bool::falseObj());
EXPECT_EQ(Interpreter::isTrue(thread_, *false_object), Bool::falseObj());
}
TEST_F(InterpreterTest, IsTrueInt) {
HandleScope scope(thread_);
Object true_value(&scope, runtime_->newInt(1234));
EXPECT_EQ(Interpreter::isTrue(thread_, *true_value), Bool::trueObj());
Object false_value(&scope, runtime_->newInt(0));
EXPECT_EQ(Interpreter::isTrue(thread_, *false_value), Bool::falseObj());
}
TEST_F(InterpreterTest, IsTrueWithDunderBoolRaisingPropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __bool__(self):
raise UserWarning('')
value = Foo()
)")
.isError());
Object value(&scope, mainModuleAt(runtime_, "value"));
Object result(&scope, Interpreter::isTrue(thread_, *value));
EXPECT_TRUE(raised(*result, LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, IsTrueWithDunderLenRaisingPropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __len__(self):
raise UserWarning('')
value = Foo()
)")
.isError());
Object value(&scope, mainModuleAt(runtime_, "value"));
Object result(&scope, Interpreter::isTrue(thread_, *value));
EXPECT_TRUE(raised(*result, LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, IsTrueWithIntSubclassDunderLenUsesBaseInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo(int): pass
class Bar:
def __init__(self, length):
self.length = Foo(length)
def __len__(self):
return self.length
true_value = Bar(10)
false_value = Bar(0)
)")
.isError());
Object true_value(&scope, mainModuleAt(runtime_, "true_value"));
Object false_value(&scope, mainModuleAt(runtime_, "false_value"));
EXPECT_EQ(Interpreter::isTrue(thread_, *true_value), Bool::trueObj());
EXPECT_EQ(Interpreter::isTrue(thread_, *false_value), Bool::falseObj());
}
TEST_F(InterpreterTest, IsTrueDunderLen) {
HandleScope scope(thread_);
List nonempty_list(&scope, runtime_->newList());
Object elt(&scope, NoneType::object());
runtime_->listAdd(thread_, nonempty_list, elt);
EXPECT_EQ(Interpreter::isTrue(thread_, *nonempty_list), Bool::trueObj());
List empty_list(&scope, runtime_->newList());
EXPECT_EQ(Interpreter::isTrue(thread_, *empty_list), Bool::falseObj());
}
TEST_F(InterpreterTest, UnaryOperationWithIntReturnsInt) {
HandleScope scope(thread_);
Object value(&scope, runtime_->newInt(23));
Object result(&scope,
Interpreter::unaryOperation(thread_, value, ID(__pos__)));
EXPECT_TRUE(isIntEqualsWord(*result, 23));
}
TEST_F(InterpreterTest, UnaryOperationWithBadTypeRaisesTypeError) {
HandleScope scope(thread_);
Object value(&scope, NoneType::object());
Object result(&scope,
Interpreter::unaryOperation(thread_, value, ID(__invert__)));
EXPECT_TRUE(
raisedWithStr(*result, LayoutId::kTypeError,
"bad operand type for unary '__invert__': 'NoneType'"));
}
TEST_F(InterpreterTest, UnaryOperationWithCustomDunderInvertReturnsString) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __invert__(self):
return "custom invert"
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object result(&scope,
Interpreter::unaryOperation(thread_, c, ID(__invert__)));
EXPECT_TRUE(isStrEqualsCStr(*result, "custom invert"));
}
TEST_F(InterpreterTest, UnaryOperationWithCustomRaisingDunderNegPropagates) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __neg__(self):
raise UserWarning('')
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object result(&scope, Interpreter::unaryOperation(thread_, c, ID(__neg__)));
EXPECT_TRUE(raised(*result, LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, UnaryNotWithRaisingDunderBool) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C:
def __bool__(self):
raise RuntimeError("too cool for bool")
not C()
)"),
LayoutId::kRuntimeError, "too cool for bool"));
}
TEST_F(InterpreterTest, BinaryOpCachedInsertsDependencyForBothOperandsTypes) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def __add__(self, other):
return "from class A"
class B:
pass
def cache_binary_op(a, b):
return a + b
a = A()
b = B()
A__add__ = A.__add__
result = cache_binary_op(a, b)
)")
.isError());
ASSERT_TRUE(
isStrEqualsCStr(mainModuleAt(runtime_, "result"), "from class A"));
Function cache_binary_op(&scope, mainModuleAt(runtime_, "cache_binary_op"));
MutableTuple caches(&scope, cache_binary_op.caches());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Type type_a(&scope, mainModuleAt(runtime_, "A"));
Type type_b(&scope, mainModuleAt(runtime_, "B"));
BinaryOpFlags flag;
ASSERT_EQ(icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flag),
mainModuleAt(runtime_, "A__add__"));
// Verify that A.__add__ has the dependent.
Object left_op_name(&scope, runtime_->symbols()->at(ID(__add__)));
Object type_a_attr(&scope, typeValueCellAt(*type_a, *left_op_name));
ASSERT_TRUE(type_a_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*type_a_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*type_a_attr).dependencyLink()).referent(),
*cache_binary_op);
// Verify that B.__radd__ has the dependent.
Object right_op_name(&scope, runtime_->symbols()->at(ID(__radd__)));
Object type_b_attr(&scope, typeValueCellAt(*type_b, *right_op_name));
ASSERT_TRUE(type_b_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*type_b_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*type_b_attr).dependencyLink()).referent(),
*cache_binary_op);
}
TEST_F(InterpreterTest, BinaryOpInvokesSelfMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __sub__(self, other):
return (C, '__sub__', self, other)
left = C()
right = C()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object c_class(&scope, mainModuleAt(runtime_, "C"));
Object result_obj(
&scope, Interpreter::binaryOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *c_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__sub__"));
EXPECT_EQ(result.at(2), *left);
EXPECT_EQ(result.at(3), *right);
}
TEST_F(InterpreterTest, BinaryOpInvokesSelfMethodIgnoresReflectedMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __sub__(self, other):
return (C, '__sub__', self, other)
def __rsub__(self, other):
return (C, '__rsub__', self, other)
left = C()
right = C()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object c_class(&scope, mainModuleAt(runtime_, "C"));
Object result_obj(
&scope, Interpreter::binaryOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *c_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__sub__"));
EXPECT_EQ(result.at(2), *left);
EXPECT_EQ(result.at(3), *right);
}
TEST_F(InterpreterTest, BinaryOperationInvokesSubclassReflectedMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __sub__(self, other):
return (C, '__sub__', self, other)
class D(C):
def __rsub__(self, other):
return (D, '__rsub__', self, other)
left = C()
right = D()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object d_class(&scope, mainModuleAt(runtime_, "D"));
Object result_obj(
&scope, Interpreter::binaryOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *d_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__rsub__"));
EXPECT_EQ(result.at(2), *right);
EXPECT_EQ(result.at(3), *left);
}
TEST_F(InterpreterTest, BinaryOperationInvokesOtherReflectedMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
pass
class D:
def __rsub__(self, other):
return (D, '__rsub__', self, other)
left = C()
right = D()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object d_class(&scope, mainModuleAt(runtime_, "D"));
Object result_obj(
&scope, Interpreter::binaryOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *d_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__rsub__"));
EXPECT_EQ(result.at(2), *right);
EXPECT_EQ(result.at(3), *left);
}
TEST_F(
InterpreterTest,
BinaryOperationInvokesLeftMethodWhenReflectedMethodReturnsNotImplemented) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
trace = ""
class C:
def __add__(self, other):
global trace
trace += "C.__add__,"
return "C.__add__"
def __radd__(self, other):
raise Exception("should not be called")
class D(C):
def __add__(self, other):
raise Exception("should not be called")
def __radd__(self, other):
global trace
trace += "D.__radd__,"
return NotImplemented
result = C() + D()
)")
.isError());
EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "C.__add__"));
EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "trace"),
"D.__radd__,C.__add__,"));
}
TEST_F(InterpreterTest, BinaryOperationLookupPropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class RaisingDescriptor:
def __get__(self, obj, type):
raise UserWarning()
class A:
__mul__ = RaisingDescriptor()
a = A()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object result(&scope, Interpreter::binaryOperation(
thread_, Interpreter::BinaryOp::MUL, a, a));
EXPECT_TRUE(raised(*result, LayoutId::kUserWarning));
}
TEST_F(InterpreterTest,
BinaryOperationLookupReflectedMethodPropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class RaisingDescriptor:
def __get__(self, obj, type):
raise UserWarning()
class A:
def __mul__(self, other):
return 42
class B(A):
__rmul__ = RaisingDescriptor()
a = A()
b = B()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object result(&scope, Interpreter::binaryOperation(
thread_, Interpreter::BinaryOp::MUL, a, b));
EXPECT_TRUE(raised(*result, LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, BinaryOperationSetMethodSetsMethod) {
HandleScope scope(thread_);
Object v0(&scope, runtime_->newInt(13));
Object v1(&scope, runtime_->newInt(42));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationSetMethod(thread_, Interpreter::BinaryOp::SUB,
v0, v1, &method, &flags),
-29));
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpNotImplementedRetry);
Object v2(&scope, runtime_->newInt(3));
Object v3(&scope, runtime_->newInt(8));
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
-5));
}
TEST_F(InterpreterTest,
BinaryOperationSetMethodSetsReflectedMethodNotImplementedRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __init__(self, x):
self.x = x
def __sub__(self, other):
raise UserWarning("should not be called")
class ASub(A):
def __rsub__(self, other):
return (self, other)
v0 = A(3)
v1 = ASub(7)
v2 = A(8)
v3 = ASub(2)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
Object result_obj(&scope, Interpreter::binaryOperationSetMethod(
thread_, Interpreter::BinaryOp::SUB, v0, v1,
&method, &flags));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), v1);
EXPECT_EQ(result.at(1), v0);
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpReflected | kBinaryOpNotImplementedRetry);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
result_obj =
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3);
ASSERT_TRUE(result.isTuple());
result = *result_obj;
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), v3);
EXPECT_EQ(result.at(1), v2);
}
TEST_F(InterpreterTest, BinaryOperationSetMethodSetsReflectedMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __init__(self, x):
self.x = x
class B:
def __init__(self, x):
self.x = x
def __rsub__(self, other):
return other.x - self.x
v0 = A(-4)
v1 = B(8)
v2 = A(33)
v3 = B(-12)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationSetMethod(thread_, Interpreter::BinaryOp::SUB,
v0, v1, &method, &flags),
-12));
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpReflected);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
45));
}
TEST_F(InterpreterTest, BinaryOperationSetMethodSetsMethodNotImplementedRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __init__(self, x):
self.x = x
def __sub__(self, other):
return other.x - self.x
class B:
def __init__(self, x):
self.x = x
def __rsub__(self, other):
return self.x - other.x
v0 = A(4)
v1 = B(6)
v2 = A(9)
v3 = B(1)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationSetMethod(thread_, Interpreter::BinaryOp::SUB,
v0, v1, &method, &flags),
2));
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpNotImplementedRetry);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
-8));
}
TEST_F(InterpreterTest, DoBinaryOpWithCacheHitCallsCachedMethod) {
HandleScope scope(thread_);
word left = SmallInt::kMaxValue + 1;
word right = -13;
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, BINARY_SUBTRACT, 0, RETURN_VALUE, 0,
};
Object left_obj(&scope, runtime_->newInt(left));
Object right_obj(&scope, runtime_->newInt(right));
Tuple consts(&scope, runtime_->newTupleWith2(left_obj, right_obj));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update inline cache.
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
ASSERT_TRUE(function.caches().isTuple());
MutableTuple caches(&scope, function.caches());
BinaryOpFlags dummy;
ASSERT_FALSE(icLookupBinaryOp(*caches, 0, LayoutId::kLargeInt,
LayoutId::kSmallInt, &dummy)
.isErrorNotFound());
// Call from inline cache.
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
}
TEST_F(InterpreterTest, DoBinaryOpWithCacheHitCallsRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyInt(int):
def __sub__(self, other):
return NotImplemented
def __rsub__(self, other):
return NotImplemented
v0 = MyInt(3)
v1 = 7
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Tuple consts(&scope, runtime_->newTupleWith2(v0, v1));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, BINARY_SUBTRACT, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update inline cache.
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, function), -4));
ASSERT_TRUE(function.caches().isTuple());
MutableTuple caches(&scope, function.caches());
BinaryOpFlags dummy;
ASSERT_FALSE(
icLookupBinaryOp(*caches, 0, v0.layoutId(), v1.layoutId(), &dummy)
.isErrorNotFound());
// Should hit the cache for __sub__ and then call binaryOperationRetry().
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, function), -4));
}
TEST_F(InterpreterTest, DoBinaryOpWithSmallIntsRewritesOpcode) {
HandleScope scope(thread_);
word left = 7;
word right = -13;
Object left_obj(&scope, runtime_->newInt(left));
Object right_obj(&scope, runtime_->newInt(right));
Tuple consts(&scope, runtime_->newTupleWith2(left_obj, right_obj));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, BINARY_SUBTRACT, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), BINARY_SUB_SMALLINT);
// Updated opcode returns the same value.
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
}
static bool functionMatchesRef2(const Function& function,
const Object& reference, const Object& arg0,
const Object& arg1) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object expected(&scope, Interpreter::call2(thread, reference, arg0, arg1));
EXPECT_FALSE(expected.isError());
Object actual(&scope, Interpreter::call2(thread, function, arg0, arg1));
EXPECT_FALSE(actual.isError());
return Runtime::objectEquals(thread, *expected, *actual) == Bool::trueObj();
}
// Test that `function(arg0, arg1) == reference(arg0, arg1)` with the assumption
// that `function` contains a `BINARY_OP_MONOMORPHIC` opcode that will be
// specialized to `opcode_specialized` when called with `arg0` and `arg1`.
// Calling the function with `arg_o` should trigger a revert to
// `BINARY_OP_MONOMORPHIC`.
static void testBinaryOpRewrite(const Function& function,
const Function& reference,
Bytecode opcode_specialized, const Object& arg0,
const Object& arg1, const Object& arg_o) {
EXPECT_TRUE(containsBytecode(function, BINARY_OP_ANAMORPHIC));
EXPECT_TRUE(functionMatchesRef2(function, reference, arg0, arg1));
EXPECT_FALSE(containsBytecode(function, BINARY_OP_ANAMORPHIC));
EXPECT_TRUE(containsBytecode(function, opcode_specialized));
EXPECT_TRUE(functionMatchesRef2(function, reference, arg1, arg0));
EXPECT_TRUE(containsBytecode(function, opcode_specialized));
EXPECT_TRUE(functionMatchesRef2(function, reference, arg0, arg_o));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
EXPECT_FALSE(containsBytecode(function, opcode_specialized));
EXPECT_TRUE(functionMatchesRef2(function, reference, arg0, arg1));
}
TEST_F(InterpreterTest, CallFunctionAnamorphicRewritesToCallFunctionTypeNew) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __new__(cls):
return object.__new__(cls)
def foo(fn):
return fn()
def non_type():
return 5
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC));
Type type(&scope, mainModuleAt(runtime_, "C"));
Object expected(&scope, Interpreter::call1(thread_, function, type));
EXPECT_FALSE(expected.isError());
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_NEW));
EXPECT_EQ(expected.layoutId(), type.instanceLayoutId());
Object non_type(&scope, mainModuleAt(runtime_, "non_type"));
expected = Interpreter::call1(thread_, function, non_type);
EXPECT_FALSE(expected.isError());
EXPECT_TRUE(isIntEqualsWord(*expected, 5));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
}
TEST_F(InterpreterTest,
CallFunctionTypeNewWithNewDunderNewRewritesToCallFunction) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __new__(cls):
return object.__new__(cls)
def foo(fn):
return fn()
def new_new(cls):
return 5
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC));
Type type(&scope, mainModuleAt(runtime_, "C"));
Object expected(&scope, Interpreter::call1(thread_, function, type));
EXPECT_FALSE(expected.isError());
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_NEW));
EXPECT_EQ(expected.layoutId(), type.instanceLayoutId());
// Invalidate cache
Object new_new(&scope, mainModuleAt(runtime_, "new_new"));
typeAtPutById(thread_, type, ID(__new__), new_new);
// Cache miss
expected = Interpreter::call1(thread_, function, type);
EXPECT_FALSE(expected.isError());
EXPECT_TRUE(isIntEqualsWord(*expected, 5));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
}
TEST_F(InterpreterTest,
CallFunctionTypeNewWithNewDunderInitRewritesToCallFunction) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
pass
def foo(fn):
return fn()
def new_init(self):
pass
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC));
Type type(&scope, mainModuleAt(runtime_, "C"));
Object expected(&scope, Interpreter::call1(thread_, function, type));
EXPECT_FALSE(expected.isError());
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_NEW));
EXPECT_EQ(expected.layoutId(), type.instanceLayoutId());
// Invalidate cache
Object new_init(&scope, mainModuleAt(runtime_, "new_init"));
typeAtPutById(thread_, type, ID(__new__), new_init);
// Cache miss
expected = Interpreter::call1(thread_, function, type);
EXPECT_FALSE(expected.isError());
EXPECT_EQ(expected.layoutId(), type.instanceLayoutId());
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
}
TEST_F(InterpreterTest, BinaryOpAnamorphicRewritesToBinaryAddSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function(a, b):
return a + b
reference = int.__add__
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "function"));
Function reference(&scope, mainModuleAt(runtime_, "reference"));
Object arg0(&scope, SmallInt::fromWord(34));
Object arg1(&scope, SmallInt::fromWord(12));
const uword digits2[] = {0x12345678, 0xabcdef};
Object arg_l(&scope, runtime_->newLargeIntWithDigits(digits2));
testBinaryOpRewrite(function, reference, BINARY_ADD_SMALLINT, arg0, arg1,
arg_l);
}
TEST_F(InterpreterTest, BinaryOpAnamorphicRewritesToBinaryMulSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function(a, b):
return a * b
reference = int.__mul__
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "function"));
Function reference(&scope, mainModuleAt(runtime_, "reference"));
Object arg0(&scope, SmallInt::fromWord(34));
Object arg1(&scope, SmallInt::fromWord(12));
const uword digits2[] = {0x12345678, 0xabcdef};
Object arg_l(&scope, runtime_->newLargeIntWithDigits(digits2));
testBinaryOpRewrite(function, reference, BINARY_MUL_SMALLINT, arg0, arg1,
arg_l);
}
TEST_F(InterpreterTest, BinaryOpAnamorphicRewritesToBinarySubSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function(a, b):
return a - b
reference = int.__sub__
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "function"));
Function reference(&scope, mainModuleAt(runtime_, "reference"));
Object arg0(&scope, SmallInt::fromWord(94));
Object arg1(&scope, SmallInt::fromWord(21));
const uword digits2[] = {0x12345678, 0xabcdef};
Object arg_l(&scope, runtime_->newLargeIntWithDigits(digits2));
testBinaryOpRewrite(function, reference, BINARY_SUB_SMALLINT, arg0, arg1,
arg_l);
}
TEST_F(InterpreterTest, BinaryOpAnamorphicRewritesToBinaryOrSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function(a, b):
return a | b
reference = int.__or__
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "function"));
Function reference(&scope, mainModuleAt(runtime_, "reference"));
Object arg0(&scope, SmallInt::fromWord(0xa5));
Object arg1(&scope, SmallInt::fromWord(0x42));
const uword digits2[] = {0x12345678, 0xabcdef};
Object arg_l(&scope, runtime_->newLargeIntWithDigits(digits2));
testBinaryOpRewrite(function, reference, BINARY_OR_SMALLINT, arg0, arg1,
arg_l);
}
TEST_F(InterpreterTest, BinaryOpAnamorphicRewritesToBinaryAndSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function(a, b):
return a & b
reference = int.__and__
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "function"));
Function reference(&scope, mainModuleAt(runtime_, "reference"));
Object arg0(&scope, SmallInt::fromWord(0xa5));
Object arg1(&scope, SmallInt::fromWord(0x42));
const uword digits2[] = {0x12345678, 0xabcdef};
Object arg_l(&scope, runtime_->newLargeIntWithDigits(digits2));
testBinaryOpRewrite(function, reference, BINARY_AND_SMALLINT, arg0, arg1,
arg_l);
}
TEST_F(InterpreterTest, BinarySubscrWithListAndSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = [1,2,3]
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt zero(&scope, SmallInt::fromWord(0));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, zero), 1));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_LIST);
SmallInt one(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, one), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_LIST);
}
TEST_F(InterpreterTest,
BinarySubscrAnamorphicRewritesToBinarySubscrMonomorphic) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
class L:
def __getitem__(self, i): return i * 2
L__getitem__ = L.__getitem__
l = L()
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Object l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(12));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 24));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
SmallInt key2(&scope, SmallInt::fromWord(13));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key2), 26));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(InterpreterTest,
BinarySubscrMonomorphicRewritesToBinarySubscrPolymorphic) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
class A:
def __getitem__(self, i): return i * 2
class B:
def __getitem__(self, i): return i * 3
a = A()
b = B()
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Object a(&scope, mainModuleAt(runtime_, "a"));
SmallInt key_a(&scope, SmallInt::fromWord(6));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, a, key_a), 12));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
Object b(&scope, mainModuleAt(runtime_, "b"));
SmallInt key_b(&scope, SmallInt::fromWord(12));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, b, key_b), 36));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_POLYMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrDictRevertsBackToBinarySubscrMonomorphicWhenNonDictObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
d = {1: 2}
s = "abc"
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Dict d(&scope, mainModuleAt(runtime_, "d"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_DICT);
// Revert back to caching __getitem__ when a non-list is observed.
Object s(&scope, mainModuleAt(runtime_, "s"));
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call2(thread_, foo, s, key), "b"));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrListRevertsBackToBinarySubscrMonomorphicWhenNonListObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = [1,2,3]
s = "abc"
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_LIST);
// Revert back to caching __getitem__ when a non-list is observed.
Object s(&scope, mainModuleAt(runtime_, "s"));
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call2(thread_, foo, s, key), "b"));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrListRevertsBackToBinarySubscrMonomorphicWhenNonSmallIntKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = [1,2,3]
large_int = 2**64
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_LIST);
// Revert back to caching __getitem__ when the key is not SmallInt.
LargeInt large_int(&scope, mainModuleAt(runtime_, "large_int"));
EXPECT_TRUE(Interpreter::call2(thread_, foo, l, large_int).isError());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrListRevertsBackToBinarySubscrMonomorphicWhenNegativeKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = [1,2,3]
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_LIST);
// Revert back to caching __getitem__ when the key is negative.
SmallInt negative(&scope, SmallInt::fromWord(-1));
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, foo, l, negative), 3));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(InterpreterTest, StoreSubscrWithDictRewritesToStoreSubscrDict) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(d, i):
d[i] = 5
return d[i]
d = {}
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
Dict d(&scope, mainModuleAt(runtime_, "d"));
SmallInt zero(&scope, SmallInt::fromWord(0));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, zero), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_DICT);
SmallInt one(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, one), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_DICT);
}
TEST_F(InterpreterTest,
StoreSubscrDictRevertsBackToStoreSubscrMonomorphicWhenNonDictObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(d, i):
d[i] = 5
return d[i]
d = {1: -1}
b = bytearray(b"0000")
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
Dict d(&scope, mainModuleAt(runtime_, "d"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_DICT);
// Revert back to caching __getitem__ when a non-dict is observed.
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, b, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_MONOMORPHIC);
}
TEST_F(InterpreterTest, StoreSubscrWithListAndSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
l[i] = 5
return l[i]
l = [1,2,3]
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt zero(&scope, SmallInt::fromWord(0));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, zero), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
SmallInt one(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, one), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
}
TEST_F(InterpreterTest,
StoreSubscrListRevertsBackToStoreSubscrMonomorphicWhenNonListObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
l[i] = 5
return l[i]
l = [1,2,3]
d = {1: -1}
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
// Revert back to caching __getitem__ when a non-list is observed.
Dict d(&scope, mainModuleAt(runtime_, "d"));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
StoreSubscrListRevertsBackToStoreSubscrMonomorphicWhenNonSmallIntKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
l[i] = 5
return l[i]
l = [1,2,3]
large_int = 2**64
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
// Revert back to caching __getitem__ when the key is not SmallInt.
LargeInt large_int(&scope, mainModuleAt(runtime_, "large_int"));
EXPECT_TRUE(Interpreter::call2(thread_, foo, l, large_int).isError());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
StoreSubscrListRevertsBackToStoreSubscrMonomorphicWhenNegativeKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
l[i] = 5
return l[i]
l = [1,2,3]
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
// Revert back to caching __getitem__ when the key is negative.
SmallInt negative(&scope, SmallInt::fromWord(-1));
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, foo, l, negative), 5));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_MONOMORPHIC);
}
TEST_F(InterpreterTest, BinarySubscrWithTupleAndSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = (1,2,3)
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Tuple l(&scope, mainModuleAt(runtime_, "l"));
SmallInt zero(&scope, SmallInt::fromWord(0));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, zero), 1));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_TUPLE);
SmallInt one(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, one), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_TUPLE);
}
TEST_F(
InterpreterTest,
BinarySubscrTupleRevertsBackToBinarySubscrMonomorphicWhenNonTupleObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = (1,2,3)
d = {1: -1}
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Tuple l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_TUPLE);
// Revert back to caching __getitem__ when a non-list is observed.
Dict d(&scope, mainModuleAt(runtime_, "d"));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, key), -1));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrTupleRevertsBackToBinarySubscrMonomorphicWhenNonSmallIntKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = (1,2,3)
large_int = 2**64
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Tuple l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_TUPLE);
// Revert back to caching __getitem__ when the key is not SmallInt.
LargeInt large_int(&scope, mainModuleAt(runtime_, "large_int"));
EXPECT_TRUE(Interpreter::call2(thread_, foo, l, large_int).isError());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(
InterpreterTest,
BinarySubscrTupleRevertsBackToBinarySubscrMonomorphicWhenNegativeKeyObserved) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
return l[i]
l = (1,2,3)
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_ANAMORPHIC);
Tuple l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 2));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_TUPLE);
// Revert back to caching __getitem__ when the key is negative.
SmallInt negative(&scope, SmallInt::fromWord(-1));
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, foo, l, negative), 3));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), BINARY_SUBSCR_MONOMORPHIC);
}
TEST_F(InterpreterTest, InplaceOpCachedInsertsDependencyForThreeAttributes) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def __imul__(self, other):
return "from class A"
class B:
pass
def cache_inplace_op(a, b):
a *= b
a = A()
b = B()
A__imul__ = A.__imul__
cache_inplace_op(a, b)
)")
.isError());
Function cache_inplace_op(&scope, mainModuleAt(runtime_, "cache_inplace_op"));
MutableTuple caches(&scope, cache_inplace_op.caches());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Type type_a(&scope, mainModuleAt(runtime_, "A"));
Type type_b(&scope, mainModuleAt(runtime_, "B"));
BinaryOpFlags flag;
ASSERT_EQ(icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flag),
mainModuleAt(runtime_, "A__imul__"));
// Verify that A.__imul__ has the dependent.
Object inplace_op_name(&scope, runtime_->symbols()->at(ID(__imul__)));
Object inplace_attr(&scope, typeValueCellAt(*type_a, *inplace_op_name));
ASSERT_TRUE(inplace_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*inplace_attr).dependencyLink().isWeakLink());
EXPECT_EQ(WeakLink::cast(ValueCell::cast(*inplace_attr).dependencyLink())
.referent(),
*cache_inplace_op);
// Verify that A.__mul__ has the dependent.
Object left_op_name(&scope, runtime_->symbols()->at(ID(__mul__)));
Object type_a_attr(&scope, typeValueCellAt(*type_a, *left_op_name));
ASSERT_TRUE(type_a_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*type_a_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*type_a_attr).dependencyLink()).referent(),
*cache_inplace_op);
// Verify that B.__rmul__ has the dependent.
Object right_op_name(&scope, runtime_->symbols()->at(ID(__rmul__)));
Object type_b_attr(&scope, typeValueCellAt(*type_b, *right_op_name));
ASSERT_TRUE(type_b_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*type_b_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*type_b_attr).dependencyLink()).referent(),
*cache_inplace_op);
}
TEST_F(InterpreterTest, ImportFromWithMissingAttributeRaisesImportError) {
HandleScope scope(thread_);
Str name(&scope, runtime_->newStrFromCStr("foo"));
Module module(&scope, runtime_->newModule(name));
Object modules(&scope, runtime_->modules());
ASSERT_FALSE(
objectSetItem(thread_, modules, name, module).isErrorException());
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "from foo import bar"),
LayoutId::kImportError,
"cannot import name 'bar' from 'foo'"));
}
TEST_F(InterpreterTest, ImportFromCallsDunderGetattribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __getattribute__(self, name):
return f"getattribute '{name}'"
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Tuple consts(&scope, runtime_->newTupleWith1(i));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Tuple names(&scope, runtime_->newTupleWith1(name));
const byte bytecode[] = {LOAD_CONST, 0, IMPORT_FROM, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
EXPECT_TRUE(isStrEqualsCStr(runCode(code), "getattribute 'foo'"));
}
TEST_F(InterpreterTest, ImportFromWithNonModuleRaisesImportError) {
HandleScope scope(thread_);
Object obj(&scope, NoneType::object());
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Tuple names(&scope, runtime_->newTupleWith1(name));
const byte bytecode[] = {LOAD_CONST, 0, IMPORT_FROM, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kImportError,
"cannot import name 'foo'"));
}
TEST_F(InterpreterTest, ImportFromWithNonModulePropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __getattribute__(self, name):
raise UserWarning()
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Tuple consts(&scope, runtime_->newTupleWith1(i));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Tuple names(&scope, runtime_->newTupleWith1(name));
const byte bytecode[] = {LOAD_CONST, 0, IMPORT_FROM, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
EXPECT_TRUE(raised(runCode(code), LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, InplaceOperationCallsInplaceMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __isub__(self, other):
return (C, '__isub__', self, other)
left = C()
right = C()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object c_class(&scope, mainModuleAt(runtime_, "C"));
Object result_obj(
&scope, Interpreter::inplaceOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *c_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__isub__"));
EXPECT_EQ(result.at(2), *left);
EXPECT_EQ(result.at(3), *right);
}
TEST_F(InterpreterTest, InplaceOperationCallsBinaryMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __sub__(self, other):
return (C, '__sub__', self, other)
left = C()
right = C()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object c_class(&scope, mainModuleAt(runtime_, "C"));
Object result_obj(
&scope, Interpreter::inplaceOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *c_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__sub__"));
EXPECT_EQ(result.at(2), *left);
EXPECT_EQ(result.at(3), *right);
}
TEST_F(InterpreterTest, InplaceOperationCallsBinaryMethodAfterNotImplemented) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __isub__(self, other):
return NotImplemented
def __sub__(self, other):
return (C, '__sub__', self, other)
left = C()
right = C()
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "left"));
Object right(&scope, mainModuleAt(runtime_, "right"));
Object c_class(&scope, mainModuleAt(runtime_, "C"));
Object result_obj(
&scope, Interpreter::inplaceOperation(thread_, Interpreter::BinaryOp::SUB,
left, right));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_EQ(result.at(0), *c_class);
EXPECT_TRUE(isStrEqualsCStr(result.at(1), "__sub__"));
EXPECT_EQ(result.at(2), *left);
EXPECT_EQ(result.at(3), *right);
}
TEST_F(InterpreterTest, InplaceOperationSetMethodSetsMethodFlagsBinaryOpRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyInt(int):
def __isub__(self, other):
return int(self) - other - 2
v0 = MyInt(9)
v1 = MyInt(-11)
v2 = MyInt(-3)
v3 = MyInt(7)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_TRUE(isIntEqualsWord(
Interpreter::inplaceOperationSetMethod(
thread_, Interpreter::BinaryOp::SUB, v0, v1, &method, &flags),
18));
EXPECT_EQ(flags, kInplaceBinaryOpRetry);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
-12));
}
TEST_F(InterpreterTest, InplaceOperationSetMethodSetsMethodFlagsReverseRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyInt(int):
pass
class MyIntSub(MyInt):
def __rpow__(self, other):
return int(other) ** int(self) - 7
v0 = MyInt(3)
v1 = MyIntSub(3)
v2 = MyInt(-4)
v3 = MyIntSub(4)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_TRUE(isIntEqualsWord(
Interpreter::inplaceOperationSetMethod(
thread_, Interpreter::BinaryOp::POW, v0, v1, &method, &flags),
20));
EXPECT_EQ(flags, kBinaryOpReflected | kBinaryOpNotImplementedRetry);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_TRUE(isIntEqualsWord(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
249));
}
TEST_F(InterpreterTest, InplaceAddWithSmallIntsRewritesOpcode) {
HandleScope scope(thread_);
word left = 7;
word right = -13;
Object left_obj(&scope, runtime_->newInt(left));
Object right_obj(&scope, runtime_->newInt(right));
Tuple consts(&scope, runtime_->newTupleWith2(left_obj, right_obj));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, INPLACE_ADD, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left + right));
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), INPLACE_ADD_SMALLINT);
// Updated opcode returns the same value.
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left + right));
}
TEST_F(InterpreterTest, InplaceAddSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
a += b
return a
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_ANAMORPHIC);
SmallInt left(&scope, SmallInt::fromWord(7));
SmallInt right(&scope, SmallInt::fromWord(-13));
rewrittenBytecodeOpAtPut(rewritten, 2, INPLACE_ADD_SMALLINT);
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(-13);
// 7 + (-13)
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, function, left, right), -6));
}
TEST_F(InterpreterTest, InplaceAddSmallIntRevertsBackToInplaceOp) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
a += b
return a
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_ANAMORPHIC);
LargeInt left(&scope, runtime_->newInt(SmallInt::kMaxValue + 1));
SmallInt right(&scope, SmallInt::fromWord(13));
rewrittenBytecodeOpAtPut(rewritten, 2, INPLACE_ADD_SMALLINT);
// LARGE_SMALL_INT += SMALL_INT
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, function, left, right),
SmallInt::kMaxValue + 1 + 13));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_MONOMORPHIC);
}
TEST_F(InterpreterTest, InplaceSubtractWithSmallIntsRewritesOpcode) {
HandleScope scope(thread_);
word left = 7;
word right = -13;
Object left_obj(&scope, runtime_->newInt(left));
Object right_obj(&scope, runtime_->newInt(right));
Tuple consts(&scope, runtime_->newTupleWith2(left_obj, right_obj));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, INPLACE_SUBTRACT, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), INPLACE_SUB_SMALLINT);
// Updated opcode returns the same value.
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call0(thread_, function), left - right));
}
TEST_F(InterpreterTest, InplaceSubtractSmallInt) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
a -= b
return a
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_ANAMORPHIC);
SmallInt left(&scope, SmallInt::fromWord(7));
SmallInt right(&scope, SmallInt::fromWord(-13));
rewrittenBytecodeOpAtPut(rewritten, 2, INPLACE_SUB_SMALLINT);
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(-13);
// 7 - (-13)
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, function, left, right), 20));
}
TEST_F(InterpreterTest, InplaceSubSmallIntRevertsBackToInplaceOp) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
a -= b
return a
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_ANAMORPHIC);
LargeInt left(&scope, runtime_->newInt(SmallInt::kMaxValue + 1));
SmallInt right(&scope, SmallInt::fromWord(13));
rewrittenBytecodeOpAtPut(rewritten, 2, INPLACE_SUB_SMALLINT);
// LARGE_SMALL_INT -= SMALL_INT
EXPECT_TRUE(
isIntEqualsWord(Interpreter::call2(thread_, function, left, right),
SmallInt::kMaxValue + 1 - 13));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), INPLACE_OP_MONOMORPHIC);
}
TEST_F(InterpreterDeathTest, InvalidOpcode) {
HandleScope scope(thread_);
const byte bytecode[] = {NOP, 0, NOP, 0, UNUSED_BYTECODE_7, 17, NOP, 7};
Code code(&scope, newCodeWithBytes(bytecode));
ASSERT_DEATH(static_cast<void>(runCode(code)),
"bytecode 'UNUSED_BYTECODE_7'");
}
TEST_F(InterpreterTest, CallDescriptorGetWithBuiltinTypeDescriptors) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
def class_method_func(self): pass
def static_method_func(cls): pass
class C:
class_method = classmethod(class_method_func)
static_method = staticmethod(static_method_func)
@property
def property_field(self): return "property"
def function_field(self): pass
i = C()
)")
.isError());
HandleScope scope(thread_);
Type c(&scope, mainModuleAt(runtime_, "C"));
Type type(&scope, runtime_->typeOf(*c));
Object i(&scope, mainModuleAt(runtime_, "i"));
Object class_method_name(&scope,
Runtime::internStrFromCStr(thread_, "class_method"));
Object class_method(&scope, typeAt(c, class_method_name));
BoundMethod class_method_result(
&scope, Interpreter::callDescriptorGet(thread_, class_method, i, c));
EXPECT_EQ(class_method_result.self(), *c);
EXPECT_EQ(class_method_result.function(),
mainModuleAt(runtime_, "class_method_func"));
Object static_method_name(
&scope, Runtime::internStrFromCStr(thread_, "static_method"));
Object static_method(&scope, typeAt(c, static_method_name));
Function static_method_result(
&scope, Interpreter::callDescriptorGet(thread_, static_method, c, type));
EXPECT_EQ(*static_method_result,
mainModuleAt(runtime_, "static_method_func"));
Object property_field_name(
&scope, Runtime::internStrFromCStr(thread_, "property_field"));
Object property_field(&scope, typeAt(c, property_field_name));
Object property_field_result(
&scope, Interpreter::callDescriptorGet(thread_, property_field, i, c));
EXPECT_TRUE(isStrEqualsCStr(*property_field_result, "property"));
Object function_field_name(
&scope, Runtime::internStrFromCStr(thread_, "function_field"));
Object function_field(&scope, typeAt(c, function_field_name));
BoundMethod function_field_result(
&scope, Interpreter::callDescriptorGet(thread_, function_field, i, c));
EXPECT_EQ(function_field_result.self(), *i);
EXPECT_EQ(function_field_result.function(), *function_field);
Object none(&scope, NoneType::object());
Function function_field_result_from_none_instance(
&scope, Interpreter::callDescriptorGet(thread_, function_field, none, c));
EXPECT_EQ(function_field_result_from_none_instance, *function_field);
Type none_type(&scope, runtime_->typeAt(LayoutId::kNoneType));
BoundMethod function_field_result_from_none_instance_of_none_type(
&scope,
Interpreter::callDescriptorGet(thread_, function_field, none, none_type));
EXPECT_EQ(function_field_result_from_none_instance_of_none_type.self(),
*none);
EXPECT_EQ(function_field_result_from_none_instance_of_none_type.function(),
*function_field);
}
TEST_F(InterpreterTest, CompareInAnamorphicWithStrRewritesOpcode) {
HandleScope scope(thread_);
Object obj1(&scope, runtime_->newStrFromCStr("test"));
Object obj2(&scope, runtime_->newStrFromCStr("test string"));
Tuple consts(&scope, runtime_->newTupleWith2(obj1, obj2));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, COMPARE_IN_ANAMORPHIC, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_IN_STR);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
}
TEST_F(InterpreterTest, CompareInAnamorphicWithDictRewritesOpcode) {
HandleScope scope(thread_);
Dict dict(&scope, runtime_->newDict());
Str key(&scope, runtime_->newStrFromCStr("test"));
word key_hash = strHash(thread_, *key);
dictAtPut(thread_, dict, key, key_hash, key);
Tuple consts(&scope, runtime_->newTupleWith2(key, dict));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, COMPARE_IN_ANAMORPHIC, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_IN_DICT);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
}
TEST_F(InterpreterTest, CompareInAnamorphicWithTupleRewritesOpcode) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newStrFromCStr("test"));
Tuple tuple(&scope, runtime_->newTupleWith1(obj));
Tuple consts(&scope, runtime_->newTupleWith2(obj, tuple));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, COMPARE_IN_ANAMORPHIC, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_IN_TUPLE);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
}
TEST_F(InterpreterTest, CompareInAnamorphicWithListRewritesOpcode) {
HandleScope scope(thread_);
List list(&scope, runtime_->newList());
Object value0(&scope, runtime_->newStrFromCStr("value0"));
Object value1(&scope, runtime_->newStrFromCStr("test"));
listInsert(thread_, list, value0, 0);
listInsert(thread_, list, value1, 1);
Tuple consts(&scope, runtime_->newTupleWith2(value1, list));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, COMPARE_IN_ANAMORPHIC, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_IN_LIST);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
}
// To a rich comparison on two instances of the same type. In each case, the
// method on the left side of the comparison should be used.
TEST_F(InterpreterTest, CompareOpSameType) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
def __lt__(self, other):
return self.value < other.value
c10 = C(10)
c20 = C(20)
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "c10"));
Object right(&scope, mainModuleAt(runtime_, "c20"));
Object left_lt_right(&scope, Interpreter::compareOperation(
thread_, CompareOp::LT, left, right));
EXPECT_EQ(left_lt_right, Bool::trueObj());
Object right_lt_left(&scope, Interpreter::compareOperation(
thread_, CompareOp::LT, right, left));
EXPECT_EQ(right_lt_left, Bool::falseObj());
}
TEST_F(InterpreterTest, CompareOpFallback) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
c10 = C(10)
c20 = C(20)
)")
.isError());
Object left(&scope, mainModuleAt(runtime_, "c10"));
Object right(&scope, mainModuleAt(runtime_, "c20"));
Object left_eq_right(&scope, Interpreter::compareOperation(
thread_, CompareOp::EQ, left, right));
EXPECT_EQ(left_eq_right, Bool::falseObj());
Object left_ne_right(&scope, Interpreter::compareOperation(
thread_, CompareOp::NE, left, right));
EXPECT_EQ(left_ne_right, Bool::trueObj());
Object right_eq_left(&scope, Interpreter::compareOperation(
thread_, CompareOp::EQ, left, right));
EXPECT_EQ(right_eq_left, Bool::falseObj());
Object right_ne_left(&scope, Interpreter::compareOperation(
thread_, CompareOp::NE, left, right));
EXPECT_EQ(right_ne_left, Bool::trueObj());
}
TEST_F(InterpreterTest, CompareOpSubclass) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
called = None
class A:
def __eq__(self, other):
global called
if (called is not None):
called = "ERROR"
else:
called = "A"
return False
class B:
def __eq__(self, other):
global called
if (called is not None):
called = "ERROR"
else:
called = "B"
return True
class C(A):
def __eq__(self, other):
global called
if (called is not None):
called = "ERROR"
else:
called = "C"
return True
a = A()
b = B()
c = C()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
// Comparisons where rhs is not a subtype of lhs try lhs.__eq__(rhs) first.
Object a_eq_b(&scope,
Interpreter::compareOperation(thread_, CompareOp::EQ, a, b));
EXPECT_EQ(a_eq_b, Bool::falseObj());
Object called(&scope, mainModuleAt(runtime_, "called"));
EXPECT_TRUE(isStrEqualsCStr(*called, "A"));
Object called_name(&scope, Runtime::internStrFromCStr(thread_, "called"));
Object none(&scope, NoneType::object());
Module main(&scope, findMainModule(runtime_));
moduleAtPut(thread_, main, called_name, none);
Object b_eq_a(&scope,
Interpreter::compareOperation(thread_, CompareOp::EQ, b, a));
EXPECT_EQ(b_eq_a, Bool::trueObj());
called = mainModuleAt(runtime_, "called");
EXPECT_TRUE(isStrEqualsCStr(*called, "B"));
moduleAtPut(thread_, main, called_name, none);
Object c_eq_a(&scope,
Interpreter::compareOperation(thread_, CompareOp::EQ, c, a));
EXPECT_EQ(c_eq_a, Bool::trueObj());
called = mainModuleAt(runtime_, "called");
EXPECT_TRUE(isStrEqualsCStr(*called, "C"));
// When rhs is a subtype of lhs, only rhs.__eq__(rhs) is tried.
moduleAtPut(thread_, main, called_name, none);
Object a_eq_c(&scope,
Interpreter::compareOperation(thread_, CompareOp::EQ, a, c));
EXPECT_EQ(a_eq_c, Bool::trueObj());
called = mainModuleAt(runtime_, "called");
EXPECT_TRUE(isStrEqualsCStr(*called, "C"));
}
TEST_F(InterpreterTest, CompareOpWithStrsRewritesOpcode) {
HandleScope scope(thread_);
Object obj1(&scope, runtime_->newStrFromCStr("abc"));
Object obj2(&scope, runtime_->newStrFromCStr("def"));
Tuple consts(&scope, runtime_->newTupleWith2(obj1, obj2));
const byte bytecode[] = {
LOAD_CONST, 0,
LOAD_CONST, 1,
COMPARE_OP, static_cast<byte>(CompareOp::EQ),
RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::falseObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_EQ_STR);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::falseObj());
}
TEST_F(InterpreterTest, CompareOpWithNeOperatorWithStrsRewritesToCompareNeStr) {
HandleScope scope(thread_);
Object obj1(&scope, runtime_->newStrFromCStr("abc"));
Object obj2(&scope, runtime_->newStrFromCStr("def"));
Tuple consts(&scope, runtime_->newTupleWith2(obj1, obj2));
const byte bytecode[] = {
LOAD_CONST, 0,
LOAD_CONST, 1,
COMPARE_OP, static_cast<byte>(CompareOp::NE),
RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
EXPECT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_NE_STR);
// Updated opcode returns the same value.
EXPECT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
// Revert the opcode back to COMPARE_OP_MONOMIRPHIC in case a non-str argument
// is observed by evaluating `str_obj` != `tuple_obj`.
consts.atPut(0, runtime_->emptyTuple());
EXPECT_EQ(Interpreter::call0(thread_, function), Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2),
COMPARE_OP_MONOMORPHIC);
}
TEST_F(InterpreterTest, CompareOpSmallIntsRewritesOpcode) {
HandleScope scope(thread_);
word left = 7;
word right = -13;
Object obj1(&scope, runtime_->newInt(left));
Object obj2(&scope, runtime_->newInt(right));
Tuple consts(&scope, runtime_->newTupleWith2(obj1, obj2));
const byte bytecode[] = {
LOAD_CONST, 0,
LOAD_CONST, 1,
COMPARE_OP, static_cast<byte>(CompareOp::LT),
RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
// Update the opcode.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::falseObj());
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten_bytecode, 2), COMPARE_LT_SMALLINT);
// Updated opcode returns the same value.
ASSERT_EQ(Interpreter::call0(thread_, function), Bool::falseObj());
}
TEST_F(InterpreterTest, CompareOpWithSmallInts) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
return a == b
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_OP_ANAMORPHIC);
SmallInt left(&scope, SmallInt::fromWord(7));
SmallInt right(&scope, SmallInt::fromWord(-13));
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_EQ_SMALLINT);
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(-13);
// 7 == -13
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
// 7 == 7
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(7);
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_EQ_SMALLINT);
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_NE_SMALLINT);
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(7);
// 7 != 7
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
left = SmallInt::fromWord(7);
right = SmallInt::fromWord(-13);
// 7 != -13
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_NE_SMALLINT);
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_GT_SMALLINT);
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(10);
// 10 > 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(-10);
// 10 > -10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_GT_SMALLINT);
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_GE_SMALLINT);
left = SmallInt::fromWord(-10);
right = SmallInt::fromWord(10);
// -10 >= 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(10);
// 10 >= 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
left = SmallInt::fromWord(11);
right = SmallInt::fromWord(10);
// 11 > = 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_GE_SMALLINT);
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_LT_SMALLINT);
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(-10);
// 10 < -10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
left = SmallInt::fromWord(-10);
right = SmallInt::fromWord(10);
// -10 < 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_LT_SMALLINT);
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_LE_SMALLINT);
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(-10);
// 10 <= -10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::falseObj());
left = SmallInt::fromWord(10);
right = SmallInt::fromWord(10);
// 10 <= 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
left = SmallInt::fromWord(9);
right = SmallInt::fromWord(10);
// 9 <= 10
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_LE_SMALLINT);
}
TEST_F(InterpreterTest, CompareOpWithSmallIntsRevertsBackToCompareOp) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(a, b):
return a == b
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_OP_ANAMORPHIC);
LargeInt left(&scope, runtime_->newInt(SmallInt::kMaxValue + 1));
LargeInt right(&scope, runtime_->newInt(SmallInt::kMaxValue + 1));
rewrittenBytecodeOpAtPut(rewritten, 2, COMPARE_EQ_SMALLINT);
// LARGE_SMALL_INT == SMALL_INT
EXPECT_EQ(Interpreter::call2(thread_, function, left, right),
Bool::trueObj());
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 2), COMPARE_OP_MONOMORPHIC);
}
TEST_F(InterpreterTest, CompareOpSetMethodSetsMethod) {
HandleScope scope(thread_);
Object v0(&scope, runtime_->newInt(39));
Object v1(&scope, runtime_->newInt(11));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
EXPECT_EQ(Interpreter::compareOperationSetMethod(thread_, CompareOp::LT, v0,
v1, &method, &flags),
Bool::falseObj());
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpNotImplementedRetry);
Object v2(&scope, runtime_->newInt(3));
Object v3(&scope, runtime_->newInt(8));
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
EXPECT_EQ(
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3),
Bool::trueObj());
}
TEST_F(InterpreterTest, CompareOpSetMethodSetsReverseMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
pass
class B(A):
def __ge__(self, other):
return (self, other)
a1 = A()
b1 = B()
a2 = A()
b2 = B()
)")
.isError());
Object a1(&scope, mainModuleAt(runtime_, "a1"));
Object b1(&scope, mainModuleAt(runtime_, "b1"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
Object result_obj(
&scope, Interpreter::compareOperationSetMethod(thread_, CompareOp::LE, a1,
b1, &method, &flags));
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpReflected | kBinaryOpNotImplementedRetry);
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), b1);
EXPECT_EQ(result.at(1), a1);
Object a2(&scope, mainModuleAt(runtime_, "a2"));
Object b2(&scope, mainModuleAt(runtime_, "b2"));
ASSERT_EQ(a1.layoutId(), a2.layoutId());
ASSERT_EQ(b1.layoutId(), b2.layoutId());
result_obj =
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *a2, *b2);
ASSERT_TRUE(result_obj.isTuple());
result = *result_obj;
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), b2);
EXPECT_EQ(result.at(1), a2);
}
TEST_F(InterpreterTest,
CompareOpSetMethodSetsReverseMethodNotImplementedRetry) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __init__(self, x):
self.x = x
def __le__(self, other):
raise UserWarning("should not be called")
class ASub(A):
def __ge__(self, other):
return (self, other)
v0 = A(3)
v1 = ASub(7)
v2 = A(8)
v3 = ASub(2)
)")
.isError());
Object v0(&scope, mainModuleAt(runtime_, "v0"));
Object v1(&scope, mainModuleAt(runtime_, "v1"));
Object v2(&scope, mainModuleAt(runtime_, "v2"));
Object v3(&scope, mainModuleAt(runtime_, "v3"));
Object method(&scope, NoneType::object());
BinaryOpFlags flags;
Object result_obj(
&scope, Interpreter::compareOperationSetMethod(thread_, CompareOp::LE, v0,
v1, &method, &flags));
EXPECT_TRUE(method.isFunction());
EXPECT_EQ(flags, kBinaryOpReflected | kBinaryOpNotImplementedRetry);
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), v1);
EXPECT_EQ(result.at(1), v0);
ASSERT_EQ(v0.layoutId(), v2.layoutId());
ASSERT_EQ(v1.layoutId(), v3.layoutId());
result_obj =
Interpreter::binaryOperationWithMethod(thread_, *method, flags, *v2, *v3);
ASSERT_TRUE(result_obj.isTuple());
result = *result_obj;
ASSERT_EQ(result.length(), 2);
EXPECT_EQ(result.at(0), v3);
EXPECT_EQ(result.at(1), v2);
}
TEST_F(InterpreterTest,
CompareOpInvokesLeftMethodWhenReflectedMethodReturnsNotImplemented) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
trace = ""
class C:
def __ge__(self, other):
global trace
trace += "C.__ge__,"
return "C.__ge__"
def __le__(self, other):
raise Exception("should not be called")
class D(C):
def __ge__(self, other):
raise Exception("should not be called")
def __le__(self, other):
global trace
trace += "D.__le__,"
return NotImplemented
result = C() >= D()
)")
.isError());
EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "C.__ge__"));
EXPECT_TRUE(
isStrEqualsCStr(mainModuleAt(runtime_, "trace"), "D.__le__,C.__ge__,"));
}
TEST_F(
InterpreterTest,
CompareOpCachedInsertsDependencyForBothOperandsTypesAppropriateAttributes) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def __ge__(self, other):
return "from class A"
class B:
pass
def cache_compare_op(a, b):
return a >= b
a = A()
b = B()
A__ge__ = A.__ge__
result = cache_compare_op(a, b)
)")
.isError());
ASSERT_TRUE(
isStrEqualsCStr(mainModuleAt(runtime_, "result"), "from class A"));
Function cache_compare_op(&scope, mainModuleAt(runtime_, "cache_compare_op"));
MutableTuple caches(&scope, cache_compare_op.caches());
Object a_obj(&scope, mainModuleAt(runtime_, "a"));
Object b_obj(&scope, mainModuleAt(runtime_, "b"));
BinaryOpFlags flag;
EXPECT_EQ(
icLookupBinaryOp(*caches, 0, a_obj.layoutId(), b_obj.layoutId(), &flag),
mainModuleAt(runtime_, "A__ge__"));
// Verify that A.__ge__ has the dependent.
Type a_type(&scope, mainModuleAt(runtime_, "A"));
Object left_op_name(&scope, runtime_->symbols()->at(ID(__ge__)));
Object a_type_attr(&scope, typeValueCellAt(*a_type, *left_op_name));
ASSERT_TRUE(a_type_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*a_type_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*a_type_attr).dependencyLink()).referent(),
*cache_compare_op);
// Verify that B.__le__ has the dependent.
Type b_type(&scope, mainModuleAt(runtime_, "B"));
Object right_op_name(&scope, runtime_->symbols()->at(ID(__le__)));
Object b_type_attr(&scope, typeValueCellAt(*b_type, *right_op_name));
ASSERT_TRUE(b_type_attr.isValueCell());
ASSERT_TRUE(ValueCell::cast(*b_type_attr).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*b_type_attr).dependencyLink()).referent(),
*cache_compare_op);
}
TEST_F(InterpreterTest, DoStoreFastStoresValue) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(1111));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.varcount = 2;
const byte bytecode[] = {LOAD_CONST, 0, 0, 0, STORE_FAST, 1, 0, 0,
LOAD_FAST, 1, 0, 0, RETURN_VALUE, 0, 0, 0};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
EXPECT_TRUE(isIntEqualsWord(runCodeNoBytecodeRewriting(code), 1111));
}
TEST_F(InterpreterTest, DoLoadFastReverseLoadsValue) {
HandleScope scope(thread_);
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(22));
Object obj3(&scope, SmallInt::fromWord(333));
Object obj4(&scope, SmallInt::fromWord(4444));
Tuple consts(&scope, runtime_->newTupleWith4(obj1, obj2, obj3, obj4));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.varcount = 4;
const byte bytecode[] = {
LOAD_CONST, 0, 0, 0, STORE_FAST, 0, 0, 0,
LOAD_CONST, 1, 0, 0, STORE_FAST, 1, 0, 0,
LOAD_CONST, 2, 0, 0, STORE_FAST, 2, 0, 0,
LOAD_CONST, 3, 0, 0, STORE_FAST, 3, 0, 0,
LOAD_FAST_REVERSE, 3, 0, 0, // 1
LOAD_FAST_REVERSE, 2, 0, 0, // 22
LOAD_FAST_REVERSE, 0, 0, 0, // 4444
LOAD_FAST_REVERSE, 1, 0, 0, // 333
BUILD_TUPLE, 4, 0, 0, RETURN_VALUE, 0, 0, 0,
};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
Object result_obj(&scope, runCodeNoBytecodeRewriting(code));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_TRUE(isIntEqualsWord(result.at(0), 1));
EXPECT_TRUE(isIntEqualsWord(result.at(1), 22));
EXPECT_TRUE(isIntEqualsWord(result.at(2), 4444));
EXPECT_TRUE(isIntEqualsWord(result.at(3), 333));
}
TEST_F(InterpreterTest,
DoLoadFastReverseFromUninitializedLocalRaisesUnboundLocalError) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.varcount = 3;
const byte bytecode[] = {
LOAD_CONST, 0, 0, 0, STORE_FAST, 0, 0, 0, LOAD_CONST, 0, 0, 0,
STORE_FAST, 2, 0, 0, DELETE_FAST, 2, 0, 0, LOAD_FAST_REVERSE, 0, 0, 0,
RETURN_VALUE, 0, 0, 0,
};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
EXPECT_TRUE(raisedWithStr(
runCodeNoBytecodeRewriting(code), LayoutId::kUnboundLocalError,
"local variable 'var2' referenced before assignment"));
}
TEST_F(InterpreterTest, DoStoreFastReverseStoresValue) {
HandleScope scope(thread_);
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(22));
Object obj3(&scope, SmallInt::fromWord(333));
Object obj4(&scope, SmallInt::fromWord(4444));
Tuple consts(&scope, runtime_->newTupleWith4(obj1, obj2, obj3, obj4));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.varcount = 4;
const byte bytecode[] = {
LOAD_CONST, 0, 0, 0, STORE_FAST_REVERSE, 0, 0, 0,
LOAD_CONST, 1, 0, 0, STORE_FAST_REVERSE, 1, 0, 0,
LOAD_CONST, 2, 0, 0, STORE_FAST_REVERSE, 3, 0, 0,
LOAD_CONST, 3, 0, 0, STORE_FAST_REVERSE, 2, 0, 0,
LOAD_FAST, 0, 0, 0, // 333
LOAD_FAST, 1, 0, 0, // 4444
LOAD_FAST, 2, 0, 0, // 22
LOAD_FAST, 3, 0, 0, // 1
BUILD_TUPLE, 4, 0, 0, RETURN_VALUE, 0, 0, 0,
};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
Object result_obj(&scope, runCodeNoBytecodeRewriting(code));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 4);
EXPECT_TRUE(isIntEqualsWord(result.at(0), 333));
EXPECT_TRUE(isIntEqualsWord(result.at(1), 4444));
EXPECT_TRUE(isIntEqualsWord(result.at(2), 22));
EXPECT_TRUE(isIntEqualsWord(result.at(3), 1));
}
TEST_F(InterpreterTest, DoStoreSubscrWithNoSetitemRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "1[5] = 'foo'"),
LayoutId::kTypeError,
"'int' object does not support item assignment"));
}
TEST_F(InterpreterTest, DoStoreSubscrWithDescriptorPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class A:
def __get__(self, *args):
raise RuntimeError("foo")
class B:
__setitem__ = A()
b = B()
b[5] = 'foo'
)"),
LayoutId::kRuntimeError, "foo"));
}
TEST_F(InterpreterTest, DoDeleteSubscrWithNoDelitemRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "del 1[5]"),
LayoutId::kTypeError,
"'int' object does not support item deletion"));
}
TEST_F(InterpreterTest, DoDeleteSubscrWithDescriptorPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class A:
def __get__(self, *args):
raise RuntimeError("foo")
class B:
__delitem__ = A()
b = B()
del b[5]
)"),
LayoutId::kRuntimeError, "foo"));
}
TEST_F(InterpreterTest, DoDeleteSubscrDoesntPushToStack) {
HandleScope scope(thread_);
List list(&scope, runtime_->newList());
Int one(&scope, runtime_->newInt(1));
runtime_->listEnsureCapacity(thread_, list, 1);
list.setNumItems(1);
list.atPut(0, *one);
Object obj1(&scope, SmallInt::fromWord(42));
Object obj3(&scope, SmallInt::fromWord(0));
Tuple consts(&scope, runtime_->newTupleWith3(obj1, list, obj3));
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, LOAD_CONST, 2,
DELETE_SUBSCR, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result_obj(&scope, runCode(code));
ASSERT_TRUE(result_obj.isInt());
Int result(&scope, *result_obj);
EXPECT_EQ(result.asWord(), 42);
EXPECT_EQ(list.numItems(), 0);
}
TEST_F(InterpreterTest, GetIterWithSequenceReturnsIterator) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Sequence:
def __getitem__(s, i):
return ("foo", "bar")[i]
seq = Sequence()
)")
.isError());
HandleScope scope(thread_);
Object obj(&scope, mainModuleAt(runtime_, "seq"));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {
LOAD_CONST, 0, GET_ITER, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result_obj(&scope, runCode(code));
EXPECT_TRUE(runtime_->isIterator(thread_, result_obj));
Type result_type(&scope, runtime_->typeOf(*result_obj));
EXPECT_TRUE(isStrEqualsCStr(result_type.name(), "iterator"));
}
TEST_F(InterpreterTest,
GetIterWithRaisingDescriptorDunderIterPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Desc:
def __get__(self, obj, type):
raise UserWarning("foo")
class C:
__iter__ = Desc()
it = C()
result = [x for x in it]
)"),
LayoutId::kTypeError,
"'C' object is not iterable"));
}
TEST_F(InterpreterTest, SequenceContains) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
a = {1, 2}
b = 1
c = 3
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Object contains_true(&scope,
Interpreter::sequenceContains(thread_, b, container));
Object contains_false(&scope,
Interpreter::sequenceContains(thread_, c, container));
EXPECT_EQ(contains_true, Bool::trueObj());
EXPECT_EQ(contains_false, Bool::falseObj());
}
TEST_F(InterpreterTest, SequenceIterSearchWithNoDunderIterRaisesTypeError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
container = C()
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "container"));
Object val(&scope, NoneType::object());
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest,
SequenceIterSearchWithNonCallableDunderIterRaisesTypeError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
__iter__ = None
container = C()
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "container"));
Object val(&scope, NoneType::object());
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, SequenceIterSearchWithNoDunderNextRaisesTypeError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D: pass
class C:
def __iter__(self):
return D()
container = C()
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "container"));
Object val(&scope, NoneType::object());
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest,
SequenceIterSearchWithNonCallableDunderNextRaisesTypeError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
__next__ = None
class C:
def __iter__(self):
return D()
container = C()
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "container"));
Object val(&scope, NoneType::object());
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, SequenceIterSearchWithListReturnsTrue) {
HandleScope scope(thread_);
List container(&scope, listFromRange(1, 3));
Object val(&scope, SmallInt::fromWord(2));
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
ASSERT_FALSE(result.isError());
EXPECT_EQ(result, Bool::trueObj());
}
TEST_F(InterpreterTest, SequenceIterSearchWithListReturnsFalse) {
HandleScope scope(thread_);
Object container(&scope, listFromRange(1, 3));
Object val(&scope, SmallInt::fromWord(5));
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
ASSERT_FALSE(result.isError());
EXPECT_EQ(result, Bool::falseObj());
}
TEST_F(InterpreterTest, SequenceIterSearchWithSequenceSearchesIterator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Seq:
def __getitem__(s, i):
return ("foo", "bar", 42)[i]
seq_iter = Seq()
)")
.isError());
Object seq_iter(&scope, mainModuleAt(runtime_, "seq_iter"));
Object obj_in_seq(&scope, SmallInt::fromWord(42));
Object contains_true(
&scope, Interpreter::sequenceIterSearch(thread_, obj_in_seq, seq_iter));
EXPECT_EQ(contains_true, Bool::trueObj());
Object obj_not_in_seq(&scope, NoneType::object());
Object contains_false(&scope, Interpreter::sequenceIterSearch(
thread_, obj_not_in_seq, seq_iter));
EXPECT_EQ(contains_false, Bool::falseObj());
}
TEST_F(InterpreterTest,
SequenceIterSearchWithIterThatRaisesPropagatesException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __iter__(self):
raise ZeroDivisionError("boom")
container = C()
)")
.isError());
Object container(&scope, mainModuleAt(runtime_, "container"));
Object val(&scope, SmallInt::fromWord(5));
Object result(&scope,
Interpreter::sequenceIterSearch(thread_, val, container));
EXPECT_TRUE(raised(*result, LayoutId::kZeroDivisionError));
}
TEST_F(InterpreterTest, ContextManagerCallEnterExit) {
const char* src = R"(
a = 1
class Foo:
def __enter__(self):
global a
a = 2
def __exit__(self, e, t, b):
global a
a = 3
b = 0
with Foo():
b = a
)";
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, src).isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
EXPECT_TRUE(isIntEqualsWord(*a, 3));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
}
TEST_F(InterpreterTest, ContextManagerCallEnterExitOfNotFunctionType) {
const char* src = R"(
class MyFunction:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, instance_type):
return self.fn
a = 1
def my_enter():
global a
a = 2
def my_exit(e, t, b):
global a
a = 3
class Foo:
__enter__ = MyFunction(my_enter)
__exit__ = MyFunction(my_exit)
b = 0
with Foo():
b = a
)";
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, src).isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
EXPECT_TRUE(isIntEqualsWord(*a, 3));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
}
TEST_F(InterpreterTest, StackCleanupAfterCallFunction) {
// Build the following function
// def foo(arg0=1, arg1=2):
// return 42
//
// Then call as foo(1) and verify that the stack is cleaned up after
// default argument expansion
//
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.argcount = 2;
const byte bytecode[] = {LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function callee(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(2));
Tuple defaults(&scope, runtime_->newTupleWith2(obj1, obj2));
callee.setDefaults(*defaults);
// Save starting value stack top
RawObject* value_stack_start = thread_->stackPointer();
// Push function pointer and argument
thread_->stackPush(*callee);
thread_->stackPush(SmallInt::fromWord(1));
// Make sure we got the right result and stack is back where it should be
EXPECT_TRUE(isIntEqualsWord(Interpreter::call(thread_, 1), 42));
EXPECT_EQ(value_stack_start, thread_->stackPointer());
}
TEST_F(InterpreterTest, StackCleanupAfterCallExFunction) {
// Build the following function
// def foo(arg0=1, arg1=2):
// return 42
//
// Then call as "f=(2,); foo(*f)" and verify that the stack is cleaned up
// after ex and default argument expansion
//
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.argcount = 2;
const byte bytecode[] = {LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function callee(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(2));
Tuple defaults(&scope, runtime_->newTupleWith2(obj1, obj2));
callee.setDefaults(*defaults);
// Save starting value stack top
RawObject* value_stack_start = thread_->stackPointer();
// Push function pointer and argument
Object arg(&scope, SmallInt::fromWord(2));
Tuple ex(&scope, runtime_->newTupleWith1(arg));
thread_->stackPush(*callee);
thread_->stackPush(*ex);
// Make sure we got the right result and stack is back where it should be
EXPECT_TRUE(isIntEqualsWord(Interpreter::callEx(thread_, 0), 42));
EXPECT_EQ(value_stack_start, thread_->stackPointer());
}
TEST_F(InterpreterTest, StackCleanupAfterCallKwFunction) {
HandleScope scope(thread_);
// Build the following function
// def foo(arg0=1, arg1=2):
// return 42
//
// Then call as "foo(b=4)" and verify that the stack is cleaned up after
// ex and default argument expansion
//
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
Tuple names(&scope, runtime_->emptyTuple());
Locals locals;
locals.argcount = 2;
const byte bytecode[] = {LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConstsNamesLocals(bytecode, consts, names,
&locals));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function callee(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
Object default1(&scope, SmallInt::fromWord(1));
Object default2(&scope, SmallInt::fromWord(2));
Tuple defaults(&scope, runtime_->newTupleWith2(default1, default2));
callee.setDefaults(*defaults);
// Save starting value stack top
RawObject* value_stack_start = thread_->stackPointer();
// Push function pointer and argument
Object arg(&scope, Runtime::internStrFromCStr(thread_, "arg1"));
Tuple arg_names(&scope, runtime_->newTupleWith1(arg));
thread_->stackPush(*callee);
thread_->stackPush(SmallInt::fromWord(4));
thread_->stackPush(*arg_names);
// Make sure we got the right result and stack is back where it should be
EXPECT_TRUE(isIntEqualsWord(Interpreter::callKw(thread_, 1), 42));
EXPECT_EQ(value_stack_start, thread_->stackPointer());
}
TEST_F(InterpreterTest, LookupMethodInvokesDescriptor) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f(): pass
class D:
def __get__(self, obj, owner):
return f
class C:
__call__ = D()
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object f(&scope, mainModuleAt(runtime_, "f"));
Object method(&scope, Interpreter::lookupMethod(thread_, c, ID(__call__)));
EXPECT_EQ(*f, *method);
}
TEST_F(InterpreterTest, PrepareCallableCallUnpacksBoundMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo():
pass
meth = C().foo
)")
.isError());
Object meth_obj(&scope, mainModuleAt(runtime_, "meth"));
ASSERT_TRUE(meth_obj.isBoundMethod());
thread_->stackPush(*meth_obj);
thread_->stackPush(SmallInt::fromWord(1234));
ASSERT_EQ(thread_->valueStackSize(), 2);
word nargs = 1;
Interpreter::PrepareCallableResult result =
Interpreter::prepareCallableCall(thread_, nargs, nargs);
ASSERT_TRUE(result.function.isFunction());
ASSERT_EQ(result.nargs, 2);
ASSERT_EQ(thread_->valueStackSize(), 3);
EXPECT_TRUE(isIntEqualsWord(thread_->stackPeek(0), 1234));
EXPECT_TRUE(thread_->stackPeek(1).isInstance());
EXPECT_EQ(thread_->stackPeek(2), result.function);
}
TEST_F(InterpreterTest, CallExWithListSubclassCallsDunderIter) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C(list):
def __iter__(self):
raise UserWarning('foo')
def f(a, b, c):
return (a, b, c)
c = C([1, 2, 3])
f(*c)
)"),
LayoutId::kUserWarning, "foo"));
}
static RawObject ALIGN_16 setPendingSignal(Thread* thread, Arguments) {
thread->runtime()->setPendingSignal(thread, SIGINT);
return NoneType::object();
}
TEST_F(InterpreterTest, CallFunctionWithInterruptSetReturnsErrorException) {
addBuiltin("set_pending_signal", setPendingSignal, {nullptr, 0}, 0);
EXPECT_FALSE(runFromCStr(runtime_, R"(
executed = False
def foo():
global executed
executed = True
def bar():
set_pending_signal()
foo()
)")
.isError());
HandleScope scope(thread_);
Object bar(&scope, mainModuleAt(runtime_, "bar"));
thread_->stackPush(*bar);
EXPECT_TRUE(
raised(Interpreter::call0(thread_, bar), LayoutId::kKeyboardInterrupt));
Object executed(&scope, mainModuleAt(runtime_, "executed"));
EXPECT_EQ(executed, Bool::falseObj());
}
static RawObject ALIGN_16 abortBuiltin(Thread*, Arguments) { abort(); }
TEST_F(InterpreterTest, CallFunctionWithBuiltinRaisesRecursionError) {
addBuiltin("abort", abortBuiltin, {nullptr, 0}, 0);
EXPECT_FALSE(runFromCStr(runtime_, R"(
x = None
def foo():
global x
x = 1
abort()
x = 2
)")
.isError());
HandleScope scope(thread_);
Object foo(&scope, mainModuleAt(runtime_, "foo"));
// Fill stack until we can fit exactly 1 function call.
RawObject* saved_sp = thread_->stackPointer();
while (!thread_->wouldStackOverflow(Frame::kSize * 2)) {
thread_->stackPush(NoneType::object());
}
EXPECT_TRUE(
raised(Interpreter::call0(thread_, foo), LayoutId::kRecursionError));
Object x(&scope, mainModuleAt(runtime_, "x"));
EXPECT_TRUE(isIntEqualsWord(*x, 1));
thread_->setStackPointer(saved_sp);
}
TEST_F(InterpreterTest, CallingUncallableRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "(1)()"),
LayoutId::kTypeError,
"'int' object is not callable"));
}
TEST_F(InterpreterTest, CallingUncallableDunderCallRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C:
__call__ = 1
c = C()
c()
)"),
LayoutId::kTypeError,
"'int' object is not callable"));
}
TEST_F(InterpreterTest,
CallingBoundMethodWithNonFunctionDunderFuncCallsDunderFunc) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
# from types import MethodType
MethodType = method
class C:
def __call__(self, arg):
return self, arg
func = C()
instance = object()
bound_method = MethodType(func, instance)
result = bound_method()
)")
.isError());
CHECK(!thread_->hasPendingException(), "no errors pls");
HandleScope scope(thread_);
Object result_obj(&scope, mainModuleAt(runtime_, "result"));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 2);
Object func(&scope, mainModuleAt(runtime_, "func"));
EXPECT_EQ(result.at(0), *func);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
EXPECT_EQ(result.at(1), *instance);
}
TEST_F(InterpreterTest, CallingNonDescriptorDunderCallRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class D: pass
class C:
__call__ = D()
c = C()
c()
)"),
LayoutId::kTypeError,
"'D' object is not callable"));
}
TEST_F(InterpreterTest, CallDescriptorReturningUncallableRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class D:
def __get__(self, instance, owner):
return 1
class C:
__call__ = D()
c = C()
c()
)"),
LayoutId::kTypeError,
"'int' object is not callable"));
}
TEST_F(InterpreterTest, LookupMethodLoopsOnCallBoundToDescriptor) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f(args):
return args
class C0:
def __get__(self, obj, owner):
return f
class C1:
__call__ = C0()
class C2:
def __get__(self, obj, owner):
return C1()
class C3:
__call__ = C2()
c = C3()
result = c(42)
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_EQ(*result, SmallInt::fromWord(42));
}
TEST_F(InterpreterTest, DunderIterReturnsNonIterable) {
const char* src = R"(
class Foo:
def __iter__(self):
return 1
a, b = Foo()
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kTypeError,
"iter() returned non-iterator of type 'int'"));
}
TEST_F(InterpreterTest, UnpackSequence) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = [1, 2, 3]
a, b, c = l
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
EXPECT_TRUE(isIntEqualsWord(*a, 1));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
EXPECT_TRUE(isIntEqualsWord(*c, 3));
}
TEST_F(InterpreterTest, UnpackSequenceWithSeqIterator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Seq:
def __getitem__(s, i):
return ("foo", "bar", 42)[i]
a, b, c = Seq()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
EXPECT_TRUE(isStrEqualsCStr(*a, "foo"));
EXPECT_TRUE(isStrEqualsCStr(*b, "bar"));
EXPECT_TRUE(isIntEqualsWord(*c, 42));
}
TEST_F(InterpreterTest, UnpackSequenceTooFewObjects) {
const char* src = R"(
l = [1, 2]
a, b, c = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"not enough values to unpack"));
}
TEST_F(InterpreterTest, UnpackSequenceTooManyObjects) {
const char* src = R"(
l = [1, 2, 3, 4]
a, b, c = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"too many values to unpack"));
}
TEST_F(InterpreterTest, UnpackTuple) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = (1, 2, 3)
a, b, c = l
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
EXPECT_TRUE(isIntEqualsWord(*a, 1));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
EXPECT_TRUE(isIntEqualsWord(*c, 3));
}
TEST_F(InterpreterTest, UnpackTupleTooFewObjects) {
const char* src = R"(
l = (1, 2)
a, b, c = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"not enough values to unpack"));
}
TEST_F(InterpreterTest, UnpackTupleTooManyObjects) {
const char* src = R"(
l = (1, 2, 3, 4)
a, b, c = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"too many values to unpack"));
}
TEST_F(InterpreterTest, PrintExprInvokesDisplayhook) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
import sys
MY_GLOBAL = 1234
def my_displayhook(value):
global MY_GLOBAL
MY_GLOBAL = value
sys.displayhook = my_displayhook
)")
.isError());
Object unique(&scope, runtime_->newList()); // unique object
Object none(&scope, NoneType::object());
Tuple consts(&scope, runtime_->newTupleWith2(unique, none));
const byte bytecode[] = {LOAD_CONST, 0, PRINT_EXPR, 0,
LOAD_CONST, 1, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
ASSERT_TRUE(runCode(code).isNoneType());
Object displayhook(&scope, moduleAtByCStr(runtime_, "sys", "displayhook"));
Object my_displayhook(&scope, mainModuleAt(runtime_, "my_displayhook"));
EXPECT_EQ(*displayhook, *my_displayhook);
Object my_global(&scope, mainModuleAt(runtime_, "MY_GLOBAL"));
EXPECT_EQ(*my_global, *unique);
}
TEST_F(InterpreterTest, PrintExprtDoesntPushToStack) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
import sys
def my_displayhook(value):
pass
sys.displayhook = my_displayhook
)")
.isError());
Object obj1(&scope, SmallInt::fromWord(42));
Object obj2(&scope, SmallInt::fromWord(0));
Tuple consts(&scope, runtime_->newTupleWith2(obj1, obj2));
// This bytecode loads 42 onto the stack, along with a value to print.
// It then returns the top of the stack, which should be 42.
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, PRINT_EXPR, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result_obj(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result_obj, 42));
}
TEST_F(InterpreterTest, GetAiterCallsAiter) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class AsyncIterable:
def __aiter__(self):
return 42
a = AsyncIterable()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Tuple consts(&scope, runtime_->newTupleWith1(a));
const byte bytecode[] = {LOAD_CONST, 0, GET_AITER, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result, 42));
}
TEST_F(InterpreterTest, GetAiterOnNonIterable) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {LOAD_CONST, 0, GET_AITER, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, BeginFinallyPushesNone) {
HandleScope scope(thread_);
Tuple consts(&scope, runtime_->emptyTuple());
const byte bytecode[] = {BEGIN_FINALLY, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(result.isNoneType());
}
TEST_F(InterpreterTest, CallFinallyPushesNextPC) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {CALL_FINALLY, 2, LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
// Address of LOAD_CONST
EXPECT_TRUE(isIntEqualsWord(*result, kCodeUnitSize));
}
TEST_F(InterpreterTest, CallFinallyJumpsWithArgDelta) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {CALL_FINALLY, 2, RETURN_VALUE, 0,
LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
// Result of LOAD_CONST
EXPECT_TRUE(isIntEqualsWord(*result, 123));
}
TEST_F(InterpreterTest, PopFinallyWithNoneExcAndZeroArgPopsExc) {
HandleScope scope(thread_);
Object return_value(&scope, SmallInt::fromWord(123));
Object exc(&scope, NoneType::object());
Tuple consts(&scope, runtime_->newTupleWith2(return_value, exc));
const byte bytecode[] = {// Load return value
LOAD_CONST, 0,
// Load exc
LOAD_CONST, 1,
// 0 means don't pop from the stack
POP_FINALLY, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result, 123));
}
TEST_F(InterpreterTest, PopFinallyWithNoneExcAndNonzeroArgPopsExc) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Object exc(&scope, NoneType::object());
Object return_value(&scope, SmallInt::fromWord(456));
Tuple consts(&scope, runtime_->newTupleWith3(obj, exc, return_value));
const byte bytecode[] = {
// Load some random stuff onto the stack
LOAD_CONST, 0,
// Load exc
LOAD_CONST, 1,
// Load return value
LOAD_CONST, 2,
// 1 means pop first before fetching exc, and then push after
POP_FINALLY, 1, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result, 456));
}
TEST_F(InterpreterTest, PopFinallyWithIntExcAndZeroArgPopsExc) {
HandleScope scope(thread_);
Object return_value(&scope, SmallInt::fromWord(123));
Object exc(&scope, SmallInt::fromWord(456));
Tuple consts(&scope, runtime_->newTupleWith2(return_value, exc));
const byte bytecode[] = {// Load return value
LOAD_CONST, 0,
// Load exc
LOAD_CONST, 1,
// 0 means don't pop from the stack
POP_FINALLY, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result, 123));
}
TEST_F(InterpreterTest, PopFinallyWithIntExcAndNonzeroArgPopsExc) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Object exc(&scope, SmallInt::fromWord(456));
Object return_value(&scope, SmallInt::fromWord(789));
Tuple consts(&scope, runtime_->newTupleWith3(obj, exc, return_value));
const byte bytecode[] = {
// Load some random stuff onto the stack
LOAD_CONST, 0,
// Load exc
LOAD_CONST, 1,
// Load return value
LOAD_CONST, 2,
// 1 means pop first before fetching exc, and then push after
POP_FINALLY, 1, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(isIntEqualsWord(*result, 789));
}
TEST_F(InterpreterTest, PopFinallyWithNonExceptHandlerRaisesSystemError) {
HandleScope scope(thread_);
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(2));
Object exc_type(&scope, SmallInt::fromWord(3));
Object exc_value(&scope, SmallInt::fromWord(4));
Object exc_tb(&scope, SmallInt::fromWord(5));
Object exc(&scope, SmallStr::fromCStr("exc"));
Object return_value(&scope, SmallInt::fromWord(7));
Tuple consts(&scope,
runtime_->newTupleWithN(7, &obj1, &obj2, &exc_type, &exc_value,
&exc_tb, &exc, &return_value));
const byte bytecode[] = {
// Load return value
LOAD_CONST, 6,
// Load exc traceback
LOAD_CONST, 4,
// Load exc value
LOAD_CONST, 3,
// Load exc type
LOAD_CONST, 2,
// Load ignored object
LOAD_CONST, 0,
// Load ignored object
LOAD_CONST, 1,
// Load exc
LOAD_CONST, 5,
// Push a non-ExceptHandler TryBlock on the block stack
SETUP_FINALLY, 0, POP_FINALLY, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kSystemError,
"popped block is not an except handler"));
}
TEST_F(InterpreterTest, EndAsyncForWithExceptionRaises) {
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 0, // exc_traceback
LOAD_CONST, 1, // exc_value
LOAD_CONST, 2, // exc_type
END_ASYNC_FOR, 0,
};
Object exc_traceback(&scope, runtime_->newTraceback());
Object exc_type(&scope, runtime_->typeAt(LayoutId::kUserWarning));
Object exc_value(&scope, runtime_->newStrFromCStr("exc message"));
Tuple consts(&scope,
runtime_->newTupleWith3(exc_traceback, exc_value, exc_type));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(
raisedWithStr(runCode(code), LayoutId::kUserWarning, "exc message"));
}
TEST_F(InterpreterTest, EndAsyncForWithStopAsyncIterationContinues) {
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 5, // dummy
SETUP_FINALLY, 10, LOAD_CONST, 0, // exc_traceback
LOAD_CONST, 1, // exc_value
LOAD_CONST, 2, // exc_type
LOAD_CONST, 3, // stop_async_iteration
RAISE_VARARGS, 1, END_ASYNC_FOR, 4, LOAD_CONST, 5, // dummy
RETURN_VALUE, 0, LOAD_CONST, 4, // 42
RETURN_VALUE, 0,
};
Object exc_traceback(&scope, runtime_->newTraceback());
Object exc_value(&scope, runtime_->newStrFromCStr("exc message"));
Object exc_type(&scope, runtime_->typeAt(LayoutId::kUserWarning));
Object stop_async_iteration(&scope,
runtime_->typeAt(LayoutId::kStopAsyncIteration));
Object value(&scope, runtime_->newInt(42));
Object dummy(&scope, runtime_->newInt(-7));
Tuple consts(&scope,
runtime_->newTupleWithN(6, &exc_traceback, &exc_value, &exc_type,
&stop_async_iteration, &value, &dummy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(isIntEqualsWord(runCode(code), 42));
}
TEST_F(InterpreterTest, BeforeAsyncWithCallsDunderAenter) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
enter = None
exit = None
class M:
def __aenter__(self):
global enter
enter = self
def __aexit__(self, exc_type, exc_value, traceback):
global exit
exit = self
manager = M()
)")
.isError());
Object manager(&scope, mainModuleAt(runtime_, "manager"));
Object main_obj(&scope, findMainModule(runtime_));
ASSERT_TRUE(main_obj.isModule());
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith2(obj, manager));
const byte bytecode[] = {LOAD_CONST, 1, BEFORE_ASYNC_WITH, 0, POP_TOP, 0,
LOAD_CONST, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(isIntEqualsWord(runCode(code), 42));
Object enter(&scope, mainModuleAt(runtime_, "enter"));
EXPECT_EQ(*enter, *manager);
Object exit(&scope, mainModuleAt(runtime_, "exit"));
EXPECT_EQ(*exit, NoneType::object());
}
TEST_F(InterpreterTest, BeforeAsyncWithRaisesAttributeErrorIfAexitNotDefined) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class M:
pass
manager = M()
)")
.isError());
Object manager(&scope, mainModuleAt(runtime_, "manager"));
Tuple consts(&scope, runtime_->newTupleWith1(manager));
const byte bytecode[] = {LOAD_CONST, 0, BEFORE_ASYNC_WITH, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kAttributeError,
"'M' object has no attribute '__aexit__'"));
}
TEST_F(InterpreterTest, BeforeAsyncWithRaisesAttributeErrorIfAenterNotDefined) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class M:
def __aexit__(self):
pass
manager = M()
)")
.isError());
Object manager(&scope, mainModuleAt(runtime_, "manager"));
Tuple consts(&scope, runtime_->newTupleWith1(manager));
const byte bytecode[] = {LOAD_CONST, 0, BEFORE_ASYNC_WITH, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kAttributeError,
"'M' object has no attribute '__aenter__'"));
}
TEST_F(InterpreterTest,
BeforeAsyncWithPropagatesExceptionIfResolvingAexitDynamicallyRaises) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __get__(self, obj, type=None):
raise RuntimeError("foo")
class M:
__aexit__ = A()
async def __aenter__(self):
pass
manager = M()
)")
.isError());
Object manager(&scope, mainModuleAt(runtime_, "manager"));
Tuple consts(&scope, runtime_->newTupleWith1(manager));
const byte bytecode[] = {LOAD_CONST, 0, BEFORE_ASYNC_WITH, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kRuntimeError, "foo"));
}
TEST_F(InterpreterTest,
BeforeAsyncWithPropagatesExceptionIfResolvingAenterDynamicallyRaises) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
def __get__(self, obj, type=None):
raise RuntimeError("foo")
class M:
__aenter__ = A()
async def __aexit__(self, a, b, c):
pass
manager = M()
)")
.isError());
Object manager(&scope, mainModuleAt(runtime_, "manager"));
Tuple consts(&scope, runtime_->newTupleWith1(manager));
const byte bytecode[] = {LOAD_CONST, 0, BEFORE_ASYNC_WITH, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_TRUE(raisedWithStr(runCode(code), LayoutId::kRuntimeError, "foo"));
}
TEST_F(InterpreterTest, SetupAsyncWithPushesBlock) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(42));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {
LOAD_CONST, 0, SETUP_ASYNC_WITH, 0, POP_BLOCK, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
EXPECT_EQ(runCode(code), SmallInt::fromWord(42));
}
TEST_F(InterpreterTest, UnpackSequenceEx) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = [1, 2, 3, 4, 5, 6, 7]
a, b, c, *d, e, f, g = l
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
EXPECT_TRUE(isIntEqualsWord(*a, 1));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
EXPECT_TRUE(isIntEqualsWord(*c, 3));
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isList());
List list(&scope, *d);
EXPECT_EQ(list.numItems(), 1);
EXPECT_TRUE(isIntEqualsWord(list.at(0), 4));
Object e(&scope, mainModuleAt(runtime_, "e"));
Object f(&scope, mainModuleAt(runtime_, "f"));
Object g(&scope, mainModuleAt(runtime_, "g"));
EXPECT_TRUE(isIntEqualsWord(*e, 5));
EXPECT_TRUE(isIntEqualsWord(*f, 6));
EXPECT_TRUE(isIntEqualsWord(*g, 7));
}
TEST_F(InterpreterTest, UnpackSequenceExWithSeqIterator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Seq:
def __getitem__(s, i):
return ("foo", "bar", 42)[i]
a, *b = Seq()
)")
.isError());
EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "a"), "foo"));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_PYLIST_EQ(b, {"bar", 42});
}
TEST_F(InterpreterTest, UnpackSequenceExWithNoElementsAfter) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = [1, 2, 3, 4]
a, b, *c = l
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
EXPECT_TRUE(isIntEqualsWord(*a, 1));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
ASSERT_TRUE(c.isList());
List list(&scope, *c);
ASSERT_EQ(list.numItems(), 2);
EXPECT_TRUE(isIntEqualsWord(list.at(0), 3));
EXPECT_TRUE(isIntEqualsWord(list.at(1), 4));
}
TEST_F(InterpreterTest, UnpackSequenceExWithNoElementsBefore) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = [1, 2, 3, 4]
*a, b, c = l
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
ASSERT_TRUE(a.isList());
List list(&scope, *a);
ASSERT_EQ(list.numItems(), 2);
EXPECT_TRUE(isIntEqualsWord(list.at(0), 1));
EXPECT_TRUE(isIntEqualsWord(list.at(1), 2));
EXPECT_TRUE(isIntEqualsWord(*b, 3));
EXPECT_TRUE(isIntEqualsWord(*c, 4));
}
TEST_F(InterpreterTest, BuildMapCallsDunderHashAndPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C:
def __hash__(self):
raise ValueError('foo')
d = {C(): 4}
)"),
LayoutId::kValueError, "foo"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithDict) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
d = {**{'a': 1, 'b': 2}, 'c': 3, **{'d': 4}}
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithListKeysMapping) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __init__(self):
self.idx = 0
self._items = [('a', 1), ('b', 2), ('c', 3)]
def keys(self):
return [x[0] for x in self._items]
def __getitem__(self, key):
for k, v in self._items:
if key == k:
return v
raise KeyError()
d = {**Foo(), 'd': 4}
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithTupleKeysMapping) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __init__(self):
self.idx = 0
self._items = [('a', 1), ('b', 2), ('c', 3)]
def keys(self):
return ('a', 'b', 'c')
def __getitem__(self, key):
for k, v in self._items:
if key == k:
return v
raise KeyError()
d = {**Foo(), 'd': 4}
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithIterableKeysMapping) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class KeysIter:
def __init__(self, keys):
self.idx = 0
self.keys = keys
def __iter__(self):
return self
def __next__(self):
if self.idx == len(self.keys):
raise StopIteration
r = self.keys[self.idx]
self.idx += 1
return r
class Foo:
def __init__(self):
self.idx = 0
self._items = [('a', 1), ('b', 2), ('c', 3)]
def keys(self):
return KeysIter([x[0] for x in self._items])
def __getitem__(self, key):
for k, v in self._items:
if key == k:
return v
raise KeyError()
d = {**Foo(), 'd': 4}
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithNonMapping) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
pass
d = {**Foo(), 'd': 4}
)"),
LayoutId::kTypeError,
"'Foo' object is not a mapping"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithUnsubscriptableMapping) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def __init__(self):
self.idx = 0
self._items = [('a', 1), ('b', 2), ('c', 3)]
def keys(self):
return ('a', 'b', 'c')
d = {**Foo(), 'd': 4}
)"),
LayoutId::kTypeError,
"'Foo' object is not a mapping"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithNonIterableKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def __init__(self):
self.idx = 0
self._items = [('a', 1), ('b', 2), ('c', 3)]
def keys(self):
return None
def __getitem__(self, key):
pass
d = {**Foo(), 'd': 4}
)"),
LayoutId::kTypeError, "keys() is not iterable"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithBadIteratorKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class KeysIter:
def __iter__(self):
return self
class Foo:
def __init__(self):
pass
def keys(self):
return KeysIter()
def __getitem__(self, key):
pass
d = {**Foo(), 'd': 4}
)"),
LayoutId::kTypeError, "keys() is not iterable"));
}
TEST_F(InterpreterTest, BuildSetCallsDunderHashAndPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C:
def __hash__(self):
raise ValueError('foo')
s = {C()}
)"),
LayoutId::kValueError, "foo"));
}
TEST_F(InterpreterTest, UnpackSequenceExWithTooFewObjectsBefore) {
const char* src = R"(
l = [1, 2]
a, b, c, *d = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"not enough values to unpack"));
}
TEST_F(InterpreterTest, UnpackSequenceExWithTooFewObjectsAfter) {
const char* src = R"(
l = [1, 2]
*a, b, c, d = l
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kValueError,
"not enough values to unpack"));
}
TEST_F(InterpreterTest, BuildTupleUnpackWithCall) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(*args):
return args
t = foo(*(1,2), *(3, 4))
)")
.isError());
Object t(&scope, mainModuleAt(runtime_, "t"));
ASSERT_TRUE(t.isTuple());
Tuple tuple(&scope, *t);
EXPECT_TRUE(isIntEqualsWord(tuple.at(0), 1));
EXPECT_TRUE(isIntEqualsWord(tuple.at(1), 2));
EXPECT_TRUE(isIntEqualsWord(tuple.at(2), 3));
EXPECT_TRUE(isIntEqualsWord(tuple.at(3), 4));
}
TEST_F(InterpreterTest, FunctionDerefsVariable) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def outer():
var = 1
def inner():
return var
del var
return 0
v = outer()
)")
.isError());
Object v(&scope, mainModuleAt(runtime_, "v"));
EXPECT_TRUE(isIntEqualsWord(*v, 0));
}
TEST_F(InterpreterTest, FunctionAccessesUnboundVariable) {
const char* src = R"(
def outer():
var = 1
def inner():
return var
del var
return var
v = outer()
)";
EXPECT_TRUE(
raisedWithStr(runFromCStr(runtime_, src), LayoutId::kUnboundLocalError,
"local variable 'var' referenced before assignment"));
}
TEST_F(InterpreterTest, ImportStarImportsPublicSymbols) {
HandleScope scope(thread_);
Object module_src(&scope, runtime_->newStrFromCStr(R"(
def public_symbol():
return 1
def public_symbol2():
return 2
)"));
Object filename(&scope, runtime_->newStrFromCStr("<test string>"));
// Preload the module
Object name(&scope, runtime_->newStrFromCStr("test_module"));
Code code(&scope, compile(thread_, module_src, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
ASSERT_FALSE(executeModuleFromCode(thread_, code, name).isError());
ASSERT_FALSE(runFromCStr(runtime_, R"(
from test_module import *
a = public_symbol()
b = public_symbol2()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isIntEqualsWord(*a, 1));
EXPECT_TRUE(isIntEqualsWord(*b, 2));
}
TEST_F(InterpreterTest, ImportStarDoesNotImportPrivateSymbols) {
HandleScope scope(thread_);
Object module_src(&scope, runtime_->newStrFromCStr(R"(
def public_symbol():
return 1
def _private_symbol():
return 2
)"));
Object filename(&scope, runtime_->newStrFromCStr("<test string>"));
// Preload the module
Object name(&scope, runtime_->newStrFromCStr("test_module"));
Code code(&scope, compile(thread_, module_src, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
ASSERT_FALSE(executeModuleFromCode(thread_, code, name).isError());
const char* main_src = R"(
from test_module import *
a = public_symbol()
b = _private_symbol()
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, main_src),
LayoutId::kNameError,
"name '_private_symbol' is not defined"));
}
TEST_F(InterpreterTest, ImportStarWorksWithDictImplicitGlobals) {
HandleScope scope(thread_);
Object module_src(&scope, runtime_->newStrFromCStr(R"(
def foo():
return "bar"
def baz():
return "quux"
)"));
Object filename(&scope, runtime_->newStrFromCStr("<test string>"));
// Preload the module
Object name(&scope, runtime_->newStrFromCStr("test_module"));
Code module_code(&scope, compile(thread_, module_src, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
ASSERT_FALSE(executeModuleFromCode(thread_, module_code, name).isError());
const char* main_src = R"(
from test_module import *
a = foo()
b = baz()
)";
Object str(&scope, runtime_->newStrFromCStr(main_src));
Code main_code(&scope, compile(thread_, str, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
Module main_module(&scope, findMainModule(runtime_));
Dict implicit_globals(&scope, runtime_->newDict());
Object result(&scope,
thread_->exec(main_code, main_module, implicit_globals));
EXPECT_FALSE(result.isError());
EXPECT_EQ(implicit_globals.numItems(), 4);
}
TEST_F(InterpreterTest, ImportStarWorksWithUserDefinedImplicitGlobals) {
HandleScope scope(thread_);
Object module_src(&scope, runtime_->newStrFromCStr(R"(
def foo():
return "bar"
def baz():
return "quux"
)"));
Object filename(&scope, runtime_->newStrFromCStr("<test string>"));
// Preload the module
Object name(&scope, runtime_->newStrFromCStr("test_module"));
Code module_code(&scope, compile(thread_, module_src, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
ASSERT_FALSE(executeModuleFromCode(thread_, module_code, name).isError());
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.mydict = {}
def __setitem__(self, key, value):
self.mydict[key] = value
def __getitem__(self, key):
return self.mydict[key]
)")
.isError());
const char* main_src = R"(
from test_module import *
a = foo()
b = baz()
)";
Object str(&scope, runtime_->newStrFromCStr(main_src));
Code main_code(&scope, compile(thread_, str, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
Module main_module(&scope, findMainModule(runtime_));
Type implicit_globals_type(&scope, mainModuleAt(runtime_, "C"));
Object implicit_globals(
&scope, thread_->invokeMethod1(implicit_globals_type, ID(__call__)));
Object result(&scope,
thread_->exec(main_code, main_module, implicit_globals));
EXPECT_FALSE(result.isError());
}
TEST_F(InterpreterTest, ImportCallsBuiltinsDunderImport) {
ASSERT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
import builtins
def import_forbidden(name, globals, locals, fromlist, level):
raise Exception("import forbidden")
builtins.__import__ = import_forbidden
import builtins
)"),
LayoutId::kException, "import forbidden"));
}
TEST_F(InterpreterTest, GetAnextCallsAnextAndAwait) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
anext_called = None
await_called = None
class AsyncIterator:
def __anext__(self):
global anext_called
anext_called = self
return self
def __await__(self):
global await_called
await_called = self
return self
# Return from __await__ must be an "iterable" type
def __next__(self):
pass
a = AsyncIterator()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Tuple consts(&scope, runtime_->newTupleWith1(a));
const byte bytecode[] = {LOAD_CONST, 0, GET_ANEXT, 0,
BUILD_TUPLE, 2, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Tuple result(&scope, runCode(code));
EXPECT_EQ(*a, result.at(0));
EXPECT_EQ(*a, result.at(1));
Object anext(&scope, mainModuleAt(runtime_, "anext_called"));
EXPECT_EQ(*a, *anext);
Object await(&scope, mainModuleAt(runtime_, "await_called"));
EXPECT_EQ(*a, *await);
}
TEST_F(InterpreterTest, GetAnextCallsAnextButNotAwaitOnAsyncGenerator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
async def f():
yield
async_gen = f()
class AsyncIterator:
def __anext__(self):
return async_gen
async_it = AsyncIterator()
)")
.isError());
Object async_gen(&scope, mainModuleAt(runtime_, "async_gen"));
Object async_it(&scope, mainModuleAt(runtime_, "async_it"));
// The async generator object instance should not have an __await__() method.
ASSERT_TRUE(Interpreter::lookupMethod(thread_, async_gen, ID(__await__))
.isErrorNotFound());
Tuple consts(&scope, runtime_->newTupleWith1(async_it));
const byte bytecode[] = {LOAD_CONST, 0, GET_ANEXT, 0,
BUILD_TUPLE, 2, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Tuple result(&scope, runCode(code));
EXPECT_EQ(*async_it, result.at(0));
EXPECT_EQ(runtime_->typeOf(result.at(1)),
runtime_->typeAt(LayoutId::kAsyncGenerator));
}
TEST_F(InterpreterTest, GetAnextOnNonIterable) {
HandleScope scope(thread_);
Object obj(&scope, SmallInt::fromWord(123));
Tuple consts(&scope, runtime_->newTupleWith1(obj));
const byte bytecode[] = {LOAD_CONST, 0, GET_ANEXT, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, GetAnextWithInvalidAnext) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class AsyncIterator:
def __anext__(self):
return 42
a = AsyncIterator()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Tuple consts(&scope, runtime_->newTupleWith1(a));
const byte bytecode[] = {LOAD_CONST, 0, GET_ANEXT, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object result(&scope, runCode(code));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
static RawObject runCodeCallingGetAwaitableOnObject(Thread* thread,
const Object& obj) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Tuple consts(&scope, runtime->newTupleWith1(obj));
const byte bytecode[] = {LOAD_CONST, 0, GET_AWAITABLE, 0, RETURN_VALUE, 0};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
return runCode(code);
}
TEST_F(InterpreterTest, GetAwaitableCallsAwait) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
# Return from __await__ must be an "iterable" type
iterable = iter([])
class Awaitable:
def __await__(self):
return iterable
a = Awaitable()
)")
.isError());
Object iterable(&scope, mainModuleAt(runtime_, "iterable"));
Object a(&scope, mainModuleAt(runtime_, "a"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, a));
EXPECT_EQ(result, iterable);
}
TEST_F(InterpreterTest, GetAwaitableIsNoOpOnCoroutine) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
async def f(): pass
coro = f()
)")
.isError());
Object coro(&scope, mainModuleAt(runtime_, "coro"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, coro));
EXPECT_TRUE(*result == *coro);
}
TEST_F(InterpreterTest, GetAwaitableIsNoOpOnAsyncGenerator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
async def f(): yield
async_gen = f()
)")
.isError());
Object async_gen(&scope, mainModuleAt(runtime_, "async_gen"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, async_gen));
EXPECT_TRUE(*result == *async_gen);
}
TEST_F(InterpreterTest, GetAwaitableRaisesOnUnflaggedGenerator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f(): yield
generator = f()
)")
.isError());
Object generator(&scope, mainModuleAt(runtime_, "generator"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, generator));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, GetAwaitableIsNoOpOnFlaggedGenerator) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f(): yield
)")
.isError());
Function generator_function(&scope, mainModuleAt(runtime_, "f"));
generator_function.setFlags(generator_function.flags() |
RawFunction::Flags::kIterableCoroutine);
ASSERT_FALSE(runFromCStr(runtime_, R"(
generator = f()
)")
.isError());
Object generator(&scope, mainModuleAt(runtime_, "generator"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, generator));
EXPECT_TRUE(*result == *generator);
}
TEST_F(InterpreterTest, GetAwaitableOnNonAwaitable) {
HandleScope scope(thread_);
Object str(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object result(&scope, runCodeCallingGetAwaitableOnObject(thread_, str));
EXPECT_TRUE(raised(*result, LayoutId::kTypeError));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallDict) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(**kwargs):
return kwargs
d = foo(**{'a': 1, 'b': 2}, **{'c': 3, 'd': 4})
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallTupleKeys) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __init__(self, d):
self.d = d
def keys(self):
return ('c', 'd')
def __getitem__(self, key):
return self.d[key]
def foo(**kwargs):
return kwargs
d = foo(**{'a': 1, 'b': 2}, **Foo({'c': 3, 'd': 4}))
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallListKeys) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __init__(self, d):
self.d = d
def keys(self):
return ['c', 'd']
def __getitem__(self, key):
return self.d[key]
def foo(**kwargs):
return kwargs
d = foo(**{'a': 1, 'b': 2}, **Foo({'c': 3, 'd': 4}))
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallIteratorKeys) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Iter:
def __init__(self, keys):
self.idx = 0
self.keys = keys
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.keys):
raise StopIteration()
r = self.keys[self.idx]
self.idx += 1
return r
def __length_hint__(self):
return len(self.keys) - self.idx
class Foo:
def __init__(self, d):
self.d = d
def keys(self):
return Iter(['c', 'd'])
def __getitem__(self, key):
return self.d[key]
def foo(**kwargs):
return kwargs
d = foo(**{'a': 1, 'b': 2}, **Foo({'c': 3, 'd': 4}))
)")
.isError());
Object d(&scope, mainModuleAt(runtime_, "d"));
ASSERT_TRUE(d.isDict());
Dict dict(&scope, *d);
EXPECT_EQ(dict.numItems(), 4);
Str name(&scope, runtime_->newStrFromCStr("a"));
Object el0(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el0, 1));
name = runtime_->newStrFromCStr("b");
Object el1(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el1, 2));
name = runtime_->newStrFromCStr("c");
Object el2(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el2, 3));
name = runtime_->newStrFromCStr("d");
Object el3(&scope, dictAtByStr(thread_, dict, name));
EXPECT_TRUE(isIntEqualsWord(*el3, 4));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallDictNonStrKey) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **{'c': 3, 4: 4})
)"),
LayoutId::kTypeError, "keywords must be strings"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallDictRepeatedKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **{'c': 3, 'a': 4})
)"),
LayoutId::kTypeError,
"got multiple values for keyword argument 'a'"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallNonMapping) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError,
"'Foo' object is not a mapping"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallNonSubscriptable) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError,
"'Foo' object is not a mapping"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallListKeysNonStrKey) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
return [1]
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError, "keywords must be strings"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallListKeysRepeatedKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
return ['a']
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError,
"got multiple values for keyword argument 'a'"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallTupleKeysNonStrKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
return (1,)
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError, "keywords must be strings"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallTupleKeysRepeatedKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
return ('a',)
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError,
"got multiple values for keyword argument 'a'"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallNonIterableKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def keys(self):
return None
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError, "keys() is not iterable"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallIterableWithoutNext) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Iter:
def __iter__(self):
return self
class Foo:
def keys(self):
return Iter()
def __getitem__(self, key):
pass
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError, "keys() is not iterable"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallIterableNonStrKey) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Iter:
def __init__(self, keys):
self.idx = 0
self.keys = keys
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.keys):
raise StopIteration()
r = self.keys[self.idx]
self.idx += 1
return r
def __length_hint__(self):
return len(self.keys) - self.idx
class Foo:
def keys(self):
return Iter((1, 2, 3))
def __getitem__(self, key):
return 0
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError, "keywords must be strings"));
}
TEST_F(InterpreterTest, BuildMapUnpackWithCallIterableRepeatedKeys) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Iter:
def __init__(self, keys):
self.idx = 0
self.keys = keys
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.keys):
raise StopIteration()
r = self.keys[self.idx]
self.idx += 1
return r
def __length_hint__(self):
return len(self.keys) - self.idx
class Foo:
def keys(self):
return Iter(('a', 'a'))
def __getitem__(self, key):
return 0
def foo(**kwargs):
return kwargs
foo(**{'a': 1, 'b': 2}, **Foo())
)"),
LayoutId::kTypeError,
"got multiple values for keyword argument 'a'"));
}
TEST_F(InterpreterTest, YieldFromIterReturnsIter) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class FooIterator:
def __next__(self):
pass
class Foo:
def __iter__(self):
return FooIterator()
foo = Foo()
)")
.isError());
Object foo(&scope, mainModuleAt(runtime_, "foo"));
// Create a code object and set the foo instance as a const
Tuple consts(&scope, runtime_->newTupleWith1(foo));
// Python code:
// foo = Foo()
// def bar():
// yield from foo
const byte bytecode[] = {
LOAD_CONST, 0, // (foo)
GET_YIELD_FROM_ITER, 0, // iter(foo)
RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
// Confirm that the returned value is the iterator of Foo
Object result(&scope, runCode(code));
Type result_type(&scope, runtime_->typeOf(*result));
EXPECT_TRUE(isStrEqualsCStr(result_type.name(), "FooIterator"));
}
TEST_F(InterpreterTest, YieldFromIterWithSequenceReturnsIter) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class FooSequence:
def __getitem__(self, i):
return ("foo", "bar")[i]
foo = FooSequence()
)")
.isError());
Object foo(&scope, mainModuleAt(runtime_, "foo"));
// Create a code object and set the foo instance as a const
Tuple consts(&scope, runtime_->newTupleWith1(foo));
// Python code:
// foo = FooSequence()
// def bar():
// yield from foo
const byte bytecode[] = {
LOAD_CONST, 0, // (foo)
GET_YIELD_FROM_ITER, 0, // iter(foo)
RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
// Confirm that the returned value is a sequence iterator
Object result(&scope, runCode(code));
Type result_type(&scope, runtime_->typeOf(*result));
EXPECT_TRUE(isStrEqualsCStr(result_type.name(), "iterator"));
}
TEST_F(InterpreterTest, YieldFromIterRaisesException) {
const char* src = R"(
def yield_from_func():
yield from 1
for i in yield_from_func():
pass
)";
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, src), LayoutId::kTypeError,
"'int' object is not iterable"));
}
TEST_F(InterpreterTest, YieldFromCoroutineInNonCoroutineIterRaisesException) {
const char* src = R"(
async def coro():
pass
def f():
yield from coro()
f().send(None)
)";
EXPECT_TRUE(
raisedWithStr(runFromCStr(runtime_, src), LayoutId::kTypeError,
"cannot 'yield from' a coroutine object in a non-coroutine "
"generator"));
}
TEST_F(InterpreterTest, MakeFunctionSetsDunderModule) {
HandleScope scope(thread_);
Object module_name(&scope, runtime_->newStrFromCStr("foo"));
Object module_src(&scope, runtime_->newStrFromCStr(R"(
def bar(): pass
)"));
Object filename(&scope, runtime_->newStrFromCStr("<test string>"));
Code code(&scope, compile(thread_, module_src, filename, ID(exec),
/*flags=*/0, /*optimize=*/0));
ASSERT_FALSE(executeModuleFromCode(thread_, code, module_name).isError());
ASSERT_FALSE(runFromCStr(runtime_, R"(
import foo
def baz(): pass
a = getattr(foo.bar, '__module__')
b = getattr(baz, '__module__')
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
ASSERT_TRUE(a.isStr());
EXPECT_TRUE(Str::cast(*a).equalsCStr("foo"));
Object b(&scope, mainModuleAt(runtime_, "b"));
ASSERT_TRUE(b.isStr());
EXPECT_TRUE(Str::cast(*b).equalsCStr("__main__"));
}
TEST_F(InterpreterTest, MakeFunctionSetsDunderQualname) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def bar(): pass
def baz(): pass
a = getattr(Foo.bar, '__qualname__')
b = getattr(baz, '__qualname__')
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
ASSERT_TRUE(a.isStr());
EXPECT_TRUE(Str::cast(*a).equalsCStr("Foo.bar"));
Object b(&scope, mainModuleAt(runtime_, "b"));
ASSERT_TRUE(b.isStr());
EXPECT_TRUE(Str::cast(*b).equalsCStr("baz"));
}
TEST_F(InterpreterTest, MakeFunctionSetsDunderDoc) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo():
"""This is a docstring"""
pass
def bar(): pass
)")
.isError());
Object foo(&scope, testing::mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(foo.isFunction());
Object foo_docstring(&scope, Function::cast(*foo).doc());
ASSERT_TRUE(foo_docstring.isStr());
EXPECT_TRUE(Str::cast(*foo_docstring).equalsCStr("This is a docstring"));
Object bar(&scope, testing::mainModuleAt(runtime_, "bar"));
ASSERT_TRUE(bar.isFunction());
Object bar_docstring(&scope, Function::cast(*bar).doc());
EXPECT_TRUE(bar_docstring.isNoneType());
}
TEST_F(InterpreterTest, OpcodesAreCounted) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def a(a, b):
return a + b
def func():
return a(7, 88)
)")
.isError());
Object func(&scope, mainModuleAt(runtime_, "func"));
EXPECT_EQ(thread_->opcodeCount(), 0);
ASSERT_FALSE(Interpreter::call0(thread_, func).isError());
EXPECT_EQ(thread_->opcodeCount(), 0);
runtime_->interpreter()->setOpcodeCounting(true);
runtime_->reinitInterpreter();
word count_before = thread_->opcodeCount();
ASSERT_FALSE(Interpreter::call0(thread_, func).isError());
EXPECT_EQ(thread_->opcodeCount() - count_before, 9);
runtime_->interpreter()->setOpcodeCounting(false);
runtime_->reinitInterpreter();
count_before = thread_->opcodeCount();
ASSERT_FALSE(Interpreter::call0(thread_, func).isError());
EXPECT_EQ(thread_->opcodeCount() - count_before, 0);
}
static ALIGN_16 RawObject startCounting(Thread* thread, Arguments) {
thread->runtime()->interpreter()->setOpcodeCounting(true);
thread->runtime()->reinitInterpreter();
return NoneType::object();
}
static ALIGN_16 RawObject stopCounting(Thread* thread, Arguments) {
thread->runtime()->interpreter()->setOpcodeCounting(false);
thread->runtime()->reinitInterpreter();
return NoneType::object();
}
TEST_F(InterpreterTest, ReinitInterpreterEnablesOpcodeCounting) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
addBuiltin("start_counting", startCounting, {nullptr, 0}, 0);
addBuiltin("stop_counting", stopCounting, {nullptr, 0}, 0);
EXPECT_EQ(thread_->opcodeCount(), 0);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def bar():
start_counting()
def func():
x = 5
x = 5
x = 5
x = 5
x = 5
x = 5
x = 5
x = 5
x = 5
x = 5
return 5
func()
bar()
func()
stop_counting()
func()
)")
.isError());
// I do not want to hardcode opcode counts for the calls here (since that
// may change in the future). So this just checks that we have at least
// 10*2 = 20 opcodes for a `func()` call, but no more than double that amount
// to make sure we did not consider the `foo()` call before and after
// counting was enabled.
word count = thread_->opcodeCount();
EXPECT_TRUE(20 < count && count < 40);
}
TEST_F(InterpreterTest, FunctionCallWithNonFunctionRaisesTypeError) {
HandleScope scope(thread_);
Str not_a_func(&scope, Str::empty());
thread_->stackPush(*not_a_func);
EXPECT_TRUE(raised(Interpreter::call(thread_, 0), LayoutId::kTypeError));
}
TEST_F(InterpreterTest, FunctionCallExWithNonFunctionRaisesTypeError) {
HandleScope scope(thread_);
Str not_a_func(&scope, Str::empty());
thread_->stackPush(*not_a_func);
Tuple empty_args(&scope, runtime_->emptyTuple());
thread_->stackPush(*empty_args);
EXPECT_TRUE(raisedWithStr(Interpreter::callEx(thread_, 0),
LayoutId::kTypeError,
"'str' object is not callable"));
}
TEST_F(InterpreterTest, CallExWithDescriptorDunderCall) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class FakeFunc:
def __get__(self, obj, owner):
return self
def __call__(self, arg):
return arg
class C:
__call__ = FakeFunc()
args = ["hello!"]
result = C()(*args)
)")
.isError());
EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "hello!"));
}
TEST_F(InterpreterTest, DoDeleteNameOnDictSubclass) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyDict(dict): pass
class Meta(type):
@classmethod
def __prepare__(cls, *args, **kwargs):
d = MyDict()
d['x'] = 42
return d
class C(metaclass=Meta):
del x
)")
.isError());
}
TEST_F(InterpreterTest, DoStoreNameOnDictSubclass) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyDict(dict): pass
class Meta(type):
@classmethod
def __prepare__(cls, *args, **kwargs):
return MyDict()
class C(metaclass=Meta):
x = 42
)")
.isError());
}
TEST_F(InterpreterTest, StoreSubscr) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
l = [0]
for i in range(5):
l[0] += i
)")
.isError());
HandleScope scope(thread_);
Object l_obj(&scope, testing::mainModuleAt(runtime_, "l"));
ASSERT_TRUE(l_obj.isList());
List l(&scope, *l_obj);
ASSERT_EQ(l.numItems(), 1);
EXPECT_EQ(l.at(0), SmallInt::fromWord(10));
}
TEST_F(InterpreterTest, StoreSubscrWithListRewritesToStoreSubscrList) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(l, i):
l[i] = 4
return 100
l = [1,2,3]
d = {1: -1}
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes rewritten(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_ANAMORPHIC);
List l(&scope, mainModuleAt(runtime_, "l"));
SmallInt key(&scope, SmallInt::fromWord(1));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, l, key), 100));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_LIST);
// Revert back to caching __getitem__ when a non-list is observed.
Dict d(&scope, mainModuleAt(runtime_, "d"));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call2(thread_, foo, d, key), 100));
EXPECT_EQ(rewrittenBytecodeOpAt(rewritten, 3), STORE_SUBSCR_MONOMORPHIC);
}
// TODO(bsimmers) Rewrite these exception tests to ensure that the specific
// bytecodes we care about are being exercised, so we're not be at the mercy of
// compiler optimizations or changes.
TEST_F(InterpreterTest, ExceptCatchesException) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
n = 0
try:
raise RuntimeError("something went wrong")
n = 1
except:
if n == 0:
n = 2
)")
.isError());
HandleScope scope(thread_);
Object n(&scope, testing::mainModuleAt(runtime_, "n"));
EXPECT_TRUE(isIntEqualsWord(*n, 2));
}
TEST_F(InterpreterTest, RaiseCrossesFunctions) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
def sub():
raise RuntimeError("from sub")
def main():
sub()
n = 0
try:
main()
n = 1
except:
if n == 0:
n = 2
)")
.isError());
HandleScope scope(thread_);
Object n(&scope, testing::mainModuleAt(runtime_, "n"));
EXPECT_TRUE(isIntEqualsWord(*n, 2));
}
TEST_F(InterpreterTest, RaiseFromSetsCause) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
try:
try:
raise RuntimeError
except Exception as e:
raise TypeError from e
except Exception as e:
exc = e
)")
.isError());
HandleScope scope(thread_);
Object exc_obj(&scope, testing::mainModuleAt(runtime_, "exc"));
ASSERT_EQ(exc_obj.layoutId(), LayoutId::kTypeError);
BaseException exc(&scope, *exc_obj);
EXPECT_EQ(exc.cause().layoutId(), LayoutId::kRuntimeError);
}
TEST_F(InterpreterTest, ExceptWithRightTypeCatches) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
n = 0
try:
raise RuntimeError("whoops")
n = 1
except RuntimeError:
if n == 0:
n = 2
)")
.isError());
HandleScope scope(thread_);
Object n(&scope, testing::mainModuleAt(runtime_, "n"));
EXPECT_TRUE(isIntEqualsWord(*n, 2));
}
TEST_F(InterpreterTest, ExceptWithRightTupleTypeCatches) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
n = 0
try:
raise RuntimeError()
n = 1
except (StopIteration, RuntimeError, ImportError):
if n == 0:
n = 2
)")
.isError());
HandleScope scope(thread_);
Object n(&scope, testing::mainModuleAt(runtime_, "n"));
EXPECT_TRUE(isIntEqualsWord(*n, 2));
}
TEST_F(InterpreterTest, ExceptWithWrongTypePasses) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
try:
raise RuntimeError("something went wrong")
except StopIteration:
pass
)"),
LayoutId::kRuntimeError, "something went wrong"));
}
TEST_F(InterpreterTest, ExceptWithWrongTupleTypePasses) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
try:
raise RuntimeError("something went wrong")
except (StopIteration, ImportError):
pass
)"),
LayoutId::kRuntimeError, "something went wrong"));
}
TEST_F(InterpreterTest, RaiseTypeCreatesException) {
EXPECT_TRUE(raised(runFromCStr(runtime_, "raise StopIteration"),
LayoutId::kStopIteration));
}
TEST_F(InterpreterTest, BareRaiseReraises) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyError(Exception):
pass
inner = None
outer = None
try:
try:
raise MyError()
except Exception as exc:
inner = exc
raise
except Exception as exc:
outer = exc
)")
.isError());
HandleScope scope(thread_);
Object my_error(&scope, testing::mainModuleAt(runtime_, "MyError"));
EXPECT_EQ(runtime_->typeOf(*my_error), runtime_->typeAt(LayoutId::kType));
Object inner(&scope, testing::mainModuleAt(runtime_, "inner"));
EXPECT_EQ(runtime_->typeOf(*inner), *my_error);
Object outer(&scope, testing::mainModuleAt(runtime_, "outer"));
EXPECT_EQ(*inner, *outer);
}
TEST_F(InterpreterTest, ExceptWithNonExceptionTypeRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
try:
raise RuntimeError
except str:
pass
)"),
LayoutId::kTypeError,
"catching classes that do not inherit from "
"BaseException is not allowed"));
}
TEST_F(InterpreterTest, ExceptWithNonExceptionTypeInTupleRaisesTypeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
try:
raise RuntimeError
except (StopIteration, int, RuntimeError):
pass
)"),
LayoutId::kTypeError,
"catching classes that do not inherit from "
"BaseException is not allowed"));
}
TEST_F(InterpreterTest, RaiseWithNoActiveExceptionRaisesRuntimeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "raise\n"),
LayoutId::kRuntimeError,
"No active exception to reraise"));
}
TEST_F(InterpreterTest, LoadAttrWithoutAttrUnwindsAttributeException) {
HandleScope scope(thread_);
// Set up a code object that runs: {}.foo
Object foo(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Tuple names(&scope, runtime_->newTupleWith1(foo));
Tuple consts(&scope, runtime_->emptyTuple());
// load arguments and execute the code
const byte bytecode[] = {BUILD_MAP, 0, LOAD_ATTR, 0};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
// Execute the code and make sure to get the unwinded Error
EXPECT_TRUE(runCode(code).isError());
}
TEST_F(InterpreterTest, ExplodeCallAcceptsList) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f(a, b):
return [b, a]
args = ['a', 'b']
result = f(*args)
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_PYLIST_EQ(result, {"b", "a"});
}
TEST_F(InterpreterTest, ExplodeWithIterableCalls) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
def f(a, b):
return (b, a)
def gen():
yield 1
yield 2
result = f(*gen())
)")
.isError());
Object result_obj(&scope, mainModuleAt(runtime_, "result"));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
EXPECT_TRUE(isIntEqualsWord(result.at(0), 2));
EXPECT_TRUE(isIntEqualsWord(result.at(1), 1));
}
TEST_F(InterpreterTest, ForIterAnamorphicWithBuiltinIterRewritesOpcode) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo(i, s=0):
for a in i:
s += a
return s
list_obj = [4,5]
dict_obj = {4: "a", 5: "b"}
tuple_obj = (4,5)
range_obj = range(4,6)
str_obj = "45"
def gen():
yield 5
yield 7
gen_obj = gen()
class C:
def __iter__(self):
return D()
class D:
def __init__(self):
self.used = False
def __next__(self):
if self.used:
raise StopIteration
self.used = True
return 400
user_obj = C()
)")
.isError());
Function foo(&scope, mainModuleAt(runtime_, "foo"));
MutableBytes bytecode(&scope, foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_ANAMORPHIC);
Object arg(&scope, mainModuleAt(runtime_, "list_obj"));
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 9));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_LIST);
arg = mainModuleAt(runtime_, "dict_obj");
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 9));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_DICT);
arg = mainModuleAt(runtime_, "tuple_obj");
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 9));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_TUPLE);
arg = mainModuleAt(runtime_, "range_obj");
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 9));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_RANGE);
arg = mainModuleAt(runtime_, "str_obj");
Str s(&scope, runtime_->newStrFromCStr(""));
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call2(thread_, foo, arg, s), "45"));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_STR);
arg = mainModuleAt(runtime_, "gen_obj");
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 12));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_GENERATOR);
// Resetting the opcode.
arg = mainModuleAt(runtime_, "user_obj");
EXPECT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, foo, arg), 400));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 2), FOR_ITER_MONOMORPHIC);
}
TEST_F(InterpreterTest, FormatValueCallsDunderStr) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __str__(self):
return "foobar"
result = f"{C()!s}"
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isStrEqualsCStr(*result, "foobar"));
}
TEST_F(InterpreterTest, FormatValueFallsBackToDunderRepr) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __repr__(self):
return "foobar"
result = f"{C()!s}"
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isStrEqualsCStr(*result, "foobar"));
}
TEST_F(InterpreterTest, FormatValueCallsDunderRepr) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __repr__(self):
return "foobar"
result = f"{C()!r}"
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isStrEqualsCStr(*result, "foobar"));
}
TEST_F(InterpreterTest, FormatValueAsciiCallsDunderRepr) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __repr__(self):
return "foobar"
result = f"{C()!a}"
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isStrEqualsCStr(*result, "foobar"));
}
TEST_F(InterpreterTest, BreakInTryBreaks) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
result = 0
for i in range(5):
try:
break
except:
pass
result = 10
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 10));
}
TEST_F(InterpreterTest, ContinueInExceptContinues) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
result = 0
for i in range(5):
try:
if i == 3:
raise RuntimeError()
except:
result += i
continue
result -= i
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, -4));
}
TEST_F(InterpreterTest, RaiseInLoopRaisesRuntimeError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
result = 0
try:
for i in range(5):
result += i
if i == 2:
raise RuntimeError()
result += 100
except:
result += 1000
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 1003));
}
TEST_F(InterpreterTest, ReturnInsideTryRunsFinally) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
ran_finally = False
def f():
try:
return 56789
finally:
global ran_finally
ran_finally = True
result = f()
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 56789));
Object ran_finally(&scope, mainModuleAt(runtime_, "ran_finally"));
EXPECT_EQ(*ran_finally, Bool::trueObj());
}
TEST_F(InterpreterTest, ReturnInsideFinallyOverridesEarlierReturn) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def f():
try:
return 123
finally:
return 456
result = f()
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 456));
}
TEST_F(InterpreterTest, ReturnInsideWithRunsDunderExit) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
sequence = ""
class Mgr:
def __enter__(self):
global sequence
sequence += "enter "
def __exit__(self, exc, value, tb):
global sequence
sequence += "exit"
def foo():
with Mgr():
global sequence
sequence += "in foo "
return 1234
result = foo()
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 1234));
Object sequence(&scope, mainModuleAt(runtime_, "sequence"));
EXPECT_TRUE(isStrEqualsCStr(*sequence, "enter in foo exit"));
}
TEST_F(InterpreterTest,
WithStatementWithManagerWithoutEnterRaisesAttributeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
with None:
pass
)"),
LayoutId::kAttributeError, "__enter__"));
}
TEST_F(InterpreterTest,
WithStatementWithManagerWithoutExitRaisesAttributeError) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class C:
def __enter__(self):
pass
with C():
pass
)"),
LayoutId::kAttributeError, "__exit__"));
}
TEST_F(InterpreterTest,
WithStatementWithManagerEnterRaisingPropagatesException) {
EXPECT_TRUE(raised(runFromCStr(runtime_, R"(
class C:
def __enter__(self):
raise UserWarning('')
def __exit__(self, *args):
pass
with C():
pass
)"),
LayoutId::kUserWarning));
}
TEST_F(InterpreterTest, WithStatementPropagatesException) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Mgr:
def __enter__(self):
pass
def __exit__(self, exc, value, tb):
return ()
def raises():
raise RuntimeError("It's dead, Jim")
with Mgr():
raises()
)"),
LayoutId::kRuntimeError, "It's dead, Jim"));
}
TEST_F(InterpreterTest, WithStatementPassesCorrectExceptionToExit) {
HandleScope scope(thread_);
EXPECT_TRUE(raised(runFromCStr(runtime_, R"(
raised_exc = None
exit_info = None
class Mgr:
def __enter__(self):
pass
def __exit__(self, exc, value, tb):
global exit_info
exit_info = (exc, value, tb)
def raises():
global raised_exc
raised_exc = StopIteration("nope")
raise raised_exc
with Mgr():
raises()
)"),
LayoutId::kStopIteration));
Object exit_info(&scope, mainModuleAt(runtime_, "exit_info"));
ASSERT_TRUE(exit_info.isTuple());
Tuple tuple(&scope, *exit_info);
ASSERT_EQ(tuple.length(), 3);
EXPECT_EQ(tuple.at(0), runtime_->typeAt(LayoutId::kStopIteration));
Object raised_exc(&scope, mainModuleAt(runtime_, "raised_exc"));
EXPECT_EQ(tuple.at(1), *raised_exc);
// TODO(bsimmers): Check traceback once we record them.
}
TEST_F(InterpreterTest, WithStatementSwallowsException) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class Mgr:
def __enter__(self):
pass
def __exit__(self, exc, value, tb):
return 1
def raises():
raise RuntimeError()
with Mgr():
raises()
result = 1234
)")
.isError());
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(isIntEqualsWord(*result, 1234));
}
TEST_F(InterpreterTest, WithStatementWithRaisingExitRaises) {
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Mgr:
def __enter__(self):
pass
def __exit__(self, exc, value, tb):
raise RuntimeError("from exit")
def raises():
raise RuntimeError("from raises")
with Mgr():
raises()
)"),
LayoutId::kRuntimeError, "from exit"));
// TODO(T40269344): Inspect __context__ from the raised exception.
}
TEST_F(InterpreterTest, LoadNameReturnsSameResultAsCahedValueFromLoadGlobal) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
t = 400
def update_t():
global t
t = 500
def get_t():
global t
return t
update_t()
load_name_t = t
load_global_t = get_t()
)")
.isError());
EXPECT_EQ(mainModuleAt(runtime_, "load_name_t"),
mainModuleAt(runtime_, "load_global_t"));
}
TEST_F(InterpreterTest, LoadGlobalCachedReturnsModuleDictValue) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
a = 400
def foo():
return a + a
result = foo()
)")
.isError());
EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 800));
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(
isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches, 0)), 400));
}
TEST_F(InterpreterTest,
LoadGlobalCachedReturnsBuiltinDictValueAndSetsPlaceholder) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
__builtins__.a = 400
def foo():
return a + a
result = foo()
)")
.isError());
EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 800));
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(
isIntEqualsWord(valueCellValue(icLookupGlobalVar(*caches, 0)), 400));
Module module(&scope, function.moduleObject());
Object name(&scope, Runtime::internStrFromCStr(thread_, "a"));
RawObject module_entry = NoneType::object();
EXPECT_TRUE(attributeValueCellAt(*module, *name, &module_entry));
ASSERT_TRUE(module_entry.isValueCell());
EXPECT_TRUE(ValueCell::cast(module_entry).isPlaceholder());
}
TEST_F(InterpreterTest, StoreGlobalCachedInvalidatesCachedBuiltinToBeShadowed) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
__builtins__.a = 400
def foo():
return a + a
def bar():
# Shadowing `__builtins__.a`.
global a
a = 123
foo()
bar()
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(icLookupGlobalVar(*caches, 0).isNoneType());
}
TEST_F(InterpreterTest, DeleteGlobalInvalidatesCachedValue) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
a = 400
def foo():
return a + a
def bar():
global a
del a
foo()
bar()
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(icLookupGlobalVar(*caches, 0).isNoneType());
}
TEST_F(InterpreterTest, StoreNameInvalidatesCachedBuiltinToBeShadowed) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
__builtins__.a = 400
def foo():
return a + a
foo()
a = 800
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(icLookupGlobalVar(*caches, 0).isNoneType());
}
TEST_F(InterpreterTest, DeleteNameInvalidatesCachedGlobalVar) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
a = 400
def foo():
return a + a
foo()
del a
)")
.isError());
Function function(&scope, mainModuleAt(runtime_, "foo"));
ASSERT_TRUE(isStrEqualsCStr(
Tuple::cast(Code::cast(function.code()).names()).at(0), "a"));
MutableTuple caches(&scope, function.caches());
EXPECT_TRUE(icLookupGlobalVar(*caches, 0).isNoneType());
}
TEST_F(
InterpreterTest,
StoreAttrCachedInvalidatesInstanceOffsetCachesByAssigningTypeDescriptor) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 400
def get_foo(c):
return c.foo
def do_not_invalidate0():
C.bar = property (lambda self: "data descriptor in a different attr")
def do_not_invalidate1():
C.foo = 9999
def invalidate():
C.foo = property (lambda self: "data descriptor")
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Function get_foo(&scope, mainModuleAt(runtime_, "get_foo"));
Function do_not_invalidate0(&scope,
mainModuleAt(runtime_, "do_not_invalidate0"));
Function do_not_invalidate1(&scope,
mainModuleAt(runtime_, "do_not_invalidate1"));
Function invalidate(&scope, mainModuleAt(runtime_, "invalidate"));
MutableTuple caches(&scope, get_foo.caches());
// Load the cache
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, get_foo, c), 400));
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Assign a data descriptor to a different attribute name.
ASSERT_TRUE(Interpreter::call0(thread_, do_not_invalidate0).isNoneType());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Assign a non-data descriptor to the cache's attribute name.
ASSERT_TRUE(Interpreter::call0(thread_, do_not_invalidate1).isNoneType());
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Assign a data descriptor the cache's attribute name that actually causes
// invalidation.
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
// Verify that the cache is empty and calling get_foo() returns a fresh value.
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call1(thread_, get_foo, c),
"data descriptor"));
}
TEST_F(InterpreterTest,
StoreAttrCachedInvalidatesTypeAttrCachesByUpdatingTypeAttribute) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo(self):
return 400;
def call_foo(c):
return c.foo()
def do_not_invalidate():
C.bar = lambda c: "new type attr"
def invalidate():
C.foo = lambda c: "new type attr"
old_foo = C.foo
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Function old_foo(&scope, mainModuleAt(runtime_, "old_foo"));
Function call_foo(&scope, mainModuleAt(runtime_, "call_foo"));
Function do_not_invalidate(&scope,
mainModuleAt(runtime_, "do_not_invalidate"));
Function invalidate(&scope, mainModuleAt(runtime_, "invalidate"));
MutableTuple caches(&scope, call_foo.caches());
// Load the cache
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, call_foo, c), 400));
ASSERT_EQ(icLookupAttr(*caches, 1, c.layoutId()), *old_foo);
// Assign a non-data descriptor to different attribute name.
ASSERT_TRUE(Interpreter::call0(thread_, do_not_invalidate).isNoneType());
ASSERT_EQ(icLookupAttr(*caches, 1, c.layoutId()), *old_foo);
// Invalidate the cache.
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
// Verify that the cache is empty and calling get_foo() returns a fresh value.
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call1(thread_, call_foo, c),
"new type attr"));
}
TEST_F(
InterpreterTest,
StoreAttrCachedInvalidatesAttributeCachesByUpdatingMatchingTypeAttributesOfSuperclass) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class B:
pass
class C(B):
def __init__(self):
self.foo = 400
class D(C):
pass
def get_foo(c):
return c.foo
def do_not_invalidate():
D.foo = property (lambda self: "data descriptor")
def invalidate():
B.foo = property (lambda self: "data descriptor")
c = C()
)")
.isError());
Type type_b(&scope, mainModuleAt(runtime_, "B"));
Type type_c(&scope, mainModuleAt(runtime_, "C"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Function get_foo(&scope, mainModuleAt(runtime_, "get_foo"));
Function do_not_invalidate(&scope,
mainModuleAt(runtime_, "do_not_invalidate"));
Function invalidate(&scope, mainModuleAt(runtime_, "invalidate"));
MutableTuple caches(&scope, get_foo.caches());
// Load the cache.
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(isIntEqualsWord(Interpreter::call1(thread_, get_foo, c), 400));
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Updating a subclass' type attribute doesn't invalidate the cache.
ASSERT_TRUE(Interpreter::call0(thread_, do_not_invalidate).isNoneType());
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Verify that all type dictionaries in C's mro have dependentices to get_foo.
Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object result(&scope, typeValueCellAt(*type_b, *foo_name));
ASSERT_TRUE(result.isValueCell());
ASSERT_TRUE(ValueCell::cast(*result).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*result).dependencyLink()).referent(),
*get_foo);
result = typeValueCellAt(*type_c, *foo_name);
ASSERT_TRUE(result.isValueCell());
ASSERT_TRUE(ValueCell::cast(*result).dependencyLink().isWeakLink());
EXPECT_EQ(
WeakLink::cast(ValueCell::cast(*result).dependencyLink()).referent(),
*get_foo);
// Invalidate the cache.
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
// Verify that the cache is empty and calling get_foo() returns a fresh value.
EXPECT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call1(thread_, get_foo, c),
"data descriptor"));
}
TEST_F(InterpreterTest, StoreAttrCachedInvalidatesBinaryOpCaches) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
def cache_A_add(a, b):
return a + b
class A:
def __add__(self, other): return "A.__add__"
class B:
pass
def update_A_add():
A.__add__ = lambda self, other: "new A.__add__"
a = A()
b = B()
A_add = A.__add__
cache_A_add(a, b)
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object a_add(&scope, mainModuleAt(runtime_, "A_add"));
Function cache_a_add(&scope, mainModuleAt(runtime_, "cache_A_add"));
BinaryOpFlags flags_out;
// Ensure that A.__add__ is cached in cache_A_add.
Object cached_in_cache_a_add(
&scope, icLookupBinaryOp(MutableTuple::cast(cache_a_add.caches()), 0,
a.layoutId(), b.layoutId(), &flags_out));
ASSERT_EQ(cached_in_cache_a_add, *a_add);
// Ensure that cache_a_add is being tracked as a dependent from A.__add__.
Type type_a(&scope, mainModuleAt(runtime_, "A"));
Str dunder_add(&scope, runtime_->symbols()->at(ID(__add__)));
ValueCell a_add_value_cell(&scope, typeValueCellAt(*type_a, *dunder_add));
ASSERT_FALSE(a_add_value_cell.isPlaceholder());
EXPECT_EQ(WeakLink::cast(a_add_value_cell.dependencyLink()).referent(),
*cache_a_add);
// Ensure that cache_a_add is being tracked as a dependent from B.__radd__.
Type type_b(&scope, mainModuleAt(runtime_, "B"));
Str dunder_radd(&scope, runtime_->symbols()->at(ID(__radd__)));
ValueCell b_radd_value_cell(&scope, typeValueCellAt(*type_b, *dunder_radd));
ASSERT_TRUE(b_radd_value_cell.isPlaceholder());
EXPECT_EQ(WeakLink::cast(b_radd_value_cell.dependencyLink()).referent(),
*cache_a_add);
// Updating A.__add__ invalidates the cache.
Function invalidate(&scope, mainModuleAt(runtime_, "update_A_add"));
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
// Verify that the cache is evicted.
EXPECT_TRUE(icLookupBinaryOp(MutableTuple::cast(cache_a_add.caches()), 0,
a.layoutId(), b.layoutId(), &flags_out)
.isErrorNotFound());
// Verify that the dependencies are deleted.
EXPECT_TRUE(a_add_value_cell.dependencyLink().isNoneType());
EXPECT_TRUE(b_radd_value_cell.dependencyLink().isNoneType());
}
TEST_F(InterpreterTest, StoreAttrCachedInvalidatesCompareOpTypeAttrCaches) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
def cache_compare_op(a, b):
return a >= b
class A:
def __le__(self, other): return True
def __ge__(self, other): return True
class B:
def __le__(self, other): return True
def __ge__(self, other): return True
def do_not_invalidate():
A.__le__ = lambda self, other: False
B.__ge__ = lambda self, other: False
def invalidate():
A.__ge__ = lambda self, other: False
a = A()
b = B()
A__ge__ = A.__ge__
c = cache_compare_op(a, b)
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object type_a__dunder_ge(&scope, mainModuleAt(runtime_, "A__ge__"));
// Ensure that A.__ge__ is cached.
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));
ASSERT_EQ(*cached, *type_a__dunder_ge);
// Updating irrelevant compare op dunder functions doesn't trigger
// invalidation.
Function do_not_invalidate(&scope,
mainModuleAt(runtime_, "do_not_invalidate"));
ASSERT_TRUE(Interpreter::call0(thread_, do_not_invalidate).isNoneType());
cached = icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flags_out);
EXPECT_EQ(*cached, *type_a__dunder_ge);
// Updating relevant compare op dunder functions triggers invalidation.
Function invalidate(&scope, mainModuleAt(runtime_, "invalidate"));
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
ASSERT_TRUE(
icLookupBinaryOp(*caches, 0, a.layoutId(), b.layoutId(), &flags_out)
.isErrorNotFound());
}
TEST_F(InterpreterTest, StoreAttrCachedInvalidatesInplaceOpCaches) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
def cache_A_iadd(a, b):
a += b
class A:
def __iadd__(self, other): return "A.__iadd__"
class B:
pass
def update_A_iadd():
A.__iadd__ = lambda self, other: "new A.__add__"
a = A()
b = B()
A_iadd = A.__iadd__
cache_A_iadd(a, b)
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object a_iadd(&scope, mainModuleAt(runtime_, "A_iadd"));
Function cache_a_iadd(&scope, mainModuleAt(runtime_, "cache_A_iadd"));
BinaryOpFlags flags_out;
// Ensure that A.__iadd__ is cached in cache_A_iadd.
Object cached_in_cache_a_iadd(
&scope, icLookupBinaryOp(MutableTuple::cast(cache_a_iadd.caches()), 0,
a.layoutId(), b.layoutId(), &flags_out));
ASSERT_EQ(cached_in_cache_a_iadd, *a_iadd);
// Ensure that cache_a_iadd is being tracked as a dependent from A.__iadd__.
Type type_a(&scope, mainModuleAt(runtime_, "A"));
Str dunder_iadd(&scope, runtime_->symbols()->at(ID(__iadd__)));
ValueCell a_iadd_value_cell(&scope, typeValueCellAt(*type_a, *dunder_iadd));
ASSERT_FALSE(a_iadd_value_cell.isPlaceholder());
EXPECT_EQ(WeakLink::cast(a_iadd_value_cell.dependencyLink()).referent(),
*cache_a_iadd);
Str dunder_add(&scope, runtime_->symbols()->at(ID(__add__)));
ValueCell a_add_value_cell(&scope, typeValueCellAt(*type_a, *dunder_add));
ASSERT_TRUE(a_add_value_cell.isPlaceholder());
EXPECT_EQ(WeakLink::cast(a_add_value_cell.dependencyLink()).referent(),
*cache_a_iadd);
// Ensure that cache_a_iadd is being tracked as a dependent from B.__riadd__.
Type type_b(&scope, mainModuleAt(runtime_, "B"));
Str dunder_radd(&scope, runtime_->symbols()->at(ID(__radd__)));
ValueCell b_radd_value_cell(&scope, typeValueCellAt(*type_b, *dunder_radd));
ASSERT_TRUE(b_radd_value_cell.isPlaceholder());
EXPECT_EQ(WeakLink::cast(b_radd_value_cell.dependencyLink()).referent(),
*cache_a_iadd);
// Updating A.__iadd__ invalidates the cache.
Function invalidate(&scope, mainModuleAt(runtime_, "update_A_iadd"));
ASSERT_TRUE(Interpreter::call0(thread_, invalidate).isNoneType());
// Verify that the cache is evicted.
EXPECT_TRUE(icLookupBinaryOp(MutableTuple::cast(cache_a_iadd.caches()), 0,
a.layoutId(), b.layoutId(), &flags_out)
.isErrorNotFound());
// Verify that the dependencies are deleted.
EXPECT_TRUE(a_iadd_value_cell.dependencyLink().isNoneType());
EXPECT_TRUE(a_add_value_cell.dependencyLink().isNoneType());
EXPECT_TRUE(b_radd_value_cell.dependencyLink().isNoneType());
}
TEST_F(InterpreterTest, LoadMethodLoadingMethodFollowedByCallMethod) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.val = 40
def compute(self, arg0, arg1):
return self.val + arg0 + arg1
def test():
return c.compute(10, 20)
c = C()
)")
.isError());
Function test_function(&scope, mainModuleAt(runtime_, "test"));
MutableBytes bytecode(&scope, test_function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_ANAMORPHIC);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 4), CALL_METHOD);
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, test_function), 70));
}
TEST_F(InterpreterTest, LoadMethodInitDoesNotCacheInstanceAttributes) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.val = 40
def foo(a, b): return a + b
c = C()
c.compute = foo
def test():
return c.compute(10, 20)
)")
.isError());
Function test_function(&scope, mainModuleAt(runtime_, "test"));
MutableBytes bytecode(&scope, test_function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_ANAMORPHIC);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 4), CALL_METHOD);
Object c(&scope, mainModuleAt(runtime_, "c"));
LayoutId layout_id = c.layoutId();
MutableTuple caches(&scope, test_function.caches());
// Cache miss.
ASSERT_TRUE(
icLookupAttr(*caches, rewrittenBytecodeArgAt(bytecode, 1), layout_id)
.isErrorNotFound());
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, test_function), 30));
// Still cache miss.
ASSERT_TRUE(
icLookupAttr(*caches, rewrittenBytecodeArgAt(bytecode, 1), layout_id)
.isErrorNotFound());
}
TEST_F(InterpreterTest, LoadMethodCachedCachingFunctionFollowedByCallMethod) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.val = 40
def compute(self, arg0, arg1):
return self.val + arg0 + arg1
def test():
return c.compute(10, 20)
c = C()
)")
.isError());
Function test_function(&scope, mainModuleAt(runtime_, "test"));
MutableBytes bytecode(&scope, test_function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_ANAMORPHIC);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 4), CALL_METHOD);
// Cache miss.
Object c(&scope, mainModuleAt(runtime_, "c"));
LayoutId layout_id = c.layoutId();
MutableTuple caches(&scope, test_function.caches());
ASSERT_TRUE(
icLookupAttr(*caches, rewrittenBytecodeArgAt(bytecode, 1), layout_id)
.isErrorNotFound());
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, test_function), 70));
// Cache hit.
ASSERT_TRUE(
icLookupAttr(*caches, rewrittenBytecodeArgAt(bytecode, 1), layout_id)
.isFunction());
EXPECT_TRUE(isIntEqualsWord(Interpreter::call0(thread_, test_function), 70));
}
TEST_F(InterpreterTest, LoadMethodCachedDoesNotCacheProperty) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
@property
def foo(self): return lambda: 1234
def call_foo(c):
return c.foo()
c = C()
call_foo(c)
)")
.isError());
Function call_foo(&scope, mainModuleAt(runtime_, "call_foo"));
MutableBytes bytecode(&scope, call_foo.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_ANAMORPHIC);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 2), CALL_METHOD);
MutableTuple caches(&scope, call_foo.caches());
EXPECT_TRUE(icIsCacheEmpty(caches, rewrittenBytecodeArgAt(bytecode, 1)));
}
TEST_F(InterpreterTest, LoadMethodUpdatesOpcodeWithCaching) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo(self):
return 4
class D:
def foo(self):
return -4
def test(c):
return c.foo()
c = C()
d = D()
)")
.isError());
Function test_function(&scope, mainModuleAt(runtime_, "test"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Object d(&scope, mainModuleAt(runtime_, "d"));
MutableBytes bytecode(&scope, test_function.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_ANAMORPHIC);
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, test_function, c), 4));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_INSTANCE_FUNCTION);
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, test_function, d), -4));
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_METHOD_POLYMORPHIC);
}
TEST_F(InterpreterTest, DoLoadImmediate) {
HandleScope scope(thread_);
EXPECT_FALSE(runFromCStr(runtime_, R"(
def test():
return None
result = test()
)")
.isError());
Function test_function(&scope, mainModuleAt(runtime_, "test"));
MutableBytes bytecode(&scope, test_function.rewrittenBytecode());
// Verify that rewriting replaces LOAD_CONST for LOAD_IMMEDIATE.
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 0), LOAD_IMMEDIATE);
EXPECT_EQ(rewrittenBytecodeArgAt(bytecode, 0),
static_cast<byte>(NoneType::object().raw()));
EXPECT_TRUE(mainModuleAt(runtime_, "result").isNoneType());
}
TEST_F(InterpreterTest, LoadAttrCachedInsertsExecutingFunctionAsDependent) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 400
def cache_attribute(c):
return c.foo
c = C()
)")
.isError());
HandleScope scope(thread_);
Type type_c(&scope, mainModuleAt(runtime_, "C"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Function cache_attribute(&scope, mainModuleAt(runtime_, "cache_attribute"));
MutableTuple caches(&scope, cache_attribute.caches());
ASSERT_EQ(caches.length(), 2 * kIcPointersPerEntry);
// Load the cache.
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(
isIntEqualsWord(Interpreter::call1(thread_, cache_attribute, c), 400));
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Verify that cache_attribute function is added as a dependent.
Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
ValueCell value_cell(&scope, typeValueCellAt(*type_c, *foo_name));
ASSERT_TRUE(value_cell.dependencyLink().isWeakLink());
EXPECT_EQ(WeakLink::cast(value_cell.dependencyLink()).referent(),
*cache_attribute);
}
TEST_F(InterpreterTest,
LoadAttrInstanceOnInvalidatedCacheUpdatesCacheCorrectly) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = "instance attribute"
def cache_attribute(c):
return c.foo
def invalidate_attribute(c):
C.foo = property(lambda e: "descriptor attribute")
c = C()
)")
.isError());
HandleScope scope(thread_);
Object c(&scope, mainModuleAt(runtime_, "c"));
Function cache_attribute(&scope, mainModuleAt(runtime_, "cache_attribute"));
MutableBytes bytecode(&scope, cache_attribute.rewrittenBytecode());
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_ATTR_ANAMORPHIC);
Tuple caches(&scope, cache_attribute.caches());
ASSERT_EQ(caches.length(), 2 * kIcPointersPerEntry);
// Load the cache.
ASSERT_EQ(icCurrentState(*caches, 1), ICState::kAnamorphic);
ASSERT_TRUE(isStrEqualsCStr(Interpreter::call1(thread_, cache_attribute, c),
"instance attribute"));
ASSERT_EQ(icCurrentState(*caches, 1), ICState::kMonomorphic);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_ATTR_INSTANCE);
// Invalidate the cache.
Function invalidate_attribute(&scope,
mainModuleAt(runtime_, "invalidate_attribute"));
ASSERT_TRUE(
Interpreter::call1(thread_, invalidate_attribute, c).isNoneType());
ASSERT_EQ(icCurrentState(*caches, 1), ICState::kAnamorphic);
ASSERT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_ATTR_INSTANCE);
// Load the cache again.
EXPECT_TRUE(isStrEqualsCStr(Interpreter::call1(thread_, cache_attribute, c),
"descriptor attribute"));
EXPECT_EQ(icCurrentState(*caches, 1), ICState::kMonomorphic);
EXPECT_EQ(rewrittenBytecodeOpAt(bytecode, 1), LOAD_ATTR_INSTANCE_PROPERTY);
}
TEST_F(InterpreterTest, StoreAttrCachedInsertsExecutingFunctionAsDependent) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 400
def cache_attribute(c):
c.foo = 500
c = C()
)")
.isError());
HandleScope scope(thread_);
Type type_c(&scope, mainModuleAt(runtime_, "C"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Function cache_attribute(&scope, mainModuleAt(runtime_, "cache_attribute"));
MutableTuple caches(&scope, cache_attribute.caches());
ASSERT_EQ(caches.length(), 2 * kIcPointersPerEntry);
// Load the cache.
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isErrorNotFound());
ASSERT_TRUE(Interpreter::call1(thread_, cache_attribute, c).isNoneType());
ASSERT_TRUE(icLookupAttr(*caches, 1, c.layoutId()).isSmallInt());
// Verify that cache_attribute function is added as a dependent.
Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
ValueCell value_cell(&scope, typeValueCellAt(*type_c, *foo_name));
ASSERT_TRUE(value_cell.dependencyLink().isWeakLink());
EXPECT_EQ(WeakLink::cast(value_cell.dependencyLink()).referent(),
*cache_attribute);
}
TEST_F(InterpreterTest, StoreAttrsCausingShadowingInvalidatesCache) {
EXPECT_FALSE(runFromCStr(runtime_, R"(
class A:
def foo(self): return 40
class B(A):
def foo(self): return 50
class C(B):
pass
def function_that_caches_attr_lookup(a, b, c):
return a.foo() + b.foo() + c.foo()
def func_that_causes_shadowing_of_attr_a():
A.foo = lambda self: 300
def func_that_causes_shadowing_of_attr_b():
B.foo = lambda self: 200
# Caching A.foo and B.foo in cache_attribute.
a = A()
b = B()
c = C()
a_foo = A.foo
b_foo = B.foo
function_that_caches_attr_lookup(a, b, 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 a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
Object c(&scope, mainModuleAt(runtime_, "c"));
Function function_that_caches_attr_lookup(
&scope, mainModuleAt(runtime_, "function_that_caches_attr_lookup"));
MutableTuple caches(&scope, function_that_caches_attr_lookup.caches());
// 0: global variable
// 1: a.foo
// 2: b.foo
// 3: binary op cache
// 4: c.foo
// 5, binary op cache
Function a_foo(&scope, mainModuleAt(runtime_, "a_foo"));
Function b_foo(&scope, mainModuleAt(runtime_, "b_foo"));
ASSERT_EQ(caches.length(), 6 * kIcPointersPerEntry);
ASSERT_EQ(icLookupAttr(*caches, 1, a.layoutId()), *a_foo);
ASSERT_EQ(icLookupAttr(*caches, 2, b.layoutId()), *b_foo);
ASSERT_EQ(icLookupAttr(*caches, 4, c.layoutId()), *b_foo);
// Verify that function_that_caches_attr_lookup cached the attribute lookup
// and appears on the dependency list of A.foo.
Object foo_name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
ValueCell foo_in_a(&scope, typeValueCellAt(*type_a, *foo_name));
ASSERT_TRUE(foo_in_a.dependencyLink().isWeakLink());
ASSERT_EQ(WeakLink::cast(foo_in_a.dependencyLink()).referent(),
*function_that_caches_attr_lookup);
// Verify that function_that_caches_attr_lookup cached the attribute lookup
// and appears on the dependency list of B.foo.
ValueCell foo_in_b(&scope, typeValueCellAt(*type_b, *foo_name));
ASSERT_TRUE(foo_in_b.dependencyLink().isWeakLink());
ASSERT_EQ(WeakLink::cast(foo_in_b.dependencyLink()).referent(),
*function_that_caches_attr_lookup);
// Verify that function_that_caches_attr_lookup cached the attribute lookup
// and appears on the dependency list of C.foo.
ValueCell foo_in_c(&scope, typeValueCellAt(*type_c, *foo_name));
ASSERT_TRUE(foo_in_c.dependencyLink().isWeakLink());
ASSERT_EQ(WeakLink::cast(foo_in_c.dependencyLink()).referent(),
*function_that_caches_attr_lookup);
// Change the class A so that any caches that reference A.foo are invalidated.
Function func_that_causes_shadowing_of_attr_a(
&scope, mainModuleAt(runtime_, "func_that_causes_shadowing_of_attr_a"));
ASSERT_TRUE(Interpreter::call0(thread_, func_that_causes_shadowing_of_attr_a)
.isNoneType());
// Verify that the cache for A.foo is cleared out, and dependent does not
// depend on A.foo anymore.
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isErrorNotFound());
EXPECT_TRUE(foo_in_a.dependencyLink().isNoneType());
// Check that any lookups of B have not been invalidated.
EXPECT_EQ(icLookupAttr(*caches, 2, b.layoutId()), *b_foo);
EXPECT_EQ(WeakLink::cast(foo_in_b.dependencyLink()).referent(),
*function_that_caches_attr_lookup);
// Check that any lookups of C have not been invalidated.
EXPECT_EQ(icLookupAttr(*caches, 4, c.layoutId()), *b_foo);
EXPECT_EQ(WeakLink::cast(foo_in_c.dependencyLink()).referent(),
*function_that_caches_attr_lookup);
// Invalidate the cache for B.foo.
Function func_that_causes_shadowing_of_attr_b(
&scope, mainModuleAt(runtime_, "func_that_causes_shadowing_of_attr_b"));
ASSERT_TRUE(Interpreter::call0(thread_, func_that_causes_shadowing_of_attr_b)
.isNoneType());
// Check that caches for A are still invalidated.
EXPECT_TRUE(icLookupAttr(*caches, 1, a.layoutId()).isErrorNotFound());
EXPECT_TRUE(foo_in_a.dependencyLink().isNoneType());
// Check that caches for B and C got just invalidated since they refer to
// B.foo.
EXPECT_TRUE(icLookupAttr(*caches, 2, b.layoutId()).isErrorNotFound());
EXPECT_TRUE(foo_in_b.dependencyLink().isNoneType());
EXPECT_TRUE(icLookupAttr(*caches, 4, c.layoutId()).isErrorNotFound());
EXPECT_TRUE(foo_in_c.dependencyLink().isNoneType());
}
TEST_F(InterpreterTest, IntrinsicWithSlowPathDoesNotAlterStack) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newList());
thread_->stackPush(*obj);
Module module(&scope, runtime_->findModuleById(ID(_builtins)));
Function tuple_len_func(&scope,
moduleAtById(thread_, module, ID(_tuple_len)));
IntrinsicFunction function =
reinterpret_cast<IntrinsicFunction>(tuple_len_func.intrinsic());
ASSERT_NE(function, nullptr);
ASSERT_FALSE(function(thread_));
EXPECT_EQ(thread_->stackPeek(0), *obj);
}
TEST_F(JitTest, CompileFunctionSetsEntryAsm) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
Object obj1(&scope, NoneType::object());
Tuple consts(&scope, runtime_->newTupleWith1(obj1));
const byte bytecode[] = {
LOAD_CONST,
0,
RETURN_VALUE,
0,
};
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Object qualname(&scope, Str::empty());
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
}
// Create the function:
// def caller():
// return foo()
// without rewriting the bytecode.
static RawObject createTrampolineFunction(Thread* thread) {
HandleScope scope(thread);
Str foo(&scope, Runtime::internStrFromCStr(thread, "foo"));
Runtime* runtime = thread->runtime();
Tuple names(&scope, runtime->newTupleWith1(foo));
Tuple consts(&scope, runtime->emptyTuple());
const byte bytecode[] = {
LOAD_GLOBAL, 0, CALL_FUNCTION, 0, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
Str qualname(&scope, runtime->newStrFromCStr("qualname"));
Module module(&scope, findMainModule(runtime));
Function function(
&scope, runtime->newFunctionWithCode(thread, qualname, code, module));
Bytes bytecode_bytes(&scope, runtime->newBytesWithAll(bytecode));
MutableBytes rewritten(&scope, expandBytecode(thread, bytecode_bytes));
function.setRewrittenBytecode(*rewritten);
return *function;
}
// Create the function:
// def caller():
// return foo(obj)
// where obj is the parameter to createTrampolineFunction1, without rewriting
// the bytecode.
static RawObject createTrampolineFunction1(Thread* thread, const Object& obj) {
HandleScope scope(thread);
Str foo(&scope, Runtime::internStrFromCStr(thread, "foo"));
Runtime* runtime = thread->runtime();
Tuple names(&scope, runtime->newTupleWith1(foo));
Tuple consts(&scope, runtime->newTupleWith1(obj));
const byte bytecode[] = {
LOAD_GLOBAL, 0, LOAD_CONST, 0, CALL_FUNCTION, 1, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
Str qualname(&scope, runtime->newStrFromCStr("qualname"));
Module module(&scope, findMainModule(runtime));
Function function(
&scope, runtime->newFunctionWithCode(thread, qualname, code, module));
Bytes bytecode_bytes(&scope, runtime->newBytesWithAll(bytecode));
MutableBytes rewritten(&scope, expandBytecode(thread, bytecode_bytes));
function.setRewrittenBytecode(*rewritten);
return *function;
}
// Create the function:
// def caller():
// return foo(left, right)
// where obj is the parameter to createTrampolineFunction2, without rewriting
// the bytecode.
static RawObject createTrampolineFunction2(Thread* thread, const Object& left,
const Object& right) {
HandleScope scope(thread);
Str foo(&scope, Runtime::internStrFromCStr(thread, "foo"));
Runtime* runtime = thread->runtime();
Tuple names(&scope, runtime->newTupleWith1(foo));
Tuple consts(&scope, runtime->newTupleWith2(left, right));
const byte bytecode[] = {
LOAD_GLOBAL, 0, LOAD_CONST, 0, LOAD_CONST, 1,
CALL_FUNCTION, 2, RETURN_VALUE, 0,
};
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names));
Str qualname(&scope, runtime->newStrFromCStr("qualname"));
Module module(&scope, findMainModule(runtime));
Function function(
&scope, runtime->newFunctionWithCode(thread, qualname, code, module));
Bytes bytecode_bytes(&scope, runtime->newBytesWithAll(bytecode));
MutableBytes rewritten(&scope, expandBytecode(thread, bytecode_bytes));
function.setRewrittenBytecode(*rewritten);
return *function;
}
// Replace the bytecode with an empty bytes object after a function has been
// compiled so that the function cannot be interpreted normally. This is useful
// for ensuring that we are running the JITed function.
static void setEmptyBytecode(const Function& function) {
function.setRewrittenBytecode(SmallBytes::empty());
}
static RawObject compileAndCallJITFunction(Thread* thread,
const Function& function) {
HandleScope scope(thread);
Function caller(&scope, createTrampolineFunction(thread));
compileFunction(thread, function);
setEmptyBytecode(function);
return Interpreter::call0(thread, caller);
}
static RawObject compileAndCallJITFunction1(Thread* thread,
const Function& function,
const Object& param) {
HandleScope scope(thread);
Function caller(&scope, createTrampolineFunction1(thread, param));
compileFunction(thread, function);
setEmptyBytecode(function);
return Interpreter::call0(thread, caller);
}
static RawObject compileAndCallJITFunction2(Thread* thread,
const Function& function,
const Object& param1,
const Object& param2) {
HandleScope scope(thread);
Function caller(&scope, createTrampolineFunction2(thread, param1, param2));
compileFunction(thread, function);
setEmptyBytecode(function);
return Interpreter::call0(thread, caller);
}
TEST_F(JitTest, CallFunctionWithTooFewArgsRaisesTypeError) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
return (1, 2, 3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_CONST));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(
raisedWithStr(*result, LayoutId::kTypeError,
"'foo' takes min 1 positional arguments but 0 given"));
}
// TODO(T89353729): Add test for calling a JIT function with a signal set.
TEST_F(JitTest, LoadConstLoadsConstant) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return (1, 2, 3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_CONST));
Object result_obj(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result_obj.isTuple());
Tuple result(&scope, *result_obj);
ASSERT_EQ(result.length(), 3);
EXPECT_TRUE(isIntEqualsWord(result.at(0), 1));
EXPECT_TRUE(isIntEqualsWord(result.at(1), 2));
EXPECT_TRUE(isIntEqualsWord(result.at(2), 3));
}
TEST_F(JitTest, LoadBoolLoadsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return True
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_BOOL));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, LoadImmediateLoadsImmediate) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return None
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_IMMEDIATE));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, NoneType::object());
}
TEST_F(JitTest, LoadFastReverseLoadsLocal) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
var = 5
return var
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_FAST_REVERSE));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 5));
}
TEST_F(JitTest, LoadFastReverseWithUnboundNameRaisesUnboundLocalError) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
var = 5
del var
return var
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_FAST_REVERSE));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Function deopt_caller(&scope, createTrampolineFunction(thread_));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(raised(*result, LayoutId::kUnboundLocalError));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, LoadFastReverseUncheckedLoadsParameter) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(param):
return param
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_FAST_REVERSE_UNCHECKED));
Object param(&scope, SmallInt::fromWord(123));
Object result(&scope, compileAndCallJITFunction1(thread_, function, param));
EXPECT_TRUE(isIntEqualsWord(*result, 123));
}
TEST_F(JitTest, StoreFastReverseWritesToParameter) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(param):
param = 3
return param
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_FAST_REVERSE_UNCHECKED));
Object param(&scope, SmallInt::fromWord(123));
Object result(&scope, compileAndCallJITFunction1(thread_, function, param));
EXPECT_TRUE(isIntEqualsWord(*result, 3));
}
TEST_F(JitTest, CompareIsWithSameObjectsReturnsTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return 123 is 123
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_IS));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, CompareIsWithDifferentObjectsReturnsFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return 123 is 124
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_IS));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, CompareIsNotWithSameObjectsReturnsFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return 123 is not 123
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_IS_NOT));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, CompareIsNotWithDifferentObjectsReturnsTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return 123 is not 124
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_IS_NOT));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, BinaryAddSmallintWithSmallIntsReturnsInt) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left + right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_ADD_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_ADD_SMALLINT));
Object left(&scope, SmallInt::fromWord(5));
Object right(&scope, SmallInt::fromWord(10));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_TRUE(isIntEqualsWord(*result, 15));
}
TEST_F(JitTest, BinaryAndSmallintWithSmallIntsReturnsInt) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left & right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_AND_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_AND_SMALLINT));
Object left(&scope, SmallInt::fromWord(0xff));
Object right(&scope, SmallInt::fromWord(0x0f));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_TRUE(isIntEqualsWord(*result, 0x0f));
}
TEST_F(JitTest, BinaryOrSmallintWithSmallIntsReturnsInt) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left | right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_OR_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_OR_SMALLINT));
Object left(&scope, SmallInt::fromWord(0xf0));
Object right(&scope, SmallInt::fromWord(0x0f));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_TRUE(isIntEqualsWord(*result, 0xff));
}
TEST_F(JitTest, BinarySubSmallintWithSmallIntsReturnsInt) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left - right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_SUB_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_SUB_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_TRUE(isIntEqualsWord(*result, 3));
}
TEST_F(JitTest, CompareEqSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left == right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_EQ_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_EQ_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, CompareNeSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left != right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_NE_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_NE_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, CompareGtSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left > right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_GT_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_GT_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, CompareGeSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left >= right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_GE_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_GE_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, CompareLtSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left < right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_LT_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_LT_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, CompareLeSmallintWithSmallIntsReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left <= right
# Rewrite BINARY_OP_ANAMORPHIC to COMPARE_LE_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, COMPARE_LE_SMALLINT));
Object left(&scope, SmallInt::fromWord(7));
Object right(&scope, SmallInt::fromWord(4));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, UnaryNotWithBoolReturnsBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
return not obj
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, UNARY_NOT));
Object param(&scope, Bool::trueObj());
Object result(&scope, compileAndCallJITFunction1(thread_, function, param));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, BinaryAddSmallintWithNonSmallintDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left + right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_ADD_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_ADD_SMALLINT));
Object left_int(&scope, SmallInt::fromWord(5));
Object right_int(&scope, SmallInt::fromWord(10));
void* entry_before = function.entryAsm();
Function caller(&scope,
createTrampolineFunction2(thread_, left_int, right_int));
compileFunction(thread_, function);
Object result(&scope, Interpreter::call0(thread_, caller));
EXPECT_NE(function.entryAsm(), entry_before);
Object left_str(&scope, SmallStr::fromCStr("hello"));
Object right_str(&scope, SmallStr::fromCStr(" world"));
Function deopt_caller(
&scope, createTrampolineFunction2(thread_, left_str, right_str));
result = Interpreter::call0(thread_, deopt_caller);
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
EXPECT_TRUE(isStrEqualsCStr(*result, "hello world"));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, BinarySubscrListReturnsItem) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
return obj[0]
# Rewrite BINARY_SUBSCR_ANAMORPHIC to BINARY_SUBSCR_LIST
foo([3, 2, 1])
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_LIST));
List list(&scope, runtime_->newList());
Object obj(&scope, SmallStr::fromCStr("bar"));
runtime_->listAdd(thread_, list, obj);
Object result(&scope, compileAndCallJITFunction1(thread_, function, list));
EXPECT_TRUE(isStrEqualsCStr(*result, "bar"));
}
TEST_F(JitTest, BinarySubscrListWithNonListDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
return obj[0]
# Rewrite BINARY_SUBSCR_ANAMORPHIC to BINARY_SUBSCR_LIST
foo([3, 2, 1])
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_LIST));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object obj(&scope, SmallInt::fromWord(7));
Object non_list(&scope, runtime_->newTupleWith1(obj));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, non_list));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_MONOMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 7));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, StoreSubscrListStoresItem) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
obj[0] = 123
# Rewrite STORE_SUBSCR_ANAMORPHIC to STORE_SUBSCR_LIST
foo([3, 2, 1])
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_SUBSCR_LIST));
List list(&scope, runtime_->newList());
Object obj(&scope, SmallStr::fromCStr("bar"));
runtime_->listAdd(thread_, list, obj);
Object result(&scope, compileAndCallJITFunction1(thread_, function, list));
EXPECT_EQ(*result, NoneType::object());
EXPECT_TRUE(isIntEqualsWord(list.at(0), 123));
}
TEST_F(JitTest, StoreSubscrListWithNonListDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C(list):
pass
def foo(obj):
obj[0] = 123
# Rewrite STORE_SUBSCR_ANAMORPHIC to STORE_SUBSCR_LIST
foo([3, 2, 1])
instance = C([4, 5, 6])
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_SUBSCR_LIST));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
List instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_EQ(function.entryAsm(), entry_before);
EXPECT_EQ(*result, NoneType::object());
EXPECT_TRUE(isIntEqualsWord(instance.at(0), 123));
}
TEST_F(JitTest, InplaceAddSmallintAddsIntegers) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
obj += 1
return obj
# Rewrite INPLACE_OP_ANAMORPHIC to INPLACE_ADD_SMALLINT
foo(1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, INPLACE_ADD_SMALLINT));
Object obj(&scope, SmallInt::fromWord(12));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 13));
}
TEST_F(JitTest, InplaceAddSmallintWithNonIntDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
left += right
return left
# Rewrite INPLACE_OP_MONOMORPHIC to INPLACE_ADD_SMALLINT
foo(1, 2)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, INPLACE_ADD_SMALLINT));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Str left(&scope, SmallStr::fromCStr("hello"));
Str right(&scope, SmallStr::fromCStr(" world"));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, left, right));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_EQ(function.entryAsm(), entry_before);
EXPECT_TRUE(isStrEqualsCStr(*result, "hello world"));
}
TEST_F(JitTest, InplaceSubSmallintSubsIntegers) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
obj -= 1
return obj
# Rewrite INPLACE_OP_ANAMORPHIC to INPLACE_SUB_SMALLINT
foo(1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, INPLACE_SUB_SMALLINT));
Object obj(&scope, SmallInt::fromWord(12));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 11));
}
TEST_F(JitTest, InplaceSubSmallintWithNonIntDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C(int):
pass
def foo(obj):
obj -= 1
return obj
# Rewrite INPLACE_OP_MONOMORPHIC to INPLACE_SUB_SMALLINT
foo(1)
instance = C(12)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, INPLACE_SUB_SMALLINT));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_EQ(function.entryAsm(), entry_before);
EXPECT_TRUE(runtime_->isInstanceOfInt(*result));
EXPECT_TRUE(isIntEqualsWord(intUnderlying(*result), 11));
}
TEST_F(JitTest, LoadAttrInstanceWithInstanceReturnsAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.foo = value
def foo(obj):
return obj.foo
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE
foo(C(4))
instance = C(10)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_INSTANCE));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 10));
}
TEST_F(JitTest, LoadAttrInstanceWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.foo = value
class D:
def __init__(self, value):
self.foo = value
def foo(obj):
return obj.foo
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE
foo(C(4))
instance = D(10)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_INSTANCE));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 10));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, JumpAbsoluteJumps) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
JUMP_ABSOLUTE, 4, // to LOAD_CONST, 1
LOAD_CONST, 0, LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Tuple consts(&scope, runtime_->newTupleWith2(none, one));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 1));
}
TEST_F(JitTest, JumpForwardJumps) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
JUMP_FORWARD, 2, // to LOAD_CONST, 1
LOAD_CONST, 0, LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Tuple consts(&scope, runtime_->newTupleWith2(none, one));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 1));
}
TEST_F(JitTest, PopJumpIfTrueJumpsIfTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 2, POP_JUMP_IF_TRUE, 8, // to LOAD_CONST, 1
LOAD_CONST, 0, JUMP_FORWARD, 2, // to RETURN_VALUE
LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Object truthy(&scope, Bool::trueObj());
Tuple consts(&scope, runtime_->newTupleWith3(none, one, truthy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 1));
}
TEST_F(JitTest, PopJumpIfTrueJumpsIfTrueNonBool) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 2, POP_JUMP_IF_TRUE, 8, // to LOAD_CONST, 1
LOAD_CONST, 0, JUMP_FORWARD, 2, // to RETURN_VALUE
LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Object truthy(&scope, runtime_->newTupleWith1(one));
Tuple consts(&scope, runtime_->newTupleWith3(none, one, truthy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Function caller(&scope, createTrampolineFunction(thread_));
compileFunction(thread_, function);
Object result(&scope, Interpreter::call0(thread_, caller));
EXPECT_TRUE(isIntEqualsWord(*result, 1));
}
TEST_F(JitTest, PopJumpIfTrueDoesNotJumpIfFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 2, POP_JUMP_IF_TRUE, 8, // to LOAD_CONST, 1
LOAD_CONST, 0, JUMP_FORWARD, 2, // to RETURN_VALUE
LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Object falsy(&scope, Bool::falseObj());
Tuple consts(&scope, runtime_->newTupleWith3(none, one, falsy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, NoneType::object());
}
TEST_F(JitTest, PopJumpIfFalseJumpsIfFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 2, POP_JUMP_IF_FALSE, 8, // to LOAD_CONST, 1
LOAD_CONST, 0, JUMP_FORWARD, 2, // to RETURN_VALUE
LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Object falsy(&scope, Bool::falseObj());
Tuple consts(&scope, runtime_->newTupleWith3(none, one, falsy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 1));
}
TEST_F(JitTest, PopJumpIfFalseDoesNotJumpIfTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 2, POP_JUMP_IF_FALSE, 8, // to LOAD_CONST, 1
LOAD_CONST, 0, JUMP_FORWARD, 2, // to RETURN_VALUE
LOAD_CONST, 1, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object one(&scope, SmallInt::fromWord(1));
Object truthy(&scope, Bool::trueObj());
Tuple consts(&scope, runtime_->newTupleWith3(none, one, truthy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, NoneType::object());
}
TEST_F(JitTest, JumpIfTrueOrPopJumpsIfTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 1, JUMP_IF_TRUE_OR_POP, 6, // to RETURN_VALUE
LOAD_CONST, 0, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object truthy(&scope, Bool::trueObj());
Tuple consts(&scope, runtime_->newTupleWith2(none, truthy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(JitTest, JumpIfTrueOrPopPopsIfFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 1, JUMP_IF_TRUE_OR_POP, 6, // to RETURN_VALUE
LOAD_CONST, 0, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object falsy(&scope, Bool::falseObj());
Tuple consts(&scope, runtime_->newTupleWith2(none, falsy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, NoneType::object());
}
TEST_F(JitTest, JumpIfFalseOrPopJumpsIfFalse) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 1, JUMP_IF_FALSE_OR_POP, 6, // to RETURN_VALUE
LOAD_CONST, 0, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object falsy(&scope, Bool::falseObj());
Tuple consts(&scope, runtime_->newTupleWith2(none, falsy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, Bool::falseObj());
}
TEST_F(JitTest, JumpIfFalseOrPopPopsIfTrue) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 1, JUMP_IF_FALSE_OR_POP, 6, // to RETURN_VALUE
LOAD_CONST, 0, RETURN_VALUE, 0,
};
Object none(&scope, NoneType::object());
Object truthy(&scope, Bool::trueObj());
Tuple consts(&scope, runtime_->newTupleWith2(none, truthy));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_EQ(*result, NoneType::object());
}
TEST_F(JitTest, ForIterListIteratesOverList) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
result = 0
for item in obj:
result += item
return result
# Rewrite FOR_ITER_ANAMORPHIC with FOR_ITER_LIST
foo([1, 2, 3])
instance = [4, 5, 6]
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_LIST));
List list(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, list));
EXPECT_TRUE(isIntEqualsWord(*result, 15));
}
TEST_F(JitTest, ForIterListWithNonListDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class D:
def __next__(self):
raise StopIteration
class C:
def __iter__(self):
return D()
def foo(obj):
result = 0
for item in obj:
result += item
return result
# Rewrite FOR_ITER_ANAMORPHIC to FOR_ITER_LIST
foo([1, 2, 3])
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_LIST));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_MONOMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 0));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, ForIterRangeIteratesOverRange) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
result = 0
for item in obj:
result += item
return result
# Rewrite FOR_ITER_ANAMORPHIC with FOR_ITER_RANGE
foo(range(1, 4))
instance = range(4, 7)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_RANGE));
Range range(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, range));
EXPECT_TRUE(isIntEqualsWord(*result, 15));
}
TEST_F(JitTest, ForIterRangeWithNonRangeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class D:
def __next__(self):
raise StopIteration
class C:
def __iter__(self):
return D()
def foo(obj):
result = 0
for item in obj:
result += item
return result
# Rewrite FOR_ITER_ANAMORPHIC to FOR_ITER_RANGE
foo(range(1, 4))
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_RANGE));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, FOR_ITER_MONOMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 0));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, LoadAttrInstanceTypeBoundMethodWithInstanceReturnsBoundMethod) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo(self):
pass
def foo(obj):
return obj.foo
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE_TYPE_BOUND_METHOD
foo(C())
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_INSTANCE_TYPE_BOUND_METHOD));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result_obj(&scope, compileAndCallJITFunction1(thread_, function, obj));
ASSERT_TRUE(result_obj.isBoundMethod());
BoundMethod result(&scope, *result_obj);
EXPECT_EQ(result.self(), *obj);
}
TEST_F(JitTest, LoadAttrInstanceTypeBoundMethodWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo(self):
pass
class D:
def foo(self):
pass
def foo(obj):
return obj.foo
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE_TYPE_BOUND_METHOD
foo(C())
instance = D()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_INSTANCE_TYPE_BOUND_METHOD));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result_obj(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_POLYMORPHIC));
ASSERT_TRUE(result_obj.isBoundMethod());
BoundMethod result(&scope, *result_obj);
EXPECT_EQ(result.self(), *instance);
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, LoadAttrPolymorphicWithCacheHitReturnsAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
class D(C):
pass
def foo(obj):
return obj.value
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE
foo(C(1))
# Rewrite LOAD_ATTR_INSTANCE to LOAD_ATTR_POLYMORPHIC
foo(D(2))
instance = C(3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_POLYMORPHIC));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 3));
}
TEST_F(JitTest, LoadAttrPolymorphicWithCacheMissReturnsAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
class D(C):
pass
class E(C):
pass
def foo(obj):
return obj.value
# Rewrite LOAD_ATTR_ANAMORPHIC to LOAD_ATTR_INSTANCE
foo(C(1))
# Rewrite LOAD_ATTR_INSTANCE to LOAD_ATTR_POLYMORPHIC
foo(D(2))
instance = E(3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_POLYMORPHIC));
compileFunction(thread_, function);
// Can't use compileAndCallJITFunction1 because the doLoadAttrPolymorphic
// fallback needs to read the cache index off the bytecode.
void* entry_jit = function.entryAsm();
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, LOAD_ATTR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 3));
EXPECT_NE(function.entryAsm(), entry_jit);
}
TEST_F(JitTest, StoreAttrInstanceWithInstanceStoresAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.foo = value
def foo(obj):
obj.foo = 17
return obj.foo
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE
foo(C(4))
instance = C(10)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
}
TEST_F(JitTest, StoreAttrInstanceWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.foo = value
class D:
def __init__(self, value):
self.foo = value
def foo(obj):
obj.foo = 17
return obj.foo
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE
foo(C(4))
instance = D(10)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, StoreAttrPolymorphicWithCacheHitReturnsAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
class D(C):
pass
def foo(obj):
obj.value = 17
return obj.value
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE
foo(C(1))
# Rewrite STORE_ATTR_INSTANCE to STORE_ATTR_POLYMORPHIC
foo(D(2))
instance = C(3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_POLYMORPHIC));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
}
TEST_F(JitTest, StoreAttrPolymorphicWithCacheMissReturnsAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.value = value
class D(C):
pass
class E(C):
pass
def foo(obj):
obj.value = 17
return obj.value
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE
foo(C(1))
# Rewrite STORE_ATTR_INSTANCE to STORE_ATTR_POLYMORPHIC
foo(D(2))
instance = E(3)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_POLYMORPHIC));
compileFunction(thread_, function);
// Can't use compileAndCallJITFunction1 because the doStoreAttrPolymorphic
// fallback needs to read the cache index off the bytecode.
void* entry_jit = function.entryAsm();
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
EXPECT_NE(function.entryAsm(), entry_jit);
}
TEST_F(JitTest, StoreAttrInstanceOverflowWithInstanceStoresAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
pass
def foo(obj):
obj.foo = 17
return obj.foo
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE_OVERFLOW
obj1 = C()
obj1.foo = 1
foo(obj1)
instance = C()
instance.foo = 1
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE_OVERFLOW));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
}
TEST_F(JitTest, StoreAttrInstanceOverflowWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
pass
class D:
pass
def foo(obj):
obj.foo = 17
return obj.foo
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE_OVERFLOW
obj1 = C()
obj1.foo = 1
foo(obj1)
instance = D()
instance.foo = 2
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE_OVERFLOW));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object instance(&scope, mainModuleAt(runtime_, "instance"));
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 17));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, BuildListReturnsList) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return [1, 2, 3]
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_LIST));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_PYLIST_EQ(result, {1, 2, 3});
}
TEST_F(JitTest, BuildListUnpackReturnsList) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
a = [2, 3]
return [1, *a]
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_LIST_UNPACK));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_PYLIST_EQ(result, {1, 2, 3});
}
TEST_F(JitTest, BuildMapReturnsDict) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return {"hello": "world"}
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_MAP));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isDict());
EXPECT_EQ(Dict::cast(*result).numItems(), 1);
}
TEST_F(JitTest, BuildMapUnpackReturnsDict) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
a = {"goodbye": "world"}
return {"hello": "world", **a}
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_MAP_UNPACK));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isDict());
EXPECT_EQ(Dict::cast(*result).numItems(), 2);
}
TEST_F(JitTest, BuildSetReturnsSet) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
return {"hello", "world"}
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_SET));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isSet());
EXPECT_EQ(Set::cast(*result).numItems(), 2);
}
TEST_F(JitTest, BuildSetUnpackReturnsSet) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
a = {"goodbye", "world"}
return {"hello", "world", *a}
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_SET_UNPACK));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isSet());
EXPECT_EQ(Set::cast(*result).numItems(), 3);
}
TEST_F(JitTest, BuildTupleReturnsTuple) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
a = 1
return (a, 2)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_TUPLE));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isTuple());
EXPECT_EQ(Tuple::cast(*result).length(), 2);
}
TEST_F(JitTest, BuildTupleUnpackReturnsTuple) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo():
a = (2, 3)
return (1, *a)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BUILD_TUPLE_UNPACK));
Object result(&scope, compileAndCallJITFunction(thread_, function));
ASSERT_TRUE(result.isTuple());
EXPECT_EQ(Tuple::cast(*result).length(), 3);
}
TEST_F(JitTest, BuildStringReturnsString) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, BUILD_STRING, 2, RETURN_VALUE, 0,
};
Object left(&scope, SmallStr::fromCStr("hello"));
Object right(&scope, SmallStr::fromCStr(" world"));
Tuple consts(&scope, runtime_->newTupleWith2(left, right));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isStrEqualsCStr(*result, "hello world"));
}
TEST_F(JitTest, FormatValueReturnsString) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(obj):
return f"foo{obj}bar"
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, FORMAT_VALUE));
Object obj(&scope, SmallInt::fromWord(123));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isStrEqualsCStr(*result, "foo123bar"));
}
TEST_F(JitTest, DupTopTwoDuplicatesTwoTwoStackElements) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, DUP_TOP_TWO, 0,
BUILD_LIST, 4, RETURN_VALUE, 0,
};
Object left(&scope, SmallInt::fromWord(1));
Object right(&scope, SmallInt::fromWord(2));
Tuple consts(&scope, runtime_->newTupleWith2(left, right));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_PYLIST_EQ(result, {1, 2, 1, 2});
}
TEST_F(JitTest, RotFourRotatesStackElements) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, LOAD_CONST, 2, LOAD_CONST, 3,
ROT_FOUR, 0, BUILD_LIST, 4, RETURN_VALUE, 0,
};
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(2));
Object obj3(&scope, SmallInt::fromWord(3));
Object obj4(&scope, SmallInt::fromWord(4));
Tuple consts(&scope, runtime_->newTupleWith4(obj1, obj2, obj3, obj4));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_PYLIST_EQ(result, {4, 1, 2, 3});
}
TEST_F(JitTest, RotThreeRotatesStackElements) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
HandleScope scope(thread_);
const byte bytecode[] = {
LOAD_CONST, 0, LOAD_CONST, 1, LOAD_CONST, 2,
ROT_THREE, 0, BUILD_LIST, 3, RETURN_VALUE, 0,
};
Object obj1(&scope, SmallInt::fromWord(1));
Object obj2(&scope, SmallInt::fromWord(2));
Object obj3(&scope, SmallInt::fromWord(3));
Tuple consts(&scope, runtime_->newTupleWith3(obj1, obj2, obj3));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Str qualname(&scope, SmallStr::fromCStr("foo"));
Module module(&scope, findMainModule(runtime_));
Function function(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
moduleAtPutByCStr(thread_, module, "foo", function);
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_PYLIST_EQ(result, {3, 1, 2});
}
TEST_F(JitTest, UnaryNegativeCallsDunderNeg) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __neg__(self):
return 5
def foo(obj):
return -obj
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, UNARY_NEGATIVE));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 5));
}
TEST_F(JitTest, UnaryPositiveCallsDunderPos) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __pos__(self):
return 5
def foo(obj):
return +obj
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, UNARY_POSITIVE));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 5));
}
TEST_F(JitTest, UnaryInvertCallsDunderInvert) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __invert__(self):
return 5
def foo(obj):
return ~obj
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, UNARY_INVERT));
Object obj(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope, compileAndCallJITFunction1(thread_, function, obj));
EXPECT_TRUE(isIntEqualsWord(*result, 5));
}
TEST_F(JitTest, BinaryMulSmallintWithSmallIntsReturnsInt) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_MUL_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_MUL_SMALLINT));
Object left(&scope, SmallInt::fromWord(5));
Object right(&scope, SmallInt::fromWord(10));
Object result(&scope,
compileAndCallJITFunction2(thread_, function, left, right));
EXPECT_TRUE(isIntEqualsWord(*result, 50));
}
TEST_F(JitTest, BinaryMulSmallintWithNonSmallintDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_MUL_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_MUL_SMALLINT));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object left_str(&scope, SmallStr::fromCStr("hello"));
Object right(&scope, SmallInt::fromWord(2));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, left_str, right));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
EXPECT_TRUE(isStrEqualsCStr(*result, "hellohello"));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, BinaryMulSmallintWithOverflowDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_MUL_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_MUL_SMALLINT));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object left(&scope, SmallInt::fromWord(SmallInt::kMaxValue));
Object right(&scope, SmallInt::fromWord(2));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, left, right));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, SmallInt::kMaxValue * 2));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, BinaryMulSmallintWithUnderflowDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
EXPECT_FALSE(runFromCStr(runtime_, R"(
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_MUL_SMALLINT
foo(1, 1)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_MUL_SMALLINT));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object left(&scope, SmallInt::fromWord(SmallInt::kMinValue));
Object right(&scope, SmallInt::fromWord(2));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, left, right));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, SmallInt::kMinValue * 2));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, CallFunctionWithInterpretedFunctionCallsFunction) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
def bar(a, b):
return a + b
def foo():
return bar(3, 4)
# Rewrite CALL_FUNCTION_ANAMORPHIC to CALL_FUNCTION
foo()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 7));
}
TEST_F(JitTest, CallFunctionWithGeneratorFunctionCallsFunction) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
def bar(a, b):
yield a + b
def foo():
return bar(3, 4)
# Rewrite CALL_FUNCTION_ANAMORPHIC to CALL_FUNCTION
foo()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(result.isGenerator());
}
static ALIGN_16 RawObject addTwoNumbers(Thread*, Arguments args) {
return SmallInt::fromWord(SmallInt::cast(args.get(0)).value() +
SmallInt::cast(args.get(1)).value());
}
TEST_F(JitTest, CallFunctionWithBuiltinFunctionCallsFunction) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
const char* params[] = {"a", "b"};
addBuiltin("bar", addTwoNumbers, params, 0);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def foo():
return bar(3, 4)
# Rewrite CALL_FUNCTION_ANAMORPHIC to CALL_FUNCTION
foo()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
Object result(&scope, compileAndCallJITFunction(thread_, function));
EXPECT_TRUE(isIntEqualsWord(*result, 7));
}
TEST_F(JitTest, CallFunctionWithCallableCallsDunderCall) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
def function():
return 5
def foo(fn):
return fn()
# Rewrite CALL_FUNCTION_ANAMORPHIC to CALL_FUNCTION
foo(function)
class C:
def __call__(self):
return 10
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION));
Object callable(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope,
compileAndCallJITFunction1(thread_, function, callable));
EXPECT_TRUE(isIntEqualsWord(*result, 10));
}
TEST_F(JitTest, BinarySubscrMonomorphicCallsDunderGetitem) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __getitem__(self, key):
return key * 2
def foo(ls):
return ls[3]
# Rewrite BINARY_SUBSCR_ANAMORPHIC to BINARY_SUBSCR_MONOMORPHIC
foo(C())
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_MONOMORPHIC));
Object callable(&scope, mainModuleAt(runtime_, "instance"));
Object result(&scope,
compileAndCallJITFunction1(thread_, function, callable));
EXPECT_TRUE(isIntEqualsWord(*result, 6));
}
TEST_F(JitTest, BinarySubscrMonomorphicWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __getitem__(self, key):
return 7
class D:
def __getitem__(self, key):
return 13
def foo(ls):
return ls[3]
# Rewrite BINARY_SUBSCR_ANAMORPHIC to BINARY_SUBSCR_MONOMORPHIC
foo(C())
instance = D()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_MONOMORPHIC));
Object instance(&scope, mainModuleAt(runtime_, "instance"));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Function deopt_caller(&scope, createTrampolineFunction1(thread_, instance));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_SUBSCR_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 13));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, StoreAttrInstanceUpdateWithInstanceStoresAttribute) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.bar = value
foo = C.__init__
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE_UPDATE
instance = C(10)
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE_UPDATE));
// Don't use compileAndCallJITFunction2 in this function because the C++
// handler needs to be able to read the cache index off the bytecode.
compileFunction(thread_, function);
Object self(&scope, mainModuleAt(runtime_, "instance"));
Object value(&scope, SmallInt::fromWord(10));
Function caller(&scope, createTrampolineFunction2(thread_, self, value));
Object result(&scope, Interpreter::call0(thread_, caller));
EXPECT_TRUE(result.isNoneType());
}
TEST_F(JitTest, StoreAttrInstanceUpdateWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, value):
self.bar = value
foo = C.__init__
# Rewrite STORE_ATTR_ANAMORPHIC to STORE_ATTR_INSTANCE_UPDATE
instance = C(10)
# Change the layout of `instance'
instance.attr = "blah"
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE_UPDATE));
void* entry_before = function.entryAsm();
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object self(&scope, mainModuleAt(runtime_, "instance"));
Object value(&scope, SmallInt::fromWord(10));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, self, value));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(result.isNoneType());
EXPECT_TRUE(containsBytecode(function, STORE_ATTR_INSTANCE));
EXPECT_EQ(function.entryAsm(), entry_before);
}
TEST_F(JitTest, BinaryOpMonomorphicCallsCachedFunction) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __mul__(self, other):
return other * 10
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_OP_MONOMORPHIC
foo(C(), 1)
instance = C()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
// Don't use compileAndCallJITFunction2 in this function because the C++
// handler needs to be able to read the cache index off the bytecode.
compileFunction(thread_, function);
Object left(&scope, mainModuleAt(runtime_, "instance"));
Object right(&scope, SmallInt::fromWord(5));
Function caller(&scope, createTrampolineFunction2(thread_, left, right));
Object result(&scope, Interpreter::call0(thread_, caller));
EXPECT_TRUE(isIntEqualsWord(*result, 50));
}
TEST_F(JitTest, BinaryOpMonomorphicWithNewTypeDeoptimizes) {
if (useCppInterpreter()) {
GTEST_SKIP();
}
// Don't use compileAndCallJITFunction2 in this function because we want to
// test deoptimizing back into the interpreter. This requires valid bytecode.
EXPECT_FALSE(runFromCStr(runtime_, R"(
class C:
def __mul__(self, other):
return other * 10
class D(C):
pass
def foo(left, right):
return left * right
# Rewrite BINARY_OP_ANAMORPHIC to BINARY_MUL_SMALLINT
foo(C(), 1)
instance = D()
)")
.isError());
HandleScope scope(thread_);
Function function(&scope, mainModuleAt(runtime_, "foo"));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_MONOMORPHIC));
void* entry_before = function.entryAsm();
compileFunction(thread_, function);
EXPECT_NE(function.entryAsm(), entry_before);
Object left(&scope, mainModuleAt(runtime_, "instance"));
Object right(&scope, SmallInt::fromWord(2));
Function deopt_caller(&scope,
createTrampolineFunction2(thread_, left, right));
Object result(&scope, Interpreter::call0(thread_, deopt_caller));
EXPECT_TRUE(containsBytecode(function, BINARY_OP_POLYMORPHIC));
EXPECT_TRUE(isIntEqualsWord(*result, 20));
EXPECT_EQ(function.entryAsm(), entry_before);
}
} // namespace testing
} // namespace py