runtime/bytecode-test.cpp (782 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "bytecode.h"
#include "gtest/gtest.h"
#include "ic.h"
#include "test-utils.h"
namespace py {
namespace testing {
using BytecodeTest = RuntimeFixture;
TEST_F(BytecodeTest, NextBytecodeOpReturnsNextBytecodeOpPair) {
HandleScope scope(thread_);
const byte bytecode_raw[] = {
NOP, 99, 0, 0, EXTENDED_ARG, 0xca, 0, 0,
LOAD_ATTR, 0xfe, 0, 0, LOAD_GLOBAL, 10, 0, 0,
EXTENDED_ARG, 1, 0, 0, EXTENDED_ARG, 2, 0, 0,
EXTENDED_ARG, 3, 0, 0, LOAD_ATTR, 4, 0, 0};
Bytes original_bytecode(&scope, runtime_->newBytesWithAll(bytecode_raw));
MutableBytes bytecode(
&scope, runtime_->mutableBytesFromBytes(thread_, original_bytecode));
word index = 0;
BytecodeOp bc = nextBytecodeOp(bytecode, &index);
EXPECT_EQ(bc.bc, NOP);
EXPECT_EQ(bc.arg, 99);
bc = nextBytecodeOp(bytecode, &index);
EXPECT_EQ(bc.bc, LOAD_ATTR);
EXPECT_EQ(bc.arg, 0xcafe);
bc = nextBytecodeOp(bytecode, &index);
EXPECT_EQ(bc.bc, LOAD_GLOBAL);
EXPECT_EQ(bc.arg, 10);
bc = nextBytecodeOp(bytecode, &index);
EXPECT_EQ(bc.bc, LOAD_ATTR);
EXPECT_EQ(bc.arg, 0x01020304);
}
TEST_F(BytecodeTest, OpargFromObject) {
EXPECT_EQ(NoneType::object(),
objectFromOparg(opargFromObject(NoneType::object())));
EXPECT_EQ(SmallInt::fromWord(-1),
objectFromOparg(opargFromObject(SmallInt::fromWord(-1))));
EXPECT_EQ(SmallInt::fromWord(-64),
objectFromOparg(opargFromObject(SmallInt::fromWord(-64))));
EXPECT_EQ(SmallInt::fromWord(0),
objectFromOparg(opargFromObject(SmallInt::fromWord(0))));
EXPECT_EQ(SmallInt::fromWord(63),
objectFromOparg(opargFromObject(SmallInt::fromWord(63))));
EXPECT_EQ(Str::empty(), objectFromOparg(opargFromObject(Str::empty())));
// Not immediate since it doesn't fit in byte.
EXPECT_NE(SmallInt::fromWord(64),
objectFromOparg(opargFromObject(SmallInt::fromWord(64))));
}
TEST_F(BytecodeTest, RewriteBytecodeWithMoreThanCacheLimitCapsRewriting) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
static const int cache_limit = 65536;
byte bytecode[(cache_limit + 2) * kCompilerCodeUnitSize];
std::memset(bytecode, 0, sizeof bytecode);
for (word i = 0; i < cache_limit; i++) {
bytecode[i * kCompilerCodeUnitSize] = LOAD_ATTR;
bytecode[(i * kCompilerCodeUnitSize) + 1] = i * 3;
}
// LOAD_GLOBAL 527 == 4 * 128 + 15.
bytecode[cache_limit * kCompilerCodeUnitSize] = EXTENDED_ARG;
bytecode[cache_limit * kCompilerCodeUnitSize + 1] = 4;
bytecode[(cache_limit + 1) * kCompilerCodeUnitSize] = LOAD_GLOBAL;
bytecode[(cache_limit + 1) * kCompilerCodeUnitSize + 1] = 15;
// Expanded bytecode, as if by Runtime::expandBytecode
byte rewritten_bytecode[(cache_limit + 2) * kCodeUnitSize];
std::memset(rewritten_bytecode, 0, sizeof rewritten_bytecode);
for (word i = 0; i < cache_limit; i++) {
rewritten_bytecode[i * kCodeUnitSize] = LOAD_ATTR;
rewritten_bytecode[(i * kCodeUnitSize) + 1] = i * 3;
}
// LOAD_GLOBAL 527 == 4 * 128 + 15.
rewritten_bytecode[cache_limit * kCodeUnitSize] = EXTENDED_ARG;
rewritten_bytecode[cache_limit * kCodeUnitSize + 1] = 4;
rewritten_bytecode[(cache_limit + 1) * kCodeUnitSize] = LOAD_GLOBAL;
rewritten_bytecode[(cache_limit + 1) * kCodeUnitSize + 1] = 15;
word global_names_length = 600;
Tuple consts(&scope, runtime_->emptyTuple());
MutableTuple names(&scope, runtime_->newMutableTuple(global_names_length));
for (word i = 0; i < global_names_length; i++) {
names.atPut(i, runtime_->newStrFromFmt("g%w", i));
}
Tuple names_tuple(&scope, names.becomeImmutable());
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names_tuple));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
Object rewritten_bytecode_obj(&scope, function.rewrittenBytecode());
// The bytecode hasn't changed.
EXPECT_TRUE(
isMutableBytesEqualsBytes(rewritten_bytecode_obj, rewritten_bytecode));
// The cache for LOAD_GLOBAL was populated.
EXPECT_GT(Tuple::cast(function.caches()).length(), 527);
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesLoadAttrOperations) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
NOP, 99, EXTENDED_ARG, 0xca, LOAD_ATTR, 0xfe,
NOP, 106, EXTENDED_ARG, 1, EXTENDED_ARG, 2,
EXTENDED_ARG, 3, LOAD_ATTR, 4, LOAD_ATTR, 77,
};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
NOP,
99,
0,
0,
EXTENDED_ARG,
0xca,
0,
0,
LOAD_ATTR_ANAMORPHIC,
0xfe,
0,
0,
NOP,
106,
0,
0,
EXTENDED_ARG,
1,
0,
0,
EXTENDED_ARG,
2,
0,
0,
EXTENDED_ARG,
3,
0,
0,
LOAD_ATTR_ANAMORPHIC,
4,
1,
0,
LOAD_ATTR_ANAMORPHIC,
77,
2,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
ASSERT_TRUE(function.caches().isTuple());
Tuple caches(&scope, function.caches());
EXPECT_EQ(caches.length(), 3 * kIcPointersPerEntry);
for (word i = 0, length = caches.length(); i < length; i++) {
EXPECT_TRUE(caches.at(i).isNoneType()) << "index " << i;
}
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesLoadConstOperations) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {LOAD_CONST, 0, LOAD_CONST, 1, LOAD_CONST, 2,
LOAD_CONST, 3, LOAD_CONST, 4};
// Immediate objects.
Object obj0(&scope, NoneType::object());
Object obj1(&scope, SmallInt::fromWord(0));
Object obj2(&scope, Str::empty());
// Not immediate since it doesn't fit in byte.
Object obj3(&scope, SmallInt::fromWord(64));
// Not immediate since it's a heap object.
Object obj4(&scope, runtime_->newList());
Tuple consts(&scope,
runtime_->newTupleWithN(5, &obj0, &obj1, &obj2, &obj3, &obj4));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
byte expected[] = {
LOAD_IMMEDIATE,
static_cast<byte>(opargFromObject(NoneType::object())),
0,
0,
LOAD_IMMEDIATE,
static_cast<byte>(opargFromObject(SmallInt::fromWord(0))),
0,
0,
LOAD_IMMEDIATE,
static_cast<byte>(opargFromObject(Str::empty())),
0,
0,
LOAD_CONST,
3,
0,
0,
LOAD_CONST,
4,
0,
0,
};
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesLoadConstToLoadBool) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {LOAD_CONST, 0, LOAD_CONST, 1};
// Immediate objects.
Object obj0(&scope, Bool::trueObj());
Object obj1(&scope, Bool::falseObj());
Tuple consts(&scope, runtime_->newTupleWith2(obj0, obj1));
Code code(&scope, newCodeWithBytesConsts(bytecode, consts));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
byte expected[] = {
LOAD_BOOL, 0x80, 0, 0, LOAD_BOOL, 0, 0, 0,
};
MutableBytes rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesLoadMethodOperations) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
NOP, 99, EXTENDED_ARG, 0xca, LOAD_METHOD, 0xfe,
NOP, 160, EXTENDED_ARG, 1, EXTENDED_ARG, 2,
EXTENDED_ARG, 3, LOAD_METHOD, 4, LOAD_METHOD, 77,
};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
NOP,
99,
0,
0,
EXTENDED_ARG,
0xca,
0,
0,
LOAD_METHOD_ANAMORPHIC,
0xfe,
0,
0,
NOP,
160,
0,
0,
EXTENDED_ARG,
1,
0,
0,
EXTENDED_ARG,
2,
0,
0,
EXTENDED_ARG,
3,
0,
0,
LOAD_METHOD_ANAMORPHIC,
4,
1,
0,
LOAD_METHOD_ANAMORPHIC,
77,
2,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
ASSERT_TRUE(function.caches().isTuple());
Tuple caches(&scope, function.caches());
EXPECT_EQ(caches.length(), 3 * kIcPointersPerEntry);
for (word i = 0, length = caches.length(); i < length; i++) {
EXPECT_TRUE(caches.at(i).isNoneType()) << "index " << i;
}
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesStoreAttr) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {STORE_ATTR, 48};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
STORE_ATTR_ANAMORPHIC,
48,
0,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesBinaryOpcodes) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
BINARY_MATRIX_MULTIPLY,
0,
BINARY_POWER,
0,
BINARY_MULTIPLY,
0,
BINARY_MODULO,
0,
BINARY_ADD,
0,
BINARY_SUBTRACT,
0,
BINARY_FLOOR_DIVIDE,
0,
BINARY_TRUE_DIVIDE,
0,
BINARY_LSHIFT,
0,
BINARY_RSHIFT,
0,
BINARY_AND,
0,
BINARY_XOR,
0,
BINARY_OR,
0,
};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MATMUL),
0,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::POW),
1,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MUL),
2,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MOD),
3,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::ADD),
4,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::SUB),
5,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::FLOORDIV),
6,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::TRUEDIV),
7,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::LSHIFT),
8,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::RSHIFT),
9,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::AND),
10,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::XOR),
11,
0,
BINARY_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::OR),
12,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesInplaceOpcodes) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
INPLACE_MATRIX_MULTIPLY,
0,
INPLACE_POWER,
0,
INPLACE_MULTIPLY,
0,
INPLACE_MODULO,
0,
INPLACE_ADD,
0,
INPLACE_SUBTRACT,
0,
INPLACE_FLOOR_DIVIDE,
0,
INPLACE_TRUE_DIVIDE,
0,
INPLACE_LSHIFT,
0,
INPLACE_RSHIFT,
0,
INPLACE_AND,
0,
INPLACE_XOR,
0,
INPLACE_OR,
0,
};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MATMUL),
0,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::POW),
1,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MUL),
2,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::MOD),
3,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::ADD),
4,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::SUB),
5,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::FLOORDIV),
6,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::TRUEDIV),
7,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::LSHIFT),
8,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::RSHIFT),
9,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::AND),
10,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::XOR),
11,
0,
INPLACE_OP_ANAMORPHIC,
static_cast<word>(Interpreter::BinaryOp::OR),
12,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesCompareOpOpcodes) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
COMPARE_OP, CompareOp::LT, COMPARE_OP, CompareOp::LE,
COMPARE_OP, CompareOp::EQ, COMPARE_OP, CompareOp::NE,
COMPARE_OP, CompareOp::GT, COMPARE_OP, CompareOp::GE,
COMPARE_OP, CompareOp::IN, COMPARE_OP, CompareOp::NOT_IN,
COMPARE_OP, CompareOp::IS, COMPARE_OP, CompareOp::IS_NOT,
COMPARE_OP, CompareOp::EXC_MATCH,
};
Code code(&scope, newCodeWithBytes(bytecode));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
COMPARE_OP_ANAMORPHIC,
CompareOp::LT,
0,
0,
COMPARE_OP_ANAMORPHIC,
CompareOp::LE,
1,
0,
COMPARE_OP_ANAMORPHIC,
CompareOp::EQ,
2,
0,
COMPARE_OP_ANAMORPHIC,
CompareOp::NE,
3,
0,
COMPARE_OP_ANAMORPHIC,
CompareOp::GT,
4,
0,
COMPARE_OP_ANAMORPHIC,
CompareOp::GE,
5,
0,
COMPARE_IN_ANAMORPHIC,
0,
6,
0,
COMPARE_OP,
CompareOp::NOT_IN,
0,
0,
COMPARE_IS,
0,
0,
0,
COMPARE_IS_NOT,
0,
0,
0,
COMPARE_OP,
CompareOp::EXC_MATCH,
0,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesReservesCachesForGlobalVariables) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
const byte bytecode[] = {
LOAD_GLOBAL, 0, STORE_GLOBAL, 1, LOAD_ATTR, 9, DELETE_GLOBAL, 2,
STORE_NAME, 3, DELETE_NAME, 4, LOAD_ATTR, 9, LOAD_NAME, 5,
};
Tuple consts(&scope, runtime_->emptyTuple());
MutableTuple names(&scope, runtime_->newMutableTuple(12));
for (word i = 0; i < 12; i++) {
names.atPut(i, runtime_->newStrFromFmt("g%w", i));
}
Tuple names_tuple(&scope, names.becomeImmutable());
Code code(&scope, newCodeWithBytesConstsNames(bytecode, consts, names_tuple));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
LOAD_GLOBAL,
0,
0,
0,
STORE_GLOBAL,
1,
0,
0,
// Note that LOAD_ATTR's cache index starts at 6 to reserve the first 6
// cache lines for 12 global variables.
LOAD_ATTR_ANAMORPHIC,
9,
6,
0,
DELETE_GLOBAL,
2,
0,
0,
STORE_NAME,
3,
0,
0,
DELETE_NAME,
4,
0,
0,
LOAD_ATTR_ANAMORPHIC,
9,
7,
0,
LOAD_NAME,
5,
0,
0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
Tuple caches(&scope, function.caches());
word num_global = 6;
word num_attr = 2;
EXPECT_EQ(caches.length(), (num_global + num_attr) * kIcPointersPerEntry);
}
TEST_F(
BytecodeTest,
RewriteBytecodeDoesNotRewriteLoadFastAndStoreFastOpcodesWithLargeLocalCount) {
HandleScope scope(thread_);
Object arg0(&scope, Runtime::internStrFromCStr(thread_, "arg0"));
Object var0(&scope, Runtime::internStrFromCStr(thread_, "var0"));
Object var1(&scope, Runtime::internStrFromCStr(thread_, "var1"));
Tuple varnames(&scope, runtime_->newTupleWith3(arg0, var0, var1));
Object freevar0(&scope, Runtime::internStrFromCStr(thread_, "freevar0"));
Tuple freevars(&scope, runtime_->newTupleWith1(freevar0));
Object cellvar0(&scope, Runtime::internStrFromCStr(thread_, "cellvar0"));
Tuple cellvars(&scope, runtime_->newTupleWith1(cellvar0));
word argcount = 1;
// Set nlocals > 255
word nlocals = kMaxByte + 3;
byte bytecode[] = {
LOAD_FAST, 2, LOAD_FAST, 1, LOAD_FAST, 1,
STORE_FAST, 2, STORE_FAST, 1, STORE_FAST, 0,
};
Bytes code_code(&scope, runtime_->newBytesWithAll(bytecode));
Object empty_tuple(&scope, runtime_->emptyTuple());
Object empty_string(&scope, Str::empty());
Object lnotab(&scope, Bytes::empty());
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
Code code(&scope,
runtime_->newCode(argcount, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0, nlocals,
/*stacksize=*/0, /*flags=*/flags, code_code,
/*consts=*/empty_tuple, /*names=*/empty_tuple,
varnames, freevars, cellvars,
/*filename=*/empty_string, /*name=*/empty_string,
/*firstlineno=*/0, lnotab));
Module module(&scope, findMainModule(runtime_));
Function function(&scope, runtime_->newFunctionWithCode(thread_, empty_string,
code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
LOAD_FAST, 2, 0, 0, LOAD_FAST, 1, 0, 0, LOAD_FAST, 1, 0, 0,
STORE_FAST, 2, 0, 0, STORE_FAST, 1, 0, 0, STORE_FAST, 0, 0, 0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
EXPECT_TRUE(function.caches().isNoneType());
}
TEST_F(BytecodeTest, RewriteBytecodeRewritesLoadFastAndStoreFastOpcodes) {
HandleScope scope(thread_);
Object arg0(&scope, Runtime::internStrFromCStr(thread_, "arg0"));
Object var0(&scope, Runtime::internStrFromCStr(thread_, "var0"));
Object var1(&scope, Runtime::internStrFromCStr(thread_, "var1"));
Tuple varnames(&scope, runtime_->newTupleWith3(arg0, var0, var1));
Object freevar0(&scope, Runtime::internStrFromCStr(thread_, "freevar0"));
Tuple freevars(&scope, runtime_->newTupleWith1(freevar0));
Object cellvar0(&scope, Runtime::internStrFromCStr(thread_, "cellvar0"));
Tuple cellvars(&scope, runtime_->newTupleWith1(cellvar0));
word argcount = 1;
word nlocals = 3;
byte bytecode[] = {
LOAD_FAST, 2, LOAD_FAST, 1, LOAD_FAST, 1,
STORE_FAST, 2, STORE_FAST, 1, STORE_FAST, 0,
};
Bytes code_code(&scope, runtime_->newBytesWithAll(bytecode));
Object empty_tuple(&scope, runtime_->emptyTuple());
Object empty_string(&scope, Str::empty());
Object lnotab(&scope, Bytes::empty());
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
Code code(&scope,
runtime_->newCode(argcount, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0, nlocals,
/*stacksize=*/0, /*flags=*/flags, code_code,
/*consts=*/empty_tuple, /*names=*/empty_tuple,
varnames, freevars, cellvars,
/*filename=*/empty_string, /*name=*/empty_string,
/*firstlineno=*/0, lnotab));
Module module(&scope, findMainModule(runtime_));
Function function(&scope, runtime_->newFunctionWithCode(thread_, empty_string,
code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
LOAD_FAST_REVERSE, 2, 0, 0, LOAD_FAST_REVERSE, 3, 0, 0,
LOAD_FAST_REVERSE, 3, 0, 0, STORE_FAST_REVERSE, 2, 0, 0,
STORE_FAST_REVERSE, 3, 0, 0, STORE_FAST_REVERSE, 4, 0, 0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
EXPECT_TRUE(function.caches().isNoneType());
}
TEST_F(BytecodeTest,
RewriteBytecodeRewritesLoadFastToLoadFastReverseBoundForArguments) {
HandleScope scope(thread_);
Object arg0(&scope, Runtime::internStrFromCStr(thread_, "arg0"));
Object var0(&scope, Runtime::internStrFromCStr(thread_, "var0"));
Object var1(&scope, Runtime::internStrFromCStr(thread_, "var1"));
Tuple varnames(&scope, runtime_->newTupleWith3(arg0, var0, var1));
Object freevar0(&scope, Runtime::internStrFromCStr(thread_, "freevar0"));
Tuple freevars(&scope, runtime_->newTupleWith1(freevar0));
Object cellvar0(&scope, Runtime::internStrFromCStr(thread_, "cellvar0"));
Tuple cellvars(&scope, runtime_->newTupleWith1(cellvar0));
word argcount = 1;
word nlocals = 3;
byte bytecode[] = {
LOAD_FAST, 2, LOAD_FAST, 1, LOAD_FAST, 0,
STORE_FAST, 2, STORE_FAST, 1, STORE_FAST, 0,
};
Bytes code_code(&scope, runtime_->newBytesWithAll(bytecode));
Object empty_tuple(&scope, runtime_->emptyTuple());
Object empty_string(&scope, Str::empty());
Object lnotab(&scope, Bytes::empty());
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
Code code(&scope,
runtime_->newCode(argcount, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0, nlocals,
/*stacksize=*/0, /*flags=*/flags, code_code,
/*consts=*/empty_tuple, /*names=*/empty_tuple,
varnames, freevars, cellvars,
/*filename=*/empty_string, /*name=*/empty_string,
/*firstlineno=*/0, lnotab));
Module module(&scope, findMainModule(runtime_));
Function function(&scope, runtime_->newFunctionWithCode(thread_, empty_string,
code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
LOAD_FAST_REVERSE, 2, 0, 0, LOAD_FAST_REVERSE, 3, 0, 0,
LOAD_FAST_REVERSE_UNCHECKED, 4, 0, 0, STORE_FAST_REVERSE, 2, 0, 0,
STORE_FAST_REVERSE, 3, 0, 0, STORE_FAST_REVERSE, 4, 0, 0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
EXPECT_TRUE(function.caches().isNoneType());
}
TEST_F(
BytecodeTest,
RewriteBytecodeRewritesLoadFastToLoadFastReverseWhenDeleteFastIsPresent) {
HandleScope scope(thread_);
Object arg0(&scope, Runtime::internStrFromCStr(thread_, "arg0"));
Object var0(&scope, Runtime::internStrFromCStr(thread_, "var0"));
Object var1(&scope, Runtime::internStrFromCStr(thread_, "var1"));
Tuple varnames(&scope, runtime_->newTupleWith3(arg0, var0, var1));
Object freevar0(&scope, Runtime::internStrFromCStr(thread_, "freevar0"));
Tuple freevars(&scope, runtime_->newTupleWith1(freevar0));
Object cellvar0(&scope, Runtime::internStrFromCStr(thread_, "cellvar0"));
Tuple cellvars(&scope, runtime_->newTupleWith1(cellvar0));
word argcount = 1;
word nlocals = 3;
const byte bytecode[] = {
LOAD_FAST, 2, LOAD_FAST, 1, LOAD_FAST, 0, STORE_FAST, 2,
STORE_FAST, 1, STORE_FAST, 0, DELETE_FAST, 0,
};
Bytes code_code(&scope, runtime_->newBytesWithAll(bytecode));
Object empty_tuple(&scope, runtime_->emptyTuple());
Object empty_string(&scope, Str::empty());
Object lnotab(&scope, Bytes::empty());
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
Code code(&scope,
runtime_->newCode(argcount, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0, nlocals,
/*stacksize=*/0, /*flags=*/flags, code_code,
/*consts=*/empty_tuple, /*names=*/empty_tuple,
varnames, freevars, cellvars,
/*filename=*/empty_string, /*name=*/empty_string,
/*firstlineno=*/0, lnotab));
Module module(&scope, findMainModule(runtime_));
Function function(&scope, runtime_->newFunctionWithCode(thread_, empty_string,
code, module));
// newFunctionWithCode() calls rewriteBytecode().
byte expected[] = {
LOAD_FAST_REVERSE, 2, 0, 0, LOAD_FAST_REVERSE, 3, 0, 0,
LOAD_FAST_REVERSE, 4, 0, 0, STORE_FAST_REVERSE, 2, 0, 0,
STORE_FAST_REVERSE, 3, 0, 0, STORE_FAST_REVERSE, 4, 0, 0,
DELETE_FAST, 0, 0, 0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
EXPECT_TRUE(function.caches().isNoneType());
}
TEST_F(BytecodeTest,
RewriteBytecodeDoesNotRewriteFunctionsWithNoOptimizedNorNewLocalsFlag) {
HandleScope scope(thread_);
Object name(&scope, Str::empty());
Tuple consts(&scope, runtime_->emptyTuple());
Tuple names(&scope, runtime_->emptyTuple());
const byte bytecode[] = {
NOP, 99, EXTENDED_ARG, 0xca, LOAD_ATTR, 0xfe,
NOP, 106, EXTENDED_ARG, 1, EXTENDED_ARG, 2,
EXTENDED_ARG, 3, LOAD_ATTR, 4, LOAD_ATTR, 77,
};
Code code(&scope,
newCodeWithBytesConstsNamesFlags(bytecode, consts, names, 0));
Module module(&scope, findMainModule(runtime_));
Function function(&scope,
runtime_->newFunctionWithCode(thread_, name, code, module));
byte expected[] = {
NOP, 99, 0, 0, EXTENDED_ARG, 0xca, 0, 0,
LOAD_ATTR, 0xfe, 0, 0, NOP, 106, 0, 0,
EXTENDED_ARG, 1, 0, 0, EXTENDED_ARG, 2, 0, 0,
EXTENDED_ARG, 3, 0, 0, LOAD_ATTR, 4, 0, 0,
LOAD_ATTR, 77, 0, 0,
};
Object rewritten_bytecode(&scope, function.rewrittenBytecode());
EXPECT_TRUE(isMutableBytesEqualsBytes(rewritten_bytecode, expected));
}
} // namespace testing
} // namespace py