mirror of
https://gitee.com/openharmony/arkcompiler_runtime_core
synced 2025-04-12 07:34:13 +00:00
Remove unused code
Remove unused code Issue: #I622DW Signed-off-by: qiuyu <qiuyu22@huawei.com> Change-Id: Ifc80f3f63691a71f51701f061a817fb84b7e6e41
This commit is contained in:
parent
8aa66da914
commit
bafd16e5a3
@ -126,59 +126,6 @@ static panda_file::Type::TypeId GetTypeId(Value::Type type)
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValueType(Value::Type value_type, Type type, const Program &program)
|
||||
{
|
||||
auto value_type_id = GetTypeId(value_type);
|
||||
if (value_type_id != type.GetId()) {
|
||||
SetLastError("Inconsistent element (" + AnnotationElement::TypeToString(value_type) +
|
||||
") and function's return type (" + type.GetName() + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value_type) {
|
||||
case Value::Type::STRING:
|
||||
case Value::Type::RECORD:
|
||||
case Value::Type::ANNOTATION:
|
||||
case Value::Type::ENUM: {
|
||||
auto it = program.record_table.find(type.GetName());
|
||||
if (it == program.record_table.cend()) {
|
||||
SetLastError("Record " + type.GetName() + " not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &record = it->second;
|
||||
if (value_type == Value::Type::ANNOTATION && !record.metadata->IsAnnotation() &&
|
||||
!record.metadata->IsRuntimeAnnotation() && !record.metadata->IsRuntimeTypeAnnotation() &&
|
||||
!record.metadata->IsTypeAnnotation()) {
|
||||
SetLastError("Record " + type.GetName() + " isn't annotation");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value_type == Value::Type::ENUM && (record.metadata->GetAccessFlags() & ACC_ENUM) == 0) {
|
||||
SetLastError("Record " + type.GetName() + " isn't enum");
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Value::Type::ARRAY: {
|
||||
if (!type.IsArray()) {
|
||||
SetLastError("Inconsistent element (" + AnnotationElement::TypeToString(value_type) +
|
||||
") and function's return type (" + type.GetName() + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
std::string AsmEmitter::GetMethodSignatureFromProgram(const std::string &name, const Program &program)
|
||||
{
|
||||
@ -265,146 +212,6 @@ panda_file::LiteralItem *AsmEmitter::CreateLiteralItem(
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValueRecordCase(const Value *value, const Program &program)
|
||||
{
|
||||
auto t = value->GetAsScalar()->GetValue<Type>();
|
||||
if (!t.IsObject()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto record_name = t.GetName();
|
||||
bool is_found;
|
||||
if (t.IsArray()) {
|
||||
auto it = program.array_types.find(t);
|
||||
is_found = it != program.array_types.cend();
|
||||
} else {
|
||||
auto it = program.record_table.find(record_name);
|
||||
is_found = it != program.record_table.cend();
|
||||
}
|
||||
|
||||
if (!is_found) {
|
||||
SetLastError("Incorrect value: record " + record_name + " not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValueMethodCase(const Value *value, const Program &program)
|
||||
{
|
||||
auto function_name = value->GetAsScalar()->GetValue<std::string>();
|
||||
auto it = program.function_table.find(function_name);
|
||||
if (it == program.function_table.cend()) {
|
||||
SetLastError("Incorrect value: function " + function_name + " not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValueEnumCase(const Value *value, Type type, const Program &program)
|
||||
{
|
||||
auto enum_value = value->GetAsScalar()->GetValue<std::string>();
|
||||
auto record_name = GetOwnerName(enum_value);
|
||||
auto field_name = GetItemName(enum_value);
|
||||
|
||||
if (record_name != type.GetName()) {
|
||||
SetLastError("Incorrect value: Expected " + type.GetName() + " enum record");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &record = program.record_table.find(record_name)->second;
|
||||
auto it = std::find_if(record.field_list.cbegin(), record.field_list.cend(),
|
||||
[&field_name](const Field &field) { return field.name == field_name; });
|
||||
if (it == record.field_list.cend()) {
|
||||
SetLastError("Incorrect value: Enum field " + enum_value + " not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &field = *it;
|
||||
if ((field.metadata->GetAccessFlags() & ACC_ENUM) == 0) {
|
||||
SetLastError("Incorrect value: Field " + enum_value + " isn't enum");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValueArrayCase(const Value *value, Type type, const Program &program)
|
||||
{
|
||||
auto component_type = type.GetComponentType();
|
||||
auto value_component_type = value->GetAsArray()->GetComponentType();
|
||||
|
||||
if (value_component_type == Value::Type::VOID && value->GetAsArray()->GetValues().empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CheckValueType(value_component_type, component_type, program)) {
|
||||
SetLastError("Incorrect array's component type: " + GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &elem_value : value->GetAsArray()->GetValues()) {
|
||||
if (!CheckValue(&elem_value, component_type, program)) {
|
||||
SetLastError("Incorrect array's element: " + GetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool AsmEmitter::CheckValue(const Value *value, Type type, const Program &program)
|
||||
{
|
||||
auto value_type = value->GetType();
|
||||
|
||||
if (!CheckValueType(value_type, type, program)) {
|
||||
SetLastError("Incorrect type: " + GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value_type) {
|
||||
case Value::Type::RECORD: {
|
||||
if (!CheckValueRecordCase(value, program)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Value::Type::METHOD: {
|
||||
if (!CheckValueMethodCase(value, program)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Value::Type::ENUM: {
|
||||
if (!CheckValueEnumCase(value, type, program)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Value::Type::ARRAY: {
|
||||
if (!CheckValueArrayCase(value, type, program)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
ScalarValueItem *AsmEmitter::CreateScalarStringValueItem(ItemContainer *container, const Value *value,
|
||||
std::vector<ScalarValueItem> *out)
|
||||
@ -639,29 +446,9 @@ AnnotationItem *AsmEmitter::CreateAnnotationItem(ItemContainer *container, const
|
||||
|
||||
ASSERT(tag_type != '0');
|
||||
|
||||
auto function_name = record.name + "." + name;
|
||||
|
||||
function_name = GetMethodSignatureFromProgram(function_name, program);
|
||||
|
||||
if (record.HasImplementation()) {
|
||||
auto func_it = program.function_table.find(function_name);
|
||||
if (func_it == program.function_table.cend()) {
|
||||
// Definitions of the system annotations may be absent.
|
||||
// So print message and continue if corresponding function isn't found.
|
||||
LOG(INFO, ASSEMBLER) << "Function " << function_name << " not found";
|
||||
} else {
|
||||
auto &function = func_it->second;
|
||||
|
||||
if (!CheckValue(value, function.return_type, program)) {
|
||||
SetLastError("Incorrect annotation element " + function_name + ": " + GetLastError());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto *item = CreateValueItem(container, value, program, entities);
|
||||
if (item == nullptr) {
|
||||
SetLastError("Cannot create value item for annotation element " + function_name + ": " + GetLastError());
|
||||
SetLastError("Cannot create value item for annotation element " + name + ": " + GetLastError());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -150,13 +150,6 @@ private:
|
||||
bool emit_debug_info, AsmEntityCollections &entities,
|
||||
std::unordered_map<panda_file::Type::TypeId, panda_file::PrimitiveTypeItem *> primitive_types);
|
||||
|
||||
static bool CheckValueType(Value::Type value_type, Type type, const Program &program);
|
||||
|
||||
static bool CheckValueEnumCase(const Value *value, Type type, const Program &program);
|
||||
static bool CheckValueArrayCase(const Value *value, Type type, const Program &program);
|
||||
static bool CheckValueMethodCase(const Value *value, const Program &program);
|
||||
static bool CheckValueRecordCase(const Value *value, const Program &program);
|
||||
static bool CheckValue(const Value *value, Type type, const Program &program);
|
||||
static std::string GetMethodSignatureFromProgram(const std::string &name, const Program &program);
|
||||
|
||||
static panda_file::LiteralItem *CreateLiteralItem(
|
||||
|
@ -24,12 +24,8 @@ config("bytecodeopt_public_config") {
|
||||
}
|
||||
|
||||
libarkbytecodeopt_sources = [
|
||||
"$ark_root/bytecode_optimizer/bytecodeopt_peepholes.cpp",
|
||||
"$ark_root/bytecode_optimizer/canonicalization.cpp",
|
||||
"$ark_root/bytecode_optimizer/check_resolver.cpp",
|
||||
"$ark_root/bytecode_optimizer/codegen.cpp",
|
||||
"$ark_root/bytecode_optimizer/common.cpp",
|
||||
"$ark_root/bytecode_optimizer/const_array_resolver.cpp",
|
||||
"$ark_root/bytecode_optimizer/optimize_bytecode.cpp",
|
||||
"$ark_root/bytecode_optimizer/reg_acc_alloc.cpp",
|
||||
"$ark_root/bytecode_optimizer/reg_encoder.cpp",
|
||||
|
@ -1,153 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "bytecodeopt_peepholes.h"
|
||||
|
||||
#include "libpandafile/bytecode_instruction-inl.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
bool BytecodeOptPeepholes::RunImpl()
|
||||
{
|
||||
VisitGraph();
|
||||
return IsApplied();
|
||||
}
|
||||
|
||||
CallInst *FindCtorCall(Inst *new_object, const compiler::ClassInst *load)
|
||||
{
|
||||
auto *adapter = new_object->GetBasicBlock()->GetGraph()->GetRuntime();
|
||||
for (auto &user : new_object->GetUsers()) {
|
||||
auto *inst = user.GetInst();
|
||||
if (inst->GetOpcode() == Opcode::NullCheck) {
|
||||
return FindCtorCall(inst, load);
|
||||
}
|
||||
if (inst->GetOpcode() != Opcode::CallStatic) {
|
||||
continue;
|
||||
}
|
||||
auto call = inst->CastToCallStatic();
|
||||
if (adapter->IsConstructor(call->GetCallMethod(), load->GetTypeId())) {
|
||||
return call;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CallInst *CreateInitObject(compiler::GraphVisitor *v, compiler::ClassInst *load, const CallInst *call_init)
|
||||
{
|
||||
auto *graph = static_cast<BytecodeOptPeepholes *>(v)->GetGraph();
|
||||
auto *init_object = static_cast<CallInst *>(graph->CreateInst(compiler::Opcode::InitObject));
|
||||
init_object->SetType(compiler::DataType::REFERENCE);
|
||||
|
||||
auto input_types_count = call_init->GetInputsCount();
|
||||
init_object->AllocateInputTypes(graph->GetAllocator(), input_types_count);
|
||||
init_object->AddInputType(compiler::DataType::REFERENCE);
|
||||
init_object->AppendInput(load);
|
||||
for (size_t i = 1; i < input_types_count; ++i) {
|
||||
auto input_inst = call_init->GetInput(i).GetInst();
|
||||
init_object->AddInputType(input_inst->GetType());
|
||||
init_object->AppendInput(input_inst);
|
||||
}
|
||||
|
||||
init_object->SetCallMethodId(call_init->GetCallMethodId());
|
||||
init_object->SetCallMethod(static_cast<const CallInst *>(call_init)->GetCallMethod());
|
||||
return init_object;
|
||||
}
|
||||
|
||||
void ReplaceNewObjectUsers(Inst *new_object, Inst *null_check, CallInst *init_object)
|
||||
{
|
||||
for (auto it = new_object->GetUsers().begin(); it != new_object->GetUsers().end();
|
||||
it = new_object->GetUsers().begin()) {
|
||||
auto user = it->GetInst();
|
||||
if (user != null_check) {
|
||||
user->SetInput(it->GetIndex(), init_object);
|
||||
} else {
|
||||
new_object->RemoveUser(&(*it));
|
||||
}
|
||||
}
|
||||
|
||||
// Update throwable instructions data
|
||||
auto graph = new_object->GetBasicBlock()->GetGraph();
|
||||
if (graph->IsInstThrowable(new_object)) {
|
||||
graph->ReplaceThrowableInst(new_object, init_object);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeOptPeepholes::VisitNewObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
auto load = static_cast<compiler::ClassInst *>(inst->GetInput(0).GetInst());
|
||||
ASSERT(load != nullptr);
|
||||
|
||||
CallInst *call_init = FindCtorCall(inst, load);
|
||||
if (call_init == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst->GetBasicBlock() != call_init->GetBasicBlock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The optimization is correct only if there are no side-effects between NewObject and constructor call.
|
||||
// For simplicity, we abort it if any instruction except NullCheck and SaveState appears in-between.
|
||||
// Moreover, when we are inside a try block, local register state also matters, because it may be used inside
|
||||
// catch blocks. In such case we also abort if there are any instructions in corresponding bytecode.
|
||||
const auto graph = static_cast<BytecodeOptPeepholes *>(v)->GetGraph();
|
||||
const size_t NEWOBJ_SIZE =
|
||||
BytecodeInstruction::Size( // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
BytecodeInstruction(graph->GetRuntime()->GetMethodCode(graph->GetMethod()) + inst->GetPc()).GetFormat());
|
||||
if (inst->GetBasicBlock()->IsTry() && call_init->GetPc() - inst->GetPc() > NEWOBJ_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inst *null_check = nullptr;
|
||||
for (auto *i = inst->GetNext(); i != call_init; i = i->GetNext()) {
|
||||
if (i->GetOpcode() != Opcode::SaveState && i->GetOpcode() != Opcode::NullCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (i->GetOpcode() == Opcode::SaveState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto null_check_input : i->GetInputs()) {
|
||||
if (null_check_input.GetInst() == inst) {
|
||||
ASSERT(null_check == nullptr);
|
||||
null_check = i;
|
||||
}
|
||||
}
|
||||
if (null_check == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto *init_object = CreateInitObject(v, load, call_init);
|
||||
call_init->InsertBefore(init_object);
|
||||
init_object->SetPc(call_init->GetPc());
|
||||
|
||||
ReplaceNewObjectUsers(inst, null_check, init_object);
|
||||
inst->ClearFlag(compiler::inst_flags::NO_DCE);
|
||||
if (null_check != nullptr) {
|
||||
null_check->ReplaceUsers(init_object);
|
||||
null_check->ClearFlag(compiler::inst_flags::NO_DCE);
|
||||
null_check->RemoveInputs();
|
||||
null_check->GetBasicBlock()->ReplaceInst(null_check,
|
||||
static_cast<BytecodeOptPeepholes *>(v)->GetGraph()->CreateInstNOP());
|
||||
}
|
||||
ASSERT(!call_init->HasUsers());
|
||||
call_init->ClearFlag(compiler::inst_flags::NO_DCE);
|
||||
|
||||
static_cast<BytecodeOptPeepholes *>(v)->SetIsApplied();
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef PANDA_BYTECODE_OPTIMIZER_BYTECODEOPT_PEEPHOLES_H_
|
||||
#define PANDA_BYTECODE_OPTIMIZER_BYTECODEOPT_PEEPHOLES_H_
|
||||
|
||||
#include "bytecodeopt_options.h"
|
||||
#include "compiler/optimizer/pass.h"
|
||||
#include "compiler/optimizer/ir/basicblock.h"
|
||||
#include "compiler/optimizer/ir/graph.h"
|
||||
#include "compiler/optimizer/ir/graph_visitor.h"
|
||||
#include "compiler/optimizer/ir/inst.h"
|
||||
#include "libpandabase/utils/arena_containers.h"
|
||||
#include "runtime_adapter.h"
|
||||
|
||||
/*
|
||||
* BytecodeOptPeepholes
|
||||
*
|
||||
* BytecodeOptPeepholes includes now only transformation of NewObject and related instructions into
|
||||
* InitObject
|
||||
*/
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
using compiler::BasicBlock;
|
||||
using compiler::CallInst;
|
||||
using compiler::Inst;
|
||||
using compiler::Opcode;
|
||||
|
||||
class BytecodeOptPeepholes : public compiler::Optimization, public compiler::GraphVisitor {
|
||||
public:
|
||||
explicit BytecodeOptPeepholes(compiler::Graph *graph) : compiler::Optimization(graph) {}
|
||||
|
||||
~BytecodeOptPeepholes() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "BytecodeOptPeepholes";
|
||||
}
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsBytecodeOptPeepholes();
|
||||
}
|
||||
|
||||
/* This visitor replaces combination of NewObject, SaveState,
|
||||
* NullCheck and CallStatic with InitObject. It is used in order to replace newobj, sta and call
|
||||
* in some ark bytecode with one instruction initobj.
|
||||
*/
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
static void VisitNewObject(GraphVisitor *v, Inst *inst);
|
||||
|
||||
public:
|
||||
bool IsApplied()
|
||||
{
|
||||
return is_applied_;
|
||||
}
|
||||
|
||||
#include "compiler/optimizer/ir/visitor.inc"
|
||||
|
||||
private:
|
||||
void SetIsApplied()
|
||||
{
|
||||
is_applied_ = true;
|
||||
}
|
||||
|
||||
bool is_applied_ {false};
|
||||
};
|
||||
|
||||
} // namespace panda::bytecodeopt
|
||||
|
||||
#endif // PANDA_BYTECODE_OPTIMIZER_BYTECODEOPT_PEEPHOLES_H_
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "canonicalization.h"
|
||||
|
||||
#include "compiler/optimizer/ir/basicblock.h"
|
||||
#include "compiler/optimizer/ir/inst.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
bool Canonicalization::RunImpl()
|
||||
{
|
||||
Canonicalization visitor(GetGraph());
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
for (auto inst : bb->AllInsts()) {
|
||||
if (inst->IsCommutative()) {
|
||||
visitor.VisitCommutative(inst);
|
||||
} else {
|
||||
visitor.VisitInstruction(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
return visitor.GetStatus();
|
||||
}
|
||||
|
||||
static bool IsDominateReverseInputs(const compiler::Inst *inst)
|
||||
{
|
||||
auto input0 = inst->GetInput(0).GetInst();
|
||||
auto input1 = inst->GetInput(1).GetInst();
|
||||
return input0->IsDominate(input1);
|
||||
}
|
||||
|
||||
static bool ConstantFitsCompareImm(const Inst *cst, uint32_t size)
|
||||
{
|
||||
ASSERT(cst->GetOpcode() == Opcode::Constant);
|
||||
if (compiler::DataType::IsFloatType(cst->GetType())) {
|
||||
return false;
|
||||
}
|
||||
auto val = cst->CastToConstant()->GetIntValue();
|
||||
return (size == compiler::HALF_SIZE) && (val == 0);
|
||||
}
|
||||
|
||||
static bool BetterToSwapCompareInputs(const compiler::Inst *inst, const compiler::Inst *input0,
|
||||
const compiler::Inst *input1)
|
||||
{
|
||||
if (!input0->IsConst()) {
|
||||
return false;
|
||||
}
|
||||
if (!input1->IsConst()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
compiler::DataType::Type type = inst->CastToCompare()->GetOperandsType();
|
||||
uint32_t size = (type == compiler::DataType::UINT64 || type == compiler::DataType::INT64) ? compiler::WORD_SIZE
|
||||
: compiler::HALF_SIZE;
|
||||
return ConstantFitsCompareImm(input0, size) && !ConstantFitsCompareImm(input1, size);
|
||||
}
|
||||
|
||||
static bool SwapInputsIfNecessary(compiler::Inst *inst, const bool necessary)
|
||||
{
|
||||
if (!necessary) {
|
||||
return false;
|
||||
}
|
||||
auto input0 = inst->GetInput(0).GetInst();
|
||||
auto input1 = inst->GetInput(1).GetInst();
|
||||
if ((inst->GetOpcode() == compiler::Opcode::Compare) && !BetterToSwapCompareInputs(inst, input0, input1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inst->SwapInputs();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Canonicalization::TrySwapConstantInput(Inst *inst)
|
||||
{
|
||||
return SwapInputsIfNecessary(inst, inst->GetInput(0).GetInst()->IsConst());
|
||||
}
|
||||
|
||||
bool Canonicalization::TrySwapReverseInput(Inst *inst)
|
||||
{
|
||||
return SwapInputsIfNecessary(inst, IsDominateReverseInputs(inst));
|
||||
}
|
||||
|
||||
void Canonicalization::VisitCommutative(Inst *inst)
|
||||
{
|
||||
ASSERT(inst->IsCommutative());
|
||||
ASSERT(inst->GetInputsCount() == 2); // 2 is COMMUTATIVE_INPUT_COUNT
|
||||
if (options.GetOptLevel() > 1) {
|
||||
result_ = TrySwapReverseInput(inst);
|
||||
}
|
||||
result_ = TrySwapConstantInput(inst) || result_;
|
||||
}
|
||||
|
||||
// It is not allowed to move a constant input1 with a single user (it's processed Compare instruction).
|
||||
// This is necessary for further merging of the constant and the If instrution in the Lowering pass
|
||||
bool AllowSwap(const compiler::Inst *inst)
|
||||
{
|
||||
auto input1 = inst->GetInput(1).GetInst();
|
||||
if (!input1->IsConst()) {
|
||||
return true;
|
||||
}
|
||||
for (const auto &user : input1->GetUsers()) {
|
||||
if (user.GetInst() != inst) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Canonicalization::VisitCompare([[maybe_unused]] GraphVisitor *v, Inst *inst_base)
|
||||
{
|
||||
auto inst = inst_base->CastToCompare();
|
||||
if (AllowSwap(inst) && SwapInputsIfNecessary(inst, IsDominateReverseInputs(inst))) {
|
||||
auto revert_cc = SwapOperandsConditionCode(inst->GetCc());
|
||||
inst->SetCc(revert_cc);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt
|
@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef PANDA_BYTECODE_OPT_CANONICALIZATION_H
|
||||
#define PANDA_BYTECODE_OPT_CANONICALIZATION_H
|
||||
|
||||
#include "bytecodeopt_options.h"
|
||||
#include "compiler/optimizer/ir/graph.h"
|
||||
#include "compiler/optimizer/pass.h"
|
||||
#include "compiler/optimizer/ir/graph_visitor.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
using panda::compiler::BasicBlock;
|
||||
using panda::compiler::Inst;
|
||||
using panda::compiler::Opcode;
|
||||
|
||||
class Canonicalization : public compiler::Optimization, public compiler::GraphVisitor {
|
||||
public:
|
||||
explicit Canonicalization(compiler::Graph *graph) : compiler::Optimization(graph) {}
|
||||
~Canonicalization() override = default;
|
||||
bool RunImpl() override;
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "Canonicalization";
|
||||
}
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCanonicalization();
|
||||
}
|
||||
|
||||
bool GetStatus() const
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
void VisitCommutative(Inst *inst);
|
||||
static void VisitCompare([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
|
||||
static bool TrySwapReverseInput(Inst *inst);
|
||||
static bool TrySwapConstantInput(Inst *inst);
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
private:
|
||||
bool result_ {false};
|
||||
};
|
||||
} // namespace panda::bytecodeopt
|
||||
|
||||
#endif // PANDA_BYTECODE_OPT_CANONICALIZATION_H
|
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "check_resolver.h"
|
||||
#include "compiler/optimizer/ir/basicblock.h"
|
||||
#include "compiler/optimizer/ir/inst.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
bool CheckResolver::RunImpl()
|
||||
{
|
||||
bool applied = false;
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
for (auto inst : bb->Insts()) {
|
||||
auto op = inst->GetOpcode();
|
||||
|
||||
// replace check
|
||||
if (IsCheck(inst)) {
|
||||
size_t i = (op == compiler::Opcode::BoundsCheck || op == compiler::Opcode::RefTypeCheck) ? 1 : 0;
|
||||
auto input = inst->GetInput(i).GetInst();
|
||||
inst->ReplaceUsers(input);
|
||||
inst->ClearFlag(compiler::inst_flags::NO_DCE); // DCE will remove the check inst
|
||||
applied = true;
|
||||
}
|
||||
|
||||
// set NO_DCE for Div, Mod, LoadArray, LoadStatic and LoadObject
|
||||
if (NeedSetNoDCE(inst)) {
|
||||
inst->SetFlag(compiler::inst_flags::NO_DCE);
|
||||
applied = true;
|
||||
}
|
||||
|
||||
// mark LenArray whose users are not all check as no_dce
|
||||
// mark LenArray as no_hoist in all cases
|
||||
if (inst->GetOpcode() == compiler::Opcode::LenArray) {
|
||||
bool no_dce = !inst->HasUsers();
|
||||
for (const auto &usr : inst->GetUsers()) {
|
||||
if (!IsCheck(usr.GetInst())) {
|
||||
no_dce = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (no_dce) {
|
||||
inst->SetFlag(compiler::inst_flags::NO_DCE);
|
||||
}
|
||||
inst->SetFlag(compiler::inst_flags::NO_HOIST);
|
||||
}
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
bool CheckResolver::NeedSetNoDCE(const compiler::Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case compiler::Opcode::Div:
|
||||
case compiler::Opcode::Mod:
|
||||
case compiler::Opcode::LoadArray:
|
||||
case compiler::Opcode::LoadStatic:
|
||||
case compiler::Opcode::LoadObject:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckResolver::IsCheck(const compiler::Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case compiler::Opcode::BoundsCheck:
|
||||
case compiler::Opcode::NullCheck:
|
||||
case compiler::Opcode::NegativeCheck:
|
||||
case compiler::Opcode::ZeroCheck:
|
||||
case compiler::Opcode::RefTypeCheck:
|
||||
case compiler::Opcode::BoundsCheckI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef PANDA_CHECK_RESOLVER_H
|
||||
#define PANDA_CHECK_RESOLVER_H
|
||||
|
||||
#include "compiler/optimizer/ir/graph.h"
|
||||
#include "compiler/optimizer/ir/inst.h"
|
||||
#include "compiler/optimizer/pass.h"
|
||||
#include "utils/arena_containers.h"
|
||||
|
||||
/*
|
||||
* Check Resolver.
|
||||
*
|
||||
* Check Resolver is a bytecodeopt-specific pass. In bytecode optimizer, we do
|
||||
* not need all check-instructions, such as BoundsCheck, NullCheck, NegativeCheck
|
||||
* and ZeroCheck, because the throws of these checks will be embedded in their
|
||||
* underlying instructions during runtime.
|
||||
* For the sake of saving ROM size, we can delete these check-instructions. Besides,
|
||||
* when bytecode optimizer optimizes the code in which there exist operations on the
|
||||
* element of an array, in the generated code the redundant asm lenarr will be generated
|
||||
* with ldarr and starr. Here lenarr is generated from IR LenArray and LenArray is an
|
||||
* input of BoundsCheck. CodeGen will encode LenArray but ignore BoundsCheck. That is
|
||||
* why dead lenarr remains. So we can also benefit from the size of generated bytecode
|
||||
* by deleting check-instructions.
|
||||
* However, these LenArray that are generated from asm lenarr should be keeped, as they
|
||||
* may throw in the original code logic. For LoadArray, Div, Mod, LoadStatic and LoadObject,
|
||||
* since they can throw but they are not generated as inputs of check-instructions, We
|
||||
* should keep all such insts.
|
||||
*
|
||||
* For every check-instruction, we replace the corresponding input of its users by the data
|
||||
* flow input. Then we clear its NO_DCE flag such that it can be removed by DCE pass. We set
|
||||
* the NO_DCE flag for the insts that should be keeped.
|
||||
*/
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
class CheckResolver : public compiler::Optimization {
|
||||
public:
|
||||
explicit CheckResolver(compiler::Graph *graph) : compiler::Optimization(graph) {}
|
||||
~CheckResolver() override = default;
|
||||
bool RunImpl() override;
|
||||
static bool IsCheck(const compiler::Inst *inst);
|
||||
static bool NeedSetNoDCE(const compiler::Inst *inst);
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "CheckResolver";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace panda::bytecodeopt
|
||||
|
||||
#endif // PANDA_CHECK_RESOLVER_H
|
@ -360,26 +360,6 @@ void BytecodeGen::EncodeSta(compiler::Register reg, compiler::DataType::Type typ
|
||||
result_.emplace_back(sta);
|
||||
}
|
||||
|
||||
void BytecodeGen::CallHandler(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void BytecodeGen::VisitCallStatic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHandler(visitor, inst);
|
||||
}
|
||||
|
||||
void BytecodeGen::VisitCallVirtual(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHandler(visitor, inst);
|
||||
}
|
||||
|
||||
void BytecodeGen::VisitInitObject(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHandler(visitor, inst);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-size)
|
||||
void BytecodeGen::VisitIf(GraphVisitor *v, Inst *inst_base)
|
||||
{
|
||||
|
@ -102,9 +102,6 @@ public:
|
||||
}
|
||||
static void VisitSpillFill(GraphVisitor *v, Inst *inst);
|
||||
static void VisitConstant(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCallStatic(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCallVirtual(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitInitObject(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCatchPhi(GraphVisitor *visitor, Inst *inst);
|
||||
|
||||
static void VisitIf(GraphVisitor *v, Inst *inst_base);
|
||||
@ -114,7 +111,6 @@ public:
|
||||
static void IfImmNonZero(GraphVisitor *v, Inst *inst_base);
|
||||
static void IfImm64(GraphVisitor *v, Inst *inst_base);
|
||||
static void VisitIntrinsic(GraphVisitor *v, Inst *inst_base);
|
||||
static void CallHandler(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitLoadString(GraphVisitor *v, Inst *inst_base);
|
||||
static void VisitReturn(GraphVisitor *v, Inst *inst_base);
|
||||
|
||||
|
@ -1,335 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "assembler/assembly-literals.h"
|
||||
#include "compiler/optimizer/ir/basicblock.h"
|
||||
#include "compiler/optimizer/optimizations/peepholes.h"
|
||||
#include "const_array_resolver.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
|
||||
static constexpr size_t STOREARRAY_INPUTS_NUM = 3;
|
||||
static constexpr size_t SINGLE_DIM_ARRAY_RANK = 1;
|
||||
static constexpr size_t MIN_ARRAY_ELEMENTS_AMOUNT = 2;
|
||||
|
||||
bool ConstArrayResolver::RunImpl()
|
||||
{
|
||||
if (ir_interface_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FindConstantArrays()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete instructions for storing array elements
|
||||
RemoveArraysFill();
|
||||
|
||||
// replace old NewArray instructions with new SaveState + LoadConst instructions
|
||||
InsertLoadConstArrayInsts();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsPatchAllowedOpcode(Opcode opcode)
|
||||
{
|
||||
switch (opcode) {
|
||||
case Opcode::StoreArray:
|
||||
case Opcode::LoadString:
|
||||
case Opcode::Constant:
|
||||
case Opcode::Cast:
|
||||
case Opcode::SaveState:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::vector<pandasm::LiteralArray::Literal>> ConstArrayResolver::FillLiteralArray(Inst *inst, size_t size)
|
||||
{
|
||||
std::vector<pandasm::LiteralArray::Literal> literals;
|
||||
std::vector<Inst *> store_insts;
|
||||
|
||||
auto next = inst->GetNext();
|
||||
size_t index = 0;
|
||||
|
||||
// are looking for instructions for uninterrupted filling the array
|
||||
while ((next != nullptr) && (index < size)) {
|
||||
// check whether the instruction is allowed inside the filling patch
|
||||
if (!IsPatchAllowedOpcode(next->GetOpcode())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// find instructions for storing array elements
|
||||
if (next->GetOpcode() != Opcode::StoreArray) {
|
||||
next = next->GetNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto store_array_inst = next->CastToStoreArray();
|
||||
if (store_array_inst->GetArray() != inst) {
|
||||
break;
|
||||
}
|
||||
|
||||
// create a literal from the array element, if possible
|
||||
pandasm::LiteralArray::Literal literal;
|
||||
if (!FillLiteral(store_array_inst, &literal)) {
|
||||
// if not, then we can't create a constant literal array
|
||||
return std::nullopt;
|
||||
}
|
||||
literals.push_back(literal);
|
||||
store_insts.push_back(next);
|
||||
|
||||
index++;
|
||||
next = next->GetNext();
|
||||
}
|
||||
|
||||
// save the literal array only if it is completely filled
|
||||
// or its size exceeds the minimum number of elements to save
|
||||
if ((index < size) || (store_insts.size() < MIN_ARRAY_ELEMENTS_AMOUNT)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// save the store instructions for deleting them later
|
||||
const_arrays_fill_.emplace(inst, std::move(store_insts));
|
||||
return std::optional<std::vector<pandasm::LiteralArray::Literal>> {std::move(literals)};
|
||||
}
|
||||
|
||||
void ConstArrayResolver::AddIntroLiterals(pandasm::LiteralArray *lt_ar)
|
||||
{
|
||||
// add an element that stores the array size (it will be stored in the first element)
|
||||
pandasm::LiteralArray::Literal len_lit;
|
||||
len_lit.tag_ = panda_file::LiteralTag::INTEGER;
|
||||
len_lit.value_ = static_cast<uint32_t>(lt_ar->literals_.size());
|
||||
lt_ar->literals_.insert(lt_ar->literals_.begin(), len_lit);
|
||||
|
||||
// add an element that stores the array type (it will be stored in the zero element)
|
||||
pandasm::LiteralArray::Literal tag_lit;
|
||||
tag_lit.tag_ = panda_file::LiteralTag::TAGVALUE;
|
||||
tag_lit.value_ = static_cast<uint8_t>(lt_ar->literals_.back().tag_);
|
||||
lt_ar->literals_.insert(lt_ar->literals_.begin(), tag_lit);
|
||||
}
|
||||
|
||||
bool ConstArrayResolver::IsMultidimensionalArray(compiler::NewArrayInst *inst)
|
||||
{
|
||||
auto array_type = pandasm::Type::FromName(ir_interface_->GetTypeIdByOffset(inst->GetTypeId()));
|
||||
return array_type.GetRank() > SINGLE_DIM_ARRAY_RANK;
|
||||
}
|
||||
|
||||
static bool IsSameBB(Inst *inst1, Inst *inst2)
|
||||
{
|
||||
return inst1->GetBasicBlock() == inst2->GetBasicBlock();
|
||||
}
|
||||
|
||||
static bool IsSameBB(Inst *inst, compiler::BasicBlock *bb)
|
||||
{
|
||||
return inst->GetBasicBlock() == bb;
|
||||
}
|
||||
|
||||
static std::optional<compiler::ConstantInst *> GetConstantIfPossible(Inst *inst)
|
||||
{
|
||||
if (inst->GetOpcode() == Opcode::Cast) {
|
||||
auto input = inst->GetInput(0).GetInst();
|
||||
if ((input->GetOpcode() == Opcode::NullPtr) || !input->IsConst()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto constant_inst = compiler::ConstFoldingCastConst(inst, input, true);
|
||||
return constant_inst == nullptr ? std::nullopt : std::optional<compiler::ConstantInst *>(constant_inst);
|
||||
}
|
||||
if (inst->IsConst()) {
|
||||
return std::optional<compiler::ConstantInst *>(inst->CastToConstant());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ConstArrayResolver::FindConstantArrays()
|
||||
{
|
||||
size_t init_size = ir_interface_->GetLiteralArrayTableSize();
|
||||
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
// go through the instructions of the basic block in reverse order
|
||||
// until we meet the instruction for storing an array element
|
||||
auto inst = bb->GetLastInst();
|
||||
while ((inst != nullptr) && IsSameBB(inst, bb)) {
|
||||
if (inst->GetOpcode() != Opcode::StoreArray) {
|
||||
inst = inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
|
||||
// the patch for creating and filling an array should start with the NewArray instruction
|
||||
auto array_inst = inst->CastToStoreArray()->GetArray();
|
||||
if (array_inst->GetOpcode() != Opcode::NewArray) {
|
||||
inst = inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
auto new_array_inst = array_inst->CastToNewArray();
|
||||
|
||||
// the instructions included in the patch must be in one basic block
|
||||
if (!IsSameBB(inst, new_array_inst)) {
|
||||
inst = inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(aantipina): add the ability to save multidimensional arrays
|
||||
if (IsMultidimensionalArray(new_array_inst)) {
|
||||
if (IsSameBB(inst, new_array_inst)) {
|
||||
inst = new_array_inst->GetPrev();
|
||||
} else {
|
||||
inst = inst->GetPrev();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto array_size_inst =
|
||||
GetConstantIfPossible(new_array_inst->GetInput(compiler::NewArrayInst::INDEX_SIZE).GetInst());
|
||||
if (array_size_inst == std::nullopt) {
|
||||
inst = new_array_inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
auto array_size = (*array_size_inst)->CastToConstant()->GetIntValue();
|
||||
if (array_size < MIN_ARRAY_ELEMENTS_AMOUNT) {
|
||||
inst = new_array_inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
|
||||
// creating a literal array, if possible
|
||||
auto raw_literal_array = FillLiteralArray(new_array_inst, array_size);
|
||||
if (raw_literal_array == std::nullopt) {
|
||||
inst = new_array_inst->GetPrev();
|
||||
continue;
|
||||
}
|
||||
|
||||
pandasm::LiteralArray literal_array(*raw_literal_array);
|
||||
|
||||
// save the type and length of the array in the first two elements
|
||||
AddIntroLiterals(&literal_array);
|
||||
auto id = ir_interface_->GetLiteralArrayTableSize();
|
||||
ir_interface_->StoreLiteralArray(std::to_string(id), std::move(literal_array));
|
||||
|
||||
// save the NewArray instructions for replacing them with LoadConst instructions later
|
||||
const_arrays_init_.emplace(id, new_array_inst);
|
||||
|
||||
inst = new_array_inst->GetPrev();
|
||||
}
|
||||
}
|
||||
|
||||
// the pass worked if the size of the literal array table increased
|
||||
return init_size < ir_interface_->GetLiteralArrayTableSize();
|
||||
}
|
||||
|
||||
void ConstArrayResolver::RemoveArraysFill()
|
||||
{
|
||||
for (const auto &it : const_arrays_fill_) {
|
||||
for (const auto &store_inst : it.second) {
|
||||
store_inst->GetBasicBlock()->RemoveInst(store_inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstArrayResolver::InsertLoadConstArrayInsts()
|
||||
{
|
||||
for (const auto &[id, start_inst] : const_arrays_init_) {
|
||||
auto method = GetGraph()->GetMethod();
|
||||
compiler::LoadConstArrayInst *new_inst = GetGraph()->CreateInstLoadConstArray(REFERENCE, start_inst->GetPc());
|
||||
new_inst->SetTypeId(id);
|
||||
new_inst->SetMethod(method);
|
||||
|
||||
start_inst->ReplaceUsers(new_inst);
|
||||
start_inst->RemoveInputs();
|
||||
|
||||
compiler::SaveStateInst *save_state = GetGraph()->CreateInstSaveState();
|
||||
save_state->SetPc(start_inst->GetPc());
|
||||
save_state->SetMethod(method);
|
||||
save_state->ReserveInputs(0);
|
||||
|
||||
new_inst->SetInput(0, save_state);
|
||||
start_inst->InsertBefore(save_state);
|
||||
start_inst->GetBasicBlock()->ReplaceInst(start_inst, new_inst);
|
||||
}
|
||||
}
|
||||
|
||||
static bool FillPrimitiveLiteral(pandasm::LiteralArray::Literal *literal, panda_file::Type::TypeId type,
|
||||
compiler::ConstantInst *value_inst)
|
||||
{
|
||||
auto tag = pandasm::LiteralArray::GetArrayTagFromComponentType(type);
|
||||
literal->tag_ = tag;
|
||||
switch (tag) {
|
||||
case panda_file::LiteralTag::ARRAY_U1:
|
||||
literal->value_ = static_cast<bool>(value_inst->GetInt32Value());
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_U8:
|
||||
case panda_file::LiteralTag::ARRAY_I8:
|
||||
literal->value_ = static_cast<uint8_t>(value_inst->GetInt32Value());
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_U16:
|
||||
case panda_file::LiteralTag::ARRAY_I16:
|
||||
literal->value_ = static_cast<uint16_t>(value_inst->GetInt32Value());
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_U32:
|
||||
case panda_file::LiteralTag::ARRAY_I32:
|
||||
literal->value_ = value_inst->GetInt32Value();
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_U64:
|
||||
case panda_file::LiteralTag::ARRAY_I64:
|
||||
literal->value_ = value_inst->GetInt64Value();
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_F32:
|
||||
literal->value_ = value_inst->GetFloatValue();
|
||||
return true;
|
||||
case panda_file::LiteralTag::ARRAY_F64:
|
||||
literal->value_ = value_inst->GetDoubleValue();
|
||||
return true;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConstArrayResolver::FillLiteral(compiler::StoreInst *store_array_inst, pandasm::LiteralArray::Literal *literal)
|
||||
{
|
||||
if (store_array_inst->GetInputsCount() > STOREARRAY_INPUTS_NUM) {
|
||||
return false;
|
||||
}
|
||||
auto raw_elem_inst = store_array_inst->GetStoredValue();
|
||||
auto new_array_inst = store_array_inst->GetArray();
|
||||
|
||||
auto array_type =
|
||||
pandasm::Type::FromName(ir_interface_->GetTypeIdByOffset(new_array_inst->CastToNewArray()->GetTypeId()));
|
||||
auto component_type = array_type.GetComponentType();
|
||||
auto component_type_name = array_type.GetComponentName();
|
||||
|
||||
if (pandasm::Type::IsPandaPrimitiveType(component_type_name)) {
|
||||
auto value_inst = GetConstantIfPossible(raw_elem_inst);
|
||||
|
||||
if (value_inst == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return FillPrimitiveLiteral(literal, component_type.GetId(), *value_inst);
|
||||
}
|
||||
|
||||
auto string_type =
|
||||
pandasm::Type::FromDescriptor(panda::panda_file::GetStringClassDescriptor(ir_interface_->GetSourceLang()));
|
||||
if ((raw_elem_inst->GetOpcode() == Opcode::LoadString) && (component_type_name == string_type.GetName())) {
|
||||
literal->tag_ = panda_file::LiteralTag::ARRAY_STRING;
|
||||
std::string string_value = ir_interface_->GetStringIdByOffset(raw_elem_inst->CastToLoadString()->GetTypeId());
|
||||
literal->value_ = string_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt
|
@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef PANDA_CONST_ARRAY_RESOLVER_H
|
||||
#define PANDA_CONST_ARRAY_RESOLVER_H
|
||||
|
||||
#include "assembler/assembly-function.h"
|
||||
#include "bytecodeopt_options.h"
|
||||
#include "ir_interface.h"
|
||||
#include "compiler/optimizer/ir/graph.h"
|
||||
#include "compiler/optimizer/pass.h"
|
||||
#include "compiler/optimizer/ir/inst.h"
|
||||
#include "compiler/optimizer/optimizations/const_folding.h"
|
||||
|
||||
namespace panda::bytecodeopt {
|
||||
using panda::compiler::Inst;
|
||||
using panda::compiler::Opcode;
|
||||
using namespace panda::compiler::DataType;
|
||||
class ConstArrayResolver : public compiler::Optimization {
|
||||
public:
|
||||
explicit ConstArrayResolver(compiler::Graph *graph, BytecodeOptIrInterface *iface)
|
||||
: compiler::Optimization(graph),
|
||||
ir_interface_(iface),
|
||||
const_arrays_init_(graph->GetLocalAllocator()->Adapter()),
|
||||
const_arrays_fill_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
~ConstArrayResolver() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "ConstArrayResolver";
|
||||
}
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsConstArrayResolver();
|
||||
}
|
||||
|
||||
private:
|
||||
bool FindConstantArrays();
|
||||
void RemoveArraysFill();
|
||||
void InsertLoadConstArrayInsts();
|
||||
std::optional<std::vector<pandasm::LiteralArray::Literal>> FillLiteralArray(Inst *inst, size_t size);
|
||||
bool FillLiteral(compiler::StoreInst *store_array_inst, pandasm::LiteralArray::Literal *literal);
|
||||
void AddIntroLiterals(pandasm::LiteralArray *lt_ar);
|
||||
bool IsMultidimensionalArray(compiler::NewArrayInst *inst);
|
||||
|
||||
private:
|
||||
BytecodeOptIrInterface *ir_interface_ {nullptr};
|
||||
ArenaMap<uint32_t, compiler::NewArrayInst *> const_arrays_init_; // const_arrays_[literalarray_id] = new_array_inst
|
||||
ArenaMap<Inst *, std::vector<Inst *>>
|
||||
const_arrays_fill_; // const_arrays_[new_array_inst] = {store_array_inst_1, ... , store_array_inst_n}
|
||||
};
|
||||
} // namespace panda::bytecodeopt
|
||||
|
||||
#endif // PANDA_CONST_ARRAY_RESOLVER_H
|
@ -19,24 +19,14 @@
|
||||
#include "assembler/extensions/extensions.h"
|
||||
#include "bytecode_instruction.h"
|
||||
#include "bytecodeopt_options.h"
|
||||
#include "bytecodeopt_peepholes.h"
|
||||
#include "canonicalization.h"
|
||||
#include "check_resolver.h"
|
||||
#include "codegen.h"
|
||||
#include "common.h"
|
||||
#include "const_array_resolver.h"
|
||||
#include "compiler/optimizer/ir/constants.h"
|
||||
#include "compiler/optimizer/ir_builder/ir_builder.h"
|
||||
#include "compiler/optimizer/ir_builder/pbc_iterator.h"
|
||||
#include "compiler/optimizer/optimizations/branch_elimination.h"
|
||||
#include "compiler/optimizer/optimizations/code_sink.h"
|
||||
#include "compiler/optimizer/optimizations/cse.h"
|
||||
#include "compiler/optimizer/optimizations/cleanup.h"
|
||||
#include "compiler/optimizer/optimizations/licm.h"
|
||||
#include "compiler/optimizer/optimizations/lowering.h"
|
||||
#include "compiler/optimizer/optimizations/lse.h"
|
||||
#include "compiler/optimizer/optimizations/move_constants.h"
|
||||
#include "compiler/optimizer/optimizations/peepholes.h"
|
||||
#include "compiler/optimizer/optimizations/regalloc/reg_alloc.h"
|
||||
#include "compiler/optimizer/optimizations/vn.h"
|
||||
#include "libpandabase/mem/arena_allocator.h"
|
||||
@ -58,22 +48,7 @@ template <typename T>
|
||||
constexpr void RunOpts(compiler::Graph *graph, [[maybe_unused]] BytecodeOptIrInterface *iface)
|
||||
{
|
||||
graph->RunPass<compiler::Cleanup>();
|
||||
#ifndef NDEBUG
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
|
||||
if constexpr (std::is_same_v<T, compiler::Lowering>) {
|
||||
graph->SetLowLevelInstructionsEnabled();
|
||||
}
|
||||
#endif // NDEBUG
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
if constexpr (std::is_same_v<T, compiler::Licm>) {
|
||||
graph->RunPass<compiler::Licm>(compiler::options.GetCompilerLicmHoistLimit());
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (std::is_same_v<T, ConstArrayResolver>) {
|
||||
graph->RunPass<ConstArrayResolver>(iface);
|
||||
// NOLINTNEXTLINE(readability-misleading-indentation)
|
||||
} else {
|
||||
graph->RunPass<T>();
|
||||
}
|
||||
graph->RunPass<T>();
|
||||
}
|
||||
|
||||
template <typename First, typename Second, typename... Rest>
|
||||
@ -86,31 +61,14 @@ constexpr void RunOpts(compiler::Graph *graph, BytecodeOptIrInterface *iface = n
|
||||
bool RunOptimizations(compiler::Graph *graph, BytecodeOptIrInterface *iface)
|
||||
{
|
||||
constexpr int OPT_LEVEL_0 = 0;
|
||||
constexpr int OPT_LEVEL_1 = 1;
|
||||
constexpr int OPT_LEVEL_2 = 2;
|
||||
|
||||
if (panda::bytecodeopt::options.GetOptLevel() == OPT_LEVEL_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
graph->RunPass<CheckResolver>();
|
||||
graph->RunPass<compiler::Cleanup>();
|
||||
// NB! Canonicalization and compiler::Lowering should be present in all levels
|
||||
// since without Lowering pass, RegEncoder will not work for Compare instructions,
|
||||
// and we will not be able to optimize functions where there is branching.
|
||||
// Lowering can't work without Canonicalization pass.
|
||||
if (graph->IsDynamicMethod()) {
|
||||
RunOpts<compiler::ValNum, compiler::Lowering, compiler::MoveConstants>(graph);
|
||||
} else if (panda::bytecodeopt::options.GetOptLevel() == OPT_LEVEL_1) {
|
||||
RunOpts<Canonicalization, compiler::Lowering>(graph);
|
||||
} else if (panda::bytecodeopt::options.GetOptLevel() == OPT_LEVEL_2) {
|
||||
// ConstArrayResolver Pass is disabled as it requires fixes for stability
|
||||
RunOpts<ConstArrayResolver, compiler::BranchElimination, compiler::ValNum, compiler::Cse, compiler::Peepholes,
|
||||
compiler::Licm, compiler::Lse, compiler::ValNum, compiler::Cse, Canonicalization, compiler::Lowering,
|
||||
compiler::MoveConstants, BytecodeOptPeepholes>(graph, iface);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
ASSERT(graph->IsDynamicMethod());
|
||||
RunOpts<compiler::ValNum, compiler::Lowering, compiler::MoveConstants>(graph);
|
||||
|
||||
// this pass should run just before register allocator
|
||||
graph->RunPass<compiler::Cleanup>();
|
||||
|
@ -348,7 +348,7 @@ void RegEncoder::InsertSpillsForDynInputsInst(compiler::Inst *inst)
|
||||
RegContentVec spill_vec(GetGraph()->GetLocalAllocator()->Adapter()); // spill_vec is used to handle callrange
|
||||
|
||||
auto nargs = inst->GetInputsCount() - (inst->RequireState() ? 1 : 0);
|
||||
auto start = GetStartInputIndex(inst);
|
||||
auto start = 0;
|
||||
bool range = IsIntrinsicRange(inst) || (nargs - start > MAX_NUM_NON_RANGE_ARGS && CanHoldRange(inst));
|
||||
|
||||
compiler::Register temp = range ? range_temps_start_ : 0;
|
||||
@ -387,33 +387,6 @@ void RegEncoder::InsertSpillsForDynInputsInst(compiler::Inst *inst)
|
||||
AddMoveBefore(inst, spill_vec);
|
||||
}
|
||||
|
||||
size_t RegEncoder::GetStartInputIndex(compiler::Inst *inst)
|
||||
{
|
||||
return inst->GetOpcode() == compiler::Opcode::InitObject ? 1 : 0; // exclude LoadAndInitClass
|
||||
}
|
||||
|
||||
static void AddMoveAfter(Inst *inst, compiler::Register src, RegContent dst)
|
||||
{
|
||||
auto sf_inst = inst->GetBasicBlock()->GetGraph()->CreateInstSpillFill();
|
||||
sf_inst->AddMove(src, dst.reg, dst.type);
|
||||
LOG(DEBUG, BYTECODE_OPTIMIZER) << "RegEncoder: Move v" << static_cast<int>(dst.reg) << " <- v"
|
||||
<< static_cast<int>(src) << " was added";
|
||||
inst->GetBasicBlock()->InsertAfter(sf_inst, inst);
|
||||
}
|
||||
|
||||
static bool isMoveAfter(const compiler::Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case compiler::Opcode::LoadObject:
|
||||
// Special case for LoadObject, because it can be register instruction.
|
||||
return inst->GetDstReg() != compiler::ACC_REG_ID;
|
||||
case compiler::Opcode::NewArray:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegEncoder::InsertSpillsForInst(compiler::Inst *inst)
|
||||
{
|
||||
ASSERT(state_ == RegEncoderState::INSERT_SPILLS);
|
||||
@ -427,10 +400,6 @@ void RegEncoder::InsertSpillsForInst(compiler::Inst *inst)
|
||||
|
||||
compiler::Register temp = 0;
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
// TODO(mbolshov): make a better solution to skip instructions, that are not relevant to bytecode_opt
|
||||
if (inst->GetInput(i).GetInst()->GetOpcode() == Opcode::LoadAndInitClass) {
|
||||
continue;
|
||||
}
|
||||
auto reg = inst->GetSrcReg(i);
|
||||
if (RegNeedsRenumbering(reg) && reg >= NUM_COMPACTLY_ENCODED_REGS) {
|
||||
auto res = spill_map.emplace(reg, RegContent(temp, GetRegType(inst->GetInputType(i))));
|
||||
@ -447,13 +416,6 @@ void RegEncoder::InsertSpillsForInst(compiler::Inst *inst)
|
||||
}
|
||||
|
||||
AddMoveBefore(inst, spill_map);
|
||||
if (isMoveAfter(inst)) {
|
||||
auto reg = inst->GetDstReg();
|
||||
if (RegNeedsRenumbering(reg) && reg >= NUM_COMPACTLY_ENCODED_REGS) {
|
||||
inst->SetDstReg(temp);
|
||||
AddMoveAfter(inst, temp, RegContent(reg, GetRegType(inst->GetType())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void IncTempsIfNeeded(const compiler::Register reg, compiler::Register &num_temps)
|
||||
@ -476,7 +438,7 @@ void RegEncoder::CalculateNumNeededTempsForInst(compiler::Inst *inst)
|
||||
ASSERT(inst->IsStaticCall() || inst->IsVirtualCall() || inst->IsInitObject() || inst->IsIntrinsic());
|
||||
|
||||
auto nargs = inst->GetInputsCount() - (inst->RequireState() ? 1 : 0);
|
||||
size_t start = inst->GetOpcode() == compiler::Opcode::InitObject ? 1 : 0;
|
||||
size_t start = 0;
|
||||
|
||||
if (nargs - start > MAX_NUM_NON_RANGE_ARGS) { // is call.range
|
||||
return;
|
||||
@ -493,16 +455,8 @@ void RegEncoder::CalculateNumNeededTempsForInst(compiler::Inst *inst)
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
// TODO(mbolshov): make a better solution to skip instructions, that are not relevant to bytecode_opt
|
||||
if (inst->GetInput(i).GetInst()->GetOpcode() == Opcode::LoadAndInitClass) {
|
||||
continue;
|
||||
}
|
||||
IncTempsIfNeeded(inst->GetSrcReg(i), num_temps);
|
||||
}
|
||||
|
||||
if (isMoveAfter(inst)) {
|
||||
IncTempsIfNeeded(inst->GetDstReg(), num_temps);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(num_temps <= MAX_NUM_INPUTS);
|
||||
@ -531,21 +485,6 @@ void RegEncoder::Check8Width([[maybe_unused]] compiler::Inst *inst)
|
||||
// TODO(aantipina): implement after it became possible to use register numbers more than 256 (#2697)
|
||||
}
|
||||
|
||||
void RegEncoder::VisitCallStatic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHelper(visitor, inst);
|
||||
}
|
||||
|
||||
void RegEncoder::VisitCallVirtual(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHelper(visitor, inst);
|
||||
}
|
||||
|
||||
void RegEncoder::VisitInitObject(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
CallHelper(visitor, inst);
|
||||
}
|
||||
|
||||
void RegEncoder::VisitIntrinsic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
auto re = static_cast<RegEncoder *>(visitor);
|
||||
@ -557,75 +496,5 @@ void RegEncoder::VisitIntrinsic(GraphVisitor *visitor, Inst *inst)
|
||||
re->Check8Width(inst->CastToIntrinsic());
|
||||
}
|
||||
|
||||
void RegEncoder::VisitLoadObject(GraphVisitor *v, Inst *inst_base)
|
||||
{
|
||||
bool is_acc_type = inst_base->GetDstReg() == compiler::ACC_REG_ID;
|
||||
|
||||
auto re = static_cast<RegEncoder *>(v);
|
||||
auto inst = inst_base->CastToLoadObject();
|
||||
switch (inst->GetType()) {
|
||||
case compiler::DataType::BOOL:
|
||||
case compiler::DataType::UINT8:
|
||||
case compiler::DataType::INT8:
|
||||
case compiler::DataType::UINT16:
|
||||
case compiler::DataType::INT16:
|
||||
case compiler::DataType::UINT32:
|
||||
case compiler::DataType::INT32:
|
||||
case compiler::DataType::INT64:
|
||||
case compiler::DataType::UINT64:
|
||||
case compiler::DataType::FLOAT32:
|
||||
case compiler::DataType::FLOAT64:
|
||||
case compiler::DataType::REFERENCE:
|
||||
if (is_acc_type) {
|
||||
re->Check8Width(inst);
|
||||
} else {
|
||||
re->Check4Width(inst);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR, BYTECODE_OPTIMIZER)
|
||||
<< "Wrong DataType for " << compiler::GetOpcodeString(inst->GetOpcode()) << " failed";
|
||||
re->success_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegEncoder::VisitStoreObject(GraphVisitor *v, Inst *inst_base)
|
||||
{
|
||||
bool is_acc_type = inst_base->GetSrcReg(1) == compiler::ACC_REG_ID;
|
||||
|
||||
auto re = static_cast<RegEncoder *>(v);
|
||||
auto inst = inst_base->CastToStoreObject();
|
||||
switch (inst->GetType()) {
|
||||
case compiler::DataType::BOOL:
|
||||
case compiler::DataType::UINT8:
|
||||
case compiler::DataType::INT8:
|
||||
case compiler::DataType::UINT16:
|
||||
case compiler::DataType::INT16:
|
||||
case compiler::DataType::UINT32:
|
||||
case compiler::DataType::INT32:
|
||||
case compiler::DataType::INT64:
|
||||
case compiler::DataType::UINT64:
|
||||
case compiler::DataType::FLOAT32:
|
||||
case compiler::DataType::FLOAT64:
|
||||
case compiler::DataType::REFERENCE:
|
||||
if (is_acc_type) {
|
||||
re->Check8Width(inst);
|
||||
} else {
|
||||
re->Check4Width(inst);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR, BYTECODE_OPTIMIZER)
|
||||
<< "Wrong DataType for " << compiler::GetOpcodeString(inst->GetOpcode()) << " failed";
|
||||
re->success_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegEncoder::VisitSpillFill([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
void RegEncoder::VisitConstant([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
void RegEncoder::VisitLoadString([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
void RegEncoder::VisitReturn([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
void RegEncoder::VisitCatchPhi([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
void RegEncoder::VisitCastValueToAnyType([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst) {}
|
||||
#include "generated/check_width.cpp"
|
||||
} // namespace panda::bytecodeopt
|
||||
|
@ -121,19 +121,7 @@ public:
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
static void VisitSpillFill([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
static void VisitConstant([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
static void VisitCatchPhi([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
static void VisitCallStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCallVirtual(GraphVisitor *v, Inst *inst);
|
||||
static void VisitInitObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitIntrinsic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadObject(GraphVisitor *v, Inst *inst_base);
|
||||
static void VisitStoreObject(GraphVisitor *v, Inst *inst_base);
|
||||
static void VisitLoadString([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
static void VisitReturn([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
|
||||
static void VisitCastValueToAnyType(GraphVisitor *v, Inst *inst);
|
||||
|
||||
#include "generated/reg_encoder_visitors.inc"
|
||||
|
||||
@ -156,7 +144,6 @@ private:
|
||||
void InsertSpills();
|
||||
void InsertSpillsForInst(compiler::Inst *inst);
|
||||
void InsertSpillsForDynInputsInst(compiler::Inst *inst);
|
||||
size_t GetStartInputIndex(compiler::Inst *inst);
|
||||
|
||||
compiler::Register GetNumArgsFromGraph() const
|
||||
{
|
||||
|
@ -1,371 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "check_resolver.h"
|
||||
#include "compiler/optimizer/optimizations/cleanup.h"
|
||||
#include "compiler/optimizer/optimizations/lowering.h"
|
||||
|
||||
namespace panda::bytecodeopt::test {
|
||||
|
||||
class LoweringTest : public CommonTest {
|
||||
};
|
||||
|
||||
// Checks the results of lowering pass if negative operands are used
|
||||
TEST_F(IrBuilderTest, Lowering)
|
||||
{
|
||||
// ISA opcodes with expected lowering IR opcodes
|
||||
std::map<std::string, compiler::Opcode> opcodes = {
|
||||
{"add", compiler::Opcode::SubI}, {"sub", compiler::Opcode::AddI}, {"mul", compiler::Opcode::MulI},
|
||||
{"and", compiler::Opcode::AndI}, {"xor", compiler::Opcode::XorI}, {"or", compiler::Opcode::OrI},
|
||||
{"div", compiler::Opcode::DivI}, {"mod", compiler::Opcode::ModI},
|
||||
};
|
||||
|
||||
const std::string template_source = R"(
|
||||
.function i32 main() {
|
||||
movi v0, 0x3
|
||||
movi v1, 0xffffffffffffffe2
|
||||
OPCODE v0, v1
|
||||
return
|
||||
}
|
||||
)";
|
||||
|
||||
for (auto const &opcode : opcodes) {
|
||||
// Specialize template source to the current opcode
|
||||
std::string source(template_source);
|
||||
size_t start_pos = source.find("OPCODE");
|
||||
source.replace(start_pos, 6 /* OPCODE */, opcode.first);
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
#ifndef NDEBUG
|
||||
GetGraph()->SetLowLevelInstructionsEnabled();
|
||||
#endif
|
||||
GetGraph()->RunPass<CheckResolver>();
|
||||
GetGraph()->RunPass<compiler::Lowering>();
|
||||
GetGraph()->RunPass<compiler::Cleanup>();
|
||||
|
||||
int32_t imm = -30;
|
||||
// Note: `AddI -30` is handled as `SubI 30`. `SubI -30` is handled as `AddI 30`.
|
||||
if (opcode.second == compiler::Opcode::AddI || opcode.second == compiler::Opcode::SubI) {
|
||||
imm = 30;
|
||||
}
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
CONSTANT(1, 3).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, opcode.second).s32().Inputs(1).Imm(imm);
|
||||
INST(3, Opcode::Return).s32().Inputs(2);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(GraphComparator().Compare(GetGraph(), expected));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LoweringTest, AddSub)
|
||||
{
|
||||
auto init = CreateEmptyGraph();
|
||||
GRAPH(init)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f32();
|
||||
CONSTANT(3, 12).s32();
|
||||
CONSTANT(4, 150).s32();
|
||||
CONSTANT(5, 0).s64();
|
||||
CONSTANT(6, 1.2f).f32();
|
||||
CONSTANT(7, -1).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(8, Opcode::Add).u32().Inputs(0, 3);
|
||||
INST(9, Opcode::Sub).u32().Inputs(0, 3);
|
||||
INST(10, Opcode::Add).u32().Inputs(0, 4);
|
||||
INST(11, Opcode::Sub).u32().Inputs(0, 4);
|
||||
INST(12, Opcode::Add).u64().Inputs(1, 5);
|
||||
INST(13, Opcode::Sub).f32().Inputs(2, 6);
|
||||
INST(14, Opcode::Sub).u32().Inputs(0, 7);
|
||||
INST(15, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(8, 9, 10, 11, 12, 13, 14, 15);
|
||||
INST(21, Opcode::Return).b().Inputs(20);
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
init->SetLowLevelInstructionsEnabled();
|
||||
#endif
|
||||
init->RunPass<compiler::Lowering>();
|
||||
init->RunPass<compiler::Cleanup>();
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f32();
|
||||
CONSTANT(4, 150).s32();
|
||||
CONSTANT(5, 0).s64();
|
||||
CONSTANT(6, 1.2f).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(22, Opcode::AddI).u32().Inputs(0).Imm(0xc);
|
||||
INST(23, Opcode::SubI).u32().Inputs(0).Imm(0xc);
|
||||
INST(10, Opcode::Add).u32().Inputs(0, 4);
|
||||
INST(11, Opcode::Sub).u32().Inputs(0, 4);
|
||||
INST(12, Opcode::Add).u64().Inputs(1, 5);
|
||||
INST(13, Opcode::Sub).f32().Inputs(2, 6);
|
||||
INST(24, Opcode::AddI).u32().Inputs(0).Imm(1);
|
||||
INST(19, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(22, 23, 10, 11, 12, 13, 24, 19);
|
||||
INST(21, Opcode::Return).b().Inputs(20);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(GraphComparator().Compare(init, expected));
|
||||
}
|
||||
|
||||
TEST_F(LoweringTest, MulDivMod)
|
||||
{
|
||||
auto init = CreateEmptyGraph();
|
||||
GRAPH(init)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f32();
|
||||
CONSTANT(3, 12).s32();
|
||||
CONSTANT(4, 150).s32();
|
||||
CONSTANT(5, 0).s64();
|
||||
CONSTANT(6, 1.2f).f32();
|
||||
CONSTANT(7, -1).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(8, Opcode::Div).s32().Inputs(0, 3);
|
||||
INST(9, Opcode::Div).u32().Inputs(0, 4);
|
||||
INST(10, Opcode::Div).u64().Inputs(1, 5);
|
||||
INST(11, Opcode::Div).f32().Inputs(2, 6);
|
||||
INST(12, Opcode::Div).s32().Inputs(0, 7);
|
||||
|
||||
INST(13, Opcode::Mod).s32().Inputs(0, 3);
|
||||
INST(14, Opcode::Mod).u32().Inputs(0, 4);
|
||||
INST(15, Opcode::Mod).u64().Inputs(1, 5);
|
||||
INST(16, Opcode::Mod).f32().Inputs(2, 6);
|
||||
INST(17, Opcode::Mod).s32().Inputs(0, 7);
|
||||
|
||||
INST(18, Opcode::Mul).s32().Inputs(0, 3);
|
||||
INST(19, Opcode::Mul).u32().Inputs(0, 4);
|
||||
INST(20, Opcode::Mul).u64().Inputs(1, 5);
|
||||
INST(21, Opcode::Mul).f32().Inputs(2, 6);
|
||||
INST(22, Opcode::Mul).s32().Inputs(0, 7);
|
||||
INST(31, Opcode::SaveState).NoVregs();
|
||||
INST(23, Opcode::CallStatic)
|
||||
.b()
|
||||
.InputsAutoType(8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 31);
|
||||
INST(24, Opcode::Return).b().Inputs(23);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
init->SetLowLevelInstructionsEnabled();
|
||||
#endif
|
||||
init->RunPass<compiler::Lowering>();
|
||||
init->RunPass<compiler::Cleanup>();
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f32();
|
||||
CONSTANT(4, 150).s32();
|
||||
CONSTANT(5, 0).s64();
|
||||
CONSTANT(6, 1.2f).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(25, Opcode::DivI).s32().Inputs(0).Imm(0xc);
|
||||
INST(9, Opcode::Div).u32().Inputs(0, 4);
|
||||
INST(10, Opcode::Div).u64().Inputs(1, 5);
|
||||
INST(11, Opcode::Div).f32().Inputs(2, 6);
|
||||
INST(26, Opcode::DivI).s32().Inputs(0).Imm(static_cast<uint64_t>(-1));
|
||||
INST(27, Opcode::ModI).s32().Inputs(0).Imm(0xc);
|
||||
INST(14, Opcode::Mod).u32().Inputs(0, 4);
|
||||
INST(15, Opcode::Mod).u64().Inputs(1, 5);
|
||||
INST(16, Opcode::Mod).f32().Inputs(2, 6);
|
||||
INST(28, Opcode::ModI).s32().Inputs(0).Imm(static_cast<uint64_t>(-1));
|
||||
INST(29, Opcode::MulI).s32().Inputs(0).Imm(0xc);
|
||||
INST(19, Opcode::Mul).u32().Inputs(0, 4);
|
||||
INST(20, Opcode::Mul).u64().Inputs(1, 5);
|
||||
INST(21, Opcode::Mul).f32().Inputs(2, 6);
|
||||
INST(30, Opcode::MulI).s32().Inputs(0).Imm(static_cast<uint64_t>(-1));
|
||||
INST(31, Opcode::SaveState).NoVregs();
|
||||
INST(23, Opcode::CallStatic)
|
||||
.b()
|
||||
.InputsAutoType(25, 9, 10, 11, 26, 27, 14, 15, 16, 28, 29, 19, 20, 21, 30, 31);
|
||||
INST(24, Opcode::Return).b().Inputs(23);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(GraphComparator().Compare(init, expected));
|
||||
}
|
||||
|
||||
TEST_F(LoweringTest, Logic)
|
||||
{
|
||||
auto init = CreateEmptyGraph();
|
||||
GRAPH(init)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(24, 1).s64();
|
||||
CONSTANT(1, 12).s32();
|
||||
CONSTANT(2, 50).s32();
|
||||
CONSTANT(25, 0).s64();
|
||||
CONSTANT(27, 300).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::Or).u32().Inputs(0, 1);
|
||||
INST(5, Opcode::Or).u32().Inputs(0, 2);
|
||||
INST(6, Opcode::And).u32().Inputs(0, 1);
|
||||
INST(8, Opcode::And).u32().Inputs(0, 2);
|
||||
INST(9, Opcode::Xor).u32().Inputs(0, 1);
|
||||
INST(11, Opcode::Xor).u32().Inputs(0, 2);
|
||||
INST(12, Opcode::Or).u8().Inputs(0, 1);
|
||||
INST(13, Opcode::And).u32().Inputs(0, 0);
|
||||
INST(26, Opcode::And).s64().Inputs(24, 25);
|
||||
INST(28, Opcode::Xor).u32().Inputs(0, 27);
|
||||
INST(29, Opcode::Or).s64().Inputs(24, 25);
|
||||
INST(30, Opcode::Xor).s64().Inputs(24, 25);
|
||||
INST(31, Opcode::And).u32().Inputs(0, 27);
|
||||
INST(32, Opcode::Or).u32().Inputs(0, 27);
|
||||
INST(15, Opcode::SaveState).NoVregs();
|
||||
INST(14, Opcode::CallStatic).b().InputsAutoType(3, 5, 6, 8, 9, 11, 12, 13, 26, 28, 29, 30, 31, 32, 15);
|
||||
INST(23, Opcode::Return).b().Inputs(14);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
init->SetLowLevelInstructionsEnabled();
|
||||
#endif
|
||||
init->RunPass<compiler::Lowering>();
|
||||
init->RunPass<compiler::Cleanup>();
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(24, 1).s64();
|
||||
CONSTANT(1, 12).s32();
|
||||
CONSTANT(25, 0).s64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(33, Opcode::OrI).u32().Inputs(0).Imm(0xc);
|
||||
INST(34, Opcode::OrI).u32().Inputs(0).Imm(0x32);
|
||||
INST(35, Opcode::AndI).u32().Inputs(0).Imm(0xc);
|
||||
INST(36, Opcode::AndI).u32().Inputs(0).Imm(0x32);
|
||||
INST(37, Opcode::XorI).u32().Inputs(0).Imm(0xc);
|
||||
INST(38, Opcode::XorI).u32().Inputs(0).Imm(0x32);
|
||||
INST(12, Opcode::Or).u8().Inputs(0, 1);
|
||||
INST(13, Opcode::And).u32().Inputs(0, 0);
|
||||
INST(26, Opcode::And).s64().Inputs(24, 25);
|
||||
INST(39, Opcode::XorI).u32().Inputs(0).Imm(0x12c);
|
||||
INST(29, Opcode::Or).s64().Inputs(24, 25);
|
||||
INST(30, Opcode::Xor).s64().Inputs(24, 25);
|
||||
INST(40, Opcode::AndI).u32().Inputs(0).Imm(0x12c);
|
||||
INST(41, Opcode::OrI).u32().Inputs(0).Imm(0x12c);
|
||||
INST(15, Opcode::SaveState).NoVregs();
|
||||
INST(14, Opcode::CallStatic).b().InputsAutoType(33, 34, 35, 36, 37, 38, 12, 13, 26, 39, 29, 30, 40, 41, 15);
|
||||
INST(23, Opcode::Return).b().Inputs(14);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(GraphComparator().Compare(init, expected));
|
||||
}
|
||||
|
||||
TEST_F(LoweringTest, Shift)
|
||||
{
|
||||
auto init = CreateEmptyGraph();
|
||||
GRAPH(init)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(24, 1).s64();
|
||||
CONSTANT(1, 12).s32();
|
||||
CONSTANT(2, 64).s32();
|
||||
CONSTANT(25, 0).s64();
|
||||
CONSTANT(27, 200).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::Shr).u32().Inputs(0, 1);
|
||||
INST(5, Opcode::Shr).u32().Inputs(0, 2);
|
||||
INST(6, Opcode::AShr).u32().Inputs(0, 1);
|
||||
INST(8, Opcode::AShr).u32().Inputs(0, 2);
|
||||
INST(9, Opcode::Shl).u32().Inputs(0, 1);
|
||||
INST(11, Opcode::Shl).u32().Inputs(0, 2);
|
||||
INST(12, Opcode::Shl).u8().Inputs(0, 1);
|
||||
INST(13, Opcode::Shr).u32().Inputs(0, 0);
|
||||
INST(26, Opcode::Shr).s64().Inputs(24, 25);
|
||||
INST(28, Opcode::AShr).s32().Inputs(0, 27);
|
||||
INST(29, Opcode::AShr).s64().Inputs(24, 25);
|
||||
INST(30, Opcode::Shl).s64().Inputs(24, 25);
|
||||
INST(31, Opcode::Shr).s32().Inputs(0, 27);
|
||||
INST(32, Opcode::Shl).s32().Inputs(0, 27);
|
||||
INST(15, Opcode::SaveState).NoVregs();
|
||||
INST(14, Opcode::CallStatic).b().InputsAutoType(3, 5, 6, 8, 9, 11, 12, 13, 26, 28, 29, 30, 31, 32, 15);
|
||||
INST(23, Opcode::Return).b().Inputs(14);
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
init->SetLowLevelInstructionsEnabled();
|
||||
#endif
|
||||
init->RunPass<compiler::Lowering>();
|
||||
init->RunPass<compiler::Cleanup>();
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(24, 1).s64();
|
||||
CONSTANT(1, 12).s32();
|
||||
CONSTANT(2, 64).s32();
|
||||
CONSTANT(25, 0).s64();
|
||||
CONSTANT(27, 200).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(33, Opcode::ShrI).u32().Inputs(0).Imm(0xc);
|
||||
INST(5, Opcode::Shr).u32().Inputs(0, 2);
|
||||
INST(34, Opcode::AShrI).u32().Inputs(0).Imm(0xc);
|
||||
INST(8, Opcode::AShr).u32().Inputs(0, 2);
|
||||
INST(35, Opcode::ShlI).u32().Inputs(0).Imm(0xc);
|
||||
INST(11, Opcode::Shl).u32().Inputs(0, 2);
|
||||
INST(12, Opcode::Shl).u8().Inputs(0, 1);
|
||||
INST(13, Opcode::Shr).u32().Inputs(0, 0);
|
||||
INST(26, Opcode::Shr).s64().Inputs(24, 25);
|
||||
INST(28, Opcode::AShr).s32().Inputs(0, 27);
|
||||
INST(29, Opcode::AShr).s64().Inputs(24, 25);
|
||||
INST(30, Opcode::Shl).s64().Inputs(24, 25);
|
||||
INST(31, Opcode::Shr).s32().Inputs(0, 27);
|
||||
INST(32, Opcode::Shl).s32().Inputs(0, 27);
|
||||
INST(15, Opcode::SaveState).NoVregs();
|
||||
INST(14, Opcode::CallStatic).b().InputsAutoType(33, 5, 34, 8, 35, 11, 12, 13, 26, 28, 29, 30, 31, 32, 15);
|
||||
INST(23, Opcode::Return).b().Inputs(14);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(GraphComparator().Compare(init, expected));
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt::test
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "check_resolver.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace panda::bytecodeopt::test {
|
||||
|
||||
TEST_F(CommonTest, CheckResolverLenArray)
|
||||
{
|
||||
auto graph = CreateEmptyGraph();
|
||||
GRAPH(graph)
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).ref();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::SaveState).NoVregs();
|
||||
INST(3, Opcode::NullCheck).ref().Inputs(1, 2);
|
||||
INST(4, Opcode::LenArray).s32().Inputs(3);
|
||||
INST(5, Opcode::BoundsCheck).s32().Inputs(4, 0, 2);
|
||||
INST(6, Opcode::LoadArray).s32().Inputs(3, 5);
|
||||
INST(7, Opcode::Return).s32().Inputs(6);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(graph->RunPass<CheckResolver>());
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt::test
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,380 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "assembler/assembly-emitter.h"
|
||||
#include "canonicalization.h"
|
||||
#include "codegen.h"
|
||||
#include "common.h"
|
||||
#include "compiler/optimizer/optimizations/lowering.h"
|
||||
#include "compiler/optimizer/optimizations/regalloc/reg_alloc_linear_scan.h"
|
||||
#include "reg_encoder.h"
|
||||
|
||||
namespace panda::bytecodeopt::test {
|
||||
|
||||
TEST_F(CommonTest, RegEncoderF32)
|
||||
{
|
||||
auto graph = CreateEmptyGraph();
|
||||
GRAPH(graph)
|
||||
{
|
||||
CONSTANT(0, 0.0).f32();
|
||||
CONSTANT(1, 0.0).f32();
|
||||
CONSTANT(2, 0.0).f32();
|
||||
CONSTANT(3, 0.0).f32();
|
||||
CONSTANT(4, 0.0).f32();
|
||||
CONSTANT(5, 0.0).f32();
|
||||
CONSTANT(6, 0.0).f32();
|
||||
CONSTANT(7, 0.0).f32();
|
||||
CONSTANT(8, 0.0).f32();
|
||||
CONSTANT(9, 0.0).f32();
|
||||
CONSTANT(10, 0.0).f32();
|
||||
CONSTANT(11, 0.0).f32();
|
||||
CONSTANT(12, 0.0).f32();
|
||||
CONSTANT(13, 0.0).f32();
|
||||
CONSTANT(14, 0.0).f32();
|
||||
CONSTANT(15, 0.0).f32();
|
||||
CONSTANT(16, 0.0).f32();
|
||||
CONSTANT(17, 0.0).f32();
|
||||
CONSTANT(18, 0.0).f32();
|
||||
CONSTANT(19, 0.0).f32();
|
||||
CONSTANT(20, 0.0).f32();
|
||||
CONSTANT(21, 0.0).f32();
|
||||
CONSTANT(22, 0.0).f32();
|
||||
CONSTANT(23, 0.0).f32();
|
||||
CONSTANT(24, 0.0).f32();
|
||||
CONSTANT(25, 0.0).f32();
|
||||
CONSTANT(26, 0.0).f32();
|
||||
CONSTANT(27, 0.0).f32();
|
||||
CONSTANT(28, 0.0).f32();
|
||||
CONSTANT(29, 0.0).f32();
|
||||
CONSTANT(30, 0.0).f32();
|
||||
CONSTANT(31, 0.0).f32();
|
||||
|
||||
CONSTANT(32, 1.0).f64();
|
||||
CONSTANT(33, 2.0).f64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
INST(40, Opcode::Sub).f64().Inputs(32, 33);
|
||||
INST(41, Opcode::Return).f64().Inputs(40);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(graph->RunPass<compiler::RegAllocLinearScan>(compiler::EmptyRegMask()));
|
||||
EXPECT_TRUE(graph->RunPass<RegEncoder>());
|
||||
EXPECT_TRUE(graph->RunPass<compiler::Cleanup>());
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
CONSTANT(32, 1.0).f64();
|
||||
CONSTANT(33, 2.0).f64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
INST(40, Opcode::Sub).f64().Inputs(32, 33);
|
||||
INST(41, Opcode::Return).f64().Inputs(40);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(GraphComparator().Compare(graph, expected));
|
||||
}
|
||||
|
||||
TEST_F(CommonTest, RegEncoderHoldingSpillFillInst)
|
||||
{
|
||||
RuntimeInterfaceMock interface(2);
|
||||
auto graph = CreateEmptyGraph();
|
||||
graph->SetRuntime(&interface);
|
||||
GRAPH(graph)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).b();
|
||||
CONSTANT(26, 0xfffffffffffffffa).s64();
|
||||
CONSTANT(27, 0x6).s64();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(4, Opcode::LoadObject).s64().Inputs(0);
|
||||
INST(10, Opcode::LoadObject).s64().Inputs(0);
|
||||
INST(11, Opcode::Add).s64().Inputs(10, 4);
|
||||
CONSTANT(52, 0x5265c00).s64();
|
||||
INST(15, Opcode::Div).s64().Inputs(11, 52);
|
||||
INST(16, Opcode::Mul).s64().Inputs(15, 52);
|
||||
INST(20, Opcode::Sub).s64().Inputs(16, 10);
|
||||
CONSTANT(53, 0x2932e00).s64();
|
||||
INST(22, Opcode::Add).s64().Inputs(20, 53);
|
||||
INST(25, Opcode::IfImm).SrcType(BOOL).CC(compiler::CC_EQ).Imm(0).Inputs(1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 4) {}
|
||||
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(28, Opcode::Phi).s64().Inputs(26, 27);
|
||||
CONSTANT(54, 0x36ee80).s64();
|
||||
INST(31, Opcode::Mul).s64().Inputs(28, 54);
|
||||
INST(32, Opcode::Add).s64().Inputs(31, 22);
|
||||
INST(33, Opcode::SaveState).NoVregs();
|
||||
INST(35, Opcode::CallVirtual).v0id().Inputs({{REFERENCE, 0}, {INT64, 32}, {NO_TYPE, 33}});
|
||||
INST(36, Opcode::SaveState).NoVregs();
|
||||
INST(37, Opcode::LoadAndInitClass).ref().Inputs(36);
|
||||
INST(39, Opcode::SaveState).NoVregs();
|
||||
INST(58, Opcode::InitObject).ref().Inputs({{REFERENCE, 37}, {REFERENCE, 0}, {NO_TYPE, 39}});
|
||||
CONSTANT(55, 0.0093026).f64();
|
||||
CONSTANT(56, 0.0098902).f64();
|
||||
CONSTANT(57, 0x1388).s64();
|
||||
INST(45, Opcode::SaveState).NoVregs();
|
||||
INST(47, Opcode::CallStatic)
|
||||
.s64()
|
||||
.Inputs({{REFERENCE, 0},
|
||||
{REFERENCE, 58},
|
||||
{BOOL, 1},
|
||||
{FLOAT64, 55},
|
||||
{FLOAT64, 56},
|
||||
{INT64, 57},
|
||||
{NO_TYPE, 45}});
|
||||
INST(48, Opcode::SaveState).NoVregs();
|
||||
INST(50, Opcode::CallVirtual).v0id().Inputs({{REFERENCE, 0}, {INT64, 4}, {NO_TYPE, 48}});
|
||||
INST(51, Opcode::Return).s64().Inputs(47);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(graph->RunPass<compiler::RegAllocLinearScan>(compiler::EmptyRegMask()));
|
||||
EXPECT_TRUE(graph->RunPass<RegEncoder>());
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).b();
|
||||
CONSTANT(26, 0xfffffffffffffffa).s64();
|
||||
CONSTANT(27, 0x6).s64();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(4, Opcode::LoadObject).s64().Inputs(0);
|
||||
INST(10, Opcode::LoadObject).s64().Inputs(0);
|
||||
INST(11, Opcode::Add).s64().Inputs(10, 4);
|
||||
CONSTANT(52, 0x5265c00).s64();
|
||||
INST(15, Opcode::Div).s64().Inputs(11, 52);
|
||||
INST(16, Opcode::Mul).s64().Inputs(15, 52);
|
||||
INST(20, Opcode::Sub).s64().Inputs(16, 10);
|
||||
CONSTANT(53, 0x2932e00).s64();
|
||||
INST(22, Opcode::Add).s64().Inputs(20, 53);
|
||||
INST(25, Opcode::IfImm).SrcType(BOOL).CC(compiler::CC_EQ).Imm(0).Inputs(1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
// SpillFill added
|
||||
INST(60, Opcode::SpillFill);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(28, Opcode::Phi).s64().Inputs(26, 27);
|
||||
CONSTANT(54, 0x36ee80).s64();
|
||||
INST(31, Opcode::Mul).s64().Inputs(28, 54);
|
||||
INST(32, Opcode::Add).s64().Inputs(31, 22);
|
||||
INST(33, Opcode::SaveState).NoVregs();
|
||||
INST(35, Opcode::CallVirtual).v0id().Inputs({{REFERENCE, 0}, {INT64, 32}, {NO_TYPE, 33}});
|
||||
INST(36, Opcode::SaveState).NoVregs();
|
||||
INST(37, Opcode::LoadAndInitClass).ref().Inputs(36);
|
||||
INST(39, Opcode::SaveState).NoVregs();
|
||||
INST(58, Opcode::InitObject).ref().Inputs({{REFERENCE, 37}, {REFERENCE, 0}, {NO_TYPE, 39}});
|
||||
CONSTANT(55, 0.0093026).f64();
|
||||
CONSTANT(56, 0.0098902).f64();
|
||||
CONSTANT(57, 0x1388).s64();
|
||||
INST(45, Opcode::SaveState).NoVregs();
|
||||
|
||||
// SpillFill added
|
||||
INST(70, Opcode::SpillFill);
|
||||
INST(47, Opcode::CallStatic)
|
||||
.s64()
|
||||
.Inputs({{REFERENCE, 0},
|
||||
{REFERENCE, 58},
|
||||
{BOOL, 1},
|
||||
{FLOAT64, 55},
|
||||
{FLOAT64, 56},
|
||||
{INT64, 57},
|
||||
{NO_TYPE, 45}});
|
||||
INST(48, Opcode::SaveState).NoVregs();
|
||||
INST(50, Opcode::CallVirtual).v0id().Inputs({{REFERENCE, 0}, {INT64, 4}, {NO_TYPE, 48}});
|
||||
INST(51, Opcode::Return).s64().Inputs(47);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(GraphComparator().Compare(graph, expected));
|
||||
}
|
||||
|
||||
TEST_F(CommonTest, RegEncoderStoreObject)
|
||||
{
|
||||
// This test covers function CheckWidthVisitor::VisitStoreObject
|
||||
RuntimeInterfaceMock interface(4);
|
||||
auto graph = CreateEmptyGraph();
|
||||
graph->SetRuntime(&interface);
|
||||
GRAPH(graph)
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).ref();
|
||||
PARAMETER(2, 2).ref();
|
||||
PARAMETER(3, 3).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
|
||||
INST(4, Opcode::SaveState).NoVregs();
|
||||
INST(6, Opcode::CallStatic).v0id().Inputs({{REFERENCE, 0}, {NO_TYPE, 4}});
|
||||
INST(9, Opcode::StoreObject).ref().Inputs(0, 2);
|
||||
INST(12, Opcode::StoreObject).s32().Inputs(0, 3);
|
||||
INST(15, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(16, Opcode::SaveState).NoVregs();
|
||||
INST(18, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 15}, {NO_TYPE, 16}});
|
||||
INST(19, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::LoadClass).ref().Inputs(19);
|
||||
INST(20, Opcode::CheckCast).Inputs(18, 21, 19);
|
||||
INST(23, Opcode::StoreObject).ref().Inputs(0, 18);
|
||||
INST(26, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(27, Opcode::SaveState).NoVregs();
|
||||
INST(29, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 26}, {NO_TYPE, 27}});
|
||||
INST(30, Opcode::SaveState).NoVregs();
|
||||
INST(32, Opcode::LoadClass).ref().Inputs(30);
|
||||
INST(31, Opcode::CheckCast).Inputs(29, 32, 30);
|
||||
INST(34, Opcode::StoreObject).ref().Inputs(0, 29);
|
||||
INST(37, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(38, Opcode::SaveState).NoVregs();
|
||||
INST(40, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 37}, {NO_TYPE, 38}});
|
||||
INST(41, Opcode::SaveState).NoVregs();
|
||||
INST(43, Opcode::LoadClass).ref().Inputs(41);
|
||||
INST(42, Opcode::CheckCast).Inputs(40, 43, 41);
|
||||
INST(45, Opcode::StoreObject).ref().Inputs(0, 40);
|
||||
INST(48, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(49, Opcode::SaveState).NoVregs();
|
||||
INST(51, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 48}, {NO_TYPE, 49}});
|
||||
INST(52, Opcode::SaveState).NoVregs();
|
||||
INST(54, Opcode::LoadClass).ref().Inputs(52);
|
||||
INST(53, Opcode::CheckCast).Inputs(51, 54, 52);
|
||||
INST(56, Opcode::StoreObject).ref().Inputs(0, 51);
|
||||
INST(57, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(graph->RunPass<compiler::RegAllocLinearScan>(compiler::EmptyRegMask()));
|
||||
EXPECT_TRUE(graph->RunPass<RegEncoder>());
|
||||
EXPECT_FALSE(graph->RunPass<compiler::Cleanup>());
|
||||
|
||||
auto expected = CreateEmptyGraph();
|
||||
GRAPH(expected)
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).ref();
|
||||
PARAMETER(2, 2).ref();
|
||||
PARAMETER(3, 3).s32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
using namespace compiler::DataType;
|
||||
|
||||
INST(4, Opcode::SaveState).NoVregs();
|
||||
INST(6, Opcode::CallStatic).v0id().Inputs({{REFERENCE, 0}, {NO_TYPE, 4}});
|
||||
INST(9, Opcode::StoreObject).ref().Inputs(0, 2);
|
||||
INST(12, Opcode::StoreObject).s32().Inputs(0, 3);
|
||||
INST(15, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(16, Opcode::SaveState).NoVregs();
|
||||
INST(18, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 15}, {NO_TYPE, 16}});
|
||||
INST(60, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::LoadClass).ref().Inputs(60);
|
||||
INST(20, Opcode::CheckCast).Inputs(18, 21, 60);
|
||||
INST(23, Opcode::StoreObject).ref().Inputs(0, 18);
|
||||
INST(26, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(27, Opcode::SaveState).NoVregs();
|
||||
INST(29, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 26}, {NO_TYPE, 27}});
|
||||
INST(63, Opcode::SaveState).NoVregs();
|
||||
INST(32, Opcode::LoadClass).ref().Inputs(63);
|
||||
INST(31, Opcode::CheckCast).Inputs(29, 32, 63);
|
||||
INST(34, Opcode::StoreObject).ref().Inputs(0, 29);
|
||||
INST(37, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(38, Opcode::SaveState).NoVregs();
|
||||
INST(40, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 37}, {NO_TYPE, 38}});
|
||||
INST(41, Opcode::SaveState).NoVregs();
|
||||
INST(43, Opcode::LoadClass).ref().Inputs(41);
|
||||
INST(42, Opcode::CheckCast).Inputs(40, 43, 41);
|
||||
INST(45, Opcode::StoreObject).ref().Inputs(0, 40);
|
||||
INST(48, Opcode::LoadObject).ref().Inputs(1);
|
||||
INST(49, Opcode::SaveState).NoVregs();
|
||||
INST(51, Opcode::CallVirtual).ref().Inputs({{REFERENCE, 48}, {NO_TYPE, 49}});
|
||||
INST(52, Opcode::SaveState).NoVregs();
|
||||
INST(54, Opcode::LoadClass).ref().Inputs(52);
|
||||
INST(53, Opcode::CheckCast).Inputs(51, 54, 52);
|
||||
INST(56, Opcode::StoreObject).ref().Inputs(0, 51);
|
||||
INST(57, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(GraphComparator().Compare(graph, expected));
|
||||
}
|
||||
|
||||
// Check processing instructions with the same args by RegEncoder.
|
||||
TEST_F(CommonTest, RegEncoderSameArgsInst)
|
||||
{
|
||||
auto src_graph = CreateEmptyGraph();
|
||||
ArenaVector<bool> reg_mask(254, false, src_graph->GetLocalAllocator()->Adapter());
|
||||
src_graph->InitUsedRegs<compiler::DataType::INT64>(®_mask);
|
||||
GRAPH(src_graph)
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).s32();
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::StoreArray).s32().Inputs(0, 1, 2).SrcReg(0, 17).SrcReg(1, 17).SrcReg(2, 5);
|
||||
INST(4, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
src_graph->RunPass<RegEncoder>();
|
||||
|
||||
auto opt_graph = CreateEmptyGraph();
|
||||
opt_graph->InitUsedRegs<compiler::DataType::INT64>(®_mask);
|
||||
GRAPH(opt_graph)
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).s32();
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::SpillFill);
|
||||
INST(4, Opcode::StoreArray).s32().Inputs(0, 1, 2);
|
||||
INST(5, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(GraphComparator().Compare(src_graph, opt_graph));
|
||||
|
||||
for (auto bb : src_graph->GetBlocksRPO()) {
|
||||
for (auto inst : bb->AllInstsSafe()) {
|
||||
if (inst->GetOpcode() == Opcode::StoreArray) {
|
||||
ASSERT_TRUE(inst->GetSrcReg(0) == inst->GetSrcReg(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace panda::bytecodeopt::test
|
@ -59,9 +59,7 @@ libarkcompiler_sources = [
|
||||
"optimizer/analysis/liveness_analyzer.cpp",
|
||||
"optimizer/analysis/liveness_use_table.cpp",
|
||||
"optimizer/analysis/loop_analyzer.cpp",
|
||||
"optimizer/analysis/monitor_analysis.cpp",
|
||||
"optimizer/analysis/object_type_propagation.cpp",
|
||||
"optimizer/analysis/reg_alloc_verifier.cpp",
|
||||
"optimizer/analysis/rpo.cpp",
|
||||
"optimizer/analysis/types_analysis.cpp",
|
||||
"optimizer/ir/analysis.cpp",
|
||||
@ -73,37 +71,26 @@ libarkcompiler_sources = [
|
||||
"optimizer/ir/graph_cloner.cpp",
|
||||
"optimizer/ir/inst.cpp",
|
||||
"optimizer/ir/locations.cpp",
|
||||
"optimizer/ir/visualizer_printer.cpp",
|
||||
"optimizer/ir_builder/inst_builder.cpp",
|
||||
"optimizer/ir_builder/ir_builder.cpp",
|
||||
"optimizer/optimizations/adjust_arefs.cpp",
|
||||
"optimizer/optimizations/balance_expressions.cpp",
|
||||
"optimizer/optimizations/branch_elimination.cpp",
|
||||
"optimizer/optimizations/checks_elimination.cpp",
|
||||
"optimizer/optimizations/cleanup.cpp",
|
||||
"optimizer/optimizations/code_sink.cpp",
|
||||
"optimizer/optimizations/const_folding.cpp",
|
||||
"optimizer/optimizations/cse.cpp",
|
||||
"optimizer/optimizations/deoptimize_elimination.cpp",
|
||||
"optimizer/optimizations/if_conversion.cpp",
|
||||
"optimizer/optimizations/inlining.cpp",
|
||||
"optimizer/optimizations/licm.cpp",
|
||||
"optimizer/optimizations/locations_builder.cpp",
|
||||
"optimizer/optimizations/loop_peeling.cpp",
|
||||
"optimizer/optimizations/loop_unroll.cpp",
|
||||
"optimizer/optimizations/lowering.cpp",
|
||||
"optimizer/optimizations/lse.cpp",
|
||||
"optimizer/optimizations/memory_barriers.cpp",
|
||||
"optimizer/optimizations/memory_coalescing.cpp",
|
||||
"optimizer/optimizations/move_constants.cpp",
|
||||
"optimizer/optimizations/object_type_check_elimination.cpp",
|
||||
"optimizer/optimizations/peepholes.cpp",
|
||||
"optimizer/optimizations/redundant_loop_elimination.cpp",
|
||||
"optimizer/optimizations/regalloc/interference_graph.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc_base.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc_graph_coloring.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc_linear_scan.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc_resolver.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_alloc_stat.cpp",
|
||||
"optimizer/optimizations/regalloc/reg_map.cpp",
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "code_info/code_info.h"
|
||||
#include "events/events.h"
|
||||
#include "trace/trace.h"
|
||||
#include "optimizer/optimizations/regalloc/reg_alloc_linear_scan.h"
|
||||
#include "optimizer/code_generator/codegen.h"
|
||||
#include "compile_method.h"
|
||||
|
||||
|
@ -1,143 +0,0 @@
|
||||
# Branch Elimination
|
||||
## Overview
|
||||
|
||||
`Branch Elimination` searches condition statements which result is known at compile-time and removes not reachable branches.
|
||||
|
||||
## Rationality
|
||||
|
||||
Reduce number of instructions and simplify control-flow.
|
||||
|
||||
## Dependence
|
||||
|
||||
* DominatorsTree
|
||||
* LoopAnalysis
|
||||
* Reverse Post Order (RPO)
|
||||
|
||||
## Algorithm
|
||||
|
||||
`Branch Elimination` optimization searches `if-true` blocks with resolvable conditional instruction.
|
||||
Condition can be resolved in the following ways:
|
||||
- Condition is constant:
|
||||
```
|
||||
T F
|
||||
/---[ 1 > 0 ]---\
|
||||
| |
|
||||
| |
|
||||
v v
|
||||
can be eliminated
|
||||
```
|
||||
- Condition is dominated by the equal one condition with the same inputs and the only one successor of the dominant reaches dominated condition
|
||||
|
||||
```
|
||||
T F
|
||||
/---[ a > b ]---\
|
||||
| |
|
||||
| T v F
|
||||
... /---[ a > b ]---\
|
||||
| | |
|
||||
v v v
|
||||
can be eliminated
|
||||
|
||||
T F
|
||||
/---[ a > b ]---\
|
||||
| |
|
||||
... |
|
||||
| |
|
||||
\-------------->|
|
||||
T v F
|
||||
/---[ a > b ]---\
|
||||
| |
|
||||
v v
|
||||
can't be eliminated - reachable from both successors
|
||||
```
|
||||
To resolve the condition result by the dominant one we use table, keeps the result of the condition in the row if the condition in the column is true:
|
||||
```
|
||||
/* CC_EQ CC_NE CC_LT CC_LE CC_GT CC_GE CC_B CC_BE CC_A CC_AE */
|
||||
/* CC_EQ */ {true, false, false, nullopt, false, nullopt, false, nullopt, false, nullopt},
|
||||
/* CC_NE */ {false, true, true, nullopt, true, nullopt, true, nullopt, true, nullopt},
|
||||
/* CC_LT */ {false, nullopt, true, nullopt, false, false, nullopt, nullopt, nullopt, nullopt},
|
||||
/* CC_LE */ {true, nullopt, true, true, false, nullopt, nullopt, nullopt, nullopt, nullopt},
|
||||
/* CC_GT */ {false, nullopt, false, false, true, nullopt, nullopt, nullopt, nullopt, nullopt},
|
||||
/* CC_GE */ {true, nullopt, false, nullopt, true, true, nullopt, nullopt, nullopt, nullopt},
|
||||
/* CC_B */ {false, nullopt, nullopt, nullopt, nullopt, nullopt, true, nullopt, false, false},
|
||||
/* CC_BE */ {true, nullopt, nullopt, nullopt, nullopt, nullopt, true, true, false, nullopt},
|
||||
/* CC_A */ {false, nullopt, nullopt, nullopt, nullopt, nullopt, false, false, true, nullopt},
|
||||
/* CC_AE */ {true, nullopt, nullopt, nullopt, nullopt, nullopt, false, nullopt, true, true},
|
||||
```
|
||||
## Pseudocode
|
||||
|
||||
TBD
|
||||
|
||||
## Examples
|
||||
|
||||
```cpp
|
||||
[0]
|
||||
T | F
|
||||
/----[2]----\
|
||||
| |
|
||||
v T v F
|
||||
[3] /----[4]----\
|
||||
| | |
|
||||
| [5] [6]
|
||||
| | |
|
||||
v v |
|
||||
[exit]<-------------/
|
||||
|
||||
|
||||
GRAPH(graph) {
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).u64();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4) {
|
||||
INST(19, Opcode::Compare).b().CC(CC_EQ).Inputs(0, 1);
|
||||
INST(4, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(19);
|
||||
}
|
||||
BASIC_BLOCK(3, 7) {
|
||||
INST(5, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(6, Opcode::Add).u64().Inputs(5, 2);
|
||||
}
|
||||
BASIC_BLOCK(4, 5, 6) {
|
||||
INST(8, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(9, Opcode::Compare).b().CC(CC_NE).Inputs(0, 1);
|
||||
INST(10, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(9);
|
||||
}
|
||||
BASIC_BLOCK(5, 7) {
|
||||
INST(11, Opcode::Sub).u64().Inputs(0, 1);
|
||||
INST(12, Opcode::Sub).u64().Inputs(11, 2);
|
||||
}
|
||||
BASIC_BLOCK(6, 7) {
|
||||
INST(14, Opcode::Mul).u64().Inputs(0, 1);
|
||||
INST(15, Opcode::Mul).u64().Inputs(14, 2);
|
||||
}
|
||||
BASIC_BLOCK(7, -1) {
|
||||
INST(17, Opcode::Phi).u64().Inputs(6, 12, 15);
|
||||
INST(18, Opcode::Return).u64().Inputs(17);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
We reaches basic block `4` when the condition `PARAMETER(0) == PARAMETER(1)` is false, so that the result of instruction `INST(9)` always true.
|
||||
No we can eliminate basic block `6` and conditional statement form basic block `4`:
|
||||
|
||||
```
|
||||
[0]
|
||||
T | F
|
||||
/----[2]----\
|
||||
| |
|
||||
v v
|
||||
[3] [4]
|
||||
| |
|
||||
| [5]
|
||||
| |
|
||||
v |
|
||||
[exit]<-------/
|
||||
```
|
||||
## Links
|
||||
|
||||
Source code:
|
||||
[branch_elimination.cpp](../optimizer/optimizations/branch_elimination.cpp)
|
||||
[branch_elimination.h](../optimizer/optimizations/branch_elimination.h)
|
||||
|
||||
Tests:
|
||||
[branch_elimination_test.cpp](../tests/branch_elimination_test.cpp)
|
@ -1,128 +0,0 @@
|
||||
# Checks Elimination
|
||||
## Overview
|
||||
**Checks Elimination** - optimization which try to reduce number of checks(NullCheck, ZeroCheck, NegativeCheck, BoundsCheck).
|
||||
|
||||
## Rationality
|
||||
Reduce number of instructions and removes unnecessary data-flow dependencies.
|
||||
|
||||
## Dependences
|
||||
* RPO
|
||||
* BoundsAnalysis
|
||||
* DomTree
|
||||
* LoopAnalysis
|
||||
|
||||
## Algorithm
|
||||
Visit all check instructions in RPO order and check specific rules.
|
||||
If one of the rules is applicable, the instruction is deleted.
|
||||
|
||||
Instruction deleted in two steps:
|
||||
1. Check instruction inputs connects to check users.
|
||||
2. Check instruction replace on `NOP` instruction.
|
||||
|
||||
### Rules
|
||||
#### All checks
|
||||
All the same checks that are dominated by the current one are deleted and consecutive checks deleted.
|
||||
#### NullCheck:
|
||||
1. NullChecks with allocation instruction input is deleted.
|
||||
2. If based on bounds analysis input of NullCheck is more then 0, check is deleted.
|
||||
|
||||
Loop invariant NullChecks replaced by `DeoptimiseIf` instruction before loop.
|
||||
|
||||
#### ZeroCheck:
|
||||
If based on bounds analysis input of ZeroCheck isn't equal than 0, check is deleted.
|
||||
#### NegativeCheck:
|
||||
If based on bounds analysis input of NegativeCheck is more then 0, check is deleted.
|
||||
#### BoundsChecks:
|
||||
If based on bounds analysis input of BoundsCheck is more or equal than 0 and less than length of array, check is deleted.
|
||||
|
||||
If BoundsCheck isn't deleted, it insert in special structure `NotFullyRedundantBoundsCheck`.
|
||||
```
|
||||
// parent_index->{Vector<bound_check>, max_val, min_val}
|
||||
using GroupedBoundsChecks = ArenaUnorderedMap<Inst*, std::tuple<ArenaVector<Inst*>, int64_t, int64_t>>;
|
||||
// loop->len_array->GroupedBoundsChecks
|
||||
using NotFullyRedundantBoundsCheck = ArenaDoubleUnorderedMap<Loop*, Inst*, GroupedBoundsChecks>;
|
||||
```
|
||||
For example, for this method:
|
||||
```
|
||||
int Foo(array a, int index) {
|
||||
BoundCheck(len_array(a), index); // id = 1
|
||||
BoundCheck(len_array(a), index+1); // id = 2
|
||||
BoundCheck(len_array(a), index-2); // id = 3
|
||||
return a[index] + a[index+1] + a[index-2];
|
||||
}
|
||||
```
|
||||
NotFullyRedundantBoundsCheck will be filled in this way:
|
||||
```
|
||||
Root_loop->len_array(a)-> index -> {{BoundsChecks 1,2,3}, max_val = 1, min_val = -2}
|
||||
```
|
||||
|
||||
For countable loops witch index in `NotFullyRedundantBoundsCheck`, algorithm try to replace boundschecks by `DeoptimiseIf` instructions before loop.
|
||||
|
||||
For example, loop:
|
||||
```
|
||||
for ( int i = 0; i < x; i++) {
|
||||
BoundCheck(i);
|
||||
a[i] = 0;
|
||||
}
|
||||
```
|
||||
will be transormed to
|
||||
```
|
||||
deoptimizeIf(x >= len_array(a));
|
||||
for ( int i = 0; i < x; i++) {
|
||||
a[i] = 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
For another BoundsCheck instructions in `NotFullyRedundantBoundsCheck` algorithm try to replace more then 2 grouped bounds checks by `DeoptimiseIf`.
|
||||
For example, this method:
|
||||
```
|
||||
int Foo(array a, int index) {
|
||||
BoundCheck(len_array(a), index); // id = 1
|
||||
BoundCheck(len_array(a), index+1); // id = 2
|
||||
BoundCheck(len_array(a), index-2); // id = 3
|
||||
return a[index] + a[index+1] + a[index-2];
|
||||
}
|
||||
```
|
||||
will be transformed to:
|
||||
```
|
||||
int Foo(array a, int index) {
|
||||
deoptimizeIf(index-2 < 0);
|
||||
deoptimizeIf(index+1 >= len_array(a));
|
||||
return a[index] + a[index+1] + a[index-2];
|
||||
}
|
||||
```
|
||||
|
||||
## Pseudocode
|
||||
TODO
|
||||
|
||||
## Examples
|
||||
|
||||
Before Checks Elimination:
|
||||
```
|
||||
1.ref Parameter -> v3, v4
|
||||
2.i64 Constant 1 -> v5, v6
|
||||
|
||||
3.ref NullCheck v1 -> v7
|
||||
4.ref NullCheck v1 -> v8
|
||||
5.i32 ZeroCheck v2 -> v9
|
||||
6.i32 NegativeCheck v2 -> v10
|
||||
```
|
||||
After Checks Elimination:
|
||||
```
|
||||
1.ref Parameter -> v3
|
||||
2.i64 Constant 1 -> v9, v10
|
||||
|
||||
3.ref NullCheck v1 -> v7, v8
|
||||
4.ref NOP
|
||||
5.i32 NOP
|
||||
6.i32 NOP
|
||||
```
|
||||
|
||||
## Links
|
||||
Source code:
|
||||
[checks_elimination.h](../optimizer/optimizations/checks_elimination.h)
|
||||
[checks_elimination.cpp](../optimizer/optimizations/checks_elimination.cpp)
|
||||
|
||||
Tests:
|
||||
[checks_elimination_test.cpp](../tests/checks_elimination_test.cpp)
|
@ -1,51 +0,0 @@
|
||||
# Constant Folding
|
||||
## Overview
|
||||
**Constant folding** - optimization which calculate instructions with constant inputs in compile time.
|
||||
## Rationality
|
||||
|
||||
Reducing the number of instructions.
|
||||
|
||||
## Dependence
|
||||
* RPO analyze.
|
||||
## Algorithm
|
||||
Visit all instructions in PRO order.
|
||||
|
||||
If instruction have constant inputs, new constant is calculate and input of user of original instruction is replaced on new constant.
|
||||
|
||||
Constant folding is called in Peephole optimization.
|
||||
## Pseudocode
|
||||
```
|
||||
for (auto inst: All insts in RPO order) {
|
||||
if (inputs is constants) {
|
||||
Calculate new value.
|
||||
Putting new constant in inputs of inst users.
|
||||
}
|
||||
}
|
||||
```
|
||||
## Examples
|
||||
Before constant folding:
|
||||
```
|
||||
1.i64 Constant 1 -> v3
|
||||
2.i64 Constant 2 -> v3
|
||||
|
||||
3.i64 Add v1, v2 -> v4 // is redundant, can calculate in compile time
|
||||
4.i64 Return v3
|
||||
```
|
||||
After constant folding:
|
||||
```
|
||||
1.i64 Constant 1
|
||||
2.i64 Constant 2
|
||||
5.i64 Constant 3 -> v4
|
||||
|
||||
3.i64 Add v1, v2
|
||||
4.i64 Return v5
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Source code:
|
||||
[constant_folding.cpp](../optimizer/optimizations/const_folding.cpp)
|
||||
[constant_folding.h](../optimizer/optimizations/const_folding.h)
|
||||
|
||||
Tests:
|
||||
[constant_folding_test.cpp](../tests/const_folding_test.cpp)
|
@ -1,237 +0,0 @@
|
||||
# Common Subexpression Elimination (Cse)
|
||||
|
||||
## Overview
|
||||
`Cse` eliminates duplicate computations. If there are two instructions whose inputs and opcodes and types are all the same, then we move users from second instruction to first instructions(first instruction is dominate), and then delete the second instruction.
|
||||
|
||||
## Rationality
|
||||
Reducing the number of instructions.
|
||||
|
||||
## Dependence
|
||||
* RPO analysis;
|
||||
* Dominators Tree;
|
||||
|
||||
## Algorithm
|
||||
Our Cse consists of three consective parts, which handle different scenario.
|
||||
|
||||
### LocalCse
|
||||
`LocalCse` eliminates the duplicate computations for every basic block. If there are two instructions whose inputs and opcodes and types are all the same, then we move users from second instruction to first instructions(first instruction is at front of the second), and then delete the second instruction.
|
||||
|
||||
### Pseudocode
|
||||
```
|
||||
ArenaVector<Inst*> deleted_insts;
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
ArenaVector<Inst*> candidates;
|
||||
for (auto inst : bb->AllInsts()) {
|
||||
if (inst is not a binary expression) {
|
||||
continue;
|
||||
}
|
||||
if (there exists instn in candidates such that inst and instn are duplicate){
|
||||
inst->ReplaceUsers(instn);
|
||||
deleted_insts.push_back(inst);
|
||||
} else {
|
||||
candidates.push_back(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto inst : deleted_insts) {
|
||||
inst->GetBasicBlock()->RemoveInst(inst);
|
||||
}
|
||||
```
|
||||
|
||||
### DomTreeCse
|
||||
`DomTreeCse` eliminates the duplicate computations along dominator tree. If there are two instructions inst1 and inst2 with the same expression such that inst1's block dominates inst2's block, then we can move the users of inst2 into inst1, and then delete inst2. Here we make a convention that a block does not dominate itself. It can be viewed as a generation of `LocalCse`.
|
||||
|
||||
### Pseudocode
|
||||
```
|
||||
ArenaVector<Inst*> candidates;
|
||||
ArenaVector<std::pair<Inst*, Inst*>> replace_pair
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (inst is not a binary expression) {
|
||||
continue;
|
||||
}
|
||||
ArenaVector<Inst*> candidates_erase_tmp;
|
||||
bool inst_available = true;
|
||||
if (there exists instn in candidates such that inst and instn have same expression){
|
||||
if (instn Dominates inst) {
|
||||
replace_pair.emplace_back(inst, instn);
|
||||
inst_available = false;
|
||||
break;
|
||||
}
|
||||
if (inst Dominates instn) {
|
||||
replace_pair.emplace_back(instn, inst);
|
||||
candidates_erase_tmp.push_back(instn);
|
||||
}
|
||||
}
|
||||
|
||||
if (inst_available) {
|
||||
candidates.push_back(inst);
|
||||
}
|
||||
for (auto instn : candidates_erase_tmp) {
|
||||
candidates.erase(instn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArenaVector<std::pair<Inst*, Inst*>> min_replace_star;
|
||||
for (auto rpair : replace_pair) {
|
||||
find the longest path of the form rpair = (inst, inst1), (inst1, inst2), ..., (inst(r-1), instr), in which every pair is in replace_pair;
|
||||
min_replace_star.emplace_back(instr, inst);
|
||||
}
|
||||
|
||||
ArenaVector<Inst*> deleted_insts;
|
||||
for (auto pair : min_replace_star) {
|
||||
pair.second->ReplaceUsers(pair.first);
|
||||
deleted_insts.push_back(pair.second);
|
||||
}
|
||||
for (auto inst : deleted_insts) {
|
||||
DeleteInstLog(inst);
|
||||
inst->GetBasicBlock()->RemoveInst(inst);
|
||||
}
|
||||
```
|
||||
|
||||
### GlobalCse
|
||||
`GlobalCse` eliminates the redundant computations whose result can be obtained from its (two) predecessors. In this case we will use a new Phi instruction to take place of the redundant instruction.
|
||||
|
||||
### Pseudocode
|
||||
```
|
||||
ArenaVector<Inst*> deleted_insts;
|
||||
ArenaVector<std::pair<Inst*, std::pair<Inst*, Inst*>>> matched_tuple;
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
if (bb->GetPredsBlocks().size() != 2) {
|
||||
continue;
|
||||
}
|
||||
ArenaVector<std::pair<Inst*, Inst*>>same_exp_pair;
|
||||
auto bbl = bb->GetPredsBlocks()[0];
|
||||
auto bbr = bb->GetPredsBlocks()[1];
|
||||
for (auto instl : bbl->Insts()) {
|
||||
if (instl is not a binary expression || instl is in deleted_insts) {
|
||||
continue;
|
||||
}
|
||||
for (auto instr : bbr->Insts()) {
|
||||
if (instr is not a binary expression || instr is in deleted_insts) {
|
||||
continue;
|
||||
}
|
||||
if (instl and instr have same expression) {
|
||||
same_exp_pair.emplace_back(instl, instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (there exists a pair in same_exp_pair whose expression is same as inst) {
|
||||
matched_tuple.emplace_back(inst, pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto tuple : matched_tuple) {
|
||||
auto inst = tuple.first;
|
||||
auto phi = GetGraph()->CreateInstPhi(inst->GetType(), inst->GetPc());
|
||||
inst->ReplaceUsers(phi);
|
||||
inst->GetBasicBlock()->AppendPhi(phi);
|
||||
auto pair = tuple.second;
|
||||
phi->AppendInput(pair.first);
|
||||
phi->AppendInput(pair.second);
|
||||
}
|
||||
|
||||
for (auto inst : deleted_insts) {
|
||||
DeleteInstLog(inst);
|
||||
inst->GetBasicBlock()->RemoveInst(inst);
|
||||
}
|
||||
|
||||
bool Cse::RunImpl() {
|
||||
LocalCse();
|
||||
DomTreeCse();
|
||||
GlobalCse();
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
Before Cse:
|
||||
```
|
||||
BB 0
|
||||
prop: start
|
||||
0.u64 Parameter arg 0 -> (v10, v6, v7, v13)
|
||||
1.u64 Parameter arg 1 -> (v10, v6, v7, v13)
|
||||
2.f64 Parameter arg 2 -> (v11, v9)
|
||||
3.f64 Parameter arg 3 -> (v11, v9)
|
||||
4.f32 Parameter arg 4 -> (v8, v12)
|
||||
5.f32 Parameter arg 5 -> (v8, v12)
|
||||
succs: [bb 2]
|
||||
|
||||
BB 2 preds: [bb 0]
|
||||
6.u64 Add v0, v1 -> (v32)
|
||||
7.u32 Sub v1, v0 -> (v32)
|
||||
8.f32 Mul v4, v5 -> (v32)
|
||||
9.f64 Div v3, v2 -> (v32)
|
||||
10.u32 Sub v1, v0 -> (v32)
|
||||
11.f64 Div v3, v2 -> (v32)
|
||||
12.f32 Mul v4, v5 -> (v32)
|
||||
13.u64 Add v0, v1 -> (v32)
|
||||
14.u64 Mod v0, v1 -> (v32)
|
||||
15.u64 Min v0, v1 -> (v32)
|
||||
16.u64 Max v0, v1 -> (v32)
|
||||
17.u64 Shl v0, v1 -> (v32)
|
||||
18.u64 Shr v0, v1 -> (v32)
|
||||
19.u64 AShr v0, v1 -> (v32)
|
||||
20.b And v0, v1 -> (v32)
|
||||
21.b Or v0, v1 -> (v32)
|
||||
22.b Xor v0, v1 -> (v32)
|
||||
23.u64 Mod v0, v1 -> (v32)
|
||||
24.u64 Min v0, v1 -> (v32)
|
||||
25.u64 Max v0, v1 -> (v32)
|
||||
26.u64 Shl v0, v1 -> (v32)
|
||||
27.u64 Shr v0, v1 -> (v32)
|
||||
28.u64 AShr v0, v1 -> (v32)
|
||||
29.b And v0, v1 -> (v32)
|
||||
30.b Or v0, v1 -> (v32)
|
||||
31.b Xor v0, v1 -> (v32)
|
||||
32.b CallStatic v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31
|
||||
33. ReturnVoid
|
||||
succs: [bb 1]
|
||||
|
||||
BB 1 preds: [bb 2]
|
||||
prop: end
|
||||
```
|
||||
After Cse:
|
||||
```
|
||||
BB 0
|
||||
prop: start
|
||||
0.u64 Parameter arg 0 -> (v10, v6, v7, v13)
|
||||
1.u64 Parameter arg 1 -> (v10, v6, v7, v13)
|
||||
2.f64 Parameter arg 2 -> (v11, v9)
|
||||
3.f64 Parameter arg 3 -> (v11, v9)
|
||||
4.f32 Parameter arg 4 -> (v8, v12)
|
||||
5.f32 Parameter arg 5 -> (v8, v12)
|
||||
succs: [bb 2]
|
||||
|
||||
BB 2 preds: [bb 0]
|
||||
6.u64 Add v0, v1 -> (v32)
|
||||
7.u32 Sub v1, v0 -> (v32)
|
||||
8.f32 Mul v4, v5 -> (v32)
|
||||
9.f64 Div v3, v2 -> (v32)
|
||||
14.u64 Mod v0, v1 -> (v32)
|
||||
15.u64 Min v0, v1 -> (v32)
|
||||
16.u64 Max v0, v1 -> (v32)
|
||||
17.u64 Shl v0, v1 -> (v32)
|
||||
18.u64 Shr v0, v1 -> (v32)
|
||||
19.u64 AShr v0, v1 -> (v32)
|
||||
20.b And v0, v1 -> (v32)
|
||||
21.b Or v0, v1 -> (v32)
|
||||
22.b Xor v0, v1 -> (v32)
|
||||
32.b CallStatic v6, v7, v8, v9, v7, v9, v8, v6, v14, v15, v16, v17, v18, v19, v20, v21, v22, v14, v15, v16, v17, v18, v19, v20, v21, v22
|
||||
33. ReturnVoid
|
||||
succs: [bb 1]
|
||||
|
||||
BB 1 preds: [bb 2]
|
||||
prop: end
|
||||
```
|
||||
|
||||
## Links
|
||||
Source code:
|
||||
[cse.cpp](../optimizer/optimizations/cse.cpp)
|
||||
[cse.h](../optimizer/optimizations/cse.h)
|
||||
|
||||
Tests:
|
||||
[cse_test.cpp](../tests/cse_test.cpp)
|
@ -1,159 +0,0 @@
|
||||
|
||||
# Inlining optimization
|
||||
|
||||
## Overview
|
||||
|
||||
Inlining optimization replaces a method call site with the body of the called method.
|
||||
|
||||
Inlining optimizations supports two types of call instructions.
|
||||
|
||||
- CallStatic - call static methods
|
||||
- CallVirtual - call virtual methods
|
||||
|
||||
Inlining of these instructions has two main difference: method resolving and guards.
|
||||
|
||||
Resolving of the CallStatic method is quite simple, since target method is explicitly determined by the call instruction
|
||||
itself. Thus, no guards is needed as well.
|
||||
|
||||
CallVirtual instruction also contains method ID, that should be called, but it also contains input object(receiver) that
|
||||
is instance of some class, that, in turn, determines which implementation of the method should be called.
|
||||
|
||||
To devirtualize virtual calls, i.e. determine target method in compile time, we use following techniques:
|
||||
- Static devirtualization
|
||||
- Devirtualization with Class Hierarchy Analysis
|
||||
- Inline caches
|
||||
|
||||
### Static devirtualization
|
||||
|
||||
Receiver is determined via ObjectTypePropagation analysis, that propagates statically known types down to the users.
|
||||
For example:
|
||||
```
|
||||
newobj v0, A
|
||||
call.virt A.foo, v0
|
||||
```
|
||||
here, we know receiver class (A) at compile time and we can inline A.foo method without any speculation.
|
||||
|
||||
### Devirtualization with Class Hierarchy Analysis
|
||||
|
||||
We use dynamic Class Hierarchy Analysis, because Panda Runtime allows dynamic class loading.
|
||||
|
||||
Class Hierarchy Analysis(CHA) is a runtime analysis that is invoked every time when a new class is loaded. The result of
|
||||
the analysis is a flag in Method class, that indicates that method has a single implementation.
|
||||
|
||||
```
|
||||
.record Test2 {}
|
||||
.record A {}
|
||||
.record B <extends=A> {}
|
||||
|
||||
.function i32 A.func(A a0) {
|
||||
ldai 1
|
||||
return
|
||||
}
|
||||
.function i32 B.func(B a0) {
|
||||
ldai 0
|
||||
return
|
||||
}
|
||||
|
||||
# Actually, here is static devirtualization makes a work, we use this assembly for simplicity and clarity.
|
||||
.function i32 Test2.main() {
|
||||
newobj v0, A # CHA set SingleImplementation for A.foo method
|
||||
call.virt A.foo, v0
|
||||
newobj v0, B # CHA set SingleImplementation for B.foo method and reset it for A.foo
|
||||
call.virt A.foo, v0
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
Since Panda VM allows instantiating a classes with abstract methods, we can't rely on the fact that abstract classes are
|
||||
never instantiated. Thus we can't apply the rule: abstract method does not break Single Implementation flag of the
|
||||
parent method.
|
||||
|
||||
We should protect all devirtualized call sites with CHA by special guards. In short, these guards check that
|
||||
devirtualized method still has Single Implementation flag.
|
||||
|
||||
CHA has dependency map, where key is a devirtualized method and value is a list of methods that employ this
|
||||
devirtualized method, f.e. inline. Once method lost Single Implementation flag, CHA searches this method in the
|
||||
dependency map and reset compilation code in the dependent methods.
|
||||
|
||||
But dependent methods (methods that inline devirtualized method) may already being executed and resetting the
|
||||
compilation code in Method class is not enough. Thus, we walk over call stack of the all threads and search frames with
|
||||
dependent methods. Then we set special `ShouldDeoptimize` flag in the frame of these dependent methods. This flag is
|
||||
exactly that CHA guards check. When method execution reaches this guard, it reads flag and see that method is no longer
|
||||
valid and deoptimize itself.
|
||||
|
||||
CHA guard comprises two instructions:
|
||||
- `IsMustDeoptimize` - checks `ShouldDeoptimize` flag in the frame, return true if it is set.
|
||||
- `DeoptimizeIf` - deoptimize method if input condition is true.
|
||||
|
||||
Disassembly of the CHA guard:
|
||||
```
|
||||
# [inst] 60.b IsMustDeoptimize r7 (v61)
|
||||
00a0: ldrb w7, [sp, #592]
|
||||
00a4: and w7, w7, #0x1
|
||||
# [inst] 61. DeoptimizeIf v60(r7), v14
|
||||
00c0: cbnz w7, #+0x3f4 (addr 0x400316e8fc)
|
||||
```
|
||||
|
||||
|
||||
### Inline caches
|
||||
|
||||
Not implemented yet.
|
||||
|
||||
## Inlining algorithm
|
||||
|
||||
After target method is determined, Inlining optimization call IrBuilderInliningAnalysis that check each bytecode
|
||||
instruction of the inlining method is suiteble for inline.
|
||||
|
||||
If method is suitable, the Inliner creates new graph and runs IrBuilder for it. After that it embeds inlined graph into
|
||||
the current graph.
|
||||
|
||||
_Pseudo code for the inlined graph embedding:_
|
||||
```python
|
||||
# Check bytecode is suitable for inlining
|
||||
if not inlined_method.bytecode.include_only_suitable_insts:
|
||||
return false
|
||||
|
||||
# Build graph for inlined method. Pass current SaveState instruction, created for call_inst.
|
||||
# This SaveState will be set as aditional input for all SaveState instructions in the inlined graph.
|
||||
inlined_graph = IrBuilder.run(inlined_method, current_save_state)
|
||||
|
||||
# Split block by call instruction
|
||||
[first_bb, second_bb] = curr_block.split_on(call_inst)
|
||||
|
||||
# Replace inlined graph incoming dataflow edges
|
||||
if call_inst.has_inputs:
|
||||
for input in call_inst.inputs:
|
||||
input.replace_succ(call_inst, inlined_graph.start_block.param_for(input))
|
||||
|
||||
# Replace inlined graph outcoming dataflow edges
|
||||
call_inst.replace_succs(inlined_graph.return_inst.input)
|
||||
inlined_graph.remove(inlined_graph.return_inst)
|
||||
|
||||
# Move constants of inlined graph to the current graph
|
||||
for constant in inlined_graph.constants:
|
||||
exisitng_constant = current_graph.get_constant(constant.value)
|
||||
if not exisitng_constant:
|
||||
current_graph.append_constant(constant)
|
||||
else:
|
||||
for succ : constant.succs:
|
||||
succ.replace_pred(constant, exisitng_constant)
|
||||
|
||||
# Connect inlined graph as successor of the first part of split block
|
||||
inlined_graph.start_block.succ.set_pred(first_bb)
|
||||
|
||||
# Move all predecessors of inlined end block to second part of split block
|
||||
for pred in inlined_graph.end_block:
|
||||
pred.replace_succ(inlined_graph.end_block, second_bb)
|
||||
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- IrBuilder
|
||||
- ObjectTypePropagation
|
||||
|
||||
## Links
|
||||
|
||||
[Inlining optimization source code](../optimizer/optimizations/inlining.cpp)
|
||||
|
||||
[Class Hierarchy Analysis source code](../../runtime/cha.cpp)
|
@ -1,93 +0,0 @@
|
||||
# Loop-invariant code motion (LICM)
|
||||
|
||||
## Overview
|
||||
`LICM` moves instructions outside the body of a loop without affecting the program semantics
|
||||
|
||||
## Rationality
|
||||
Instructions which have been hoisted out of a loop are executed less often, providing a speedup
|
||||
|
||||
## Dependence
|
||||
* Loop Analysis;
|
||||
* Dominators Tree;
|
||||
|
||||
## Algorithm
|
||||
`LICM` visits loops from inner to outer, skipping irreducible and OSR loops. In each loop it selects instructions, which can be hoisted:
|
||||
* all instruction's inputs should dominate loop's pre-header or they are selected to hoist;
|
||||
* instruction must dominate all users in the loop body;
|
||||
* instruction must dominate all loop exits;
|
||||
|
||||
Then selected instructions are moved to the loop pre-header
|
||||
## Pseudocode
|
||||
TBD
|
||||
## Examples
|
||||
```
|
||||
BB 0
|
||||
prop: start
|
||||
12.i64 Constant 0x1 -> (v3p, v13, v13, v5)
|
||||
1.i64 Constant 0xa -> (v4p)
|
||||
2.i64 Constant 0x14 -> (v10)
|
||||
succs: [bb 2]
|
||||
|
||||
BB 2 preds: [bb 6, bb 3]
|
||||
prop: head, loop 1
|
||||
3p.u64 Phi v12(bb6), v7(bb3) -> (v7, v10)
|
||||
4p.u64 Phi v1(bb6), v8(bb3) -> (v5, v7, v8)
|
||||
5.b Compare EQ u64 v4p, v12 -> (v6)
|
||||
6. IfImm NE b v5, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 3 preds: [bb 2]
|
||||
prop: loop 1
|
||||
7.u64 Mul v3p, v4p -> (v3p)
|
||||
13.u64 Mul v12, v12 -> (v8)
|
||||
8.u64 Sub v4p, v13 -> (v4p)
|
||||
succs: [bb 2]
|
||||
|
||||
|
||||
BB 4 preds: [bb 2]
|
||||
10.u64 Add v2, v3p
|
||||
11. ReturnVoid
|
||||
succs: [bb 1]
|
||||
```
|
||||
`LICM` hoists `13.u64 Mul` instruction:
|
||||
|
||||
```
|
||||
BB 0
|
||||
prop: start
|
||||
12.i64 Constant 0x1 -> (v3p, v13, v13, v5)
|
||||
1.i64 Constant 0xa -> (v4p)
|
||||
2.i64 Constant 0x14 -> (v10)
|
||||
succs: [bb 6]
|
||||
|
||||
BB 6 preds: [bb 0]
|
||||
13.u64 Mul v12, v12 -> (v8)
|
||||
succs: [bb 2]
|
||||
|
||||
BB 2 preds: [bb 6, bb 3]
|
||||
prop: head, loop 1
|
||||
3p.u64 Phi v12(bb6), v7(bb3) -> (v7, v10)
|
||||
4p.u64 Phi v1(bb6), v8(bb3) -> (v5, v7, v8)
|
||||
5.b Compare EQ u64 v4p, v12 -> (v6)
|
||||
6. IfImm NE b v5, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 3 preds: [bb 2]
|
||||
prop: loop 1
|
||||
7.u64 Mul v3p, v4p -> (v3p)
|
||||
8.u64 Sub v4p, v13 -> (v4p)
|
||||
succs: [bb 2]
|
||||
|
||||
BB 4 preds: [bb 2]
|
||||
10.u64 Add v2, v3p
|
||||
11. ReturnVoid
|
||||
succs: [bb 1]
|
||||
|
||||
```
|
||||
|
||||
## Links
|
||||
Source code:
|
||||
[licm.cpp](../optimizer/optimizations/licm.cpp)
|
||||
[licm.h](../optimizer/optimizations/licm.h)
|
||||
|
||||
Tests:
|
||||
[licm_test.cpp](../tests/licm_test.cpp)
|
@ -1,308 +0,0 @@
|
||||
# Load Store Elimination
|
||||
## Overview
|
||||
|
||||
The idea of optimization is to delete store instructions that store a value to memory that has been already written as well as delete load instructions that attempt to load a value that has been loaded earlier.
|
||||
|
||||
## Rationality
|
||||
|
||||
Elimination of load and store instructions generally reduces the number of long latency memory instructions. It should be stated that the optimization increases the register pressure and as a result can increase the amount of required spills/fills.
|
||||
|
||||
## Dependence
|
||||
|
||||
* AliasAnalysis
|
||||
* LoopAnalysis
|
||||
* Reverse Post Order (RPO)
|
||||
|
||||
## Algorithm
|
||||
|
||||
Algorithm needs to know that two memory instructions access the same memory address. This can be done using alias analysis that accepts two memory instructions and is able to say about these accesses the following
|
||||
* `MUST_ALIAS` if the instructions definitely access the same memory address
|
||||
* `NO_ALIAS` if the instructions definitely access different memory addresses.
|
||||
* `MAY_ALIAS` if analysis can't say with confidence whether the instructions access the same memory address or different (or probably they may access the same address only under some conditions but under others – not)
|
||||
|
||||
The main structure for the algorithm is a heap representation in a form of
|
||||
|
||||
`"Memory Instruction" -> "Value stored at location pointed by instruction"`
|
||||
|
||||
The heap is a representation of the real heap during execution. Each instruction that works with memory operates with a particular memory address. E.g. store instructions write values on the heap, load instructions read values from the heap. But if a load instruction tries to read a value that was written by store instruction above, this load can be eliminated because we already know the value without actual reading. The heap structure helps to keep track of values that memory instruction operates with (store instruction -- write, load instructions -- read).
|
||||
|
||||
Each basic block has its own heap. The initial value of a heap for a basic block is a merged heap from its predecessors. The result of heap merge is an intersected set of heap values on the same addresses. The initial value for entry basic block is empty heap. The empty heap is an initial value for loop headers as well because back edges can rewrite the heap values.
|
||||
|
||||
A dependence on predecessors needs us to traverse basic blocks in reverse post order so that for each basic block we already visited all it's predecessors (empty heap of one of predecessor is a back edge).
|
||||
|
||||
Once a heap is initialized for a basic block we iterate over instructions and update heap by applying the following rules to current instruction:
|
||||
|
||||
- if the instruction is a store and a stored value is equal to value from heap for this store then this store can be eliminated.
|
||||
- if the instruction is a store and a value from heap for this store is absent or differs from a new stored value then the new stored value is written into heap. The values of memory instructions that `MUST_ALIAS` this store are updated as well. All values in the heap that `MAY_ALIAS` this store instruction are invalidated.
|
||||
- if the instruction is a load and there is a value from the heap for this load then this load can be eliminated.
|
||||
- if the instruction is a load and there is no value from the heap for this load then we update heap value for this load with the result of this load. All instructions that `MUST_ALIAS` this load updated as well.
|
||||
- If the instruction can invoke GC then all references on the heap that aren't mentioned in corresponded SaveState should be invalidated.
|
||||
- if the instruction is a volatile load then the whole heap is cleared.
|
||||
- if the instruction is a call then the whole heap is cleared.
|
||||
|
||||
All instructions that can be eliminated are recorded in a separate list for eliminated instructions and erase them at the end of optimization.
|
||||
|
||||
Loops have back edges by this reason we cannot eliminate anything in single traversal because further memory operations may update heap using back edge. Therefore an initial heap for loop header is empty. But still there is a case when we can use collected heap of loop preheader (e.g. if we can guarantee that memory access is read-only inside loop).
|
||||
|
||||
The typical example is the following (in pandasm):
|
||||
|
||||
```
|
||||
.record T {
|
||||
i32[] array <static>
|
||||
}
|
||||
|
||||
.function i32 T.foo() {
|
||||
movi v2, 0 #res
|
||||
movi v0, 0 #i
|
||||
ldobj.obj v1, T.array
|
||||
ldarr v1
|
||||
sta v3
|
||||
loop:
|
||||
jge v3, loop_exit
|
||||
lda v0
|
||||
ldobj.obj v1, T.array
|
||||
ldarr v1
|
||||
add2 v2
|
||||
sta v2
|
||||
inci v0, 1
|
||||
lda v0
|
||||
jmp loop
|
||||
loop_exit:
|
||||
lda v2
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
It will be translated in the following code:
|
||||
|
||||
```
|
||||
BB 2 preds: [bb 0]
|
||||
3.ref LoadObject 242 v0 -> (v6)
|
||||
6.i32 LenArray v3 -> (v30, v16)
|
||||
30.i32 Cmp v7, v6 -> (v31)
|
||||
31.b Compare GE i32 v30, v7 -> (v32)
|
||||
32. IfImm NE b v31, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 4 preds: [bb 2, bb 4]
|
||||
prop: head, loop 1
|
||||
10p.i32 Phi v7(bb2), v27(bb4) -> (v25, v27)
|
||||
11p.i32 Phi v7(bb2), v26(bb4) -> (v26)
|
||||
20.ref LoadObject 242 v0 -> (v25)
|
||||
25.i32 LoadArray v20, v10p -> (v26)
|
||||
26.i32 Add v25, v11p -> (v11p, v35p)
|
||||
27.i32 Add v10p, v28 -> (v10p, v16)
|
||||
16.b Compare GE i32 v27, v6 -> (v17)
|
||||
17. IfImm NE b v16, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 3 preds: [bb 2, bb 4]
|
||||
35p.i32 Phi v7(bb2), v26(bb4) -> (v29)
|
||||
29.i32 Return v35p
|
||||
succs: [bb 1]
|
||||
```
|
||||
|
||||
Here is `v20` inside loop that can be eliminated due to `v3`. However we can't record `v20` to list of loads that will be eliminated because further in the loop we can have a store instruction like the following:
|
||||
|
||||
```
|
||||
v28.ref StoreObject 242 v0, v40
|
||||
```
|
||||
|
||||
To handle this, each loop has its list of memory accesses that was loaded before loop (heap of preheader). We call these memory accesses as phi-candidates. Each phi-candidate has its own list of aliased memory accesses that will be collected further. While traversing a loop we check aliasing of each memory access inside the loop with phi-candidates. If a memory access is `MAY_ALIAS`ed or `MUST_ALIAS`ed we put this memory access to the corresponding lists of phi-candidates. Irreducible loops do not have preheader so we ignore them for optimization.
|
||||
|
||||
Finally, we iterate over all memory accesses collected in lists for each phi-candidate. If there is at least one store among them, we drop this phi-candidate because it is overwritten somewhere inside the loop. If there are only load instructions among them, then we can replace memory accesses inside the loop by values obtained from outside the loop (replace only `MUST_ALIAS`ed accesses).
|
||||
|
||||
The following rules are added to the list above to handle loops:
|
||||
|
||||
- visiting any memory access inside a reducible loop we check the aliasing with phi-candidates of this loop and all outer loop and record aliased ones to a corresponding candidate.
|
||||
- all phi-candidates of a loop (and of all outer loops of this loop) are invalidated if any of instructions that clear heap have been met in this loop.
|
||||
|
||||
After iterating the whole graph the following actions are done
|
||||
- iterate over phi-candidates with aliased accesses.
|
||||
* If any of aliased accesses for a candidate is a store, we do nothing.
|
||||
* If among aliased accesses only loads, add `MUST_ALIAS`ed load to elimination list.
|
||||
- erase all instructions from elimination list
|
||||
|
||||
## Pseudocode
|
||||
|
||||
```
|
||||
bool LSE::RunImpl() {
|
||||
Heap heap;
|
||||
PhiCands phis;
|
||||
|
||||
LSEVisitor visitor(GetGraph(), &heap, &phis);
|
||||
for (auto block : GetGraph()->GetBlocksRPO()) {
|
||||
if (block->IsLoopHeader()) {
|
||||
// Put heap[block->GetLoop()->GetPreheader()] values to phis[block->GetLoop()]
|
||||
// heap[block] is empty
|
||||
MergeHeapValuesForLoop(block, &heap, &phis);
|
||||
} else {
|
||||
// Put in heap[block] values that are the same in block's predecessors
|
||||
MergeHeapValuesForBlock(block, &heap, &mustrix);
|
||||
}
|
||||
|
||||
for (auto inst : block->Insts()) {
|
||||
if (IsHeapInvalidatingInst(inst)) {
|
||||
heap[block].clear();
|
||||
auto loop = block->GetLoop();
|
||||
while (!loop->IsRoot()) {
|
||||
phis.at(block->GetLoop()).clear();
|
||||
loop = loop->GetOuterLoop();
|
||||
}
|
||||
} else if (IsGCInst(inst)) {
|
||||
SaveStateInst *ss = inst->GetSaveState();
|
||||
if (inst->GetOpcode() == Opcode::SafePoint) {
|
||||
ss = inst->CastToSafePoint();
|
||||
}
|
||||
// Invalidate references not mentioned in SaveState
|
||||
visitor.InvalidateRefs(block, ss);
|
||||
} else if (inst->IsLoad()) {
|
||||
visitor.VisitLoad(inst);
|
||||
} else if (inst->IsStore()) {
|
||||
visitor.VisitStore(inst, inst->GetStoredValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Append instructions, that can be eliminated due to loop preheader heap values, to elimination list
|
||||
visitor.FinalizeLoops();
|
||||
|
||||
// Erase collected instructions
|
||||
for (auto elim : visitor.GetEliminations()) {
|
||||
DeleteInstruction(elim.inst, elim.value); // Replace elim.inst with elim.value
|
||||
}
|
||||
}
|
||||
|
||||
void LSEVisitor::VisitStore(Inst *inst, Inst *val) {
|
||||
BasicBlock *block = inst->GetBasicBlock();
|
||||
auto &block_heap = heap_.at(block);
|
||||
// If a value stored on the heap is already there, we eliminate this store instruction
|
||||
auto mem = HeapHasEqaulValue(inst, val);
|
||||
if (mem != nullptr && LSE::CanEliminateInstruction(inst)) {
|
||||
eliminations_[inst] = block_heap[mem];
|
||||
return;
|
||||
}
|
||||
|
||||
// If this store MUST_ALIAS any inst from phis[block], it prohibits any replacements of this phi candidate
|
||||
UpdatePhis(inst);
|
||||
|
||||
// Erase all aliased values, because they may be overwritten
|
||||
for (auto heap_iter : block_heap) {
|
||||
if (inst.CheckAlias(heap_iter) != NO_ALIAS) {
|
||||
block_heap.erase(heap_iter);
|
||||
}
|
||||
}
|
||||
|
||||
// Record a stored value to heap[block]
|
||||
block_heap[inst] = {inst, val};
|
||||
}
|
||||
|
||||
void LSEVisitor::VisitLoad(Inst *inst) {
|
||||
BasicBlock *block = inst->GetBasicBlock();
|
||||
auto &block_heap = heap_.at(block);
|
||||
// We already know the value on the heap -> eliminate inst
|
||||
auto mem = HeapHasValue(inst);
|
||||
if (mem != nullptr && LSE::CanEliminateInstruction(inst))
|
||||
eliminations_[inst] = block_heap[mem]; // Store the value to replace instruction with it
|
||||
return;
|
||||
}
|
||||
|
||||
// If this load MUST_ALIAS any inst from phis[block] it can be further replaced with value outside the loop
|
||||
UpdatePhis(inst);
|
||||
|
||||
// Record loaded value to heap[block] and update all MUST_ALIASes
|
||||
block_heap[inst] = {inst, inst};
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
### Loading stored value in `if` block
|
||||
Before:
|
||||
```
|
||||
BB 2 preds: [bb 0]
|
||||
7.i32 StoreArray v0, v2, v1
|
||||
9.b Compare GT i32 v1, v8 -> (v10)
|
||||
10. IfImm NE b v9, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 4 preds: [bb 2]
|
||||
15.i32 LoadArray v0, v2 -> (v21)
|
||||
20.i32 LoadArray v0, v2 -> (v21)
|
||||
21.i32 Add v15, v20 -> (v22p)
|
||||
succs: [bb 3]
|
||||
|
||||
BB 3 preds: [bb 2, bb 4]
|
||||
22p.i32 Phi v1(bb2), v21(bb4) -> (v23)
|
||||
23.i32 Return v22p
|
||||
succs: [bb 1]
|
||||
```
|
||||
Here we can see that instruction `v15` and `v20` load a values stored by `v7`. As a result we can substitute these loads with stored value.
|
||||
|
||||
After:
|
||||
```
|
||||
BB 2 preds: [bb 0]
|
||||
7.i32 StoreArray v0, v2, v1
|
||||
9.b Compare GT i32 v1, v8 -> (v10)
|
||||
10. IfImm NE b v9, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 4 preds: [bb 2]
|
||||
21.i32 Add v1, v1 -> (v22p)
|
||||
succs: [bb 3]
|
||||
|
||||
BB 3 preds: [bb 2, bb 4]
|
||||
22p.i32 Phi v1(bb2), v21(bb4) -> (v23)
|
||||
23.i32 Return v22p
|
||||
succs: [bb 1]
|
||||
```
|
||||
### Object access elimination in loop
|
||||
In this example we load array from object and sum its elements.
|
||||
```
|
||||
BB 2 preds: [bb 0]
|
||||
3.ref LoadObject 242 v0 -> (v6)
|
||||
6.i32 LenArray v3 -> (v30, v16)
|
||||
30.i32 Cmp v7, v6 -> (v31)
|
||||
31.b Compare GE i32 v30, v7 -> (v32)
|
||||
32. IfImm NE b v31, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 4 preds: [bb 2, bb 4]
|
||||
prop: head, loop 1
|
||||
10p.i32 Phi v7(bb2), v27(bb4) -> (v25, v27)
|
||||
11p.i32 Phi v7(bb2), v26(bb4) -> (v26)
|
||||
20.ref LoadObject 242 v0 -> (v25)
|
||||
25.i32 LoadArray v20, v10p -> (v26)
|
||||
26.i32 Add v25, v11p -> (v11p, v35p)
|
||||
27.i32 Add v10p, v28 -> (v10p, v16)
|
||||
16.b Compare GE i32 v27, v6 -> (v17)
|
||||
17. IfImm NE b v16, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
```
|
||||
Until the `LoadObject` accesses a volatile field, we can eliminate `v20` inside the loop and use `v3` instead.
|
||||
```
|
||||
BB 2 preds: [bb 0]
|
||||
3.ref LoadObject 242 v0 -> (v6, v25)
|
||||
6.i32 LenArray v3 -> (v30, v16)
|
||||
30.i32 Cmp v7, v6 -> (v31)
|
||||
31.b Compare GE i32 v30, v7 -> (v32)
|
||||
32. IfImm NE b v31, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
|
||||
BB 4 preds: [bb 2, bb 4]
|
||||
prop: head, loop 1
|
||||
10p.i32 Phi v7(bb2), v27(bb4) -> (v25, v27)
|
||||
11p.i32 Phi v7(bb2), v26(bb4) -> (v26)
|
||||
25.i32 LoadArray v3, v10p -> (v26)
|
||||
26.i32 Add v25, v11p -> (v11p, v35p)
|
||||
27.i32 Add v10p, v28 -> (v10p, v16)
|
||||
16.b Compare GE i32 v27, v6 -> (v17)
|
||||
17. IfImm NE b v16, 0x0
|
||||
succs: [bb 3, bb 4]
|
||||
```
|
||||
## Links
|
||||
|
||||
Source code:
|
||||
[lse.cpp](../optimizer/optimizations/lse.cpp)
|
||||
[lse.h](../optimizer/optimizations/lse.h)
|
||||
|
||||
Tests:
|
||||
[lse_test.cpp](../tests/lse_test.cpp)
|
@ -1,86 +0,0 @@
|
||||
# Peepholes
|
||||
## Overview
|
||||
**Peepholes** - optimizations which try to apply some small rules for instruction.
|
||||
## Rationality
|
||||
|
||||
Reducing the number of instructions.
|
||||
|
||||
## Dependence
|
||||
|
||||
* RPO analyze.
|
||||
|
||||
## Algorithm
|
||||
|
||||
Visit all instructions in PRO order.
|
||||
|
||||
For instruction tried to apply some rules.
|
||||
|
||||
Peephole not remove instruction, it's only replace it on another and replace users on new instruction.
|
||||
|
||||
### Rules
|
||||
This some of the rules:
|
||||
* Constant folding
|
||||
* Partial constant instruction replace by constant (a *= 0 -> a = 0)
|
||||
* Putting constant input on second place for commutative instructions (ex. Add, Mul, ...)
|
||||
* Grouping instructions (ex. b=a+2, c=b-4 -> c=a-2)
|
||||
* Remove redundant instructions (ex. b=a+0, b=a&1)
|
||||
* Replace instructions for equal but more cheap (ex. a*=4 - > a<<=2, b*=-1 -> b = -b )
|
||||
* De Morgan rules for `and` and `or` instructions.
|
||||
*...
|
||||
## Pseudocode
|
||||
```
|
||||
for (auto inst: All insts in RPO order) {
|
||||
try to apply rules
|
||||
}
|
||||
```
|
||||
## Examples
|
||||
Before peepholes:
|
||||
```
|
||||
1.i64 Parameter -> v6 // x
|
||||
2.i64 Parameter -> v8 // y
|
||||
3.i64 Constant -1 -> v10 // -1
|
||||
4.i64 Constant 1 -> v6, v7 // 1
|
||||
5.i64 Constant 2 -> v8 // 2
|
||||
|
||||
6. Add v1, v4 -> v7 // x+1
|
||||
7. Add v6, v4 -> v9 // (x+1)+1
|
||||
8. Mul v2, v5 -> v9 // y*2
|
||||
9. Mul v7, v8 -> v10 // ((x+1)+1)*(y*2)
|
||||
10. Mul v9, v3 -> v11 // (((x+1)+1)*(y*2))*-1
|
||||
11. Return v10
|
||||
```
|
||||
After peepholes:
|
||||
```
|
||||
1.i64 Parameter -> v6, v12 // x
|
||||
2.i64 Parameter -> v8, v13 // y
|
||||
3.i64 Constant -1 -> v10 // -1
|
||||
4.i64 Constant 1 -> v6, v7 // 1
|
||||
5.i64 Constant 2 -> v8, v12 // 2
|
||||
|
||||
// (x+1)+1 -> x+2
|
||||
6. Add v1, v4 // x+1
|
||||
7. Add v6, v4 // (x+1)+1
|
||||
12.Add v1, v5 -> v9 // x+2
|
||||
|
||||
//y*2 -> y + y
|
||||
8. Mul v2, v5 // y*2
|
||||
13.Add v2, v2 -> v9 // y+y
|
||||
|
||||
9. Mul v12, v13 -> v14 // (x+2)*(y+y)
|
||||
|
||||
// z*-1 -> -z
|
||||
10.Mul v9, v3 // (x+2)*(y+y)*(-1)
|
||||
14.Neg v9 -> v11 // -((x+2)*(y+y))
|
||||
|
||||
11.Return v14
|
||||
```
|
||||
## Links
|
||||
[constant folding](constant_folding_doc.md)
|
||||
|
||||
Source code:
|
||||
[peepholes.cpp](../optimizer/optimizations/peepholes.cpp)
|
||||
[peepholes.h](../optimizer/optimizations/peepholes.h)
|
||||
|
||||
Tests:
|
||||
[peepholes_test.cpp](../tests/peepholes_test.cpp)
|
||||
|
@ -1,162 +0,0 @@
|
||||
# Register allocator
|
||||
## Overview
|
||||
|
||||
`Register allocator` assigns program variables to target CPU registers
|
||||
|
||||
## Rationality
|
||||
|
||||
Minimize load/store operations with memory
|
||||
|
||||
## Dependence
|
||||
* LivenessAnalysis
|
||||
* DominatorsTree
|
||||
* Reverse Post Order (RPO)
|
||||
|
||||
## Algorithm
|
||||
|
||||
Current algorithm is based on paper "Linear Scan Register Allocation" by Massimiliano Poletto. There are 4 main stages:
|
||||
|
||||
### Assigning registers and stack slots to the instructions' intervals
|
||||
|
||||
`Register allocator` works with instructions' lifetime intervals, computed by `LivenessAnalyzer`. It scans forward through the intervals, ordered by increasing starting point and assign registers while the number of the intervals at the point is less then the number of available registers. If at some point there is no available register to assign to the interval, `Register allocator` spills one active
|
||||
interval to the stack. Intervals for general and vector registers are independent.
|
||||
|
||||
### Inputs resolution
|
||||
|
||||
#### Resolve instructions inputs and output
|
||||
|
||||
At this point register allocator resolves location for each instruction's input depending on register or stack slot that was assigned to an input instruction's life interval and assigns destination location.
|
||||
|
||||
If input's life interval was split into multiple segments then a location is taken from a segment covering the user. In case when input's interval was split at the user the location could be taken from both segments (the segment that ends before the user and the segment that starts at the user) and a register is preferred over a stack slot.
|
||||
|
||||
For PHI's inputs having segmented life intervals location at the end of the preceding block is taken.
|
||||
|
||||
For SaveState instruction input locations are taken at the SaveState's user rather then at SaveState itself, because this instruction should capture program state at its user. If SaveState have several users (for example, for virtual calls single SaveState instruction is reused by both NullCheck and CallVirtual) and at least one of the SaveState's input has different location at these users then SaveState is copied and original instruction is replaced by the copy for one of its inputs.
|
||||
|
||||
#### Adding spill-fill instructions
|
||||
|
||||
`SpillFillInst` is a pseudo instruction, holds information about moving data between registers and stack.
|
||||
|
||||
```cpp
|
||||
struct SpillFillData {
|
||||
LocationType src_type : 4;
|
||||
LocationType dst_type : 4;
|
||||
uint8_t src;
|
||||
uint8_t dest;
|
||||
DataType::Type type;
|
||||
};
|
||||
|
||||
enum LocationType {
|
||||
INVALID_LOCATION = 0,
|
||||
REGISTER = 1,
|
||||
STACK = 2,
|
||||
IMMEDIATE = 3,
|
||||
};
|
||||
```
|
||||
|
||||
`Register allocator` iterates over instructions and inserts `SpillFillInst` in the following cases:
|
||||
* Pop instruction's input form preassigned stack slot to spill-fill register;
|
||||
* Push instruction's result from spill-fill register to preassigned stack slot;
|
||||
* Load immediate to register or slot;
|
||||
* For each phi input move it to phi destination register or stack slot if theirs locations are different;
|
||||
|
||||
Besides, `SpillFillInst` is assigned to the dymamic-inputs instructions (SaveStateInst, CallInst, IntrinsicInst) and containts information about theirs inputs location in the `src_type` and `src` fields. Fields `dst_type` and `dest` are not used in that case and should be `INVALID`.
|
||||
|
||||
### Life intervals split resolving
|
||||
|
||||
On this stage `Register allocator` connects life intervals that were split into multiple segments during assignment stage using spill-fill instructions. If different locations were assigned to a segment live at the end of some basic block and its sibling live at the beginning of a succeeding block then spill-fills are inserted to control-flow edge to copy value from predecessor's location to successor's location.
|
||||
|
||||
### Spill-fills resolving
|
||||
|
||||
On this stage `Register allocator` checks if there are overwriting in the chain of spill-fill moves and reorders them if needed:
|
||||
```
|
||||
from:
|
||||
R1 -> R2, R2 -> R3
|
||||
to:
|
||||
R2 -> R3, R1 -> R3
|
||||
```
|
||||
Cyclical dependent moves will be resolved with spill-fill register:
|
||||
```
|
||||
from:
|
||||
R1 -> R2, R2 -> R1
|
||||
to:
|
||||
R1 -> TEMP, R2-> R1, TEMP -> R2
|
||||
```
|
||||
|
||||
### Parameters locations
|
||||
|
||||
Depending on calling convention for target CPU parameters can be located either on registers or in the stack slots. This information is stored with `SpillFillData` inside parameter's instruction and it is filled at the IR-building stage.
|
||||
|
||||
|
||||
## Pseudocode
|
||||
|
||||
TBD
|
||||
|
||||
## Examples
|
||||
|
||||
```cpp
|
||||
Test Graph:
|
||||
[0]
|
||||
|
|
||||
v
|
||||
/-----[2]<----\
|
||||
| | |
|
||||
| v |
|
||||
| [3]-----/
|
||||
|
|
||||
\---->[4]
|
||||
|
|
||||
v
|
||||
[exit]
|
||||
|
||||
GRAPH(GetGraph()) {
|
||||
CONSTANT(0, 1);
|
||||
CONSTANT(1, 10);
|
||||
CONSTANT(2, 20);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4) {
|
||||
INST(3, Opcode::Phi).u64().Inputs({{0, 0}, {3, 7}});
|
||||
INST(4, Opcode::Phi).u64().Inputs({{0, 1}, {3, 8}});
|
||||
INST(5, Opcode::Compare).b().Inputs(4, 0);
|
||||
INST(6, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(5);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 2) {
|
||||
INST(7, Opcode::Mul).u64().Inputs(3, 4);
|
||||
INST(8, Opcode::Sub).u64().Inputs(4, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, -1) {
|
||||
INST(10, Opcode::Add).u64().Inputs(2, 3);
|
||||
INST(11, Opcode::Return).u64().Inputs(10);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
|
||||
----------------------------------------------------------------
|
||||
ID INST(INPUTS) LIFE NUMBER LIFE INTERVALS ARM64 REGS
|
||||
----------------------------------------------------------------
|
||||
0. Constant 2 [2-22] R4
|
||||
1. Constant 4 [4-8] R5
|
||||
2. Constant 6 [6-24] R6
|
||||
|
||||
3. Phi (0,7) 8 [8-16][22-24] R7
|
||||
4. Phi (1,8) 8 [8-18] R8
|
||||
5. Cmp (4,0) 10 [10-12] R9
|
||||
6. If (5) 12 -
|
||||
|
||||
7. Mul (3,4) 16 [16-22] R5
|
||||
8. Sub (4,0) 18 [18-22] R9
|
||||
9. Add (2,3) 24 [24-26] R4
|
||||
```
|
||||
|
||||
## Links
|
||||
Source code:
|
||||
|
||||
[reg_alloc_linear_scan.cpp](../optimizer/optimizations/regalloc/reg_alloc_linear_scan.cpp)
|
||||
|
||||
[reg_alloc_linear_scan.h](../optimizer/optimizations/regalloc/regalloc/reg_alloc_linear_scan.h)
|
||||
|
||||
Tests:
|
||||
|
||||
[reg_alloc_linear_scan_test.cpp](../tests/reg_alloc_linear_scan_test.cpp)
|
@ -54,8 +54,6 @@
|
||||
* change.
|
||||
*
|
||||
* To add new instructions to alias analysis please consider following:
|
||||
* - IsLocalAlias method: to add instructions that create new object
|
||||
* - ParseInstruction method: to add a new instruction alias analysis works for
|
||||
* - AliasAnalysis class: to add a visitor for a new instruction that should be analyzed
|
||||
*
|
||||
* TODO(Evgenii Kudriashov): Prior to walking the graph in steps 5 and 6, We
|
||||
@ -95,14 +93,6 @@ bool AliasAnalysis::RunImpl()
|
||||
it.first->second.insert(pair.second);
|
||||
}
|
||||
|
||||
// Build graph
|
||||
for (auto pair : *copy_) {
|
||||
auto it = chains_->try_emplace(pair.first, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(pair.first.GetBase() == nullptr || pair.first.GetBase()->GetOpcode() != Opcode::NullCheck);
|
||||
ASSERT(pair.second.GetBase() == nullptr || pair.second.GetBase()->GetOpcode() != Opcode::NullCheck);
|
||||
it.first->second.push_back(pair.second);
|
||||
}
|
||||
|
||||
SolveConstraints();
|
||||
|
||||
#ifndef NDEBUG
|
||||
@ -121,11 +111,9 @@ void AliasAnalysis::Init()
|
||||
auto allocator = GetGraph()->GetLocalAllocator();
|
||||
chains_ = allocator->New<PointerMap<ArenaVector<Pointer>>>(allocator->Adapter());
|
||||
direct_ = allocator->New<PointerPairVector>(allocator->Adapter());
|
||||
copy_ = allocator->New<PointerPairVector>(allocator->Adapter());
|
||||
inputs_set_ = allocator->New<ArenaSet<Inst *>>(allocator->Adapter());
|
||||
ASSERT(chains_ != nullptr);
|
||||
ASSERT(direct_ != nullptr);
|
||||
ASSERT(copy_ != nullptr);
|
||||
ASSERT(inputs_set_ != nullptr);
|
||||
points_to_.clear();
|
||||
}
|
||||
@ -243,149 +231,9 @@ void AliasAnalysis::Dump(std::ostream *out) const
|
||||
|
||||
AliasType AliasAnalysis::CheckInstAlias(Inst *mem1, Inst *mem2) const
|
||||
{
|
||||
ASSERT(mem1->IsMemory() && mem2->IsMemory());
|
||||
Pointer p1 = {};
|
||||
Pointer p2 = {};
|
||||
|
||||
if (!ParseInstruction(mem1, &p1) || !ParseInstruction(mem2, &p2)) {
|
||||
return MAY_ALIAS;
|
||||
}
|
||||
|
||||
// Instructions with different types cannot alias each other. Handle
|
||||
// difference only in numeric types for now.
|
||||
if (IsTypeNumeric(mem1->GetType()) && IsTypeNumeric(mem2->GetType()) &&
|
||||
GetTypeSize(mem1->GetType(), GetGraph()->GetArch()) != GetTypeSize(mem2->GetType(), GetGraph()->GetArch())) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
// Instructions with a primitive type and the reference type cannot alias each other.
|
||||
if ((IsTypeNumeric(mem1->GetType()) && IsReference(mem2->GetType())) ||
|
||||
(IsTypeNumeric(mem2->GetType()) && IsReference(mem1->GetType()))) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
|
||||
return CheckMemAddress(p1, p2);
|
||||
}
|
||||
|
||||
AliasType AliasAnalysis::CheckRefAlias(Inst *ref1, Inst *ref2) const
|
||||
{
|
||||
ASSERT(ref1->GetType() == DataType::REFERENCE);
|
||||
ASSERT(ref2->GetType() == DataType::REFERENCE);
|
||||
return CheckMemAddress(Pointer::CreateObject(ref1), Pointer::CreateObject(ref2));
|
||||
}
|
||||
|
||||
/**
|
||||
* We have 5 types of pointers: OBJECT, OBJECT_FIELD, POOL_CONSTANT,
|
||||
* STATIC_FIELD and ARRAY_ELEMENT. They correspond to groups of memory storing
|
||||
* and loading instructions. Assuming that these groups cannot load the same
|
||||
* memory address (at IR level), it is true that different types of pointers
|
||||
* cannot alias each other.
|
||||
*
|
||||
* Global pointers such as STATIC_FIELD and POOL_CONSTANT are checked through
|
||||
* their unique TypeId.
|
||||
*
|
||||
* For OBJECT_FIELDs we look at objects they referenced to. If they refer to
|
||||
* the same objects then depending on TypeId they MUST_ALIAS or NO_ALIAS. If
|
||||
* the situation of the referenced objects is unclear then they MAY_ALIAS.
|
||||
*
|
||||
* The same is for ARRAY_ELEMENT. Instead of TypeId we look at index and compare them.
|
||||
*
|
||||
* All function's arguments MAY_ALIAS each other. Created objects in the
|
||||
* function may not alias arguments.
|
||||
*/
|
||||
AliasType AliasAnalysis::CheckMemAddress(const Pointer &p1, const Pointer &p2) const
|
||||
{
|
||||
if (p1.GetType() != p2.GetType()) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
|
||||
if (p1.GetType() == STATIC_FIELD || p2.GetType() == STATIC_FIELD || p1.GetType() == POOL_CONSTANT ||
|
||||
p2.GetType() == POOL_CONSTANT) {
|
||||
if (p1.HasSameOffset(p2)) {
|
||||
return MUST_ALIAS;
|
||||
}
|
||||
return NO_ALIAS;
|
||||
}
|
||||
|
||||
auto base_obj1 = Pointer::CreateObject(p1.GetBase());
|
||||
auto base_obj2 = Pointer::CreateObject(p2.GetBase());
|
||||
ASSERT_DO(points_to_.find(base_obj1) != points_to_.end(),
|
||||
(std::cerr << "Undefined inst in AliasAnalysis: ", p1.GetBase()->Dump(&std::cerr)));
|
||||
ASSERT_DO(points_to_.find(base_obj2) != points_to_.end(),
|
||||
(std::cerr << "Undefined inst in AliasAnalysis: ", p2.GetBase()->Dump(&std::cerr)));
|
||||
auto &aliases1 = points_to_.at(base_obj1);
|
||||
auto &aliases2 = points_to_.at(base_obj2);
|
||||
// Find the intersection
|
||||
const Pointer *intersection = nullptr;
|
||||
for (auto &alias : aliases1) {
|
||||
if (aliases2.find(alias) != aliases2.end()) {
|
||||
intersection = &alias;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The only common intersection
|
||||
if (intersection != nullptr && aliases1.size() == 1 && aliases2.size() == 1) {
|
||||
return SingleIntersectionAliasing(p1, p2, intersection);
|
||||
}
|
||||
|
||||
// Empty intersection: check that both addresses are not parameters
|
||||
if (intersection == nullptr) {
|
||||
// If at least one set of aliases consists of only local aliases then there is NO_ALIAS
|
||||
auto is_outer = [](Pointer const &p) { return !p.IsLocal(); };
|
||||
if (std::find_if(aliases1.begin(), aliases1.end(), is_outer) == aliases1.end() ||
|
||||
std::find_if(aliases2.begin(), aliases2.end(), is_outer) == aliases2.end()) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
// Different fields cannot alias each other even if they are not created locally
|
||||
if (p1.GetType() == OBJECT_FIELD && !p1.HasSameOffset(p2)) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
if (p1.GetType() == ARRAY_ELEMENT) {
|
||||
auto equal = IsSameOffsets(p1.GetIdx(), p2.GetIdx());
|
||||
// If it is known that indices are different OR Imm indices are different then there is
|
||||
// no alias. If they are both different we can't certainly say so.
|
||||
if ((equal == Trilean::FALSE && p1.GetImm() == p2.GetImm()) ||
|
||||
(equal == Trilean::TRUE && p1.GetImm() != p2.GetImm())) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MAY_ALIAS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks aliasing if P1 and P2 point to the one single object.
|
||||
*/
|
||||
/* static */
|
||||
AliasType AliasAnalysis::SingleIntersectionAliasing(const Pointer &p1, const Pointer &p2, const Pointer *intersection)
|
||||
{
|
||||
ASSERT(p1.GetType() == p2.GetType());
|
||||
switch (p1.GetType()) {
|
||||
case ARRAY_ELEMENT:
|
||||
if (IsSameOffsets(p1.GetIdx(), p2.GetIdx()) != Trilean::TRUE) {
|
||||
return MAY_ALIAS;
|
||||
}
|
||||
if (p1.GetImm() != p2.GetImm()) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
break;
|
||||
case OBJECT_FIELD:
|
||||
if (!p1.HasSameOffset(p2)) {
|
||||
return NO_ALIAS;
|
||||
}
|
||||
break;
|
||||
case OBJECT:
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
if (intersection->IsVolatile()) {
|
||||
return MAY_ALIAS;
|
||||
}
|
||||
return MUST_ALIAS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we propagate solutions obtained from direct constraints through copy
|
||||
* constraints e.g: we have a node A with solution {a} and the node A was
|
||||
@ -480,651 +328,9 @@ void AliasAnalysis::SolveConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the address from instruction that should be checked on alias
|
||||
*/
|
||||
bool AliasAnalysis::ParseInstruction(Inst *inst, Pointer *pointer)
|
||||
{
|
||||
Pointer p {};
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadArray:
|
||||
case Opcode::LoadArrayI:
|
||||
case Opcode::StoreArray:
|
||||
case Opcode::StoreArrayI:
|
||||
p = ParseArrayElement(inst);
|
||||
break;
|
||||
case Opcode::LoadString:
|
||||
case Opcode::LoadType:
|
||||
case Opcode::UnresolvedLoadType:
|
||||
p = ParsePoolConstant(inst);
|
||||
break;
|
||||
case Opcode::LoadStatic:
|
||||
case Opcode::StoreStatic:
|
||||
case Opcode::UnresolvedLoadStatic:
|
||||
case Opcode::UnresolvedStoreStatic:
|
||||
p = ParseStaticField(inst);
|
||||
break;
|
||||
case Opcode::LoadObject:
|
||||
case Opcode::StoreObject:
|
||||
case Opcode::UnresolvedLoadObject:
|
||||
case Opcode::UnresolvedStoreObject:
|
||||
p = ParseObjectField(inst);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto base = p.GetBase();
|
||||
if (base != nullptr) {
|
||||
// Currently unhandled and return always MAY_ALIAS
|
||||
if (base->GetOpcode() == Opcode::LoadArrayPair || base->GetOpcode() == Opcode::LoadArrayPairI ||
|
||||
base->GetOpcode() == Opcode::LoadPairPart || base->GetOpcode() == Opcode::CatchPhi ||
|
||||
base->GetOpcode() == Opcode::Load || base->GetOpcode() == Opcode::LoadI ||
|
||||
base->GetOpcode() == Opcode::Store || base->GetOpcode() == Opcode::StoreI) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*pointer = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
Pointer AliasAnalysis::ParseArrayElement(Inst *inst)
|
||||
{
|
||||
uint32_t imm = 0;
|
||||
Inst *offset = nullptr;
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadArray:
|
||||
case Opcode::StoreArray:
|
||||
offset = inst->GetDataFlowInput(1);
|
||||
break;
|
||||
case Opcode::LoadArrayI:
|
||||
imm = inst->CastToLoadArrayI()->GetImm();
|
||||
break;
|
||||
case Opcode::StoreArrayI:
|
||||
imm = inst->CastToStoreArrayI()->GetImm();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
auto base = inst->GetDataFlowInput(0);
|
||||
return Pointer::CreateArrayElement(base, offset, imm);
|
||||
}
|
||||
|
||||
Pointer AliasAnalysis::ParsePoolConstant(Inst *inst)
|
||||
{
|
||||
uint32_t type_id = 0;
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadString:
|
||||
type_id = inst->CastToLoadString()->GetTypeId();
|
||||
break;
|
||||
case Opcode::LoadType:
|
||||
type_id = inst->CastToLoadType()->GetTypeId();
|
||||
break;
|
||||
case Opcode::UnresolvedLoadType:
|
||||
type_id = inst->CastToUnresolvedLoadType()->GetTypeId();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return Pointer::CreatePoolConstant(type_id);
|
||||
}
|
||||
|
||||
Pointer AliasAnalysis::ParseStaticField(Inst *inst)
|
||||
{
|
||||
uint32_t type_id = 0;
|
||||
void *type_ptr = nullptr;
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadStatic:
|
||||
type_id = inst->CastToLoadStatic()->GetTypeId();
|
||||
type_ptr = inst->CastToLoadStatic()->GetObjField();
|
||||
break;
|
||||
case Opcode::UnresolvedLoadStatic:
|
||||
type_id = inst->CastToUnresolvedLoadStatic()->GetTypeId();
|
||||
break;
|
||||
case Opcode::StoreStatic:
|
||||
type_id = inst->CastToStoreStatic()->GetTypeId();
|
||||
type_ptr = inst->CastToStoreStatic()->GetObjField();
|
||||
break;
|
||||
case Opcode::UnresolvedStoreStatic:
|
||||
type_id = inst->CastToUnresolvedStoreStatic()->GetTypeId();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return Pointer::CreateStaticField(type_id, type_ptr);
|
||||
}
|
||||
|
||||
Pointer AliasAnalysis::ParseObjectField(Inst *inst)
|
||||
{
|
||||
uint32_t type_id = 0;
|
||||
void *type_ptr = nullptr;
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadObject:
|
||||
type_id = inst->CastToLoadObject()->GetTypeId();
|
||||
type_ptr = inst->CastToLoadObject()->GetObjField();
|
||||
break;
|
||||
case Opcode::UnresolvedLoadObject:
|
||||
type_id = inst->CastToUnresolvedLoadObject()->GetTypeId();
|
||||
break;
|
||||
case Opcode::StoreObject:
|
||||
type_id = inst->CastToStoreObject()->GetTypeId();
|
||||
type_ptr = inst->CastToStoreObject()->GetObjField();
|
||||
break;
|
||||
case Opcode::UnresolvedStoreObject:
|
||||
type_id = inst->CastToUnresolvedStoreObject()->GetTypeId();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
auto base = inst->GetDataFlowInput(0);
|
||||
return Pointer::CreateObjectField(base, type_id, type_ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares offsets. Since we have a situation when we cannot determine either equal offsets or not,
|
||||
* we use three valued logic. It is handy when we want to know whether offsets are definitely
|
||||
* different or definitely equal.
|
||||
*/
|
||||
/* static */
|
||||
AliasAnalysis::Trilean AliasAnalysis::IsSameOffsets(const Inst *off1, const Inst *off2)
|
||||
{
|
||||
if (off1 == off2) {
|
||||
return Trilean::TRUE;
|
||||
}
|
||||
if (off1 == nullptr || off2 == nullptr) {
|
||||
return Trilean::FALSE;
|
||||
}
|
||||
|
||||
if (off1->GetVN() != INVALID_VN && off2->GetVN() != INVALID_VN && off1->GetVN() == off2->GetVN()) {
|
||||
return Trilean::TRUE;
|
||||
}
|
||||
|
||||
if (off1->IsConst() && off2->IsConst() &&
|
||||
off1->CastToConstant()->GetRawValue() != off2->CastToConstant()->GetRawValue()) {
|
||||
return Trilean::FALSE;
|
||||
}
|
||||
|
||||
return Trilean::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions that definitely are not an alias of anything.
|
||||
*/
|
||||
void AliasAnalysis::VisitNullPtr(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
void AliasAnalysis::VisitInitObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
void AliasAnalysis::VisitNewObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
void AliasAnalysis::VisitNewArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
void AliasAnalysis::VisitMultiArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions that can introduce references that are an alias of
|
||||
* something already existed.
|
||||
*/
|
||||
void AliasAnalysis::VisitParameter(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitIntrinsic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitBuiltin(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitCallStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitUnresolvedCallStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitCallVirtual(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitUnresolvedCallVirtual(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitGetManagedClassObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
static_cast<AliasAnalysis *>(v)->AddDirectEdge(Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions that introduce static fields (global variables).
|
||||
*/
|
||||
void AliasAnalysis::VisitLoadStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToLoadStatic();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Pointer sfield = Pointer::CreateStaticField(type_id, typed_inst->GetObjField());
|
||||
|
||||
sfield.SetVolatile(typed_inst->GetVolatile());
|
||||
|
||||
visitor->AddDirectEdge(sfield);
|
||||
visitor->AddCopyEdge(sfield, Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitUnresolvedLoadStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToUnresolvedLoadStatic();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Pointer sfield = Pointer::CreateStaticField(type_id);
|
||||
|
||||
sfield.SetVolatile(IsVolatileMemInst(typed_inst));
|
||||
|
||||
visitor->AddDirectEdge(sfield);
|
||||
visitor->AddCopyEdge(sfield, Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToStoreStatic();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Pointer sfield = Pointer::CreateStaticField(type_id, typed_inst->GetObjField());
|
||||
|
||||
sfield.SetVolatile(typed_inst->GetVolatile());
|
||||
|
||||
visitor->AddDirectEdge(sfield);
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(0)), sfield);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitUnresolvedStoreStatic(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToUnresolvedStoreStatic();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Pointer sfield = Pointer::CreateStaticField(type_id);
|
||||
|
||||
sfield.SetVolatile(IsVolatileMemInst(typed_inst));
|
||||
|
||||
visitor->AddDirectEdge(sfield);
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(0)), sfield);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions that introduce unique constant references (global constants).
|
||||
*/
|
||||
void AliasAnalysis::VisitLoadClass(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToLoadClass()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitLoadAndInitClass(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToLoadAndInitClass()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitUnresolvedLoadAndInitClass(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToUnresolvedLoadAndInitClass()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitLoadString(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToLoadString()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitLoadConstArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToLoadConstArray()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitLoadType(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToLoadType()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
void AliasAnalysis::VisitUnresolvedLoadType(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() == DataType::REFERENCE) {
|
||||
uint32_t type_id = inst->CastToUnresolvedLoadType()->GetTypeId();
|
||||
static_cast<AliasAnalysis *>(v)->AddConstantDirectEdge(inst, type_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions that introduce aliases.
|
||||
*/
|
||||
void AliasAnalysis::VisitLoadArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
Inst *arr = inst->GetDataFlowInput(0);
|
||||
Inst *idx = inst->GetDataFlowInput(1);
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, idx);
|
||||
Pointer val = Pointer::CreateObject(inst);
|
||||
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(elem, val);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
Inst *arr = inst->GetDataFlowInput(0);
|
||||
Inst *idx = inst->GetDataFlowInput(1);
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, idx);
|
||||
Pointer val = Pointer::CreateObject(inst->GetDataFlowInput(2U));
|
||||
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(val, elem);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitLoadArrayI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
Inst *arr = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, nullptr, inst->CastToLoadArrayI()->GetImm());
|
||||
Pointer val = Pointer::CreateObject(inst);
|
||||
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(elem, val);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreArrayI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
Inst *arr = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, nullptr, inst->CastToStoreArrayI()->GetImm());
|
||||
Pointer val = Pointer::CreateObject(inst->GetDataFlowInput(1));
|
||||
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(val, elem);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitLoadArrayPair(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto *load = inst->CastToLoadArrayPair();
|
||||
Inst *arr = load->GetDataFlowInput(load->GetArray());
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
for (auto &user : load->GetUsers()) {
|
||||
ASSERT(user.GetInst()->GetOpcode() == Opcode::LoadPairPart);
|
||||
auto uinst = user.GetInst()->CastToLoadPairPart();
|
||||
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, load->GetIndex(), uinst->GetImm());
|
||||
Pointer val = Pointer::CreateObject(uinst);
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(elem, val);
|
||||
visitor->AddCopyEdge(elem, Pointer::CreateObject(load));
|
||||
}
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreArrayPair(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto *store = inst->CastToStoreArrayPair();
|
||||
Inst *arr = store->GetDataFlowInput(store->GetArray());
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer el_fst = Pointer::CreateArrayElement(arr, store->GetIndex());
|
||||
Pointer el_snd = Pointer::CreateArrayElement(arr, store->GetIndex(), 1);
|
||||
Pointer val_fst = Pointer::CreateObject(store->GetDataFlowInput(store->GetStoredValue(0)));
|
||||
Pointer val_snd = Pointer::CreateObject(store->GetDataFlowInput(store->GetStoredValue(1)));
|
||||
|
||||
visitor->AddCopyEdge(obj, el_fst);
|
||||
visitor->AddCopyEdge(obj, el_snd);
|
||||
visitor->AddCopyEdge(val_fst, el_fst);
|
||||
visitor->AddCopyEdge(val_snd, el_snd);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitLoadArrayPairI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto *load = inst->CastToLoadArrayPairI();
|
||||
Inst *arr = load->GetDataFlowInput(load->GetArray());
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
for (auto &user : load->GetUsers()) {
|
||||
ASSERT(user.GetInst()->GetOpcode() == Opcode::LoadPairPart);
|
||||
auto uinst = user.GetInst()->CastToLoadPairPart();
|
||||
|
||||
Pointer elem = Pointer::CreateArrayElement(arr, nullptr, load->GetImm() + uinst->GetImm());
|
||||
Pointer val = Pointer::CreateObject(uinst);
|
||||
visitor->AddCopyEdge(obj, elem);
|
||||
visitor->AddCopyEdge(elem, val);
|
||||
visitor->AddCopyEdge(elem, Pointer::CreateObject(load));
|
||||
}
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreArrayPairI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto *store = inst->CastToStoreArrayPairI();
|
||||
Inst *arr = store->GetDataFlowInput(store->GetArray());
|
||||
Pointer obj = Pointer::CreateObject(arr);
|
||||
Pointer el_fst = Pointer::CreateArrayElement(arr, nullptr, store->GetImm());
|
||||
Pointer el_snd = Pointer::CreateArrayElement(arr, nullptr, store->GetImm() + 1);
|
||||
Pointer val_fst = Pointer::CreateObject(store->GetDataFlowInput(store->GetFirstValue()));
|
||||
Pointer val_snd = Pointer::CreateObject(store->GetDataFlowInput(store->GetSecondValue()));
|
||||
|
||||
visitor->AddCopyEdge(obj, el_fst);
|
||||
visitor->AddCopyEdge(obj, el_snd);
|
||||
visitor->AddCopyEdge(val_fst, el_fst);
|
||||
visitor->AddCopyEdge(val_snd, el_snd);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitLoadObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToLoadObject();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Inst *dfobj = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(dfobj);
|
||||
Pointer ifield = Pointer::CreateObjectField(dfobj, type_id, typed_inst->GetObjField());
|
||||
Pointer to = Pointer::CreateObject(inst);
|
||||
|
||||
ifield.SetVolatile(typed_inst->GetVolatile());
|
||||
|
||||
visitor->AddCopyEdge(obj, ifield);
|
||||
visitor->AddCopyEdge(ifield, to);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitUnresolvedLoadObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToUnresolvedLoadObject();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Inst *dfobj = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(dfobj);
|
||||
Pointer ifield = Pointer::CreateObjectField(dfobj, type_id);
|
||||
Pointer to = Pointer::CreateObject(inst);
|
||||
|
||||
ifield.SetVolatile(IsVolatileMemInst(typed_inst));
|
||||
|
||||
visitor->AddCopyEdge(obj, ifield);
|
||||
visitor->AddCopyEdge(ifield, to);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitStoreObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToStoreObject();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Inst *dfobj = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(dfobj);
|
||||
Pointer ifield = Pointer::CreateObjectField(dfobj, type_id, typed_inst->GetObjField());
|
||||
Pointer val = Pointer::CreateObject(inst->GetDataFlowInput(1));
|
||||
|
||||
ifield.SetVolatile(typed_inst->GetVolatile());
|
||||
|
||||
visitor->AddCopyEdge(obj, ifield);
|
||||
visitor->AddCopyEdge(val, ifield);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitUnresolvedStoreObject(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto typed_inst = inst->CastToUnresolvedStoreObject();
|
||||
uint32_t type_id = typed_inst->GetTypeId();
|
||||
Inst *dfobj = inst->GetDataFlowInput(0);
|
||||
Pointer obj = Pointer::CreateObject(dfobj);
|
||||
Pointer ifield = Pointer::CreateObjectField(dfobj, type_id);
|
||||
Pointer val = Pointer::CreateObject(inst->GetDataFlowInput(1));
|
||||
|
||||
ifield.SetVolatile(IsVolatileMemInst(inst));
|
||||
|
||||
visitor->AddCopyEdge(obj, ifield);
|
||||
visitor->AddCopyEdge(val, ifield);
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitCatchPhi(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
auto inputs_set = visitor->GetClearInputsSet();
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
inputs_set->insert(inst->GetDataFlowInput(i));
|
||||
}
|
||||
|
||||
for (auto input_inst : *inputs_set) {
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(input_inst), Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitPhi(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(i)), Pointer::CreateObject(inst));
|
||||
}
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitSelect(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
ASSERT(inst->GetInputsCount() == 4U);
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(0)), Pointer::CreateObject(inst));
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(1)), Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitSelectImm(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
ASSERT(inst->GetInputsCount() == 3U);
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(0)), Pointer::CreateObject(inst));
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(1)), Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitMov(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->GetType() != DataType::REFERENCE) {
|
||||
return;
|
||||
}
|
||||
auto visitor = static_cast<AliasAnalysis *>(v);
|
||||
visitor->AddCopyEdge(Pointer::CreateObject(inst->GetDataFlowInput(0)), Pointer::CreateObject(inst));
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitCastAnyTypeValue(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
@ -1133,33 +339,4 @@ void AliasAnalysis::VisitCastAnyTypeValue(GraphVisitor *v, Inst *inst)
|
||||
}
|
||||
}
|
||||
|
||||
void AliasAnalysis::VisitDefault([[maybe_unused]] Inst *inst)
|
||||
{
|
||||
/* Ignore the following instructions with REFERENCE type intentionally */
|
||||
switch (inst->GetOpcode()) {
|
||||
// Handled on its input
|
||||
case Opcode::LoadPairPart:
|
||||
// No passes that check class references aliasing
|
||||
case Opcode::GetInstanceClass:
|
||||
case Opcode::ClassImmediate:
|
||||
// TODO(ekudriashov): Probably should be added
|
||||
case Opcode::Monitor:
|
||||
// Mitigated by using GetDataFlowInput
|
||||
case Opcode::NullCheck:
|
||||
// Mitigated by using GetDataFlowInput
|
||||
case Opcode::RefTypeCheck:
|
||||
// Irrelevant for analysis
|
||||
case Opcode::Return:
|
||||
// TODO(compiler team): support Load, Store
|
||||
case Opcode::Load:
|
||||
case Opcode::LoadI:
|
||||
case Opcode::Store:
|
||||
case Opcode::StoreI:
|
||||
return;
|
||||
default:
|
||||
ASSERT_DO(inst->GetType() != DataType::REFERENCE,
|
||||
(std::cerr << "Unsupported instruction in alias analysis: ", inst->Dump(&std::cerr)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // namespace panda::compiler
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
Pointer(PointerType type, const Inst *base, const Inst *idx, uint64_t imm, const void *type_ptr)
|
||||
: type_(type), base_(base), idx_(idx), imm_(imm), type_ptr_(type_ptr), volatile_(false)
|
||||
{
|
||||
local_ = base == nullptr ? false : IsLocalAlias(base);
|
||||
local_ = false;
|
||||
};
|
||||
|
||||
static Pointer CreateObject(const Inst *base)
|
||||
@ -67,16 +67,6 @@ public:
|
||||
return Pointer(OBJECT, base, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
static Pointer CreatePoolConstant(uint32_t type_id)
|
||||
{
|
||||
return Pointer(POOL_CONSTANT, nullptr, nullptr, type_id, nullptr);
|
||||
}
|
||||
|
||||
static Pointer CreateStaticField(uint32_t type_id, const void *type_ptr = nullptr)
|
||||
{
|
||||
return Pointer(STATIC_FIELD, nullptr, nullptr, type_id, type_ptr);
|
||||
}
|
||||
|
||||
static Pointer CreateObjectField(const Inst *base, uint32_t type_id, const void *type_ptr = nullptr)
|
||||
{
|
||||
return Pointer(OBJECT_FIELD, base, nullptr, type_id, type_ptr);
|
||||
@ -128,11 +118,6 @@ public:
|
||||
return volatile_;
|
||||
}
|
||||
|
||||
void SetVolatile(bool is_volatile)
|
||||
{
|
||||
volatile_ = is_volatile;
|
||||
}
|
||||
|
||||
void Dump(std::ostream *out) const;
|
||||
|
||||
bool HasSameOffset(const Pointer &p) const
|
||||
@ -143,78 +128,6 @@ public:
|
||||
return type_ptr_ == p.type_ptr_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns true if reference is used only in scope of current function: it
|
||||
* must be created in the function and must not escape its scope
|
||||
*/
|
||||
static bool IsLocalAlias(const Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::NullPtr:
|
||||
return true;
|
||||
case Opcode::NewArray:
|
||||
case Opcode::MultiArray:
|
||||
case Opcode::NewObject:
|
||||
case Opcode::InitObject:
|
||||
return !IsEscapingAlias(inst);
|
||||
case Opcode::NullCheck:
|
||||
case Opcode::RefTypeCheck:
|
||||
UNREACHABLE();
|
||||
/* fall-through */
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a reference escapes the scope of current function:
|
||||
* Various function calls, constructors and stores to another objects' fields, arrays
|
||||
*/
|
||||
static bool IsEscapingAlias(const Inst *inst)
|
||||
{
|
||||
for (auto &user : inst->GetUsers()) {
|
||||
switch (user.GetInst()->GetOpcode()) {
|
||||
case Opcode::NullCheck:
|
||||
case Opcode::RefTypeCheck:
|
||||
if (IsEscapingAlias(user.GetInst())) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case Opcode::StoreObject:
|
||||
case Opcode::UnresolvedStoreObject:
|
||||
case Opcode::StoreArray:
|
||||
case Opcode::StoreArrayI:
|
||||
case Opcode::StoreArrayPair:
|
||||
case Opcode::StoreArrayPairI:
|
||||
if (user.GetIndex() != 0) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case Opcode::StoreStatic:
|
||||
case Opcode::UnresolvedStoreStatic:
|
||||
case Opcode::InitObject:
|
||||
case Opcode::InitClass:
|
||||
case Opcode::LoadAndInitClass:
|
||||
case Opcode::UnresolvedLoadAndInitClass:
|
||||
case Opcode::CallStatic:
|
||||
case Opcode::UnresolvedCallStatic:
|
||||
case Opcode::CallVirtual:
|
||||
case Opcode::UnresolvedCallVirtual:
|
||||
case Opcode::CallDynamic:
|
||||
return true;
|
||||
case Opcode::Intrinsic:
|
||||
if (inst->GetFlagsMask() != 0) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
PointerType type_;
|
||||
const Inst *base_;
|
||||
@ -272,8 +185,6 @@ public:
|
||||
}
|
||||
|
||||
AliasType CheckInstAlias(Inst *mem1, Inst *mem2) const;
|
||||
AliasType CheckRefAlias(Inst *ref1, Inst *ref2) const;
|
||||
AliasType CheckMemAddress(const Pointer &p1, const Pointer &p2) const;
|
||||
void Dump(std::ostream *out) const;
|
||||
|
||||
/**
|
||||
@ -283,83 +194,16 @@ public:
|
||||
*/
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override;
|
||||
|
||||
/**
|
||||
* Instructions that definitely are not an alias of anything.
|
||||
*/
|
||||
static void VisitNullPtr(GraphVisitor *v, Inst *inst);
|
||||
static void VisitInitObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitNewObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitNewArray(GraphVisitor *v, Inst *inst);
|
||||
static void VisitMultiArray(GraphVisitor *v, Inst *inst);
|
||||
/**
|
||||
* Instructions that can introduce references that are an alias of
|
||||
* something already existed.
|
||||
*/
|
||||
static void VisitParameter(GraphVisitor *v, Inst *inst);
|
||||
static void VisitIntrinsic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitBuiltin(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCallStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedCallStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCallVirtual(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedCallVirtual(GraphVisitor *v, Inst *inst);
|
||||
static void VisitGetManagedClassObject(GraphVisitor *v, Inst *inst);
|
||||
/**
|
||||
* Instructions that introduce static fields (global variables).
|
||||
*/
|
||||
static void VisitLoadStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedLoadStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreStatic(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedStoreStatic(GraphVisitor *v, Inst *inst);
|
||||
|
||||
/**
|
||||
* Instructions that introduce unique constant references (global constants).
|
||||
*/
|
||||
static void VisitLoadClass(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadAndInitClass(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedLoadAndInitClass(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadString(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadConstArray(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadType(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedLoadType(GraphVisitor *v, Inst *inst);
|
||||
|
||||
/**
|
||||
* Instructions that introduce aliases.
|
||||
*/
|
||||
static void VisitLoadArray(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreArray(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadArrayI(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreArrayI(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadArrayPair(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreArrayPair(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadArrayPairI(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreArrayPairI(GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedLoadObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitUnresolvedStoreObject(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCatchPhi(GraphVisitor *v, Inst *inst);
|
||||
static void VisitPhi(GraphVisitor *v, Inst *inst);
|
||||
static void VisitSelect(GraphVisitor *v, Inst *inst);
|
||||
static void VisitSelectImm(GraphVisitor *v, Inst *inst);
|
||||
static void VisitMov(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCastAnyTypeValue(GraphVisitor *v, Inst *inst);
|
||||
void VisitDefault([[maybe_unused]] Inst *inst) override;
|
||||
|
||||
void AddDirectEdge(const Pointer &p)
|
||||
{
|
||||
direct_->push_back({p, p});
|
||||
}
|
||||
|
||||
void AddConstantDirectEdge(Inst *inst, uint32_t id)
|
||||
{
|
||||
direct_->push_back({Pointer::CreateObject(inst), Pointer::CreatePoolConstant(id)});
|
||||
}
|
||||
|
||||
void AddCopyEdge(const Pointer &from, const Pointer &to)
|
||||
{
|
||||
copy_->push_back({from, to});
|
||||
}
|
||||
|
||||
ArenaSet<Inst *> *GetClearInputsSet()
|
||||
{
|
||||
inputs_set_->clear();
|
||||
@ -376,16 +220,6 @@ private:
|
||||
|
||||
void SolveConstraints();
|
||||
|
||||
/* Methods to extract pointer from instruction */
|
||||
static bool ParseInstruction(Inst *inst, Pointer *pointer);
|
||||
static Pointer ParseArrayElement(Inst *inst);
|
||||
static Pointer ParsePoolConstant(Inst *inst);
|
||||
static Pointer ParseStaticField(Inst *inst);
|
||||
static Pointer ParseObjectField(Inst *inst);
|
||||
|
||||
static Trilean IsSameOffsets(const Inst *off1, const Inst *off2);
|
||||
static AliasType SingleIntersectionAliasing(const Pointer &p1, const Pointer &p2, const Pointer *intersection);
|
||||
|
||||
void DumpChains(std::ostream *out) const;
|
||||
|
||||
private:
|
||||
@ -394,7 +228,6 @@ private:
|
||||
// Local containers:
|
||||
PointerMap<ArenaVector<Pointer>> *chains_ {nullptr};
|
||||
PointerPairVector *direct_ {nullptr};
|
||||
PointerPairVector *copy_ {nullptr};
|
||||
ArenaSet<Inst *> *inputs_set_ {nullptr};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
@ -24,35 +24,15 @@
|
||||
namespace panda::compiler {
|
||||
BoundsRange::BoundsRange(int64_t val, DataType::Type type) : BoundsRange(val, val, nullptr, type) {}
|
||||
|
||||
static bool IsStringLength(const Inst *inst)
|
||||
{
|
||||
if (inst->GetOpcode() != Opcode::Shr || inst->GetInput(0).GetInst()->GetOpcode() != Opcode::LenArray) {
|
||||
return false;
|
||||
}
|
||||
auto c = inst->GetInput(1).GetInst();
|
||||
return c->IsConst() && c->CastToConstant()->GetInt64Value() == 1;
|
||||
}
|
||||
|
||||
static bool IsLenArray(const Inst *inst)
|
||||
{
|
||||
return inst->GetOpcode() == Opcode::LenArray || IsStringLength(inst);
|
||||
}
|
||||
|
||||
BoundsRange::BoundsRange(int64_t left, int64_t right, const Inst *inst, [[maybe_unused]] DataType::Type type)
|
||||
: left_(left), right_(right), len_array_(inst)
|
||||
{
|
||||
ASSERT(inst == nullptr || IsLenArray(inst));
|
||||
ASSERT(inst == nullptr);
|
||||
ASSERT(left <= right);
|
||||
ASSERT(GetMin(type) <= left);
|
||||
ASSERT(right <= GetMax(type));
|
||||
}
|
||||
|
||||
void BoundsRange::SetLenArray(const Inst *inst)
|
||||
{
|
||||
ASSERT(inst != nullptr && IsLenArray(inst));
|
||||
len_array_ = inst;
|
||||
}
|
||||
|
||||
int64_t BoundsRange::GetLeft() const
|
||||
{
|
||||
return left_;
|
||||
@ -63,106 +43,6 @@ int64_t BoundsRange::GetRight() const
|
||||
return right_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neg current range. Type of current range is saved.
|
||||
* NEG([x1, x2]) = [-x2, -x1]
|
||||
*/
|
||||
BoundsRange BoundsRange::Neg() const
|
||||
{
|
||||
auto right = left_ == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : -left_;
|
||||
auto left = right_ == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : -right_;
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abs current range. Type of current range is saved.
|
||||
* 1. if (x1 >= 0 && x2 >= 0) -> ABS([x1, x2]) = [x1, x2]
|
||||
* 2. if (x1 < 0 && x2 < 0) -> ABS([x1, x2]) = [-x2, -x1]
|
||||
* 3. if (x1 * x2 < 0) -> ABS([x1, x2]) = [0, MAX(ABS(x1), ABS(x2))]
|
||||
*/
|
||||
BoundsRange BoundsRange::Abs() const
|
||||
{
|
||||
auto val1 = left_ == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : std::abs(left_);
|
||||
auto val2 = right_ == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : std::abs(right_);
|
||||
auto right = std::max(val1, val2);
|
||||
auto left = 0;
|
||||
// NOLINTNEXTLINE (hicpp-signed-bitwise)
|
||||
if ((left_ ^ right_) >= 0) {
|
||||
left = std::min(val1, val2);
|
||||
}
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to current range. Type of current range is saved.
|
||||
* [x1, x2] + [y1, y2] = [x1 + y1, x2 + y2]
|
||||
*/
|
||||
BoundsRange BoundsRange::Add(const BoundsRange &range) const
|
||||
{
|
||||
auto left = AddWithOverflowCheck(left_, range.GetLeft());
|
||||
auto right = AddWithOverflowCheck(right_, range.GetRight());
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract from current range.
|
||||
* [x1, x2] - [y1, y2] = [x1 - y2, x2 - y1]
|
||||
*/
|
||||
BoundsRange BoundsRange::Sub(const BoundsRange &range) const
|
||||
{
|
||||
auto neg_right = (range.GetRight() == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : -range.GetRight());
|
||||
auto left = AddWithOverflowCheck(left_, neg_right);
|
||||
auto neg_left = (range.GetLeft() == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : -range.GetLeft());
|
||||
auto right = AddWithOverflowCheck(right_, neg_left);
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply current range.
|
||||
* [x1, x2] * [y1, y2] = [min(x1y1, x1y2, x2y1, x2y2), max(x1y1, x1y2, x2y1, x2y2)]
|
||||
*/
|
||||
BoundsRange BoundsRange::Mul(const BoundsRange &range) const
|
||||
{
|
||||
auto m1 = MulWithOverflowCheck(left_, range.GetLeft());
|
||||
auto m2 = MulWithOverflowCheck(left_, range.GetRight());
|
||||
auto m3 = MulWithOverflowCheck(right_, range.GetLeft());
|
||||
auto m4 = MulWithOverflowCheck(right_, range.GetRight());
|
||||
auto left = std::min(m1, std::min(m2, std::min(m3, m4)));
|
||||
auto right = std::max(m1, std::max(m2, std::max(m3, m4)));
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Division current range on constant range.
|
||||
* [x1, x2] / [y, y] = [min(x1/y, x2/y), max(x1/y, x2/y)]
|
||||
*/
|
||||
BoundsRange BoundsRange::Div(const BoundsRange &range) const
|
||||
{
|
||||
if (range.GetLeft() != range.GetRight() || range.GetLeft() == 0) {
|
||||
return BoundsRange();
|
||||
}
|
||||
auto m1 = DivWithOverflowCheck(left_, range.GetLeft());
|
||||
auto m2 = DivWithOverflowCheck(right_, range.GetLeft());
|
||||
auto left = std::min(m1, m2);
|
||||
auto right = std::max(m1, m2);
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modulus of current range.
|
||||
* [x1, x2] % [y1, y2] = [0, y2 - 1]
|
||||
*/
|
||||
/* static */
|
||||
BoundsRange BoundsRange::Mod(const BoundsRange &range)
|
||||
{
|
||||
if (range.GetRight() == 0) {
|
||||
return BoundsRange();
|
||||
}
|
||||
auto left = 0;
|
||||
auto right = (range.GetRight() < 0 ? -range.GetRight() : range.GetRight()) - 1;
|
||||
return BoundsRange(left, right);
|
||||
}
|
||||
|
||||
bool BoundsRange::IsConst() const
|
||||
{
|
||||
return left_ == right_;
|
||||
@ -185,11 +65,7 @@ bool BoundsRange::IsLess(const BoundsRange &range) const
|
||||
|
||||
bool BoundsRange::IsLess(const Inst *inst) const
|
||||
{
|
||||
ASSERT(inst != nullptr);
|
||||
if (!IsLenArray(inst)) {
|
||||
return false;
|
||||
}
|
||||
return inst == len_array_;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BoundsRange::IsMore(const BoundsRange &range) const
|
||||
@ -481,60 +357,6 @@ BoundsRange::RangePair BoundsRange::TryNarrowBoundsByCC(ConditionCode cc, Bounds
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return (left + right) or if overflows or underflows return max or min of range type.
|
||||
*/
|
||||
int64_t BoundsRange::AddWithOverflowCheck(int64_t left, int64_t right)
|
||||
{
|
||||
if (right == 0) {
|
||||
return left;
|
||||
}
|
||||
if (right > 0) {
|
||||
if (left <= (MAX_RANGE_VALUE - right)) {
|
||||
// No overflow.
|
||||
return left + right;
|
||||
}
|
||||
return MAX_RANGE_VALUE;
|
||||
}
|
||||
if (left >= (MIN_RANGE_VALUE - right)) {
|
||||
// No overflow.
|
||||
return left + right;
|
||||
}
|
||||
return MIN_RANGE_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return (left * right) or if overflows or underflows return max or min of range type.
|
||||
*/
|
||||
int64_t BoundsRange::MulWithOverflowCheck(int64_t left, int64_t right)
|
||||
{
|
||||
if (left == 0 || right == 0) {
|
||||
return 0;
|
||||
}
|
||||
int64_t left_abs = (left == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : std::abs(left));
|
||||
int64_t right_abs = (right == MIN_RANGE_VALUE ? MAX_RANGE_VALUE : std::abs(right));
|
||||
if (left_abs <= (MAX_RANGE_VALUE / right_abs)) {
|
||||
// No overflow.
|
||||
return left * right;
|
||||
}
|
||||
if ((right > 0 && left > 0) || (right < 0 && left < 0)) {
|
||||
return MAX_RANGE_VALUE;
|
||||
}
|
||||
return MIN_RANGE_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return (left / right) or MIN_RANGE VALUE for MIN_RANGE_VALUE / -1.
|
||||
*/
|
||||
int64_t BoundsRange::DivWithOverflowCheck(int64_t left, int64_t right)
|
||||
{
|
||||
ASSERT(right != 0);
|
||||
if (left == MIN_RANGE_VALUE && right == -1) {
|
||||
return MIN_RANGE_VALUE;
|
||||
}
|
||||
return left / right;
|
||||
}
|
||||
|
||||
BoundsRange BoundsRangeInfo::FindBoundsRange(const BasicBlock *block, Inst *inst) const
|
||||
{
|
||||
ASSERT(block != nullptr && inst != nullptr);
|
||||
@ -606,41 +428,6 @@ const ArenaVector<BasicBlock *> &BoundsAnalysis::GetBlocksToVisit() const
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitNeg(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeUnary<Opcode::Neg>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitAbs(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeUnary<Opcode::Abs>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitAdd(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeBinary<Opcode::Add>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitSub(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeBinary<Opcode::Sub>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitMod(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeBinary<Opcode::Mod>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitDiv(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeBinary<Opcode::Div>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitMul(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
CalcNewBoundsRangeBinary<Opcode::Mul>(v, inst);
|
||||
}
|
||||
|
||||
void BoundsAnalysis::VisitIf([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst)
|
||||
{
|
||||
UNREACHABLE();
|
||||
@ -697,41 +484,6 @@ void BoundsAnalysis::VisitPhi(GraphVisitor *v, Inst *inst)
|
||||
auto phi = inst->CastToPhi();
|
||||
auto phi_block = inst->GetBasicBlock();
|
||||
auto phi_type = phi->GetType();
|
||||
auto loop = phi_block->GetLoop();
|
||||
auto loop_parser = CountableLoopParser(*loop);
|
||||
auto loop_info = loop_parser.Parse();
|
||||
// check that loop is countable and phi is index
|
||||
if (loop_info && inst == loop_info.value().index) {
|
||||
auto loop_info_value = loop_info.value();
|
||||
ASSERT(loop_info_value.update->GetOpcode() == Opcode::Sub ||
|
||||
loop_info_value.update->GetOpcode() == Opcode::Add);
|
||||
auto lower = loop_info_value.init;
|
||||
auto upper = loop_info_value.test;
|
||||
auto cc = loop_info_value.normalized_cc;
|
||||
ASSERT(cc == CC_LE || cc == CC_LT || cc == CC_GE || cc == CC_GT);
|
||||
if (loop_info_value.update->GetOpcode() == Opcode::Sub) {
|
||||
lower = loop_info_value.test;
|
||||
upper = loop_info_value.init;
|
||||
}
|
||||
auto lower_range = bri->FindBoundsRange(phi_block, lower);
|
||||
auto upper_range = bri->FindBoundsRange(phi_block, upper);
|
||||
if (lower_range.GetLeft() <= upper_range.GetRight()) {
|
||||
BoundsRange range;
|
||||
if (cc == CC_LE || cc == CC_GE ||
|
||||
upper_range.GetRight() - lower_range.GetLeft() < static_cast<int64_t>(loop_info_value.const_step)) {
|
||||
range = BoundsRange(lower_range.GetLeft(), upper_range.GetRight());
|
||||
} else {
|
||||
range = BoundsRange(lower_range.GetLeft(),
|
||||
upper_range.GetRight() - static_cast<int64_t>(loop_info_value.const_step));
|
||||
}
|
||||
auto upper_len_array = upper_range.GetLenArray();
|
||||
if (cc != CC_LE) {
|
||||
range = UpdateLenArray(range, upper_len_array, upper);
|
||||
}
|
||||
bri->SetBoundsRange(phi_block, phi, range.FitInType(phi_type));
|
||||
return;
|
||||
}
|
||||
}
|
||||
ArenaVector<BoundsRange> ranges(phi_block->GetGraph()->GetLocalAllocator()->Adapter());
|
||||
for (auto &block : phi_block->GetPredsBlocks()) {
|
||||
ranges.emplace_back(bri->FindBoundsRange(block, phi->GetPhiInput(block)));
|
||||
@ -739,16 +491,6 @@ void BoundsAnalysis::VisitPhi(GraphVisitor *v, Inst *inst)
|
||||
bri->SetBoundsRange(phi_block, phi, BoundsRange::Union(ranges).FitInType(phi_type));
|
||||
}
|
||||
|
||||
BoundsRange BoundsAnalysis::UpdateLenArray(BoundsRange range, const Inst *len_array, const Inst *upper)
|
||||
{
|
||||
if (IsLenArray(upper)) {
|
||||
range.SetLenArray(upper);
|
||||
} else if (len_array != nullptr) {
|
||||
range.SetLenArray(len_array);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
bool BoundsAnalysis::CheckTriangleCase(const BasicBlock *block, const BasicBlock *tgt_block)
|
||||
{
|
||||
auto &preds_blocks = tgt_block->GetPredsBlocks();
|
||||
@ -786,93 +528,10 @@ void BoundsAnalysis::CalcNewBoundsRangeForCompare(GraphVisitor *v, BasicBlock *b
|
||||
*/
|
||||
if (CheckTriangleCase(block, tgt_block)) {
|
||||
auto ranges = BoundsRange::TryNarrowBoundsByCC(cc, {left_range, right_range});
|
||||
if (cc == ConditionCode::CC_LT && IsLenArray(right)) {
|
||||
ranges.first.SetLenArray(right);
|
||||
} else if (cc == ConditionCode::CC_GT && IsLenArray(left)) {
|
||||
ranges.second.SetLenArray(left);
|
||||
} else if (left_range.GetLenArray() != nullptr) {
|
||||
ranges.first.SetLenArray(left_range.GetLenArray());
|
||||
} else if (right_range.GetLenArray() != nullptr) {
|
||||
ranges.second.SetLenArray(right_range.GetLenArray());
|
||||
}
|
||||
ASSERT(left_range.GetLenArray() == nullptr);
|
||||
ASSERT(right_range.GetLenArray() == nullptr);
|
||||
bri->SetBoundsRange(tgt_block, left, ranges.first.FitInType(left->GetType()));
|
||||
bri->SetBoundsRange(tgt_block, right, ranges.second.FitInType(right->GetType()));
|
||||
}
|
||||
}
|
||||
|
||||
template <Opcode opc>
|
||||
void BoundsAnalysis::CalcNewBoundsRangeUnary(GraphVisitor *v, const Inst *inst)
|
||||
{
|
||||
if (IsFloatType(inst->GetType()) || inst->GetType() == DataType::REFERENCE || inst->GetType() == DataType::UINT64) {
|
||||
return;
|
||||
}
|
||||
auto bri = static_cast<BoundsAnalysis *>(v)->GetBoundsRangeInfo();
|
||||
auto input0 = inst->GetInput(0).GetInst();
|
||||
auto range0 = bri->FindBoundsRange(inst->GetBasicBlock(), input0);
|
||||
BoundsRange range;
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements)
|
||||
if constexpr (opc == Opcode::Neg) {
|
||||
range = range0.Neg();
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (opc == Opcode::Abs) {
|
||||
range = range0.Abs();
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
// clang-format on
|
||||
bri->SetBoundsRange(inst->GetBasicBlock(), inst, range.FitInType(inst->GetType()));
|
||||
}
|
||||
|
||||
template <Opcode opc>
|
||||
void BoundsAnalysis::CalcNewBoundsRangeBinary(GraphVisitor *v, const Inst *inst)
|
||||
{
|
||||
if (IsFloatType(inst->GetType()) || inst->GetType() == DataType::REFERENCE || inst->GetType() == DataType::UINT64) {
|
||||
return;
|
||||
}
|
||||
auto bri = static_cast<BoundsAnalysis *>(v)->GetBoundsRangeInfo();
|
||||
auto input0 = inst->GetDataFlowInput(inst->GetInput(0).GetInst());
|
||||
auto input1 = inst->GetDataFlowInput(inst->GetInput(1).GetInst());
|
||||
auto range0 = bri->FindBoundsRange(inst->GetBasicBlock(), input0);
|
||||
auto range1 = bri->FindBoundsRange(inst->GetBasicBlock(), input1);
|
||||
auto len_array0 = range0.GetLenArray();
|
||||
auto len_array1 = range1.GetLenArray();
|
||||
BoundsRange range;
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, bugprone-branch-clone)
|
||||
if constexpr (opc == Opcode::Add) {
|
||||
range = range0.Add(range1);
|
||||
if (!range1.IsNotNegative() && len_array0 != nullptr) {
|
||||
range.SetLenArray(len_array0);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (opc == Opcode::Sub) {
|
||||
range = range0.Sub(range1);
|
||||
if (range1.IsNotNegative() && len_array0 != nullptr) {
|
||||
range.SetLenArray(len_array0);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (opc == Opcode::Mod) {
|
||||
range = range0.Mod(range1);
|
||||
if (len_array1 != nullptr) {
|
||||
range.SetLenArray(len_array1);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (opc == Opcode::Mul) {
|
||||
if (!range0.IsMaxRange() || !range1.IsMaxRange()) {
|
||||
range = range0.Mul(range1);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation)
|
||||
} else if constexpr (opc == Opcode::Div) {
|
||||
range = range0.Div(range1);
|
||||
if (range0.IsNotNegative() && range1.IsNotNegative() && len_array0 != nullptr) {
|
||||
range.SetLenArray(len_array0);
|
||||
}
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
// clang-format on
|
||||
bri->SetBoundsRange(inst->GetBasicBlock(), inst, range.FitInType(inst->GetType()));
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
@ -46,8 +46,6 @@ public:
|
||||
DEFAULT_MOVE_SEMANTIC(BoundsRange);
|
||||
~BoundsRange() = default;
|
||||
|
||||
void SetLenArray(const Inst *inst);
|
||||
|
||||
const Inst *GetLenArray()
|
||||
{
|
||||
return len_array_;
|
||||
@ -58,20 +56,6 @@ public:
|
||||
|
||||
BoundsRange FitInType(DataType::Type type) const;
|
||||
|
||||
BoundsRange Neg() const;
|
||||
|
||||
BoundsRange Abs() const;
|
||||
|
||||
BoundsRange Add(const BoundsRange &range) const;
|
||||
|
||||
BoundsRange Sub(const BoundsRange &range) const;
|
||||
|
||||
BoundsRange Mul(const BoundsRange &range) const;
|
||||
|
||||
BoundsRange Div(const BoundsRange &range) const;
|
||||
|
||||
static BoundsRange Mod(const BoundsRange &range);
|
||||
|
||||
bool IsConst() const;
|
||||
|
||||
bool IsMaxRange(DataType::Type type = DataType::INT64) const;
|
||||
@ -106,12 +90,6 @@ public:
|
||||
|
||||
static RangePair TryNarrowBoundsByCC(ConditionCode cc, RangePair const &ranges);
|
||||
|
||||
static int64_t AddWithOverflowCheck(int64_t left, int64_t right);
|
||||
|
||||
static int64_t MulWithOverflowCheck(int64_t left, int64_t right);
|
||||
|
||||
static int64_t DivWithOverflowCheck(int64_t left, int64_t right);
|
||||
|
||||
static constexpr int64_t MAX_RANGE_VALUE = INT64_MAX;
|
||||
static constexpr int64_t MIN_RANGE_VALUE = INT64_MIN;
|
||||
|
||||
@ -164,13 +142,6 @@ public:
|
||||
return &bounds_range_info_;
|
||||
}
|
||||
|
||||
static void VisitNeg(GraphVisitor *v, Inst *inst);
|
||||
static void VisitAbs(GraphVisitor *v, Inst *inst);
|
||||
static void VisitAdd(GraphVisitor *v, Inst *inst);
|
||||
static void VisitSub(GraphVisitor *v, Inst *inst);
|
||||
static void VisitMod(GraphVisitor *v, Inst *inst);
|
||||
static void VisitDiv(GraphVisitor *v, Inst *inst);
|
||||
static void VisitMul(GraphVisitor *v, Inst *inst);
|
||||
static void VisitIf([[maybe_unused]] GraphVisitor *v, [[maybe_unused]] Inst *inst);
|
||||
static void VisitIfImm(GraphVisitor *v, Inst *inst);
|
||||
static void VisitPhi(GraphVisitor *v, Inst *inst);
|
||||
@ -179,14 +150,8 @@ public:
|
||||
private:
|
||||
static bool CheckTriangleCase(const BasicBlock *block, const BasicBlock *tgt_block);
|
||||
|
||||
static BoundsRange UpdateLenArray(BoundsRange range, const Inst *len_array, const Inst *upper);
|
||||
static void CalcNewBoundsRangeForCompare(GraphVisitor *v, BasicBlock *block, ConditionCode cc, Inst *left,
|
||||
Inst *right, BasicBlock *tgt_block);
|
||||
template <Opcode opc>
|
||||
static void CalcNewBoundsRangeUnary(GraphVisitor *v, const Inst *inst);
|
||||
template <Opcode opc>
|
||||
static void CalcNewBoundsRangeBinary(GraphVisitor *v, const Inst *inst);
|
||||
|
||||
private:
|
||||
BoundsRangeInfo bounds_range_info_;
|
||||
};
|
||||
|
@ -32,170 +32,7 @@ namespace panda::compiler {
|
||||
*/
|
||||
std::optional<CountableLoopInfo> CountableLoopParser::Parse()
|
||||
{
|
||||
if (loop_.IsIrreducible() || loop_.IsOsrLoop() || loop_.IsTryCatchLoop() || loop_.GetBackEdges().size() != 1 ||
|
||||
loop_.IsRoot() || loop_.IsInfinite()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto loop_exit = FindLoopExitBlock();
|
||||
if (loop_exit != loop_.GetHeader() && loop_exit != loop_.GetBackEdges()[0]) {
|
||||
return std::nullopt;
|
||||
}
|
||||
is_head_loop_exit_ = (loop_exit == loop_.GetHeader() && loop_exit != loop_.GetBackEdges()[0]);
|
||||
loop_info_.if_imm = loop_exit->GetLastInst();
|
||||
ASSERT(loop_info_.if_imm->GetOpcode() == Opcode::IfImm || loop_info_.if_imm->GetOpcode() == Opcode::If);
|
||||
auto loop_exit_cmp = loop_info_.if_imm->GetInput(0).GetInst();
|
||||
if (loop_exit_cmp->GetOpcode() != Opcode::Compare) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (is_head_loop_exit_ && !loop_exit_cmp->GetInput(0).GetInst()->IsPhi() &&
|
||||
!loop_exit_cmp->GetInput(1).GetInst()->IsPhi()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// Compare type should be signed integer
|
||||
// TODO (a.popov) Support loops with unsigned index
|
||||
auto cmp_type = loop_exit_cmp->CastToCompare()->GetOperandsType();
|
||||
if (DataType::GetCommonType(cmp_type) != DataType::INT64 || !DataType::IsTypeSigned(cmp_type)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!SetUpdateAndTestInputs()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!IsInstIncOrDec(loop_info_.update)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto const_inst = SetIndexAndRetrunConstInst();
|
||||
if (loop_info_.index->GetBasicBlock() != loop_.GetHeader()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ASSERT(loop_info_.index->IsPhi());
|
||||
auto back_edge {loop_.GetBackEdges()[0]};
|
||||
auto back_edge_idx {loop_info_.index->CastToPhi()->GetPredBlockIndex(back_edge)};
|
||||
if (loop_info_.index->GetInput(back_edge_idx).GetInst() != loop_info_.update) {
|
||||
return std::nullopt;
|
||||
}
|
||||
ASSERT(loop_info_.index->GetInputsCount() == MAX_SUCCS_NUM);
|
||||
ASSERT(const_inst->GetType() == DataType::INT64);
|
||||
loop_info_.const_step = const_inst->CastToConstant()->GetIntValue();
|
||||
loop_info_.init = loop_info_.index->GetInput(1 - back_edge_idx).GetInst();
|
||||
SetNormalizedConditionCode();
|
||||
if (!IsConditionCodeAcceptable()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return loop_info_;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if instruction is Add or Sub with constant and phi inputs
|
||||
*/
|
||||
bool CountableLoopParser::IsInstIncOrDec(Inst *inst)
|
||||
{
|
||||
if (inst->GetOpcode() != Opcode::Add && inst->GetOpcode() != Opcode::Sub) {
|
||||
return false;
|
||||
}
|
||||
ConstantInst *cnst = nullptr;
|
||||
if (inst->GetInput(0).GetInst()->IsConst() && inst->GetInput(1).GetInst()->IsPhi()) {
|
||||
cnst = inst->GetInput(0).GetInst()->CastToConstant();
|
||||
} else if (inst->GetInput(1).GetInst()->IsConst() && inst->GetInput(0).GetInst()->IsPhi()) {
|
||||
cnst = inst->GetInput(1).GetInst()->CastToConstant();
|
||||
}
|
||||
if (cnst == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t MASK = (1ULL << 63U);
|
||||
return !(DataType::IsTypeSigned(inst->GetType()) && (cnst->GetIntValue() & MASK) != 0);
|
||||
}
|
||||
|
||||
// TODO(a.popov) Suppot 'GetLoopExit()' method in the 'Loop' class
|
||||
BasicBlock *CountableLoopParser::FindLoopExitBlock()
|
||||
{
|
||||
auto outer_loop = loop_.GetOuterLoop();
|
||||
for (auto block : loop_.GetBlocks()) {
|
||||
const auto &succs = block->GetSuccsBlocks();
|
||||
auto it = std::find_if(succs.begin(), succs.end(),
|
||||
[&outer_loop](const BasicBlock *bb) { return bb->GetLoop() == outer_loop; });
|
||||
if (it != succs.end()) {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CountableLoopParser::SetUpdateAndTestInputs()
|
||||
{
|
||||
auto loop_exit_cmp = loop_info_.if_imm->GetInput(0).GetInst();
|
||||
ASSERT(loop_exit_cmp->GetOpcode() == Opcode::Compare);
|
||||
loop_info_.update = loop_exit_cmp->GetInput(0).GetInst();
|
||||
loop_info_.test = loop_exit_cmp->GetInput(1).GetInst();
|
||||
if (is_head_loop_exit_) {
|
||||
if (!loop_info_.update->IsPhi()) {
|
||||
std::swap(loop_info_.update, loop_info_.test);
|
||||
}
|
||||
ASSERT(loop_info_.update->IsPhi());
|
||||
if (loop_info_.update->GetBasicBlock() != loop_.GetHeader()) {
|
||||
return false;
|
||||
}
|
||||
auto back_edge {loop_.GetBackEdges()[0]};
|
||||
loop_info_.update = loop_info_.update->CastToPhi()->GetPhiInput(back_edge);
|
||||
} else {
|
||||
if (!IsInstIncOrDec(loop_info_.update)) {
|
||||
std::swap(loop_info_.update, loop_info_.test);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Inst *CountableLoopParser::SetIndexAndRetrunConstInst()
|
||||
{
|
||||
loop_info_.index = loop_info_.update->GetInput(0).GetInst();
|
||||
auto const_inst = loop_info_.update->GetInput(1).GetInst();
|
||||
if (loop_info_.index->IsConst()) {
|
||||
loop_info_.index = loop_info_.update->GetInput(1).GetInst();
|
||||
const_inst = loop_info_.update->GetInput(0).GetInst();
|
||||
}
|
||||
return const_inst;
|
||||
}
|
||||
|
||||
void CountableLoopParser::SetNormalizedConditionCode()
|
||||
{
|
||||
auto loop_exit = loop_info_.if_imm->GetBasicBlock();
|
||||
ASSERT(loop_exit != nullptr);
|
||||
auto loop_exit_cmp = loop_info_.if_imm->GetInput(0).GetInst();
|
||||
ASSERT(loop_exit_cmp->GetOpcode() == Opcode::Compare);
|
||||
auto cc = loop_exit_cmp->CastToCompare()->GetCc();
|
||||
if (loop_info_.test == loop_exit_cmp->GetInput(0).GetInst()) {
|
||||
cc = SwapOperandsConditionCode(cc);
|
||||
}
|
||||
ASSERT(loop_info_.if_imm->CastToIfImm()->GetImm() == 0);
|
||||
if (loop_info_.if_imm->CastToIfImm()->GetCc() == CC_EQ) {
|
||||
cc = GetInverseConditionCode(cc);
|
||||
} else {
|
||||
ASSERT(loop_info_.if_imm->CastToIfImm()->GetCc() == CC_NE);
|
||||
}
|
||||
auto loop = loop_exit->GetLoop();
|
||||
if (loop_exit->GetFalseSuccessor()->GetLoop() == loop) {
|
||||
cc = GetInverseConditionCode(cc);
|
||||
} else {
|
||||
ASSERT(loop_exit->GetTrueSuccessor()->GetLoop() == loop);
|
||||
}
|
||||
loop_info_.normalized_cc = cc;
|
||||
}
|
||||
|
||||
bool CountableLoopParser::IsConditionCodeAcceptable()
|
||||
{
|
||||
auto cc = loop_info_.normalized_cc;
|
||||
bool is_inc = loop_info_.update->GetOpcode() == Opcode::Add;
|
||||
// Condition should be: inc <= test | inc < test
|
||||
if (is_inc && cc != CC_LE && cc != CC_LT) {
|
||||
return false;
|
||||
}
|
||||
// Condition should be: dec >= test | dec > test
|
||||
if (!is_inc && cc != CC_GE && cc != CC_GT) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace panda::compiler
|
||||
|
@ -49,12 +49,7 @@ public:
|
||||
std::optional<CountableLoopInfo> Parse();
|
||||
|
||||
private:
|
||||
bool IsInstIncOrDec(Inst *inst);
|
||||
bool SetUpdateAndTestInputs();
|
||||
Inst *SetIndexAndRetrunConstInst();
|
||||
void SetNormalizedConditionCode();
|
||||
bool IsConditionCodeAcceptable();
|
||||
BasicBlock *FindLoopExitBlock();
|
||||
|
||||
private:
|
||||
const Loop &loop_;
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include "liveness_analyzer.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/optimizations/locations_builder.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
LivenessAnalyzer::LivenessAnalyzer(Graph *graph)
|
||||
@ -41,9 +40,6 @@ LivenessAnalyzer::LivenessAnalyzer(Graph *graph)
|
||||
|
||||
bool LivenessAnalyzer::RunImpl()
|
||||
{
|
||||
if (!RunLocationsBuilder(GetGraph())) {
|
||||
return false;
|
||||
}
|
||||
GetGraph()->RunPass<DominatorsTree>();
|
||||
GetGraph()->RunPass<LoopAnalyzer>();
|
||||
ResetLiveness();
|
||||
@ -236,7 +232,6 @@ void LivenessAnalyzer::BuildInstLifeNumbers()
|
||||
}
|
||||
life_number += LIFE_NUMBER_GAP;
|
||||
SetInstLifeNumber(inst, life_number);
|
||||
SetUsePositions(inst, life_number);
|
||||
insts_by_life_number_.push_back(inst);
|
||||
}
|
||||
life_number += LIFE_NUMBER_GAP;
|
||||
@ -244,40 +239,6 @@ void LivenessAnalyzer::BuildInstLifeNumbers()
|
||||
}
|
||||
}
|
||||
|
||||
void LivenessAnalyzer::SetUsePositions(Inst *user_inst, LifeNumber life_number)
|
||||
{
|
||||
if (GetGraph()->IsBytecodeOptimizer()) {
|
||||
return;
|
||||
}
|
||||
if (user_inst->IsCatchPhi() || user_inst->IsSaveState()) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < user_inst->GetInputsCount(); i++) {
|
||||
auto location = user_inst->GetLocation(i);
|
||||
if (!location.IsAnyRegister()) {
|
||||
continue;
|
||||
}
|
||||
auto input_inst = user_inst->GetDataFlowInput(i);
|
||||
auto li = GetInstLifeIntervals(input_inst);
|
||||
if (location.IsRegisterValid()) {
|
||||
use_table_.AddUseOnFixedLocation(input_inst, location, life_number);
|
||||
} else if (location.IsUnallocatedRegister()) {
|
||||
li->AddUsePosition(life_number);
|
||||
}
|
||||
}
|
||||
|
||||
// Constant can be defined without register
|
||||
if (user_inst->IsConst() && options.IsCompilerRematConst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If instruction required dst register, set use position in the beginning of the interval
|
||||
auto li = GetInstLifeIntervals(user_inst);
|
||||
if (!li->NoDest()) {
|
||||
li->AddUsePosition(life_number);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get lifetime intervals for each instruction
|
||||
*/
|
||||
@ -369,8 +330,6 @@ void LivenessAnalyzer::ProcessBlockLiveInstructions(BasicBlock *block, InstLiveS
|
||||
auto current_live_range = LiveRange {GetBlockLiveRange(block).GetBegin(), inst_life_number};
|
||||
AdjustInputsLifetime(inst, current_live_range, live_set);
|
||||
}
|
||||
|
||||
BlockFixedRegisters(inst);
|
||||
}
|
||||
|
||||
// The lifetime interval of phis instructions starts at the beginning of the block
|
||||
@ -619,32 +578,6 @@ void LivenessAnalyzer::DumpLocationsUsage(std::ostream &out) const
|
||||
}
|
||||
}
|
||||
|
||||
void LivenessAnalyzer::BlockFixedRegisters(Inst *inst)
|
||||
{
|
||||
if (GetGraph()->IsBytecodeOptimizer() || GetGraph()->GetMode().IsFastPath()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto block_from = GetInstLifeNumber(inst);
|
||||
if (IsCallBlockingRegisters(inst)) {
|
||||
BlockPhysicalRegisters<false>(block_from);
|
||||
BlockPhysicalRegisters<true>(block_from);
|
||||
}
|
||||
for (auto i = 0U; i < inst->GetInputsCount(); ++i) {
|
||||
BlockFixedLocationRegister(inst->GetLocation(i), block_from);
|
||||
}
|
||||
BlockFixedLocationRegister(inst->GetDstLocation(), block_from);
|
||||
|
||||
if (inst->IsParameter()) {
|
||||
BlockFixedLocationRegister(inst->CastToParameter()->GetLocationData().GetSrc(), block_from);
|
||||
// Block second parameter register that contains number of actual arguments in case of
|
||||
// dynamic methods as it is used in parameter's code generation
|
||||
if (GetGraph()->GetMode().IsDynamicMethod()) {
|
||||
BlockReg<false>(Target(GetGraph()->GetArch()).GetParamRegId(1), block_from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void LivenessAnalyzer::BlockPhysicalRegisters(LifeNumber block_from)
|
||||
{
|
||||
|
@ -583,9 +583,7 @@ private:
|
||||
LifeNumber GetLoopEnd(Loop *loop);
|
||||
LiveRange GetPropagatedLiveRange(Inst *inst, LiveRange live_range);
|
||||
void AdjustCatchPhiInputsLifetime(Inst *inst);
|
||||
void SetUsePositions(Inst *user_inst, LifeNumber life_number);
|
||||
|
||||
void BlockFixedRegisters(Inst *inst);
|
||||
template <bool is_fp>
|
||||
void BlockReg(Register reg, LifeNumber block_from);
|
||||
template <bool is_fp>
|
||||
|
@ -1,100 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "compiler_logger.h"
|
||||
#include "monitor_analysis.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
void MonitorAnalysis::MarkedMonitorRec(BasicBlock *bb, int32_t num_monitors)
|
||||
{
|
||||
ASSERT(num_monitors >= 0);
|
||||
if (num_monitors > 0) {
|
||||
bb->SetMonitorBlock(true);
|
||||
if (bb->IsEndBlock()) {
|
||||
COMPILER_LOG(DEBUG, MONITOR_ANALYSIS) << "There is MonitorEntry without MonitorExit";
|
||||
incorrect_monitors_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (inst->GetOpcode() == Opcode::Throw) {
|
||||
// The Monitor.Exit is removed from the compiled code after explicit Throw instruction
|
||||
// in the syncronized block because the execution is switching to the interpreting mode
|
||||
num_monitors = 0;
|
||||
}
|
||||
if (inst->GetOpcode() == Opcode::Monitor) {
|
||||
bb->SetMonitorBlock(true);
|
||||
if (inst->CastToMonitor()->IsEntry()) {
|
||||
bb->SetMonitorEntryBlock(true);
|
||||
++num_monitors;
|
||||
} else {
|
||||
ASSERT(inst->CastToMonitor()->IsExit());
|
||||
if (num_monitors <= 0) {
|
||||
COMPILER_LOG(DEBUG, MONITOR_ANALYSIS) << "There is MonitorExit without MonitorEntry";
|
||||
incorrect_monitors_ = true;
|
||||
return;
|
||||
}
|
||||
bb->SetMonitorExitBlock(true);
|
||||
--num_monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
enteredMonitorsCount_->at(bb->GetId()) = num_monitors;
|
||||
if (num_monitors == 0) {
|
||||
return;
|
||||
}
|
||||
for (auto succ : bb->GetSuccsBlocks()) {
|
||||
if (succ->SetMarker(marker_)) {
|
||||
continue;
|
||||
}
|
||||
MarkedMonitorRec(succ, num_monitors);
|
||||
}
|
||||
}
|
||||
|
||||
bool MonitorAnalysis::RunImpl()
|
||||
{
|
||||
auto allocator = GetGraph()->GetLocalAllocator();
|
||||
incorrect_monitors_ = false;
|
||||
enteredMonitorsCount_ = allocator->New<ArenaVector<uint32_t>>(allocator->Adapter());
|
||||
enteredMonitorsCount_->resize(GetGraph()->GetVectorBlocks().size());
|
||||
marker_ = GetGraph()->NewMarker();
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
if (bb->SetMarker(marker_)) {
|
||||
continue;
|
||||
}
|
||||
MarkedMonitorRec(bb, 0);
|
||||
if (incorrect_monitors_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
const uint32_t UNINITIALIZED = 0xFFFFFFFF;
|
||||
uint32_t count = UNINITIALIZED;
|
||||
for (auto prev : bb->GetPredsBlocks()) {
|
||||
if (count == UNINITIALIZED) {
|
||||
count = enteredMonitorsCount_->at(prev->GetId());
|
||||
} else if (count != enteredMonitorsCount_->at(prev->GetId())) {
|
||||
COMPILER_LOG(DEBUG, MONITOR_ANALYSIS)
|
||||
<< "There is an inconsistent MonitorEntry counters in parent basic blocks";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
GetGraph()->EraseMarker(marker_);
|
||||
return true;
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef COMPILER_OPTIMIZER_ANALYSIS_MONITOR_ANALYSIS_H
|
||||
#define COMPILER_OPTIMIZER_ANALYSIS_MONITOR_ANALYSIS_H
|
||||
|
||||
#include "optimizer/ir/marker.h"
|
||||
#include "optimizer/pass.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
/*
|
||||
* The analysis checks MonitorEntry and MonitorExit in the Graph and set properties for blocks:
|
||||
* 1. BlockMonitorEntry for blocks with MonitorEntry
|
||||
* 2. BlockMonitorExit for blocks with MonitorExit
|
||||
* 3. BlockMonitor for blocks between BlockMonitorEntry and BlockMonitorExit
|
||||
* The analysis returns false if there is way with MonitorEntry and without MonitorExit or Vice versa
|
||||
* For this case the analysis return false:
|
||||
* bb 1
|
||||
* | \
|
||||
* bb 2 bb 3
|
||||
* | MonitorEntry
|
||||
* | /
|
||||
* bb 4 - Conditional is equal to bb 1
|
||||
* | \
|
||||
* bb 5 bb 6
|
||||
* | MonitorExit
|
||||
* | /
|
||||
* bb 7
|
||||
*/
|
||||
class MonitorAnalysis final : public Analysis {
|
||||
public:
|
||||
using Analysis::Analysis;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "MonitorAnalysis";
|
||||
}
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
private:
|
||||
void MarkedMonitorRec(BasicBlock *bb, int32_t num_monitors);
|
||||
|
||||
Marker marker_ {UNDEF_MARKER};
|
||||
bool incorrect_monitors_ {false};
|
||||
ArenaVector<uint32_t> *enteredMonitorsCount_ {nullptr};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
#endif // COMPILER_OPTIMIZER_ANALYSIS_MONITOR_ANALYSIS_H
|
@ -1,618 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "optimizer/analysis/reg_alloc_verifier.h"
|
||||
#include "optimizer/analysis/liveness_analyzer.h"
|
||||
#include "optimizer/analysis/live_registers.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/code_generator/callconv.h"
|
||||
#include "optimizer/code_generator/codegen.h"
|
||||
#include "optimizer/optimizations/regalloc/reg_type.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
namespace {
|
||||
std::string ToString(LocationState::State state)
|
||||
{
|
||||
switch (state) {
|
||||
case LocationState::State::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case LocationState::State::KNOWN:
|
||||
return "KNOWN";
|
||||
case LocationState::State::CONFLICT:
|
||||
return "CONFLICT";
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
const LocationState &GetPhiLocationState(const BlockState &state, const ArenaVector<LocationState> &immediates,
|
||||
const LifeIntervals *interval, bool is_high)
|
||||
{
|
||||
auto location = interval->GetLocation();
|
||||
if (location.IsStack()) {
|
||||
ASSERT(!is_high);
|
||||
return state.GetStack(location.GetValue());
|
||||
}
|
||||
if (location.IsConstant()) {
|
||||
ASSERT(location.GetValue() < immediates.size());
|
||||
return immediates[location.GetValue()];
|
||||
}
|
||||
if (location.IsFpRegister()) {
|
||||
ASSERT(IsFloatType(interval->GetType()));
|
||||
return state.GetVReg(location.GetValue() + (is_high ? 1U : 0U));
|
||||
}
|
||||
ASSERT(location.IsRegister());
|
||||
return state.GetReg(location.GetValue() + (is_high ? 1U : 0U));
|
||||
}
|
||||
|
||||
LocationState &GetPhiLocationState(BlockState *state, const LifeIntervals *interval, bool is_high)
|
||||
{
|
||||
auto location = interval->GetLocation();
|
||||
if (location.IsStack()) {
|
||||
ASSERT(!is_high);
|
||||
return state->GetStack(location.GetValue());
|
||||
}
|
||||
if (location.IsFpRegister()) {
|
||||
ASSERT(IsFloatType(interval->GetType()));
|
||||
return state->GetVReg(location.GetValue() + (is_high ? 1U : 0U));
|
||||
}
|
||||
ASSERT(location.IsRegister());
|
||||
return state->GetReg(location.GetValue() + (is_high ? 1U : 0U));
|
||||
}
|
||||
|
||||
bool MergeImpl(const ArenaVector<LocationState> &from, ArenaVector<LocationState> *to)
|
||||
{
|
||||
bool updated = false;
|
||||
for (size_t offset = 0; offset < from.size(); ++offset) {
|
||||
if (to->at(offset).ShouldSkip()) {
|
||||
to->at(offset).SetSkip(false);
|
||||
continue;
|
||||
}
|
||||
updated |= to->at(offset).Merge(from[offset]);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
class BlockStates {
|
||||
public:
|
||||
BlockStates(size_t regs, size_t vregs, size_t stack_slots, size_t stack_params, ArenaAllocator *allocator)
|
||||
: start_state_(regs, vregs, stack_slots, stack_params, allocator),
|
||||
end_state_(regs, vregs, stack_slots, stack_params, allocator)
|
||||
{
|
||||
}
|
||||
~BlockStates() = default;
|
||||
NO_COPY_SEMANTIC(BlockStates);
|
||||
NO_MOVE_SEMANTIC(BlockStates);
|
||||
|
||||
BlockState &GetStart()
|
||||
{
|
||||
return start_state_;
|
||||
}
|
||||
|
||||
BlockState &GetEnd()
|
||||
{
|
||||
return end_state_;
|
||||
}
|
||||
|
||||
BlockState &ResetEndToStart()
|
||||
{
|
||||
end_state_.Copy(&start_state_);
|
||||
return end_state_;
|
||||
}
|
||||
|
||||
bool IsUpdated() const
|
||||
{
|
||||
return updated_;
|
||||
}
|
||||
|
||||
void SetUpdated(bool upd)
|
||||
{
|
||||
updated_ = upd;
|
||||
}
|
||||
|
||||
bool IsVisited() const
|
||||
{
|
||||
return visited_;
|
||||
}
|
||||
|
||||
void SetVisited(bool visited)
|
||||
{
|
||||
visited_ = visited;
|
||||
}
|
||||
|
||||
private:
|
||||
BlockState start_state_;
|
||||
BlockState end_state_;
|
||||
bool updated_ {false};
|
||||
bool visited_ {false};
|
||||
};
|
||||
|
||||
void InitStates(ArenaUnorderedMap<uint32_t, BlockStates> *blocks, const Graph *graph)
|
||||
{
|
||||
auto used_regs = graph->GetUsedRegs<DataType::INT64>()->size();
|
||||
auto used_vregs = graph->GetUsedRegs<DataType::FLOAT64>()->size();
|
||||
for (auto &bb : graph->GetVectorBlocks()) {
|
||||
if (bb == nullptr) {
|
||||
continue;
|
||||
}
|
||||
[[maybe_unused]] auto res = blocks->try_emplace(bb->GetId(), used_regs, used_vregs, MAX_NUM_STACK_SLOTS,
|
||||
MAX_NUM_STACK_SLOTS, graph->GetLocalAllocator());
|
||||
ASSERT(res.second);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAllBlocksVisited([[maybe_unused]] const ArenaUnorderedMap<uint32_t, BlockStates> &blocks)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
for (auto &block_state : blocks) {
|
||||
ASSERT(block_state.second.IsVisited());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsRegisterPair(Location location, DataType::Type type, Graph *graph)
|
||||
{
|
||||
if (!location.IsRegister()) {
|
||||
return false;
|
||||
}
|
||||
return Is64Bits(type, graph->GetArch()) && !Is64BitsArch(graph->GetArch());
|
||||
}
|
||||
|
||||
bool IsRegisterPair(const LifeIntervals *li)
|
||||
{
|
||||
if (!li->HasReg()) {
|
||||
return false;
|
||||
}
|
||||
return IsRegisterPair(Location::MakeRegister(li->GetReg()), li->GetType(),
|
||||
li->GetInst()->GetBasicBlock()->GetGraph());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool LocationState::Merge(const LocationState &other)
|
||||
{
|
||||
if (state_ == LocationState::State::CONFLICT) {
|
||||
return false;
|
||||
}
|
||||
if (state_ == LocationState::State::UNKNOWN) {
|
||||
state_ = other.state_;
|
||||
id_ = other.id_;
|
||||
return other.state_ != LocationState::State::UNKNOWN;
|
||||
}
|
||||
if (other.state_ == LocationState::State::CONFLICT) {
|
||||
state_ = LocationState::State::CONFLICT;
|
||||
return true;
|
||||
}
|
||||
if (other.state_ == LocationState::State::KNOWN && state_ == LocationState::State::KNOWN && other.id_ != id_) {
|
||||
state_ = LocationState::State::CONFLICT;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockState::BlockState(size_t regs, size_t vregs, size_t stack_slots, size_t stack_params, ArenaAllocator *alloc)
|
||||
: regs_(regs, alloc->Adapter()),
|
||||
vregs_(vregs, alloc->Adapter()),
|
||||
stack_(stack_slots, alloc->Adapter()),
|
||||
stack_param_(stack_params, alloc->Adapter()),
|
||||
stack_arg_(0, alloc->Adapter())
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
bool BlockState::Merge(const BlockState &state, const PhiInstSafeIter &phis, BasicBlock *pred,
|
||||
const ArenaVector<LocationState> &immediates, const LivenessAnalyzer &la)
|
||||
{
|
||||
bool updated = false;
|
||||
/* Phi instructions are resolved into moves at the end of predecessor blocks,
|
||||
* but corresponding locations contain ids of instructions merged by a phi.
|
||||
* Phi's location at the beginning of current block is only updated when
|
||||
* the same location at the end of predecessor holds result of an instruction
|
||||
* that is a phi's input. Otherwise phi's location state will be either remains unknown,
|
||||
* or it will be merged to conflicting state.
|
||||
*/
|
||||
constexpr std::array<bool, 2U> IS_HIGH = {false, true};
|
||||
for (auto phi : phis) {
|
||||
auto phi_interval = la.GetInstLifeIntervals(phi);
|
||||
auto input = phi->CastToPhi()->GetPhiDataflowInput(pred);
|
||||
for (size_t is_high_idx = 0; is_high_idx < (IsRegisterPair(phi_interval) ? 2U : 1U); is_high_idx++) {
|
||||
auto &input_state = GetPhiLocationState(state, immediates, phi_interval, IS_HIGH[is_high_idx]);
|
||||
auto &phi_state = GetPhiLocationState(this, phi_interval, IS_HIGH[is_high_idx]);
|
||||
|
||||
if (input_state.GetState() != LocationState::State::KNOWN) {
|
||||
continue;
|
||||
}
|
||||
if (input_state.IsValid(input)) {
|
||||
updated = phi_state.GetState() != LocationState::State::KNOWN || phi_state.GetId() != phi->GetId();
|
||||
phi_state.SetId(phi->GetId());
|
||||
// don't merge it
|
||||
phi_state.SetSkip(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
updated |= MergeImpl(state.regs_, ®s_);
|
||||
updated |= MergeImpl(state.vregs_, &vregs_);
|
||||
updated |= MergeImpl(state.stack_, &stack_);
|
||||
updated |= MergeImpl(state.stack_param_, &stack_param_);
|
||||
// note that stack_arg_ is not merged as it is only used during call handing
|
||||
return updated;
|
||||
}
|
||||
|
||||
void BlockState::Copy(BlockState *state)
|
||||
{
|
||||
std::copy(state->regs_.begin(), state->regs_.end(), regs_.begin());
|
||||
std::copy(state->vregs_.begin(), state->vregs_.end(), vregs_.begin());
|
||||
std::copy(state->stack_.begin(), state->stack_.end(), stack_.begin());
|
||||
std::copy(state->stack_param_.begin(), state->stack_param_.end(), stack_param_.begin());
|
||||
// note that stack_arg_ is not merged as it is only used during call handing
|
||||
}
|
||||
|
||||
RegAllocVerifier::RegAllocVerifier(Graph *graph, bool save_live_regs_on_call)
|
||||
: Analysis(graph),
|
||||
immediates_(graph->GetLocalAllocator()->Adapter()),
|
||||
saved_regs_(GetGraph()->GetLocalAllocator()->Adapter()),
|
||||
saved_vregs_(GetGraph()->GetLocalAllocator()->Adapter()),
|
||||
save_live_regs_(save_live_regs_on_call)
|
||||
{
|
||||
}
|
||||
|
||||
void RegAllocVerifier::InitImmediates()
|
||||
{
|
||||
immediates_.clear();
|
||||
for (size_t imm_slot = 0; imm_slot < GetGraph()->GetSpilledConstantsCount(); ++imm_slot) {
|
||||
auto con = GetGraph()->GetSpilledConstant(imm_slot);
|
||||
immediates_.emplace_back(LocationState::State::KNOWN, con->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
LocationState &RegAllocVerifier::GetLocationState(Location location)
|
||||
{
|
||||
auto loc = location.GetKind();
|
||||
auto offset = location.GetValue();
|
||||
|
||||
if (loc == LocationType::STACK) {
|
||||
return current_state_->GetStack(offset);
|
||||
}
|
||||
if (loc == LocationType::REGISTER) {
|
||||
return current_state_->GetReg(offset);
|
||||
}
|
||||
if (loc == LocationType::FP_REGISTER) {
|
||||
return current_state_->GetVReg(offset);
|
||||
}
|
||||
if (loc == LocationType::STACK_ARGUMENT) {
|
||||
return current_state_->GetStackArg(offset);
|
||||
}
|
||||
if (loc == LocationType::STACK_PARAMETER) {
|
||||
return current_state_->GetStackParam(offset);
|
||||
}
|
||||
ASSERT(loc == LocationType::IMMEDIATE);
|
||||
ASSERT(offset < immediates_.size());
|
||||
return immediates_[offset];
|
||||
}
|
||||
|
||||
// Apply callback to a location corresponding to loc, offset and type.
|
||||
// If location is represented by registers pair then apply the callback to both parts.
|
||||
// Return true if all invocations of the callback returned true.
|
||||
template <typename T>
|
||||
bool RegAllocVerifier::ForEachLocation(Location location, DataType::Type type, T callback)
|
||||
{
|
||||
bool success = callback(GetLocationState(location));
|
||||
if (IsRegisterPair(location, type, GetGraph())) {
|
||||
auto pired_reg = Location::MakeRegister(location.GetValue() + 1);
|
||||
success &= callback(GetLocationState(pired_reg));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void RegAllocVerifier::UpdateLocation(Location location, DataType::Type type, uint32_t inst_id)
|
||||
{
|
||||
ForEachLocation(location, type, [inst_id](auto &state) {
|
||||
state.SetId(inst_id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void RegAllocVerifier::HandleDest(Inst *inst)
|
||||
{
|
||||
if (inst->NoDest()) {
|
||||
return;
|
||||
}
|
||||
auto type = inst->GetType();
|
||||
if (GetGraph()->GetZeroReg() != INVALID_REG && inst->GetDstReg() == GetGraph()->GetZeroReg()) {
|
||||
UpdateLocation(Location::MakeRegister(inst->GetDstReg()), type, LocationState::ZERO_INST);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst->GetDstCount() == 1) {
|
||||
UpdateLocation(Location::MakeRegister(inst->GetDstReg(), type), type, inst->GetId());
|
||||
return;
|
||||
}
|
||||
|
||||
size_t handled_users = 0;
|
||||
for (auto &user : inst->GetUsers()) {
|
||||
if (IsPseudoUserOfMultiOutput(user.GetInst())) {
|
||||
auto inst_id = user.GetInst()->GetId();
|
||||
auto idx = user.GetInst()->CastToLoadPairPart()->GetSrcRegIndex();
|
||||
UpdateLocation(Location::MakeRegister(inst->GetDstReg(idx), type), type, inst_id);
|
||||
++handled_users;
|
||||
}
|
||||
}
|
||||
ASSERT(handled_users == inst->GetDstCount());
|
||||
}
|
||||
|
||||
BasicBlock *PropagateBlockState(BasicBlock *current_block, BlockState *current_state,
|
||||
const ArenaVector<LocationState> &immediates,
|
||||
ArenaUnorderedMap<uint32_t, BlockStates> *blocks)
|
||||
{
|
||||
BasicBlock *next_block {nullptr};
|
||||
auto &la = current_block->GetGraph()->GetAnalysis<LivenessAnalyzer>();
|
||||
for (auto succ : current_block->GetSuccsBlocks()) {
|
||||
auto phis = succ->PhiInstsSafe();
|
||||
auto &succ_state = blocks->at(succ->GetId());
|
||||
bool merge_updated = succ_state.GetStart().Merge(*current_state, phis, current_block, immediates, la);
|
||||
// if merged did not update the state, but successor is not visited yet then force its visit
|
||||
merge_updated |= !succ_state.IsVisited();
|
||||
succ_state.SetUpdated(succ_state.IsUpdated() || merge_updated);
|
||||
if (merge_updated) {
|
||||
next_block = succ;
|
||||
}
|
||||
}
|
||||
return next_block;
|
||||
}
|
||||
|
||||
bool RegAllocVerifier::RunImpl()
|
||||
{
|
||||
ArenaUnorderedMap<uint32_t, BlockStates> blocks(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
InitStates(&blocks, GetGraph());
|
||||
InitImmediates();
|
||||
implicit_null_check_handled_marker_ = GetGraph()->NewMarker();
|
||||
|
||||
success_ = true;
|
||||
current_block_ = GetGraph()->GetStartBlock();
|
||||
while (success_) {
|
||||
ASSERT(current_block_ != nullptr);
|
||||
|
||||
blocks.at(current_block_->GetId()).SetUpdated(false);
|
||||
blocks.at(current_block_->GetId()).SetVisited(true);
|
||||
// use state at the end of the block as current state and reset it to the state
|
||||
// at the beginning of the block before processing.
|
||||
current_state_ = &blocks.at(current_block_->GetId()).ResetEndToStart();
|
||||
|
||||
ProcessCurrentBlock();
|
||||
if (!success_) {
|
||||
break;
|
||||
}
|
||||
|
||||
current_block_ = PropagateBlockState(current_block_, current_state_, immediates_, &blocks);
|
||||
if (current_block_ != nullptr) {
|
||||
continue;
|
||||
}
|
||||
// pick a block pending processing
|
||||
auto it = std::find_if(blocks.begin(), blocks.end(), [](auto &t) { return t.second.IsUpdated(); });
|
||||
if (it == blocks.end()) {
|
||||
break;
|
||||
}
|
||||
auto bbs = GetGraph()->GetVectorBlocks();
|
||||
auto bb_id = it->first;
|
||||
auto block_it =
|
||||
std::find_if(bbs.begin(), bbs.end(), [bb_id](auto bb) { return bb != nullptr && bb->GetId() == bb_id; });
|
||||
ASSERT(block_it != bbs.end());
|
||||
current_block_ = *block_it;
|
||||
}
|
||||
|
||||
GetGraph()->EraseMarker(implicit_null_check_handled_marker_);
|
||||
if (success_) {
|
||||
CheckAllBlocksVisited(blocks);
|
||||
}
|
||||
return success_;
|
||||
}
|
||||
|
||||
void RegAllocVerifier::ProcessCurrentBlock()
|
||||
{
|
||||
for (auto inst : current_block_->InstsSafe()) {
|
||||
if (!success_) {
|
||||
return;
|
||||
}
|
||||
if (IsSaveRestoreRegisters(inst)) {
|
||||
HandleSaveRestoreRegisters(inst);
|
||||
continue;
|
||||
}
|
||||
if (inst->GetOpcode() == Opcode::ReturnInlined) {
|
||||
// ReturnInlined is pseudo instruction having SaveState input that
|
||||
// only prolongs SaveState input's lifetime. There are no guarantees that
|
||||
// SaveState's inputs will be stored in locations specified in SaveState
|
||||
// at ReturnInlined.
|
||||
continue;
|
||||
}
|
||||
|
||||
TryHandleImplicitNullCheck(inst);
|
||||
// pseudo user of multi output will be handled by HandleDest
|
||||
if (inst->IsSaveState() || IsPseudoUserOfMultiOutput(inst)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst->IsCall() && static_cast<CallInst *>(inst)->IsInlined()) {
|
||||
ASSERT(inst->GetDstReg() == INVALID_REG);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst->IsParameter()) {
|
||||
HandleParameter(inst->CastToParameter());
|
||||
} else if (inst->IsSpillFill()) {
|
||||
HandleSpillFill(inst->CastToSpillFill());
|
||||
} else if (inst->IsConst()) {
|
||||
HandleConst(inst->CastToConstant());
|
||||
} else {
|
||||
HandleInst(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set inst's id to corresponding location
|
||||
void RegAllocVerifier::HandleParameter(ParameterInst *inst)
|
||||
{
|
||||
auto sf = inst->GetLocationData();
|
||||
UpdateLocation(sf.GetDst(), inst->GetType(), inst->GetId());
|
||||
}
|
||||
|
||||
bool SameState(LocationState &left, LocationState &right)
|
||||
{
|
||||
return left.GetId() == right.GetId() && left.GetState() == right.GetState();
|
||||
}
|
||||
|
||||
// Copy inst's id from source location to destination location,
|
||||
// source location should contain known value.
|
||||
void RegAllocVerifier::HandleSpillFill(SpillFillInst *inst)
|
||||
{
|
||||
for (auto sf : inst->GetSpillFills()) {
|
||||
LocationState *state = nullptr;
|
||||
success_ &= ForEachLocation(
|
||||
sf.GetSrc(), sf.GetType(), [&state, &sf, inst, arch = GetGraph()->GetArch()](LocationState &st) {
|
||||
if (state == nullptr) {
|
||||
state = &st;
|
||||
} else if (!SameState(*state, st)) {
|
||||
COMPILER_LOG(ERROR, REGALLOC) << "SpillFill is accessing " << sf.GetSrc().ToString(arch)
|
||||
<< " with different state for high and low parts";
|
||||
return false;
|
||||
}
|
||||
if (st.GetState() == LocationState::State::KNOWN) {
|
||||
return true;
|
||||
}
|
||||
COMPILER_LOG(ERROR, REGALLOC)
|
||||
<< "SpillFill is copying value from location with state " << ToString(st.GetState()) << ", "
|
||||
<< inst->GetId() << " read from " << sf.GetSrc().ToString(arch);
|
||||
return false;
|
||||
});
|
||||
ASSERT(state != nullptr);
|
||||
UpdateLocation(sf.GetDst(), sf.GetType(), state->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
// Set instn's id to corresponding location.
|
||||
void RegAllocVerifier::HandleConst(ConstantInst *inst)
|
||||
{
|
||||
if (inst->GetDstReg() != INVALID_REG) {
|
||||
HandleDest(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
// if const inst does not have valid register then it was spilled to imm table.
|
||||
auto imm_slot = GetGraph()->FindSpilledConstantSlot(inst->CastToConstant());
|
||||
// zero const is a special case - we're using zero reg to encode it.
|
||||
if (imm_slot == INVALID_IMM_TABLE_SLOT && IsZeroConstantOrNullPtr(inst)) {
|
||||
return;
|
||||
}
|
||||
ASSERT(imm_slot != INVALID_IMM_TABLE_SLOT);
|
||||
}
|
||||
|
||||
// Verify instn's inputs and set instn's id to destination location.
|
||||
void RegAllocVerifier::HandleInst(Inst *inst)
|
||||
{
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
auto input = inst->GetDataFlowInput(i);
|
||||
if (input->NoDest() && !IsPseudoUserOfMultiOutput(input)) {
|
||||
ASSERT(!inst->GetLocation(i).IsFixedRegister());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto input_type = inst->GetInputType(i);
|
||||
if (input_type == DataType::NO_TYPE) {
|
||||
ASSERT(inst->GetOpcode() == Opcode::IndirectJump || inst->GetOpcode() == Opcode::CallIndirect);
|
||||
input_type = Is64BitsArch(GetGraph()->GetArch()) ? DataType::INT64 : DataType::INT32;
|
||||
} else {
|
||||
input_type = ConvertRegType(GetGraph(), input_type);
|
||||
}
|
||||
|
||||
success_ &= ForEachLocation(inst->GetLocation(i), input_type, [input, inst, i](LocationState &location) {
|
||||
if (location.GetState() != LocationState::State::KNOWN) {
|
||||
COMPILER_LOG(ERROR, REGALLOC) << "Input #" << i << " is loaded from location with state "
|
||||
<< ToString(location.GetState()) << std::endl
|
||||
<< "Affected inst: " << *inst << std::endl
|
||||
<< "Input inst: " << *input;
|
||||
return false;
|
||||
}
|
||||
if (location.IsValid(input)) {
|
||||
return true;
|
||||
}
|
||||
COMPILER_LOG(ERROR, REGALLOC) << "Input #" << i << " is loaded from location holding instruction "
|
||||
<< location.GetId() << " instead of " << input->GetId() << std::endl
|
||||
<< "Affected inst: " << *inst;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
HandleDest(inst);
|
||||
}
|
||||
|
||||
bool RegAllocVerifier::IsSaveRestoreRegisters(Inst *inst)
|
||||
{
|
||||
if (!inst->IsIntrinsic()) {
|
||||
return false;
|
||||
}
|
||||
auto intrinsic_id = inst->CastToIntrinsic()->GetIntrinsicId();
|
||||
return intrinsic_id == RuntimeInterface::IntrinsicId::INTRINSIC_SAVE_REGISTERS_EP ||
|
||||
intrinsic_id == RuntimeInterface::IntrinsicId::INTRINSIC_RESTORE_REGISTERS_EP;
|
||||
}
|
||||
|
||||
void RegAllocVerifier::HandleSaveRestoreRegisters(Inst *inst)
|
||||
{
|
||||
ASSERT(inst->IsIntrinsic());
|
||||
auto used_regs = GetGraph()->GetUsedRegs<DataType::INT64>()->size();
|
||||
switch (inst->CastToIntrinsic()->GetIntrinsicId()) {
|
||||
case RuntimeInterface::IntrinsicId::INTRINSIC_SAVE_REGISTERS_EP:
|
||||
ASSERT(saved_regs_.empty());
|
||||
for (size_t reg = 0; reg < used_regs; reg++) {
|
||||
saved_regs_.push_back(current_state_->GetReg(reg));
|
||||
}
|
||||
break;
|
||||
case RuntimeInterface::IntrinsicId::INTRINSIC_RESTORE_REGISTERS_EP:
|
||||
ASSERT(!saved_regs_.empty());
|
||||
for (size_t reg = 0; reg < used_regs; reg++) {
|
||||
if (saved_regs_[reg].GetState() == LocationState::State::UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
current_state_->GetReg(reg) = saved_regs_[reg];
|
||||
}
|
||||
saved_regs_.clear();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
HandleDest(inst);
|
||||
}
|
||||
|
||||
// Implicit null checks are not encoded as machine instructions, but instead
|
||||
// added to method's metadata that allow to treat some memory-access related
|
||||
// signals as null pointer exceptions.
|
||||
// SaveState instruction bound to implicit null check captures locations
|
||||
// of its inputs at first null check's user (See RegAllocResolver::GetExplicitUser).
|
||||
// Check if current instruction use implicit null check that was not yet handled
|
||||
// and handle its save state.
|
||||
void RegAllocVerifier::TryHandleImplicitNullCheck(Inst *inst)
|
||||
{
|
||||
NullCheckInst *nc = nullptr;
|
||||
for (auto &input : inst->GetInputs()) {
|
||||
if (input.GetInst()->IsNullCheck() && input.GetInst()->CastToNullCheck()->IsImplicit() &&
|
||||
!input.GetInst()->IsMarked(implicit_null_check_handled_marker_)) {
|
||||
nc = input.GetInst()->CastToNullCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nc == nullptr) {
|
||||
return;
|
||||
}
|
||||
nc->SetMarker(implicit_null_check_handled_marker_);
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
@ -1,238 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_ANALYSIS_REG_ALLOC_VERIFIER_H
|
||||
#define COMPILER_OPTIMIZER_ANALYSIS_REG_ALLOC_VERIFIER_H
|
||||
|
||||
#include "optimizer/pass.h"
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/ir/inst.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class BlockState;
|
||||
class LocationState;
|
||||
/*
|
||||
Analysis aimed to check correctness of registers and stack slots assignment.
|
||||
To verify correctness the analysis stores a state for each possible location
|
||||
(GP register, FP register, stack slot or immediate table slot for spilled constants).
|
||||
The state represents knowledge about a value stored in a location: it could be unknown,
|
||||
known or invalid (conflicting). The "known" state is augmented with instruction id
|
||||
whose value the location is holding.
|
||||
During verification process verifier iterates through all basic blocks and for each
|
||||
instruction within basic block performs following actions:
|
||||
1) checks that each instruction's input register holds known value and corresponding
|
||||
instruction id (from the input location) is the same as the input's id;
|
||||
2) updates instruction's output location with its id (if an instruction has destination).
|
||||
If any input location contains unknown or conflicting value then verification fails.
|
||||
|
||||
When block analysis is complete the state at the block's end have to be propagated to
|
||||
successor blocks. Propagation step perform following actions:
|
||||
1) process all successor's locations corresponding to phi instructions by checking that
|
||||
phi's location at the end of predecessor block holds result of the exact instruction
|
||||
that will be selected by the phi when control flows from the predecessor to successor.
|
||||
If this condition holds then phi's id will be written to successor's location.
|
||||
2) merge all locations (that were not updated at (1)) at the end of predecessor block
|
||||
to the same locations at the beginning of successor block.
|
||||
3) if any location was updated during two previous steps then successor block is marked
|
||||
as updated and should be verified.
|
||||
|
||||
Merge action (2) updates destination (successor block's) location with data from
|
||||
source (predecessor block's) location using following rules:
|
||||
1) if source or destination has conflicting value then destination will have conflicting value;
|
||||
2) if both values are unknown then destination value will remain unknown;
|
||||
3) if either destination or source value is known (but not both values simultaneously) then
|
||||
destination will either remain known or will be updated to known value;
|
||||
4) if both source and destination values are known and holds result of the same instruction
|
||||
then the destination remain known;
|
||||
5) if both source and destination values are known but holds results of different instructions
|
||||
then destination will be updated to conflicting value.
|
||||
|
||||
Verification process continues until there are no more blocks updated during value propagation.
|
||||
If there are no such blocks and there were no errors then verification passes.
|
||||
*/
|
||||
class RegAllocVerifier : public Analysis {
|
||||
public:
|
||||
explicit RegAllocVerifier(Graph *graph, bool save_live_regs_on_call = true);
|
||||
NO_COPY_SEMANTIC(RegAllocVerifier);
|
||||
NO_MOVE_SEMANTIC(RegAllocVerifier);
|
||||
~RegAllocVerifier() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "RegAllocVerifier";
|
||||
}
|
||||
|
||||
private:
|
||||
BasicBlock *current_block_ {nullptr};
|
||||
BlockState *current_state_ {nullptr};
|
||||
ArenaVector<LocationState> immediates_;
|
||||
// required to support Save/RestoreRegisters intrinsics
|
||||
ArenaVector<LocationState> saved_regs_;
|
||||
ArenaVector<LocationState> saved_vregs_;
|
||||
bool success_ {true};
|
||||
Marker implicit_null_check_handled_marker_ {};
|
||||
bool save_live_regs_ {false};
|
||||
|
||||
void InitImmediates();
|
||||
bool IsZeroReg(Register reg, DataType::Type type) const;
|
||||
void HandleDest(Inst *inst);
|
||||
LocationState &GetLocationState(Location location);
|
||||
void UpdateLocation(Location location, DataType::Type type, uint32_t inst_id);
|
||||
template <typename T>
|
||||
bool ForEachLocation(Location location, DataType::Type type, T callback);
|
||||
void ProcessCurrentBlock();
|
||||
void HandleParameter(ParameterInst *inst);
|
||||
void HandleSpillFill(SpillFillInst *inst);
|
||||
void HandleConst(ConstantInst *inst);
|
||||
void HandleInst(Inst *inst);
|
||||
bool IsSaveRestoreRegisters(Inst *inst);
|
||||
void HandleSaveRestoreRegisters(Inst *inst);
|
||||
void TryHandleImplicitNullCheck(Inst *inst);
|
||||
void RestoreLiveRegisters(Inst *inst);
|
||||
};
|
||||
|
||||
class LocationState {
|
||||
public:
|
||||
enum class State : uint8_t { UNKNOWN, KNOWN, CONFLICT };
|
||||
static auto constexpr ZERO_INST = INVALID_ID;
|
||||
|
||||
LocationState() = default;
|
||||
LocationState(LocationState::State state, uint32_t id) : state_(state), id_(id) {}
|
||||
~LocationState() = default;
|
||||
DEFAULT_COPY_SEMANTIC(LocationState);
|
||||
DEFAULT_MOVE_SEMANTIC(LocationState);
|
||||
|
||||
State GetState() const
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
void SetState(State state)
|
||||
{
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
void SetId(uint32_t id)
|
||||
{
|
||||
state_ = State::KNOWN;
|
||||
id_ = id;
|
||||
}
|
||||
|
||||
uint32_t GetId() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
bool Merge(const LocationState &other);
|
||||
|
||||
bool ShouldSkip() const
|
||||
{
|
||||
return skip_;
|
||||
}
|
||||
|
||||
void SetSkip(bool skip)
|
||||
{
|
||||
skip_ = skip;
|
||||
}
|
||||
|
||||
bool IsValid(const Inst *inst) const
|
||||
{
|
||||
return GetId() == inst->GetId() || (GetId() == ZERO_INST && inst->IsZeroRegInst());
|
||||
}
|
||||
|
||||
private:
|
||||
State state_ {UNKNOWN};
|
||||
uint32_t id_ {INVALID_ID};
|
||||
bool skip_ {false};
|
||||
};
|
||||
|
||||
class BlockState {
|
||||
public:
|
||||
BlockState(size_t regs, size_t vregs, size_t stack_slots, size_t stack_params, ArenaAllocator *alloc);
|
||||
~BlockState() = default;
|
||||
NO_COPY_SEMANTIC(BlockState);
|
||||
NO_MOVE_SEMANTIC(BlockState);
|
||||
LocationState &GetReg(Register reg)
|
||||
{
|
||||
ASSERT(reg < regs_.size());
|
||||
return regs_[reg];
|
||||
}
|
||||
const LocationState &GetReg(Register reg) const
|
||||
{
|
||||
ASSERT(reg < regs_.size());
|
||||
return regs_[reg];
|
||||
}
|
||||
LocationState &GetVReg(Register reg)
|
||||
{
|
||||
ASSERT(reg < vregs_.size());
|
||||
return vregs_[reg];
|
||||
}
|
||||
const LocationState &GetVReg(Register reg) const
|
||||
{
|
||||
ASSERT(reg < vregs_.size());
|
||||
return vregs_[reg];
|
||||
}
|
||||
const LocationState &GetStack(StackSlot slot) const
|
||||
{
|
||||
ASSERT(slot < stack_.size());
|
||||
return stack_[slot];
|
||||
}
|
||||
LocationState &GetStack(StackSlot slot)
|
||||
{
|
||||
ASSERT(slot < stack_.size());
|
||||
return stack_[slot];
|
||||
}
|
||||
const LocationState &GetStackArg(StackSlot slot) const
|
||||
{
|
||||
ASSERT(slot < stack_arg_.size());
|
||||
return stack_arg_[slot];
|
||||
}
|
||||
LocationState &GetStackArg(StackSlot slot)
|
||||
{
|
||||
if (slot >= stack_arg_.size()) {
|
||||
stack_arg_.resize(slot + 1);
|
||||
}
|
||||
return stack_arg_[slot];
|
||||
}
|
||||
const LocationState &GetStackParam(StackSlot slot) const
|
||||
{
|
||||
ASSERT(slot < stack_param_.size());
|
||||
return stack_param_[slot];
|
||||
}
|
||||
LocationState &GetStackParam(StackSlot slot)
|
||||
{
|
||||
if (slot >= stack_param_.size()) {
|
||||
stack_param_.resize(slot + 1);
|
||||
}
|
||||
return stack_param_[slot];
|
||||
}
|
||||
|
||||
bool Merge(const BlockState &state, const PhiInstSafeIter &phis, BasicBlock *pred,
|
||||
const ArenaVector<LocationState> &immediates, const LivenessAnalyzer &la);
|
||||
void Copy(BlockState *state);
|
||||
|
||||
private:
|
||||
ArenaVector<LocationState> regs_;
|
||||
ArenaVector<LocationState> vregs_;
|
||||
ArenaVector<LocationState> stack_;
|
||||
ArenaVector<LocationState> stack_param_;
|
||||
ArenaVector<LocationState> stack_arg_;
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_ANALYSIS_REG_ALLOC_VERIFIER_H
|
@ -19,6 +19,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "assembler/assembly-literals.h"
|
||||
#include "constants.h"
|
||||
#include "cross_values.h"
|
||||
#include "datatype.h"
|
||||
#include "ir-dyn-base-types.h"
|
||||
|
@ -1,132 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "graph.h"
|
||||
#include "basicblock.h"
|
||||
#include "dump.h"
|
||||
#include "visualizer_printer.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
void VisualizerPrinter::Print()
|
||||
{
|
||||
PrintBeginTag("cfg");
|
||||
PrintProperty("name", ArenaString(pass_name_, graph_->GetLocalAllocator()->Adapter()));
|
||||
for (const auto &block : graph_->GetBlocksRPO()) {
|
||||
PrintBasicBlock(block);
|
||||
}
|
||||
PrintEndTag("cfg");
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintBeginTag(const char *tag)
|
||||
{
|
||||
(*output_) << MakeOffset() << "begin_" << tag << "\n";
|
||||
++offset_;
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintEndTag(const char *tag)
|
||||
{
|
||||
--offset_;
|
||||
(*output_) << MakeOffset() << "end_" << tag << "\n";
|
||||
}
|
||||
|
||||
std::string VisualizerPrinter::MakeOffset()
|
||||
{
|
||||
return std::string(offset_ << 1U, ' ');
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintProperty(const char *prop, const ArenaString &value)
|
||||
{
|
||||
(*output_) << MakeOffset() << prop;
|
||||
if (!value.empty()) {
|
||||
(*output_) << " \"" << value << "\"";
|
||||
}
|
||||
(*output_) << "\n";
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintProperty(const char *prop, int value)
|
||||
{
|
||||
(*output_) << MakeOffset() << std::dec << prop << " " << value << "\n";
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintDependences(const std::string &preffix, const ArenaVector<BasicBlock *> &blocks)
|
||||
{
|
||||
(*output_) << MakeOffset() << preffix << " ";
|
||||
for (const auto &block : blocks) {
|
||||
(*output_) << "\"B" << BBId(block, graph_->GetLocalAllocator()) << "\" ";
|
||||
}
|
||||
(*output_) << "\n";
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintBasicBlock(BasicBlock *block)
|
||||
{
|
||||
PrintBeginTag("block");
|
||||
PrintProperty("name", "B" + BBId(block, graph_->GetLocalAllocator()));
|
||||
PrintProperty("from_bci", -1);
|
||||
PrintProperty("to_bci", -1);
|
||||
PrintDependences("predecessors", block->GetPredsBlocks());
|
||||
PrintDependences("successors", block->GetSuccsBlocks());
|
||||
PrintProperty("xhandlers", ArenaString("", graph_->GetLocalAllocator()->Adapter()));
|
||||
PrintProperty("flags", ArenaString("", graph_->GetLocalAllocator()->Adapter()));
|
||||
PrintProperty("loop_depth", 0);
|
||||
PrintBeginTag("states");
|
||||
PrintBeginTag("locals");
|
||||
PrintProperty("size", 0);
|
||||
PrintProperty("method", ArenaString("None", graph_->GetLocalAllocator()->Adapter()));
|
||||
PrintEndTag("locals");
|
||||
PrintEndTag("states");
|
||||
PrintBeginTag("HIR");
|
||||
PrintInsts(block);
|
||||
PrintEndTag("HIR");
|
||||
PrintEndTag("block");
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintInsts(BasicBlock *block)
|
||||
{
|
||||
if (block->AllInsts().begin() != block->AllInsts().end()) {
|
||||
for (auto inst : block->AllInsts()) {
|
||||
PrintInst(inst);
|
||||
}
|
||||
} else {
|
||||
(*output_) << MakeOffset() << "0 0 - ";
|
||||
if (block->IsStartBlock()) {
|
||||
(*output_) << "EMPTY_START_BLOCK";
|
||||
} else if (block->IsEndBlock()) {
|
||||
(*output_) << "EXIT_BLOCK";
|
||||
} else {
|
||||
(*output_) << "EMPTY_BLOCK";
|
||||
}
|
||||
(*output_) << " <|@\n";
|
||||
}
|
||||
}
|
||||
|
||||
void VisualizerPrinter::PrintInst(Inst *inst)
|
||||
{
|
||||
uint32_t users_size = 0;
|
||||
for (auto it = inst->GetUsers().begin(); it != inst->GetUsers().end(); ++it) {
|
||||
users_size++;
|
||||
}
|
||||
(*output_) << MakeOffset() << "0 " << std::dec << users_size << " ";
|
||||
(*output_) << InstId(inst, graph_->GetLocalAllocator()) << " ";
|
||||
inst->DumpOpcode(output_);
|
||||
(*output_) << " type:" << DataType::ToString(inst->GetType()) << " ";
|
||||
(*output_) << "inputs: ";
|
||||
bool has_input = inst->DumpInputs(output_);
|
||||
if (has_input && !inst->GetUsers().Empty()) {
|
||||
(*output_) << " users: ";
|
||||
}
|
||||
DumpUsers(inst, output_);
|
||||
(*output_) << " <|@\n";
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef COMPILER_OPTIMIZER_IR_VISUALIZER_PRINTER_H
|
||||
#define COMPILER_OPTIMIZER_IR_VISUALIZER_PRINTER_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace panda::compiler {
|
||||
// This class dump graph in C1Visualizer format
|
||||
// Option: --compiler-visualizer-dump
|
||||
// Open dump files:
|
||||
// 1. Download c1visualizer (http://lafo.ssw.uni-linz.ac.at/c1visualizer/c1visualizer-1.7.zip).
|
||||
// 2. Run c1visualizer for your OS from folder "bin".
|
||||
// 3. Open files.
|
||||
|
||||
class VisualizerPrinter {
|
||||
public:
|
||||
VisualizerPrinter(const Graph *graph, std::ostream *output, const char *pass_name)
|
||||
: graph_(graph), output_(output), pass_name_(pass_name)
|
||||
{
|
||||
}
|
||||
|
||||
NO_MOVE_SEMANTIC(VisualizerPrinter);
|
||||
NO_COPY_SEMANTIC(VisualizerPrinter);
|
||||
~VisualizerPrinter() = default;
|
||||
|
||||
void Print();
|
||||
|
||||
private:
|
||||
void PrintBeginTag(const char *tag);
|
||||
|
||||
void PrintEndTag(const char *tag);
|
||||
|
||||
std::string MakeOffset();
|
||||
|
||||
void PrintProperty(const char *prop, const ArenaString &value);
|
||||
|
||||
void PrintProperty(const char *prop, int value);
|
||||
|
||||
void PrintDependences(const std::string &preffix, const ArenaVector<BasicBlock *> &blocks);
|
||||
|
||||
void PrintBasicBlock(BasicBlock *block);
|
||||
|
||||
void PrintInsts(BasicBlock *block);
|
||||
|
||||
void PrintInst(Inst *inst);
|
||||
|
||||
private:
|
||||
const Graph *graph_;
|
||||
std::ostream *output_;
|
||||
const char *pass_name_;
|
||||
uint32_t offset_ {0};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_IR_VISUALIZER_PRINTER_H
|
File diff suppressed because it is too large
Load Diff
@ -19,45 +19,6 @@
|
||||
#include "compiler_logger.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
/* static */
|
||||
DataType::Type InstBuilder::ConvertPbcType(panda_file::Type type)
|
||||
{
|
||||
switch (type.GetId()) {
|
||||
case panda_file::Type::TypeId::VOID:
|
||||
return DataType::VOID;
|
||||
case panda_file::Type::TypeId::U1:
|
||||
return DataType::BOOL;
|
||||
case panda_file::Type::TypeId::I8:
|
||||
return DataType::INT8;
|
||||
case panda_file::Type::TypeId::U8:
|
||||
return DataType::UINT8;
|
||||
case panda_file::Type::TypeId::I16:
|
||||
return DataType::INT16;
|
||||
case panda_file::Type::TypeId::U16:
|
||||
return DataType::UINT16;
|
||||
case panda_file::Type::TypeId::I32:
|
||||
return DataType::INT32;
|
||||
case panda_file::Type::TypeId::U32:
|
||||
return DataType::UINT32;
|
||||
case panda_file::Type::TypeId::I64:
|
||||
return DataType::INT64;
|
||||
case panda_file::Type::TypeId::U64:
|
||||
return DataType::UINT64;
|
||||
case panda_file::Type::TypeId::F32:
|
||||
return DataType::FLOAT32;
|
||||
case panda_file::Type::TypeId::F64:
|
||||
return DataType::FLOAT64;
|
||||
case panda_file::Type::TypeId::REFERENCE:
|
||||
return DataType::REFERENCE;
|
||||
case panda_file::Type::TypeId::TAGGED:
|
||||
case panda_file::Type::TypeId::INVALID:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void InstBuilder::Prepare(bool is_inlined_graph)
|
||||
{
|
||||
SetCurrentBlock(GetGraph()->GetStartBlock());
|
||||
@ -77,11 +38,6 @@ void InstBuilder::Prepare(bool is_inlined_graph)
|
||||
|
||||
UpdateDefinition(reg_num, param_inst);
|
||||
}
|
||||
|
||||
// We don't need to create SafePoint at the beginning of the callee graph
|
||||
if (options.IsCompilerUseSafepoint() && !is_inlined_graph) {
|
||||
GetGraph()->GetStartBlock()->AppendInst(CreateSafePoint(GetGraph()->GetStartBlock()));
|
||||
}
|
||||
}
|
||||
|
||||
void InstBuilder::UpdateDefsForCatch()
|
||||
@ -350,26 +306,6 @@ SaveStateInst *InstBuilder::CreateSaveState(Opcode opc, size_t pc)
|
||||
return inst;
|
||||
}
|
||||
|
||||
ClassInst *InstBuilder::CreateLoadAndInitClassGeneric(uint32_t class_id, size_t pc)
|
||||
{
|
||||
auto class_ptr = GetRuntime()->ResolveType(GetGraph()->GetMethod(), class_id);
|
||||
ClassInst *inst = nullptr;
|
||||
if (class_ptr == nullptr) {
|
||||
ASSERT(!graph_->IsBytecodeOptimizer());
|
||||
inst = graph_->CreateInstUnresolvedLoadAndInitClass(DataType::REFERENCE, pc);
|
||||
if (!GetGraph()->IsAotMode() && !GetGraph()->IsBytecodeOptimizer()) {
|
||||
GetRuntime()->GetUnresolvedTypes()->AddTableSlot(GetMethod(), class_id,
|
||||
UnresolvedTypesInterface::SlotKind::CLASS);
|
||||
}
|
||||
} else {
|
||||
inst = graph_->CreateInstLoadAndInitClass(DataType::REFERENCE, pc);
|
||||
}
|
||||
inst->SetTypeId(class_id);
|
||||
inst->SetMethod(GetGraph()->GetMethod());
|
||||
inst->SetClass(class_ptr);
|
||||
return inst;
|
||||
}
|
||||
|
||||
DataType::Type InstBuilder::GetCurrentMethodReturnType() const
|
||||
{
|
||||
return GetRuntime()->GetMethodReturnType(GetMethod());
|
||||
@ -466,17 +402,4 @@ void InstBuilder::CleanupCatchPhis()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstBuilder::SyncWithGraph()
|
||||
{
|
||||
size_t idx = current_defs_ - &defs_[0];
|
||||
size_t size = defs_.size();
|
||||
defs_.resize(graph_->GetVectorBlocks().size(), InstVector(graph_->GetLocalAllocator()->Adapter()));
|
||||
for (size_t i = size; i < defs_.size(); i++) {
|
||||
defs_[i].resize(VREGS_AND_ARGS_COUNT + 1);
|
||||
std::copy(defs_[idx].cbegin(), defs_[idx].cend(), defs_[i].begin());
|
||||
}
|
||||
current_defs_ = &defs_[current_bb_->GetId()];
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
@ -106,16 +106,6 @@ public:
|
||||
|
||||
size_t GetPc(const uint8_t *inst_ptr) const;
|
||||
|
||||
auto CreateSafePoint(BasicBlock *bb)
|
||||
{
|
||||
return CreateSaveState(Opcode::SafePoint, bb->GetGuestPc());
|
||||
}
|
||||
|
||||
auto CreateSaveStateOsr(BasicBlock *bb)
|
||||
{
|
||||
return CreateSaveState(Opcode::SaveStateOsr, bb->GetGuestPc());
|
||||
}
|
||||
|
||||
auto CreateSaveStateDeoptimize(uint32_t pc)
|
||||
{
|
||||
return CreateSaveState(Opcode::SaveStateDeoptimize, pc);
|
||||
@ -144,8 +134,6 @@ public:
|
||||
GetGraph()->GetRuntime()->FillInstIdTypePairByPc(id, pc);
|
||||
}
|
||||
private:
|
||||
void SyncWithGraph();
|
||||
|
||||
void UpdateDefsForCatch();
|
||||
void UpdateDefsForLoopHead();
|
||||
|
||||
@ -163,23 +151,6 @@ private:
|
||||
<< *inst;
|
||||
}
|
||||
|
||||
void AddInstructions(Inst *inst1, Inst *inst2, Inst *inst3, Inst *inst4)
|
||||
{
|
||||
AddInstruction(inst1);
|
||||
AddInstruction(inst2);
|
||||
AddInstruction(inst3);
|
||||
AddInstruction(inst4);
|
||||
}
|
||||
|
||||
void AddInstructions(Inst *inst1, Inst *inst2, Inst *inst3, Inst *inst4, Inst *inst5)
|
||||
{
|
||||
AddInstruction(inst1);
|
||||
AddInstruction(inst2);
|
||||
AddInstruction(inst3);
|
||||
AddInstruction(inst4);
|
||||
AddInstruction(inst5);
|
||||
}
|
||||
|
||||
void UpdateDefinition(size_t vreg, Inst *inst)
|
||||
{
|
||||
ASSERT(vreg < current_defs_->size());
|
||||
@ -257,23 +228,6 @@ private:
|
||||
return inst;
|
||||
}
|
||||
|
||||
auto FindOrCreateFloatConstant(float value)
|
||||
{
|
||||
auto inst = GetGraph()->FindOrCreateConstant<float>(value);
|
||||
if (inst->GetId() == GetGraph()->GetCurrentInstructionId() - 1) {
|
||||
COMPILER_LOG(DEBUG, IR_BUILDER) << "create new constant: value=" << value << ", inst=" << inst->GetId();
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
enum SaveStateType {
|
||||
CHECK = 0, // side_exit = true, move_to_side_exit = true
|
||||
CALL, // side_exit = false, move_to_side_exit = false
|
||||
VIRT_CALL // side_exit = true, move_to_side_exit = false
|
||||
};
|
||||
|
||||
ClassInst *CreateLoadAndInitClassGeneric(uint32_t class_id, size_t pc);
|
||||
|
||||
Inst *CreateCast(Inst *input, DataType::Type type, DataType::Type operands_type, size_t pc)
|
||||
{
|
||||
auto cast = GetGraph()->CreateInstCast(type, pc);
|
||||
@ -285,74 +239,10 @@ private:
|
||||
return cast;
|
||||
}
|
||||
|
||||
template <Opcode opcode>
|
||||
void BuildCall(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildDynamicCall(const BytecodeInstruction *bc_inst, bool is_range);
|
||||
template <Opcode opcode>
|
||||
CallInst *BuildCallInst(RuntimeInterface::MethodPtr method, uint32_t method_id, size_t pc);
|
||||
template <Opcode opcode>
|
||||
CallInst *BuildUnresolvedCallInst(uint32_t method_id, size_t pc);
|
||||
void BuildInitClassInstForCallStatic(RuntimeInterface::MethodPtr method, uint32_t class_id, size_t pc,
|
||||
Inst *save_state);
|
||||
template <typename T>
|
||||
void SetInputsForCallInst(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read, T *inst,
|
||||
Inst *null_check, uint32_t method_id, bool has_implicit_arg, bool need_savestate);
|
||||
Inst *GetArgDefinition(const BytecodeInstruction *bc_inst, size_t idx, bool acc_read);
|
||||
template <bool is_virtual>
|
||||
void AddArgNullcheckIfNeeded(RuntimeInterface::IntrinsicId intrinsic, Inst *inst, Inst *save_state, size_t bc_addr);
|
||||
void BuildMonitor(const BytecodeInstruction *bc_inst, Inst *def, bool is_enter);
|
||||
void BuildEcma([[maybe_unused]] const BytecodeInstruction *bc_inst);
|
||||
template <bool with_speculative = false>
|
||||
void BuildEcmaAsIntrinsics([[maybe_unused]] const BytecodeInstruction *bc_inst);
|
||||
void BuildEcmaFromIrtoc([[maybe_unused]] const BytecodeInstruction *bc_inst);
|
||||
|
||||
Inst *BuildFloatInst(const BytecodeInstruction *bc_inst);
|
||||
void BuildIntrinsic(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildDefaultIntrinsic(bool is_virtual, const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildStaticCallIntrinsic(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildAbsIntrinsic(const BytecodeInstruction *bc_inst, bool acc_read);
|
||||
template <Opcode opcode>
|
||||
void BuildBinaryOperationIntrinsic(const BytecodeInstruction *bc_inst, bool acc_read);
|
||||
void BuildSqrtIntrinsic(const BytecodeInstruction *bc_inst, bool acc_read);
|
||||
void BuildIsNanIntrinsic(const BytecodeInstruction *bc_inst, bool acc_read);
|
||||
void BuildMonitorIntrinsic(const BytecodeInstruction *bc_inst, bool is_enter, bool acc_read);
|
||||
void BuildDefaultStaticIntrinsic(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildDefaultVirtualCallIntrinsic(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildVirtualCallIntrinsic(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read);
|
||||
void BuildThrow(const BytecodeInstruction *bc_inst);
|
||||
void BuildLenArray(const BytecodeInstruction *bc_inst);
|
||||
void BuildNewArray(const BytecodeInstruction *bc_inst);
|
||||
void BuildNewObject(const BytecodeInstruction *bc_inst);
|
||||
void BuildLoadConstArray(const BytecodeInstruction *bc_inst);
|
||||
template <typename T>
|
||||
void BuildUnfoldLoadConstArray(const BytecodeInstruction *bc_inst, DataType::Type type,
|
||||
const pandasm::LiteralArray &lit_array);
|
||||
void BuildInitObject(const BytecodeInstruction *bc_inst, bool is_range);
|
||||
CallInst *BuildCallStaticForInitObject(const BytecodeInstruction *bc_inst, uint32_t method_id);
|
||||
void BuildMultiDimensionalArrayObject(const BytecodeInstruction *bc_inst, bool is_range);
|
||||
void BuildInitObjectMultiDimensionalArray(const BytecodeInstruction *bc_inst, bool is_range);
|
||||
template <bool is_acc_write>
|
||||
void BuildLoadObject(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
template <bool is_acc_read>
|
||||
void BuildStoreObject(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
Inst *BuildStoreObjectInst(const BytecodeInstruction *bc_inst, DataType::Type type,
|
||||
RuntimeInterface::FieldPtr field, size_t type_id);
|
||||
void BuildLoadStatic(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
Inst *BuildLoadStaticInst(const BytecodeInstruction *bc_inst, DataType::Type type, size_t type_id,
|
||||
Inst *save_state);
|
||||
void BuildStoreStatic(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
Inst *BuildStoreStaticInst(const BytecodeInstruction *bc_inst, DataType::Type type, size_t type_id,
|
||||
Inst *store_input, Inst *save_state);
|
||||
void BuildCheckCast(const BytecodeInstruction *bc_inst);
|
||||
void BuildIsInstance(const BytecodeInstruction *bc_inst);
|
||||
Inst *BuildLoadClass(RuntimeInterface::IdType type_id, size_t pc, Inst *save_state);
|
||||
void BuildLoadArray(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
void BuildStoreArray(const BytecodeInstruction *bc_inst, DataType::Type type);
|
||||
template <bool create_ref_check>
|
||||
void BuildStoreArrayInst(const BytecodeInstruction *bc_inst, DataType::Type type, Inst *array_ref, Inst *index,
|
||||
Inst *value);
|
||||
void BuildChecksBeforeArray(const BytecodeInstruction *bc_inst, Inst *array_ref, Inst **ss, Inst **nc, Inst **al,
|
||||
Inst **bc);
|
||||
template <Opcode opcode>
|
||||
void BuildLoadFromPool(const BytecodeInstruction *bc_inst);
|
||||
void BuildCastToAnyString(const BytecodeInstruction *bc_inst);
|
||||
@ -402,9 +292,6 @@ private:
|
||||
|
||||
void SetTypeRec(Inst *inst, DataType::Type type);
|
||||
|
||||
/// Convert Panda bytecode type to COMPILER IR type
|
||||
static DataType::Type ConvertPbcType(panda_file::Type type);
|
||||
|
||||
/// Get return type of the method specified by id
|
||||
DataType::Type GetMethodReturnType(uintptr_t id) const;
|
||||
/// Get type of argument of the method specified by id
|
||||
|
@ -143,16 +143,12 @@ templates:
|
||||
% end
|
||||
% end
|
||||
% elsif inst.acc_and_operands[1].id?
|
||||
% if inst.opcode =~ /lda_const/
|
||||
BuildLoadConstArray(instruction);
|
||||
% else
|
||||
BuildLoadFromPool<Opcode::<%= inst.opcode =~ /lda_type/ ? 'LoadType' : 'LoadString' %>>(instruction);
|
||||
% if inst.opcode =~ /lda_str/
|
||||
if (GetGraph()->IsBytecodeOptimizer() && GetGraph()->IsDynamicMethod()) {
|
||||
TryFillInstIdTypePair(GetDefinitionAcc()->GetId(), static_cast<int>(GetPc(instruction->GetAddress())));
|
||||
}
|
||||
% end
|
||||
% end
|
||||
% else
|
||||
% raise "Unsupported instruction type" unless inst.acc_and_operands[1].reg?
|
||||
UpdateDefinitionAcc(GetDefinition(instruction->GetVReg<<%=inst.get_format%>, 0>()));
|
||||
@ -190,19 +186,6 @@ templates:
|
||||
auto inst = graph_->CreateInst<%= opcode %>(GetCurrentMethodReturnType(), GetPc(instruction->GetAddress()));
|
||||
<%=template('operands', inst, '')-%>
|
||||
AddInstruction(inst);
|
||||
call: |-
|
||||
% is_range = inst.opcode.include? 'range'
|
||||
% is_virtual = inst.opcode.include? 'virt'
|
||||
% is_dynamic = inst.opcode.start_with? 'calli_dyn'
|
||||
% if (is_dynamic)
|
||||
BuildDynamicCall(instruction, <%= is_range ? 'true' : 'false' %>);
|
||||
% else
|
||||
% acc_read = inst.acc_read?
|
||||
BuildCall<Opcode::<%= is_virtual ? 'CallVirtual' : 'CallStatic' %>>(instruction, <%= is_range ? 'true' : 'false' %>, <%= acc_read %>);
|
||||
% end
|
||||
monitor: |-
|
||||
% is_enter = inst.mnemonic.end_with?('enter')
|
||||
BuildMonitor(instruction, GetDefinitionAcc(), <%= is_enter %>);
|
||||
ecma: |-
|
||||
% name = inst.opcode.upcase.split('')[1]
|
||||
% case name
|
||||
@ -245,37 +228,6 @@ templates:
|
||||
}
|
||||
% end
|
||||
% end
|
||||
builtin: |-
|
||||
BuildBuiltin(instruction);
|
||||
newobj: |-
|
||||
BuildNewObject(instruction);
|
||||
initobj: |-
|
||||
% is_range = inst.opcode.include? 'range'
|
||||
BuildInitObject(instruction, <%= is_range ? 'true' : 'false' %>);
|
||||
ldobj: |-
|
||||
BuildLoadObject< <%= inst.acc_write? %> >(instruction, <%= get_type(inst.dtype) %>);
|
||||
stobj: |-
|
||||
BuildStoreObject< <%= inst.acc_read? %> >(instruction, <%= get_type(inst.type(0)) %>);
|
||||
ldstatic: |-
|
||||
BuildLoadStatic(instruction, <%= get_type(inst.dtype) %>);
|
||||
ststatic: |-
|
||||
BuildStoreStatic(instruction, <%= get_type(inst.type(0)) %>);
|
||||
ldarr: |-
|
||||
BuildLoadArray(instruction, <%= get_type(inst.type(1)) %>);
|
||||
starr: |-
|
||||
BuildStoreArray(instruction, <%= get_type(inst.type(1)) %>);
|
||||
newarr: |-
|
||||
BuildNewArray(instruction);
|
||||
lda.const: |-
|
||||
BuildLoadConstArray(instruction);
|
||||
lenarr: |-
|
||||
BuildLenArray(instruction);
|
||||
checkcast: |-
|
||||
BuildCheckCast(instruction);
|
||||
isinstance: |-
|
||||
BuildIsInstance(instruction);
|
||||
throw: |-
|
||||
BuildThrow(instruction);
|
||||
nop: |-
|
||||
unimplemented: |-
|
||||
// TODO(msherstennikov): implement
|
||||
|
@ -61,9 +61,6 @@ bool IrBuilder::RunImpl()
|
||||
GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
|
||||
GetGraph()->RunPass<LoopAnalyzer>();
|
||||
inst_builder.FixInstructions();
|
||||
if (GetGraph()->GetRuntime()->IsMemoryBarrierRequired(GetMethod())) {
|
||||
SetMemoryBarrierFlag();
|
||||
}
|
||||
|
||||
if (options.IsCompilerPrintStats() || options.WasSetCompilerDumpStatsCsv()) {
|
||||
uint64_t pbc_inst_num = 0;
|
||||
@ -77,22 +74,6 @@ bool IrBuilder::RunImpl()
|
||||
return true;
|
||||
}
|
||||
|
||||
void IrBuilder::SetMemoryBarrierFlag()
|
||||
{
|
||||
for (auto pre_end : GetGraph()->GetEndBlock()->GetPredsBlocks()) {
|
||||
if (pre_end->IsTryEnd()) {
|
||||
ASSERT(pre_end->GetPredsBlocks().size() == 1U);
|
||||
pre_end = pre_end->GetPredecessor(0);
|
||||
}
|
||||
auto last_inst = pre_end->GetLastInst();
|
||||
ASSERT(last_inst != nullptr);
|
||||
if (last_inst->GetOpcode() == Opcode::Return) {
|
||||
ASSERT(last_inst->GetType() == DataType::VOID);
|
||||
last_inst->SetFlag(inst_flags::MEM_BARRIER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IrBuilder::CheckMethodLimitations(const BytecodeInstructions &instructions, size_t vregs_count)
|
||||
{
|
||||
// TODO(a.popov) Optimize catch-phi's memory consumption and get rid of this limitation
|
||||
@ -142,31 +123,6 @@ bool IrBuilder::BuildBasicBlock(BasicBlock *bb, InstBuilder *inst_builder, const
|
||||
COMPILER_LOG(DEBUG, IR_BUILDER) << "Create save state deoptimize: " << *ss;
|
||||
}
|
||||
|
||||
if (bb->IsLoopHeader() && !bb->GetLoop()->IsTryCatchLoop()) {
|
||||
// Prepend SaveSateOSR as a first instruction in the loop header
|
||||
// TODO (a.popov) Support osr-entry for loops with catch-block back-edge
|
||||
if (GetGraph()->IsOsrMode()) {
|
||||
auto backedges = bb->GetLoop()->GetBackEdges();
|
||||
auto is_catch = [](BasicBlock *basic_block) { return basic_block->IsCatch(); };
|
||||
bool has_catch_backedge = std::find_if(backedges.begin(), backedges.end(), is_catch) != backedges.end();
|
||||
if (has_catch_backedge) {
|
||||
COMPILER_LOG(WARNING, IR_BUILDER)
|
||||
<< "Osr-entry for loops with catch-handler as back-edge is not supported";
|
||||
return false;
|
||||
}
|
||||
bb->SetOsrEntry(true);
|
||||
auto ss = inst_builder->CreateSaveStateOsr(bb);
|
||||
bb->AppendInst(ss);
|
||||
COMPILER_LOG(DEBUG, IR_BUILDER) << "create save state OSR: " << *ss;
|
||||
}
|
||||
|
||||
if (options.IsCompilerUseSafepoint()) {
|
||||
auto sp = inst_builder->CreateSafePoint(bb);
|
||||
bb->AppendInst(sp);
|
||||
COMPILER_LOG(DEBUG, IR_BUILDER) << "create safepoint: " << *sp;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(bb->GetGuestPc() != INVALID_PC);
|
||||
// If block is not in the `blocks_` vector, it's auxiliary block without instructions
|
||||
if (bb == blocks_[bb->GetGuestPc()]) {
|
||||
@ -600,31 +556,4 @@ void IrBuilder::RestoreTryEnd(const TryCodeBlock &try_block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IrBuilderInliningAnalysis::RunImpl()
|
||||
{
|
||||
auto panda_file = static_cast<panda_file::File *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
|
||||
panda_file::MethodDataAccessor mda(*panda_file,
|
||||
panda_file::File::EntityId(GetGraph()->GetRuntime()->GetMethodId(GetMethod())));
|
||||
auto code_id = mda.GetCodeId();
|
||||
if (!code_id.has_value()) {
|
||||
return false;
|
||||
}
|
||||
panda_file::CodeDataAccessor cda(*panda_file, code_id.value());
|
||||
|
||||
// TODO(msherstennikov): Support inlining methods with try/catch
|
||||
if (cda.GetTriesSize() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BytecodeInstructions instructions(GetGraph()->GetRuntime()->GetMethodCode(GetMethod()),
|
||||
GetGraph()->GetRuntime()->GetMethodCodeSize(GetMethod()));
|
||||
|
||||
for (auto inst : instructions) {
|
||||
if (!IsSuitableForInline(&inst)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace panda::compiler
|
||||
|
@ -137,7 +137,6 @@ private:
|
||||
void MarkTryCatchBlocks(Marker marker);
|
||||
template <class Callback>
|
||||
void EnumerateTryBlocksCoveredPc(uint32_t pc, const Callback &callback);
|
||||
void SetMemoryBarrierFlag();
|
||||
void ConnectTryCodeBlock(const TryCodeBlock &try_block, const ArenaMap<uint32_t, BasicBlock *> &catch_blocks);
|
||||
void ProcessThrowableInstructions(InstBuilder *inst_builder, Inst *throwable_inst);
|
||||
void RestoreTryEnd(const TryCodeBlock &try_block);
|
||||
@ -154,58 +153,6 @@ private:
|
||||
CallInst *caller_inst_ {nullptr};
|
||||
};
|
||||
|
||||
class IrBuilderInliningAnalysis : public Analysis {
|
||||
public:
|
||||
IrBuilderInliningAnalysis(Graph *graph, RuntimeInterface::MethodPtr method) : Analysis(graph), method_(method) {}
|
||||
~IrBuilderInliningAnalysis() override = default;
|
||||
NO_COPY_SEMANTIC(IrBuilderInliningAnalysis);
|
||||
NO_MOVE_SEMANTIC(IrBuilderInliningAnalysis);
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "IrBuilderInlineAnalysis";
|
||||
}
|
||||
|
||||
auto GetMethod() const
|
||||
{
|
||||
return method_;
|
||||
}
|
||||
|
||||
auto HasRuntimeCalls() const
|
||||
{
|
||||
return has_runtime_calls_;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual bool IsSuitableForInline(const BytecodeInstruction *inst);
|
||||
|
||||
private:
|
||||
RuntimeInterface::MethodPtr method_;
|
||||
bool has_runtime_calls_ {false};
|
||||
};
|
||||
|
||||
class IrBuilderExternalInliningAnalysis : public IrBuilderInliningAnalysis {
|
||||
public:
|
||||
IrBuilderExternalInliningAnalysis(Graph *graph, RuntimeInterface::MethodPtr method)
|
||||
: IrBuilderInliningAnalysis(graph, method)
|
||||
{
|
||||
}
|
||||
|
||||
NO_COPY_SEMANTIC(IrBuilderExternalInliningAnalysis);
|
||||
NO_MOVE_SEMANTIC(IrBuilderExternalInliningAnalysis);
|
||||
~IrBuilderExternalInliningAnalysis() override = default;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "IrBuilderExternalInliningAnalysis";
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsSuitableForInline(const BytecodeInstruction *inst) override;
|
||||
};
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // PANDA_IR_BUILDER_H
|
||||
|
@ -1,441 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/inst.h"
|
||||
#include "optimizer/optimizations/branch_elimination.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
/**
|
||||
* Branch Elimination optimization finds `if-true` blocks with resolvable conditional instruction.
|
||||
* Condition can be resolved in the following ways:
|
||||
* - Condition is constant;
|
||||
* - Condition is dominated by the equal one condition with the same inputs and the only one successor of the dominant
|
||||
* reaches dominated condition
|
||||
*/
|
||||
BranchElimination::BranchElimination(Graph *graph)
|
||||
: Optimization(graph),
|
||||
same_input_compares_(graph->GetLocalAllocator()->Adapter()),
|
||||
same_input_compare_any_type_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
bool BranchElimination::RunImpl()
|
||||
{
|
||||
GetGraph()->RunPass<DominatorsTree>();
|
||||
is_applied_ = false;
|
||||
same_input_compares_.clear();
|
||||
auto marker_holder = MarkerHolder(GetGraph());
|
||||
rm_block_marker_ = marker_holder.GetMarker();
|
||||
for (auto block : GetGraph()->GetBlocksRPO()) {
|
||||
// TODO (a.popov) Support BranchElimination for try-catch blocks
|
||||
if (block->IsEmpty() || block->IsTry()) {
|
||||
continue;
|
||||
}
|
||||
if (block->GetLastInst()->GetOpcode() == Opcode::IfImm) {
|
||||
VisitBlock(block);
|
||||
}
|
||||
}
|
||||
DisconnectBlocks();
|
||||
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Branch elimination complete";
|
||||
return is_applied_;
|
||||
}
|
||||
|
||||
void BranchElimination::InvalidateAnalyses()
|
||||
{
|
||||
GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
|
||||
GetGraph()->InvalidateAnalysis<AliasAnalysis>();
|
||||
GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
|
||||
InvalidateBlocksOrderAnalyzes(GetGraph());
|
||||
}
|
||||
|
||||
void BranchElimination::BranchEliminationConst(BasicBlock *if_block)
|
||||
{
|
||||
auto if_imm = if_block->GetLastInst()->CastToIfImm();
|
||||
auto condition_inst = if_block->GetLastInst()->GetInput(0).GetInst();
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Block with constant if instruction input is visited, id = "
|
||||
<< if_block->GetId();
|
||||
|
||||
uint64_t const_value = condition_inst->CastToConstant()->GetIntValue();
|
||||
bool cond_result = (const_value == if_imm->GetImm());
|
||||
if (if_imm->GetCc() == CC_NE) {
|
||||
cond_result = !cond_result;
|
||||
} else {
|
||||
ASSERT(if_imm->GetCc() == CC_EQ);
|
||||
}
|
||||
auto eliminated_successor = if_block->GetFalseSuccessor();
|
||||
if (!cond_result) {
|
||||
eliminated_successor = if_block->GetTrueSuccessor();
|
||||
}
|
||||
EliminateBranch(if_block, eliminated_successor);
|
||||
GetGraph()->GetEventWriter().EventBranchElimination(
|
||||
if_block->GetId(), if_block->GetGuestPc(), condition_inst->GetId(), condition_inst->GetPc(), "const-condition",
|
||||
eliminated_successor == if_block->GetTrueSuccessor());
|
||||
}
|
||||
|
||||
void BranchElimination::BranchEliminationCompare(BasicBlock *if_block)
|
||||
{
|
||||
auto if_imm = if_block->GetLastInst()->CastToIfImm();
|
||||
auto condition_inst = if_block->GetLastInst()->GetInput(0).GetInst();
|
||||
if (auto result = GetConditionResult(condition_inst)) {
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Compare instruction result was resolved. Instruction id = "
|
||||
<< condition_inst->GetId()
|
||||
<< ", resolved result: " << (result.value() ? "true" : "false");
|
||||
auto eliminated_successor = if_imm->GetEdgeIfInputFalse();
|
||||
if (!result.value()) {
|
||||
eliminated_successor = if_imm->GetEdgeIfInputTrue();
|
||||
}
|
||||
EliminateBranch(if_block, eliminated_successor);
|
||||
GetGraph()->GetEventWriter().EventBranchElimination(
|
||||
if_block->GetId(), if_block->GetGuestPc(), condition_inst->GetId(), condition_inst->GetPc(), "dominant-if",
|
||||
eliminated_successor == if_block->GetTrueSuccessor());
|
||||
} else {
|
||||
ConditionOps ops {condition_inst->GetInput(0).GetInst(), condition_inst->GetInput(1).GetInst()};
|
||||
auto it = same_input_compares_.try_emplace(ops, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
it.first->second.push_back(condition_inst);
|
||||
}
|
||||
}
|
||||
|
||||
void BranchElimination::BranchEliminationCompareAnyType(BasicBlock *if_block)
|
||||
{
|
||||
auto if_imm = if_block->GetLastInst()->CastToIfImm();
|
||||
auto compare_any = if_block->GetLastInst()->GetInput(0).GetInst()->CastToCompareAnyType();
|
||||
if (auto result = GetCompareAnyTypeResult(if_imm)) {
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "CompareAnyType instruction result was resolved. Instruction id = "
|
||||
<< compare_any->GetId()
|
||||
<< ", resolved result: " << (result.value() ? "true" : "false");
|
||||
auto eliminated_successor = if_imm->GetEdgeIfInputFalse();
|
||||
if (!result.value()) {
|
||||
eliminated_successor = if_imm->GetEdgeIfInputTrue();
|
||||
}
|
||||
EliminateBranch(if_block, eliminated_successor);
|
||||
GetGraph()->GetEventWriter().EventBranchElimination(if_block->GetId(), if_block->GetGuestPc(),
|
||||
compare_any->GetId(), compare_any->GetPc(), "dominant-if",
|
||||
eliminated_successor == if_block->GetTrueSuccessor());
|
||||
return;
|
||||
}
|
||||
|
||||
Inst *input = compare_any->GetInput(0).GetInst();
|
||||
auto it = same_input_compare_any_type_.try_emplace(input, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
it.first->second.push_back(compare_any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select unreachable successor and run elimination process
|
||||
* @param blocks - list of blocks with constant `if` instruction input
|
||||
*/
|
||||
void BranchElimination::VisitBlock(BasicBlock *if_block)
|
||||
{
|
||||
ASSERT(if_block != nullptr);
|
||||
ASSERT(if_block->GetGraph() == GetGraph());
|
||||
ASSERT(if_block->GetLastInst()->GetOpcode() == Opcode::IfImm);
|
||||
ASSERT(if_block->GetSuccsBlocks().size() == MAX_SUCCS_NUM);
|
||||
|
||||
auto condition_inst = if_block->GetLastInst()->GetInput(0).GetInst();
|
||||
switch (condition_inst->GetOpcode()) {
|
||||
case Opcode::Constant:
|
||||
BranchEliminationConst(if_block);
|
||||
break;
|
||||
case Opcode::Compare:
|
||||
BranchEliminationCompare(if_block);
|
||||
break;
|
||||
case Opcode::CompareAnyType:
|
||||
BranchEliminationCompareAnyType(if_block);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If `eliminated_block` has no other predecessor than `if_block`, mark `eliminated_block` to be disconnected
|
||||
* If `eliminated_block` dominates all predecessors, exclude `if_block`, mark `eliminated_block` to be disconneted
|
||||
* Else remove edge between these blocks
|
||||
* @param if_block - block with constant 'if' instruction input
|
||||
* @param eliminated_block - unreachable form `if_block`
|
||||
*/
|
||||
void BranchElimination::EliminateBranch(BasicBlock *if_block, BasicBlock *eliminated_block)
|
||||
{
|
||||
ASSERT(if_block != nullptr && if_block->GetGraph() == GetGraph());
|
||||
ASSERT(eliminated_block != nullptr && eliminated_block->GetGraph() == GetGraph());
|
||||
|
||||
// find predecessor which is not dominated by `eliminated_block`
|
||||
auto preds = eliminated_block->GetPredsBlocks();
|
||||
auto it = std::find_if(preds.begin(), preds.end(), [if_block, eliminated_block](BasicBlock *pred) {
|
||||
return pred != if_block && !eliminated_block->IsDominate(pred);
|
||||
});
|
||||
bool dominates_all_preds = (it == preds.cend());
|
||||
|
||||
if (preds.size() > 1 && !dominates_all_preds) {
|
||||
RemovePredecessorUpdateDF(eliminated_block, if_block);
|
||||
if_block->RemoveSucc(eliminated_block);
|
||||
if_block->RemoveInst(if_block->GetLastInst());
|
||||
GetGraph()->GetAnalysis<Rpo>().SetValid(true);
|
||||
// TODO (a.popov) DominatorsTree could be restored inplace
|
||||
GetGraph()->RunPass<DominatorsTree>();
|
||||
} else {
|
||||
eliminated_block->SetMarker(rm_block_marker_);
|
||||
}
|
||||
is_applied_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Before disconnecting the `block` find and disconnect all its successors dominated by it.
|
||||
* Use DFS to disconnect blocks in the PRO
|
||||
* @param block - unreachable block to disconnect from the graph
|
||||
*/
|
||||
void BranchElimination::MarkUnreachableBlocks(BasicBlock *block)
|
||||
{
|
||||
for (auto dom : block->GetDominatedBlocks()) {
|
||||
dom->SetMarker(rm_block_marker_);
|
||||
MarkUnreachableBlocks(dom);
|
||||
}
|
||||
}
|
||||
|
||||
bool AllPredecessorsMarked(BasicBlock *block, Marker marker)
|
||||
{
|
||||
if (block->GetPredsBlocks().empty()) {
|
||||
ASSERT(block->IsStartBlock());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto pred : block->GetPredsBlocks()) {
|
||||
if (!pred->IsMarked(marker)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
block->SetMarker(marker);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect selected blocks
|
||||
*/
|
||||
void BranchElimination::DisconnectBlocks()
|
||||
{
|
||||
for (auto block : GetGraph()->GetBlocksRPO()) {
|
||||
if (block->IsMarked(rm_block_marker_) || AllPredecessorsMarked(block, rm_block_marker_)) {
|
||||
MarkUnreachableBlocks(block);
|
||||
}
|
||||
}
|
||||
|
||||
const auto &rpo_blocks = GetGraph()->GetBlocksRPO();
|
||||
for (auto it = rpo_blocks.rbegin(); it != rpo_blocks.rend(); it++) {
|
||||
auto block = *it;
|
||||
if (block != nullptr && block->IsMarked(rm_block_marker_)) {
|
||||
GetGraph()->DisconnectBlock(block);
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Block was disconnected, id = " << block->GetId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the `target_block` is reachable from one successor only of the `dominant_block`
|
||||
*
|
||||
* If `target_block` is dominated by one of the successors, need to check that `target_block`
|
||||
* is NOT reachable by the other successor
|
||||
*/
|
||||
bool BlockIsReachedFromOnlySuccessor(BasicBlock *target_block, BasicBlock *dominant_block)
|
||||
{
|
||||
ASSERT(dominant_block->IsDominate(target_block));
|
||||
BasicBlock *other_succesor = nullptr;
|
||||
if (dominant_block->GetTrueSuccessor()->IsDominate(target_block)) {
|
||||
other_succesor = dominant_block->GetFalseSuccessor();
|
||||
} else if (dominant_block->GetFalseSuccessor()->IsDominate(target_block)) {
|
||||
other_succesor = dominant_block->GetTrueSuccessor();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto marker_holder = MarkerHolder(target_block->GetGraph());
|
||||
if (BlocksPathDfsSearch(marker_holder.GetMarker(), other_succesor, target_block)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(!other_succesor->IsDominate(target_block));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO (a.popov) Here can be supported more complex case:
|
||||
* when `dom_compare` has 2 or more `if_imm` users and `target_compare` is reachable from the same successors of these
|
||||
* if_imms
|
||||
*/
|
||||
Inst *FindIfImmDominatesCondition(Inst *dom_compare, Inst *target_compare)
|
||||
{
|
||||
for (auto &user : dom_compare->GetUsers()) {
|
||||
auto inst = user.GetInst();
|
||||
if (inst->GetOpcode() == Opcode::IfImm && inst->IsDominate(target_compare)) {
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve condition result if there is a dominant equal condition
|
||||
*/
|
||||
std::optional<bool> BranchElimination::GetConditionResult(Inst *condition)
|
||||
{
|
||||
ConditionOps ops {condition->GetInput(0).GetInst(), condition->GetInput(1).GetInst()};
|
||||
if (same_input_compares_.count(ops) > 0) {
|
||||
auto instructions = same_input_compares_.at(ops);
|
||||
ASSERT(!instructions.empty());
|
||||
for (auto dom_cond : instructions) {
|
||||
// Find dom_cond's if_imm, that dominates target condition
|
||||
auto if_imm = FindIfImmDominatesCondition(dom_cond, condition);
|
||||
if (if_imm == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (BlockIsReachedFromOnlySuccessor(condition->GetBasicBlock(), if_imm->GetBasicBlock())) {
|
||||
if (auto result = TryResolveResult(condition, dom_cond, if_imm->CastToIfImm())) {
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM)
|
||||
<< "Equal compare instructions were found. Dominant id = " << dom_cond->GetId()
|
||||
<< ", dominated id = " << condition->GetId();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve condition result if there is a dominant IfImmInst after CompareAnyTypeInst.
|
||||
*/
|
||||
std::optional<bool> BranchElimination::GetCompareAnyTypeResult(IfImmInst *if_imm)
|
||||
{
|
||||
auto compare_any = if_imm->GetInput(0).GetInst()->CastToCompareAnyType();
|
||||
Inst *input = compare_any->GetInput(0).GetInst();
|
||||
const auto it = same_input_compare_any_type_.find(input);
|
||||
|
||||
if (it == same_input_compare_any_type_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const ArenaVector<CompareAnyTypeInst *> &instructions = it->second;
|
||||
ASSERT(!instructions.empty());
|
||||
for (const auto dom_compare_any : instructions) {
|
||||
// Find dom_cond's if_imm, that dominates target condition.
|
||||
auto if_imm_dom_block = FindIfImmDominatesCondition(dom_compare_any, if_imm);
|
||||
if (if_imm_dom_block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!BlockIsReachedFromOnlySuccessor(if_imm->GetBasicBlock(), if_imm_dom_block->GetBasicBlock())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = TryResolveCompareAnyTypeResult(compare_any, dom_compare_any, if_imm_dom_block->CastToIfImm());
|
||||
if (!result) {
|
||||
continue;
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Equal CompareAnyType instructions were found. Dominant id = "
|
||||
<< dom_compare_any->GetId() << ", dominated id = " << compare_any->GetId();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve CompareAnyTypeInst result with the information about dominant CompareAnyTypeInst with the same inputs.
|
||||
*/
|
||||
std::optional<bool> BranchElimination::TryResolveCompareAnyTypeResult(CompareAnyTypeInst *compare_any,
|
||||
CompareAnyTypeInst *dom_compare_any,
|
||||
IfImmInst *if_imm_dom_block)
|
||||
{
|
||||
auto compare_any_bb = compare_any->GetBasicBlock();
|
||||
bool is_true_dom_branch = if_imm_dom_block->GetEdgeIfInputTrue()->IsDominate(compare_any_bb);
|
||||
|
||||
auto graph = compare_any_bb->GetGraph();
|
||||
auto language = graph->GetRuntime()->GetMethodSourceLanguage(graph->GetMethod());
|
||||
auto res = IsAnyTypeCanBeSubtypeOf(compare_any->GetAnyType(), dom_compare_any->GetAnyType(), language);
|
||||
if (!res) {
|
||||
// We cannot compare types in compile-time
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (res.value()) {
|
||||
// If CompareAnyTypeInst has the same types for any, then it can be optimized in any case.
|
||||
return is_true_dom_branch;
|
||||
}
|
||||
|
||||
// If CompareAnyTypeInst has the different types for any, then it can be optimized only in true-branch.
|
||||
if (!is_true_dom_branch) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve condition result with the information about dominant condition with the same inputs
|
||||
*
|
||||
* - The result of the dominant condition is counted using dom-tree by checking which successor of if_imm_block
|
||||
* (true/false) dominates the current block;
|
||||
*
|
||||
* - Then this result is applied to the current condition, if it is possible, using the table of condition codes
|
||||
* relation
|
||||
*/
|
||||
std::optional<bool> BranchElimination::TryResolveResult(Inst *condition, Inst *dominant_condition, IfImmInst *if_imm)
|
||||
{
|
||||
using std::nullopt;
|
||||
|
||||
// Table keeps the result of the condition in the row if the condition in the column is true
|
||||
// Order must be same as in "ir/inst.h"
|
||||
static constexpr std::array<std::array<std::optional<bool>, ConditionCode::CC_LAST + 1>, ConditionCode::CC_LAST + 1>
|
||||
// clang-format off
|
||||
COND_RELATION = {{
|
||||
// CC_EQ CC_NE CC_LT CC_LE CC_GT CC_GE CC_B CC_BE CC_A CC_AE
|
||||
{true, false, false, nullopt, false, nullopt, false, nullopt, false, nullopt}, // CC_EQ
|
||||
{false, true, true, nullopt, true, nullopt, true, nullopt, true, nullopt}, // CC_NE
|
||||
{false, nullopt, true, nullopt, false, false, nullopt, nullopt, nullopt, nullopt}, // CC_LT
|
||||
{true, nullopt, true, true, false, nullopt, nullopt, nullopt, nullopt, nullopt}, // CC_LE
|
||||
{false, nullopt, false, false, true, nullopt, nullopt, nullopt, nullopt, nullopt}, // CC_GT
|
||||
{true, nullopt, false, nullopt, true, true, nullopt, nullopt, nullopt, nullopt}, // CC_GE
|
||||
{false, nullopt, nullopt, nullopt, nullopt, nullopt, true, nullopt, false, false}, // CC_B
|
||||
{true, nullopt, nullopt, nullopt, nullopt, nullopt, true, true, false, nullopt}, // CC_BE
|
||||
{false, nullopt, nullopt, nullopt, nullopt, nullopt, false, false, true, nullopt}, // CC_A
|
||||
{true, nullopt, nullopt, nullopt, nullopt, nullopt, false, nullopt, true, true}, // CC_AE
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
auto dominant_cc = dominant_condition->CastToCompare()->GetCc();
|
||||
// Swap the dominant condition code, if inputs are reversed: 'if (a < b)' -> 'if (b > a)'
|
||||
if (condition->GetInput(0).GetInst() != dominant_condition->GetInput(0).GetInst()) {
|
||||
ASSERT(condition->GetInput(0).GetInst() == dominant_condition->GetInput(1).GetInst());
|
||||
dominant_cc = SwapOperandsConditionCode(dominant_cc);
|
||||
}
|
||||
|
||||
// Reverse the `dominant_cc` if the `condition` is reached after branching the false succesor of the
|
||||
// if_imm's basic block
|
||||
auto condition_bb = condition->GetBasicBlock();
|
||||
if (if_imm->GetEdgeIfInputFalse()->IsDominate(condition_bb)) {
|
||||
dominant_cc = GetInverseConditionCode(dominant_cc);
|
||||
} else {
|
||||
ASSERT(if_imm->GetEdgeIfInputTrue()->IsDominate(condition_bb));
|
||||
}
|
||||
// After these transformations dominant condition with current `dominant_cc` is equal to `true`
|
||||
// So `condition` result is resolved via table
|
||||
return COND_RELATION[condition->CastToCompare()->GetCc()][dominant_cc];
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_BRANCH_ELIMINATION_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_BRANCH_ELIMINATION_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/pass.h"
|
||||
#include "utils/arena_containers.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class BranchElimination : public Optimization {
|
||||
struct ConditionOps {
|
||||
Inst *lhs;
|
||||
Inst *rhs;
|
||||
};
|
||||
|
||||
struct CcEqual {
|
||||
bool operator()(const ConditionOps &obj1, const ConditionOps &obj2) const
|
||||
{
|
||||
return std::tie(obj1.lhs, obj1.rhs) == std::tie(obj2.lhs, obj2.rhs) ||
|
||||
std::tie(obj1.lhs, obj1.rhs) == std::tie(obj2.rhs, obj2.lhs);
|
||||
}
|
||||
};
|
||||
|
||||
struct CcHash {
|
||||
uint32_t operator()(const ConditionOps &obj) const
|
||||
{
|
||||
uint32_t hash = std::hash<Inst *> {}(obj.lhs);
|
||||
hash += std::hash<Inst *> {}(obj.rhs);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
explicit BranchElimination(Graph *graph);
|
||||
|
||||
NO_MOVE_SEMANTIC(BranchElimination);
|
||||
NO_COPY_SEMANTIC(BranchElimination);
|
||||
~BranchElimination() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCompilerBranchElimination();
|
||||
}
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "BranchElimination";
|
||||
}
|
||||
void InvalidateAnalyses() override;
|
||||
|
||||
private:
|
||||
void VisitBlock(BasicBlock *if_block);
|
||||
void EliminateBranch(BasicBlock *if_block, BasicBlock *eliminated_block);
|
||||
void MarkUnreachableBlocks(BasicBlock *block);
|
||||
void DisconnectBlocks();
|
||||
std::optional<bool> GetConditionResult(Inst *condition);
|
||||
std::optional<bool> TryResolveResult(Inst *condition, Inst *dominant_condition, IfImmInst *if_imm_block);
|
||||
void BranchEliminationConst(BasicBlock *if_block);
|
||||
void BranchEliminationCompare(BasicBlock *if_block);
|
||||
void BranchEliminationCompareAnyType(BasicBlock *if_block);
|
||||
std::optional<bool> GetCompareAnyTypeResult(IfImmInst *if_imm);
|
||||
std::optional<bool> TryResolveCompareAnyTypeResult(CompareAnyTypeInst *compare_any,
|
||||
CompareAnyTypeInst *dom_compare_any,
|
||||
IfImmInst *if_imm_dom_block);
|
||||
|
||||
private:
|
||||
bool is_applied_ {false};
|
||||
Marker rm_block_marker_ {UNDEF_MARKER};
|
||||
ArenaUnorderedMap<ConditionOps, InstVector, CcHash, CcEqual> same_input_compares_;
|
||||
ArenaUnorderedMap<Inst *, ArenaVector<CompareAnyTypeInst *>> same_input_compare_any_type_;
|
||||
};
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_BRANCH_ELIMINATION_H_
|
@ -1,837 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/analysis/object_type_propagation.h"
|
||||
#include "optimizer/ir/graph_visitor.h"
|
||||
#include "checks_elimination.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
bool ChecksElimination::RunImpl()
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start ChecksElimination";
|
||||
GetGraph()->RunPass<DominatorsTree>();
|
||||
GetGraph()->RunPass<LoopAnalyzer>();
|
||||
GetGraph()->RunPass<ObjectTypePropagation>();
|
||||
|
||||
VisitGraph();
|
||||
|
||||
if (options.IsCompilerEnableReplacingChecksOnDeoptimization()) {
|
||||
if (!GetGraph()->IsOsrMode()) {
|
||||
ReplaceBoundsCheckToDeoptimizationBeforeLoop();
|
||||
ReplaceNullCheckToDeoptimizationBeforeLoop();
|
||||
MoveAnyTypeCheckOutOfLoop();
|
||||
}
|
||||
ReplaceBoundsCheckToDeoptimizationInLoop();
|
||||
|
||||
ReplaceCheckMustThrowByUnconditionalDeoptimize();
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "ChecksElimination " << (IsApplied() ? "is" : "isn't") << " applied";
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Finish ChecksElimination";
|
||||
return is_applied_;
|
||||
}
|
||||
|
||||
void ChecksElimination::InvalidateAnalyses()
|
||||
{
|
||||
GetGraph()->InvalidateAnalysis<DominatorsTree>();
|
||||
GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
|
||||
GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
|
||||
GetGraph()->InvalidateAnalysis<AliasAnalysis>();
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitNullCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start visit NullCheck with id = " << inst->GetId();
|
||||
|
||||
auto ref = inst->GetInput(0).GetInst();
|
||||
static_cast<ChecksElimination *>(v)->TryRemoveDominatedNullChecks(inst, ref);
|
||||
|
||||
if (!static_cast<ChecksElimination *>(v)->TryRemoveCheck<Opcode::NullCheck>(inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "NullCheck couldn't be deleted";
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "NullCheck saved for further replacing on deoptimization";
|
||||
static_cast<ChecksElimination *>(v)->PushNewNullCheck(inst);
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitDeoptimizeIf(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
if (inst->CastToDeoptimizeIf()->GetDeoptimizeType() != DeoptimizeType::NULL_CHECK) {
|
||||
return;
|
||||
}
|
||||
auto compare = inst->GetInput(0).GetInst();
|
||||
ASSERT(compare->GetOpcode() == Opcode::Compare);
|
||||
auto ref = compare->GetInput(0).GetInst();
|
||||
ASSERT(ref->GetType() == DataType::REFERENCE);
|
||||
ASSERT(compare->GetInput(1).GetInst()->GetOpcode() == Opcode::NullPtr);
|
||||
auto visitor = static_cast<ChecksElimination *>(v);
|
||||
if (visitor->TryRemoveCheckByBounds<Opcode::NullCheck>(inst, ref)) {
|
||||
visitor->SetApplied();
|
||||
return;
|
||||
}
|
||||
visitor->TryRemoveDominatedNullChecks(inst, ref);
|
||||
|
||||
for (auto &user : ref->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
if (user_inst->GetOpcode() == Opcode::NullCheck) {
|
||||
if (inst->IsDominate(user_inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "DeoptimizeIf NULL_CHECK with id = " << inst->GetId() << " dominate on "
|
||||
<< "NullCheck with id = " << user_inst->GetId();
|
||||
visitor->ReplaceUsersAndRemoveCheck(user_inst, ref);
|
||||
} else if (user_inst->IsDominate(inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "Remove redundant DeoptimizeIf NULL_CHECK (id = " << inst->GetId() << ")";
|
||||
inst->RemoveInputs();
|
||||
inst->GetBasicBlock()->ReplaceInst(inst, visitor->GetGraph()->CreateInstNOP());
|
||||
visitor->SetApplied();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitNegativeCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start visit NegativeCheck with id = " << inst->GetId();
|
||||
if (!static_cast<ChecksElimination *>(v)->TryRemoveCheck<Opcode::NegativeCheck>(inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "NegativeCheck couldn't be deleted";
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitZeroCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start visit ZeroCheck with id = " << inst->GetId();
|
||||
if (!static_cast<ChecksElimination *>(v)->TryRemoveCheck<Opcode::ZeroCheck>(inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "ZeroCheck couldn't be deleted";
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitRefTypeCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
auto visitor = static_cast<ChecksElimination *>(v);
|
||||
auto store_inst = inst->GetDataFlowInput(inst->GetInput(1).GetInst());
|
||||
// Case: a[i] = nullptr
|
||||
if (store_inst->GetOpcode() == Opcode::NullPtr) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, store_inst);
|
||||
return;
|
||||
}
|
||||
auto array_inst = inst->GetDataFlowInput(0);
|
||||
auto ref = inst->GetInput(1).GetInst();
|
||||
// Case:
|
||||
// a[1] = obj
|
||||
// a[2] = obj
|
||||
for (auto &user : ref->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
if (user_inst->GetOpcode() == Opcode::RefTypeCheck && user_inst != inst &&
|
||||
user_inst->GetDataFlowInput(0) == array_inst && user_inst->GetInput(1).GetInst() == ref &&
|
||||
inst->InSameBlockOrDominate(user_inst)) {
|
||||
ASSERT(inst->IsDominate(user_inst));
|
||||
visitor->ReplaceUsersAndRemoveCheck(user_inst, inst);
|
||||
}
|
||||
}
|
||||
visitor->GetGraph()->RunPass<ObjectTypePropagation>();
|
||||
auto array_type_info = array_inst->GetObjectTypeInfo();
|
||||
auto store_type_info = store_inst->GetObjectTypeInfo();
|
||||
if (array_type_info) {
|
||||
auto array_class = array_type_info.GetClass();
|
||||
auto store_class = (store_type_info) ? store_type_info.GetClass() : nullptr;
|
||||
if (visitor->GetGraph()->GetRuntime()->CheckStoreArray(array_class, store_class)) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, store_inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitAnyTypeCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
auto visitor = static_cast<ChecksElimination *>(v);
|
||||
auto input_inst = inst->GetInput(0).GetInst();
|
||||
auto type = inst->CastToAnyTypeCheck()->GetAnyType();
|
||||
if (type == AnyBaseType::UNDEFINED_TYPE) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, input_inst);
|
||||
return;
|
||||
}
|
||||
auto language = visitor->GetGraph()->GetRuntime()->GetMethodSourceLanguage(visitor->GetGraph()->GetMethod());
|
||||
// from:
|
||||
// 2.any CastValueToAnyType ANY_SUBTYPE v1 -> (v4)
|
||||
// 4.any AnyTypeCheck ANY_SUBTYPE v2, v3 -> (....)
|
||||
// to:
|
||||
// 2.any CastValueToAnyType ANY_SUBTYPE v1 -> (...)
|
||||
if (input_inst->GetOpcode() == Opcode::CastValueToAnyType) {
|
||||
auto res = IsAnyTypeCanBeSubtypeOf(type, input_inst->CastToCastValueToAnyType()->GetAnyType(), language);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
if (res.value()) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, input_inst);
|
||||
} else {
|
||||
visitor->PushNewCheckMustThrow(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// from:
|
||||
// 2.any AnyTypeCheck ANY_SUBTYPE v1, v0 -> (v4)
|
||||
// 4.any AnyTypeCheck ANY_SUBTYPE v2, v3 -> (....)
|
||||
// to:
|
||||
// 2.any AnyTypeCheck ANY_SUBTYPE v1, v0 -> (...)
|
||||
if (input_inst->GetOpcode() == Opcode::AnyTypeCheck) {
|
||||
auto res = IsAnyTypeCanBeSubtypeOf(type, input_inst->CastToAnyTypeCheck()->GetAnyType(), language);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
if (res.value()) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, input_inst);
|
||||
} else {
|
||||
visitor->PushNewCheckMustThrow(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// from:
|
||||
// 2.any AnyTypeCheck ANY_SUBTYPE v1, v0 -> (v4)
|
||||
// 4.any AnyTypeCheck ANY_SUBTYPE v1, v3 -> (....)
|
||||
// to:
|
||||
// 2.any AnyTypeCheck ANY_SUBTYPE v1, v0 -> (v4,...)
|
||||
bool applied = false;
|
||||
for (auto &user : input_inst->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
if (user_inst == inst) {
|
||||
continue;
|
||||
}
|
||||
if (user_inst->GetOpcode() != Opcode::AnyTypeCheck) {
|
||||
continue;
|
||||
}
|
||||
if (!user_inst->IsDominate(inst)) {
|
||||
continue;
|
||||
}
|
||||
auto res = IsAnyTypeCanBeSubtypeOf(type, user_inst->CastToAnyTypeCheck()->GetAnyType(), language);
|
||||
if (!res) {
|
||||
continue;
|
||||
}
|
||||
applied = true;
|
||||
if (res.value()) {
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, user_inst);
|
||||
} else {
|
||||
visitor->PushNewCheckMustThrow(inst);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!applied) {
|
||||
visitor->PushNewAnyTypeCheck(inst);
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitBoundsCheck(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start visit BoundsCheck with id = " << inst->GetId();
|
||||
auto block = inst->GetBasicBlock();
|
||||
auto len_array = inst->GetInput(0).GetInst();
|
||||
auto index = inst->GetInput(1).GetInst();
|
||||
auto visitor = static_cast<ChecksElimination *>(v);
|
||||
|
||||
for (auto &user : index->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
if (user_inst != inst && user_inst->GetOpcode() == Opcode::BoundsCheck &&
|
||||
len_array == user_inst->GetInput(0).GetInst() && index == user_inst->GetInput(1).GetInst() &&
|
||||
inst->InSameBlockOrDominate(user_inst)) {
|
||||
ASSERT(inst->IsDominate(user_inst));
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "BoundsCheck with id = " << inst->GetId()
|
||||
<< " dominate on BoundsCheck with id = " << user_inst->GetId();
|
||||
visitor->ReplaceUsersAndRemoveCheck(user_inst, inst);
|
||||
}
|
||||
}
|
||||
|
||||
auto bri = block->GetGraph()->GetBoundsRangeInfo();
|
||||
auto len_array_range = bri->FindBoundsRange(block, len_array);
|
||||
auto index_range = bri->FindBoundsRange(block, index);
|
||||
if (index_range.IsNotNegative() && (index_range.IsLess(len_array_range) || index_range.IsLess(len_array))) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Index of BoundsCheck have correct bounds";
|
||||
visitor->ReplaceUsersAndRemoveCheck(inst, index);
|
||||
return;
|
||||
}
|
||||
if (index_range.IsNegative() || index_range.IsMoreOrEqual(len_array_range)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "BoundsCheck have incorrect bounds, saved for replace by unconditional deoptimize";
|
||||
visitor->PushNewCheckMustThrow(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "BoundsCheck saved for further replacing on deoptimization";
|
||||
auto loop = block->GetLoop();
|
||||
ASSERT(loop != nullptr);
|
||||
visitor->PushNewBoundsCheck(loop, len_array, index, inst);
|
||||
}
|
||||
|
||||
void ChecksElimination::VisitCheckCast(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
auto visitor = static_cast<ChecksElimination *>(v);
|
||||
visitor->GetGraph()->RunPass<ObjectTypePropagation>();
|
||||
auto result = ObjectTypeCheckElimination::TryEliminateCheckCast(inst);
|
||||
if (result != ObjectTypeCheckElimination::CheckCastEliminateType::INVALID) {
|
||||
visitor->SetApplied();
|
||||
if (result == ObjectTypeCheckElimination::CheckCastEliminateType::MUST_THROW) {
|
||||
visitor->PushNewCheckMustThrow(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::ReplaceUsersAndRemoveCheck(Inst *inst_del, Inst *inst_rep)
|
||||
{
|
||||
auto block = inst_del->GetBasicBlock();
|
||||
auto graph = block->GetGraph();
|
||||
if (graph->IsOsrMode() && block->GetLoop() != inst_rep->GetBasicBlock()->GetLoop()) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Check couldn't be deleted, becouse in OSR mode we can't replace "
|
||||
"instructions with instructions from another loop";
|
||||
return;
|
||||
}
|
||||
inst_del->ReplaceUsers(inst_rep);
|
||||
inst_del->RemoveInputs();
|
||||
block->ReplaceInst(inst_del, graph->CreateInstNOP());
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Checks elimination delete " << GetOpcodeString(inst_del->GetOpcode())
|
||||
<< " with id " << inst_del->GetId();
|
||||
graph->GetEventWriter().EventChecksElimination(GetOpcodeString(inst_del->GetOpcode()), inst_del->GetId(),
|
||||
inst_del->GetPc());
|
||||
SetApplied();
|
||||
}
|
||||
|
||||
bool ChecksElimination::IsInstIncOrDec(Inst *inst)
|
||||
{
|
||||
return (inst->GetOpcode() == Opcode::Add || inst->GetOpcode() == Opcode::Sub) &&
|
||||
inst->GetInput(1).GetInst()->IsConst();
|
||||
}
|
||||
|
||||
void ChecksElimination::InitItemForNewIndex(GroupedBoundsChecks *place, Inst *index, Inst *inst)
|
||||
{
|
||||
ASSERT(inst->GetOpcode() == Opcode::BoundsCheck);
|
||||
InstVector insts(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
insts.push_back(inst);
|
||||
int64_t val = 0;
|
||||
Inst *parent_index = index;
|
||||
if (IsInstIncOrDec(index)) {
|
||||
val = static_cast<int64_t>(index->GetInput(1).GetInst()->CastToConstant()->GetIntValue());
|
||||
parent_index = index->GetInput(0).GetInst();
|
||||
if (index->GetOpcode() == Opcode::Sub) {
|
||||
val = -val;
|
||||
}
|
||||
}
|
||||
place->emplace(parent_index, std::make_tuple(insts, val, val));
|
||||
}
|
||||
|
||||
void ChecksElimination::PushNewBoundsCheck(Loop *loop, Inst *len_array, Inst *index, Inst *inst)
|
||||
{
|
||||
ASSERT(loop != nullptr && len_array != nullptr && index != nullptr && inst != nullptr);
|
||||
ASSERT(inst->GetOpcode() == Opcode::BoundsCheck);
|
||||
if (bounds_checks_.find(loop) == bounds_checks_.end()) {
|
||||
auto it1 = bounds_checks_.emplace(loop, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(it1.second);
|
||||
auto it2 = it1.first->second.emplace(len_array, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(it2.second);
|
||||
InitItemForNewIndex(&it2.first->second, index, inst);
|
||||
} else if (bounds_checks_.at(loop).find(len_array) == bounds_checks_.at(loop).end()) {
|
||||
auto it1 = bounds_checks_.at(loop).emplace(len_array, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(it1.second);
|
||||
InitItemForNewIndex(&it1.first->second, index, inst);
|
||||
} else if (auto &len_a_bc = bounds_checks_.at(loop).at(len_array); len_a_bc.find(index) == len_a_bc.end()) {
|
||||
if (!IsInstIncOrDec(index) || len_a_bc.find(index->GetInput(0).GetInst()) == len_a_bc.end()) {
|
||||
auto parent_index = index;
|
||||
if (IsInstIncOrDec(index)) {
|
||||
parent_index = index->GetInput(0).GetInst();
|
||||
}
|
||||
InitItemForNewIndex(&len_a_bc, parent_index, inst);
|
||||
} else {
|
||||
auto val = static_cast<int64_t>(index->GetInput(1).GetInst()->CastToConstant()->GetIntValue());
|
||||
auto &item = len_a_bc.at(index->GetInput(0).GetInst());
|
||||
std::get<0>(item).push_back(inst);
|
||||
if (index->GetOpcode() == Opcode::Add && val > 0 && val > std::get<1>(item)) {
|
||||
std::get<1>(item) = val;
|
||||
} else if (index->GetOpcode() == Opcode::Add && val < 0 && val < std::get<2U>(item)) {
|
||||
std::get<2U>(item) = val;
|
||||
} else if (index->GetOpcode() == Opcode::Sub && val > 0 && (-val) < std::get<2U>(item)) {
|
||||
std::get<2U>(item) = -val;
|
||||
} else if (index->GetOpcode() == Opcode::Sub && val < 0 && (-val) > std::get<1>(item)) {
|
||||
std::get<1>(item) = -val;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT(!IsInstIncOrDec(index));
|
||||
auto &item = bounds_checks_.at(loop).at(len_array).at(index);
|
||||
std::get<0>(item).push_back(inst);
|
||||
if (std::get<1>(item) < 0) {
|
||||
std::get<1>(item) = 0;
|
||||
}
|
||||
if (std::get<2U>(item) > 0) {
|
||||
std::get<2U>(item) = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::TryRemoveDominatedNullChecks(Inst *inst, Inst *ref)
|
||||
{
|
||||
for (auto &user : ref->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
if (((user_inst->GetOpcode() == Opcode::IsInstance && !user_inst->CastToIsInstance()->GetOmitNullCheck()) ||
|
||||
(user_inst->GetOpcode() == Opcode::CheckCast && !user_inst->CastToCheckCast()->GetOmitNullCheck())) &&
|
||||
inst->IsDominate(user_inst)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "NullCheck with id = " << inst->GetId() << " dominate on " << GetOpcodeString(user_inst->GetOpcode())
|
||||
<< " with id = " << user_inst->GetId();
|
||||
if (user_inst->GetOpcode() == Opcode::IsInstance) {
|
||||
user_inst->CastToIsInstance()->SetOmitNullCheck(true);
|
||||
} else {
|
||||
user_inst->CastToCheckCast()->SetOmitNullCheck(true);
|
||||
}
|
||||
SetApplied();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Opcode opc>
|
||||
void ChecksElimination::TryRemoveDominatedChecks(Inst *inst)
|
||||
{
|
||||
for (auto &user : inst->GetInput(0).GetInst()->GetUsers()) {
|
||||
auto user_inst = user.GetInst();
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
if (user_inst->GetOpcode() == opc && user_inst != inst && user_inst->GetType() == inst->GetType() &&
|
||||
inst->InSameBlockOrDominate(user_inst)) {
|
||||
ASSERT(inst->IsDominate(user_inst));
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
<< GetOpcodeString(opc) << " with id = " << inst->GetId() << " dominate on " << GetOpcodeString(opc)
|
||||
<< " with id = " << user_inst->GetId();
|
||||
ReplaceUsersAndRemoveCheck(user_inst, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove consecutive checks: NullCheck -> NullCheck -> NullCheck
|
||||
template <Opcode opc>
|
||||
void ChecksElimination::TryRemoveConsecutiveChecks(Inst *inst)
|
||||
{
|
||||
auto end = inst->GetUsers().end();
|
||||
for (auto user = inst->GetUsers().begin(); user != end;) {
|
||||
auto user_inst = (*user).GetInst();
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
if (user_inst->GetOpcode() == opc) {
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Remove consecutive " << GetOpcodeString(opc);
|
||||
ReplaceUsersAndRemoveCheck(user_inst, inst);
|
||||
// Start iteration from beginning, because the new successors may be added.
|
||||
user = inst->GetUsers().begin();
|
||||
end = inst->GetUsers().end();
|
||||
} else {
|
||||
++user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Opcode opc>
|
||||
bool ChecksElimination::TryRemoveCheckByBounds(Inst *inst, Inst *input)
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
static_assert(opc == Opcode::ZeroCheck || opc == Opcode::NegativeCheck || opc == Opcode::NullCheck);
|
||||
ASSERT(inst->GetOpcode() == opc || (inst->GetOpcode() == Opcode::DeoptimizeIf && opc == Opcode::NullCheck));
|
||||
|
||||
auto block = inst->GetBasicBlock();
|
||||
auto bri = block->GetGraph()->GetBoundsRangeInfo();
|
||||
auto range = bri->FindBoundsRange(block, input);
|
||||
bool result = false;
|
||||
// NOLINTNEXTLINE(readability-magic-numbers, readability-braces-around-statements, bugprone-branch-clone)
|
||||
if constexpr (opc == Opcode::ZeroCheck) {
|
||||
result = range.IsLess(BoundsRange(0)) || range.IsMore(BoundsRange(0));
|
||||
} else if constexpr (opc == Opcode::NullCheck) { // NOLINT
|
||||
result = range.IsMore(BoundsRange(0));
|
||||
} else if constexpr (opc == Opcode::NegativeCheck) { // NOLINT
|
||||
result = range.IsNotNegative();
|
||||
}
|
||||
if (result) {
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << GetOpcodeString(opc) << " have correct bounds";
|
||||
ReplaceUsersAndRemoveCheck(inst, input);
|
||||
} else {
|
||||
// NOLINTNEXTLINE(readability-magic-numbers, readability-braces-around-statements)
|
||||
if constexpr (opc == Opcode::ZeroCheck || opc == Opcode::NullCheck) {
|
||||
result = range.IsEqual(BoundsRange(0));
|
||||
} else if constexpr (opc == Opcode::NegativeCheck) { // NOLINT
|
||||
result = range.IsNegative();
|
||||
}
|
||||
if (result) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
<< GetOpcodeString(opc) << " must throw, saved for replace by unconditional deoptimize";
|
||||
PushNewCheckMustThrow(inst);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Opcode opc>
|
||||
bool ChecksElimination::TryRemoveCheck(Inst *inst)
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
static_assert(opc == Opcode::ZeroCheck || opc == Opcode::NegativeCheck || opc == Opcode::NullCheck);
|
||||
ASSERT(inst->GetOpcode() == opc);
|
||||
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
TryRemoveDominatedChecks<opc>(inst);
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
TryRemoveConsecutiveChecks<opc>(inst);
|
||||
|
||||
auto input = inst->GetInput(0).GetInst();
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
return TryRemoveCheckByBounds<opc>(inst, input);
|
||||
}
|
||||
|
||||
Inst *ChecksElimination::FindSaveState(Loop *loop)
|
||||
{
|
||||
auto block = loop->GetPreHeader();
|
||||
while (block != nullptr) {
|
||||
for (const auto &inst : block->InstsSafeReverse()) {
|
||||
if (inst->GetOpcode() == Opcode::SaveStateDeoptimize || inst->GetOpcode() == Opcode::SaveState) {
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
block = block->GetDominator();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChecksElimination::InsertDeoptimization(ConditionCode cc, Inst *left, int64_t val, Inst *right, Inst *ss,
|
||||
Inst *insert_after)
|
||||
{
|
||||
auto cnst = GetGraph()->FindOrCreateConstant(val);
|
||||
auto block = insert_after->GetBasicBlock();
|
||||
Inst *new_left = nullptr;
|
||||
if (val == 0) {
|
||||
new_left = left;
|
||||
} else {
|
||||
new_left = GetGraph()->CreateInstAdd();
|
||||
new_left->SetType(left->GetType());
|
||||
new_left->SetInput(0, left);
|
||||
new_left->SetInput(1, cnst);
|
||||
block->InsertAfter(new_left, insert_after);
|
||||
}
|
||||
auto deopt_comp = GetGraph()->CreateInstCompare();
|
||||
deopt_comp->SetType(DataType::BOOL);
|
||||
deopt_comp->SetOperandsType(DataType::INT32);
|
||||
deopt_comp->SetCc(cc);
|
||||
deopt_comp->SetInput(0, new_left);
|
||||
deopt_comp->SetInput(1, right);
|
||||
auto deopt = GetGraph()->CreateInstDeoptimizeIf();
|
||||
deopt->SetDeoptimizeType(DeoptimizeType::BOUNDS_CHECK);
|
||||
deopt->SetInput(0, deopt_comp);
|
||||
deopt->SetInput(1, ss);
|
||||
deopt->SetPc(ss->GetPc());
|
||||
if (val == 0) {
|
||||
block->InsertAfter(deopt_comp, insert_after);
|
||||
} else {
|
||||
block->InsertAfter(deopt_comp, new_left);
|
||||
}
|
||||
block->InsertAfter(deopt, deopt_comp);
|
||||
}
|
||||
|
||||
Inst *ChecksElimination::InsertDeoptimization(ConditionCode cc, Inst *left, Inst *right, Inst *ss, Inst *insert_after)
|
||||
{
|
||||
auto deopt_comp = GetGraph()->CreateInstCompare();
|
||||
deopt_comp->SetType(DataType::BOOL);
|
||||
deopt_comp->SetOperandsType(left->GetType());
|
||||
deopt_comp->SetCc(cc);
|
||||
deopt_comp->SetInput(0, left);
|
||||
deopt_comp->SetInput(1, right);
|
||||
auto deopt = GetGraph()->CreateInstDeoptimizeIf();
|
||||
deopt->SetDeoptimizeType(DeoptimizeType::NULL_CHECK);
|
||||
deopt->SetInput(0, deopt_comp);
|
||||
deopt->SetInput(1, ss);
|
||||
deopt->SetPc(ss->GetPc());
|
||||
auto block = insert_after->GetBasicBlock();
|
||||
block->InsertAfter(deopt_comp, insert_after);
|
||||
block->InsertAfter(deopt, deopt_comp);
|
||||
return deopt;
|
||||
}
|
||||
|
||||
std::optional<LoopInfo> ChecksElimination::FindLoopInfo(Loop *loop)
|
||||
{
|
||||
Inst *ss = FindSaveState(loop);
|
||||
if (ss == nullptr) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "SaveState isn't founded";
|
||||
return std::nullopt;
|
||||
}
|
||||
auto loop_parser = CountableLoopParser(*loop);
|
||||
if (auto loop_info = loop_parser.Parse()) {
|
||||
auto loop_info_value = loop_info.value();
|
||||
if (loop_info_value.normalized_cc == CC_NE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
ASSERT(loop_info_value.index->GetOpcode() == Opcode::Phi);
|
||||
if (loop_info_value.update->GetOpcode() == Opcode::Add) {
|
||||
return std::make_tuple(ss, loop_info_value.init, loop_info_value.test, loop_info_value.index,
|
||||
loop_info_value.normalized_cc == CC_LE ? CC_GE : CC_GT);
|
||||
}
|
||||
ASSERT(loop_info_value.update->GetOpcode() == Opcode::Sub);
|
||||
return std::make_tuple(ss, loop_info_value.test, loop_info_value.init, loop_info_value.index,
|
||||
loop_info_value.normalized_cc);
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Not countable loop isn't supported";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Inst *ChecksElimination::InsertNewLenArray(Inst *len_array, Inst *ss)
|
||||
{
|
||||
auto result_len_array = len_array;
|
||||
if (len_array->GetOpcode() == Opcode::LenArray && !len_array->IsDominate(ss)) {
|
||||
auto null_check = len_array->GetInput(0).GetInst();
|
||||
auto ref = len_array->GetDataFlowInput(null_check);
|
||||
if (ref->IsDominate(ss)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Build new NullCheck and LenArray before loop";
|
||||
// Build deopt.nullcheck + lenarray before loop
|
||||
auto new_len_array = len_array->Clone(GetGraph());
|
||||
new_len_array->SetInput(0, ref);
|
||||
auto block = ss->GetBasicBlock();
|
||||
block->InsertAfter(new_len_array, ss);
|
||||
result_len_array = new_len_array;
|
||||
auto null_ptr = GetGraph()->GetOrCreateNullPtr();
|
||||
auto deopt = InsertDeoptimization(ConditionCode::CC_EQ, ref, null_ptr, ss, ss);
|
||||
ChecksElimination::VisitDeoptimizeIf(this, deopt);
|
||||
}
|
||||
}
|
||||
return result_len_array;
|
||||
}
|
||||
|
||||
bool ChecksElimination::TryInsertDeoptimization(LoopInfo loop_info, Inst *len_array, int64_t max_add, int64_t max_sub)
|
||||
{
|
||||
auto [ss, lower, upper, phi_index, cc] = loop_info;
|
||||
auto bri = GetGraph()->GetBoundsRangeInfo();
|
||||
auto loop = phi_index->GetBasicBlock()->GetLoop();
|
||||
auto len_array_range = bri->FindBoundsRange(loop->GetPreHeader(), len_array);
|
||||
auto upper_range = bri->FindBoundsRange(loop->GetHeader(), upper).Add(BoundsRange(max_add));
|
||||
auto lower_range = bri->FindBoundsRange(loop->GetHeader(), lower).Add(BoundsRange(max_sub));
|
||||
auto result_len_array = (len_array == upper ? len_array : InsertNewLenArray(len_array, ss));
|
||||
bool insert_new_len_array =
|
||||
len_array != result_len_array ||
|
||||
(len_array == upper && len_array->GetOpcode() == Opcode::LenArray && ss->IsDominate(len_array));
|
||||
|
||||
bool need_upper_deopt = !((cc != CC_LE && cc != CC_GE && len_array == upper) ||
|
||||
upper_range.IsLess(len_array_range) || upper_range.IsLess(len_array));
|
||||
bool need_lower_deopt = !lower_range.IsNotNegative();
|
||||
bool insert_upper_deopt =
|
||||
(result_len_array->IsDominate(ss) || insert_new_len_array) && (upper->IsDominate(ss) || len_array == upper);
|
||||
bool insert_lower_deopt = lower->IsDominate(ss);
|
||||
if (need_upper_deopt && need_lower_deopt && insert_upper_deopt && insert_lower_deopt) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Build deoptimize for lower and upper value";
|
||||
// Create deoptimize if upper >=(>) result_len_array
|
||||
InsertDeoptimization(cc, upper, max_add, result_len_array, ss, insert_new_len_array ? result_len_array : ss);
|
||||
// Create deoptimize if lower < 0
|
||||
auto zero_const = GetGraph()->FindOrCreateConstant(0);
|
||||
InsertDeoptimization(ConditionCode::CC_LT, lower, max_sub, zero_const, ss, ss);
|
||||
return true;
|
||||
}
|
||||
if (need_upper_deopt && insert_upper_deopt && !need_lower_deopt) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Build deoptimize for upper value";
|
||||
// Create deoptimize if upper >=(>) result_len_array
|
||||
InsertDeoptimization(cc, upper, max_add, result_len_array, ss, insert_new_len_array ? result_len_array : ss);
|
||||
return true;
|
||||
}
|
||||
if (need_lower_deopt && insert_lower_deopt && !need_upper_deopt) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Build deoptimize for lower value";
|
||||
// Create deoptimize if lower < 0
|
||||
auto zero_const = GetGraph()->FindOrCreateConstant(0);
|
||||
InsertDeoptimization(ConditionCode::CC_LT, lower, max_sub, zero_const, ss, ss);
|
||||
return true;
|
||||
}
|
||||
return !need_lower_deopt && !need_upper_deopt;
|
||||
}
|
||||
|
||||
void ChecksElimination::ProcessingGroupBoundsCheck(GroupedBoundsChecks *index_boundschecks, LoopInfo loop_info,
|
||||
Inst *len_array)
|
||||
{
|
||||
constexpr auto IMM_3 = 3;
|
||||
auto phi_index = std::get<IMM_3>(loop_info);
|
||||
if (index_boundschecks->find(phi_index) == index_boundschecks->end()) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Loop index isn't founded for this group";
|
||||
return;
|
||||
}
|
||||
auto &[insts_to_delete, max_add, max_sub] = index_boundschecks->at(phi_index);
|
||||
ASSERT(!insts_to_delete.empty());
|
||||
if (TryInsertDeoptimization(loop_info, len_array, max_add, max_sub)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Delete group of BoundsChecks";
|
||||
// Delete bounds checks instructions
|
||||
for (const auto &inst : insts_to_delete) {
|
||||
ReplaceUsersAndRemoveCheck(inst, inst->GetInput(1).GetInst());
|
||||
if (index_boundschecks->find(inst->GetInput(1).GetInst()) != index_boundschecks->end()) {
|
||||
index_boundschecks->erase(inst->GetInput(1).GetInst());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::ProcessingLoop(Loop *loop, ArenaUnorderedMap<Inst *, GroupedBoundsChecks> *lenarr_index_checks)
|
||||
{
|
||||
auto loop_info = FindLoopInfo(loop);
|
||||
if (loop_info == std::nullopt) {
|
||||
return;
|
||||
}
|
||||
for (auto &[len_array, index_boundschecks] : *lenarr_index_checks) {
|
||||
ProcessingGroupBoundsCheck(&index_boundschecks, loop_info.value(), len_array);
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksElimination::ReplaceBoundsCheckToDeoptimizationBeforeLoop()
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start ReplaceBoundsCheckToDeoptimizationBeforeLoop";
|
||||
for (auto &[loop, lenarr_index_checks] : bounds_checks_) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Processing loop with id = " << loop->GetId();
|
||||
if (loop->IsRoot()) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Skip root loop";
|
||||
continue;
|
||||
}
|
||||
ProcessingLoop(loop, &lenarr_index_checks);
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Finish ReplaceBoundsCheckToDeoptimizationBeforeLoop";
|
||||
}
|
||||
|
||||
void ChecksElimination::ReplaceNullCheckToDeoptimizationBeforeLoop()
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start ReplaceNullCheckToDeoptimizationBeforeLoop";
|
||||
for (auto inst : null_checks_) {
|
||||
auto block = inst->GetBasicBlock();
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto loop = block->GetLoop();
|
||||
if (loop->IsRoot() || loop->GetHeader()->IsOsrEntry() || loop->IsIrreducible()) {
|
||||
continue;
|
||||
}
|
||||
// Find save state
|
||||
Inst *ss = FindSaveState(loop);
|
||||
if (ss == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto ref = inst->GetInput(0).GetInst();
|
||||
if (ref->IsDominate(ss)) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "Replace NullCheck with id = " << inst->GetId() << " on deoptimization before loop";
|
||||
auto null_ptr = GetGraph()->GetOrCreateNullPtr();
|
||||
InsertDeoptimization(ConditionCode::CC_EQ, ref, null_ptr, ss, ss);
|
||||
ReplaceUsersAndRemoveCheck(inst, ref);
|
||||
}
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Finish ReplaceNullCheckToDeoptimizationBeforeLoop";
|
||||
}
|
||||
|
||||
void ChecksElimination::MoveAnyTypeCheckOutOfLoop()
|
||||
{
|
||||
for (auto inst : any_type_checks_) {
|
||||
auto block = inst->GetBasicBlock();
|
||||
ASSERT(block != nullptr);
|
||||
auto loop = block->GetLoop();
|
||||
if (loop->IsRoot() || loop->GetHeader()->IsOsrEntry() || loop->IsIrreducible()) {
|
||||
continue;
|
||||
}
|
||||
// Find save state
|
||||
Inst *ss = FindSaveState(loop);
|
||||
if (ss == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto input = inst->GetInput(0).GetInst();
|
||||
if (!input->IsDominate(ss)) {
|
||||
continue;
|
||||
}
|
||||
bool is_hot_pass = true;
|
||||
for (auto back_edge : loop->GetBackEdges()) {
|
||||
if (!block->IsDominate(back_edge)) {
|
||||
is_hot_pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This check is necessary in order not to take AnyTypeCheck out of slowpath.
|
||||
if (is_hot_pass) {
|
||||
block->EraseInst(inst);
|
||||
ss->GetBasicBlock()->InsertAfter(inst, ss);
|
||||
inst->SetInput(1, ss);
|
||||
inst->SetPc(ss->GetPc());
|
||||
SetApplied();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inst *ChecksElimination::FindSaveState(const InstVector &insts_to_delete)
|
||||
{
|
||||
for (auto bounds_check : insts_to_delete) {
|
||||
bool is_dominate = true;
|
||||
for (auto bounds_check_test : insts_to_delete) {
|
||||
if (bounds_check == bounds_check_test) {
|
||||
continue;
|
||||
}
|
||||
is_dominate &= bounds_check->IsDominate(bounds_check_test);
|
||||
}
|
||||
if (is_dominate) {
|
||||
constexpr auto IMM_2 = 2;
|
||||
return bounds_check->GetInput(IMM_2).GetInst();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChecksElimination::ReplaceBoundsCheckToDeoptimizationInLoop()
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Start ReplaceBoundsCheckToDeoptimizationInLoop";
|
||||
for (auto &item : bounds_checks_) {
|
||||
for (auto &[len_array, index_boundschecks_map] : item.second) {
|
||||
for (auto &[index, it] : index_boundschecks_map) {
|
||||
auto [insts_to_delete, max, min] = it;
|
||||
constexpr auto MIN_BOUNDSCHECKS_NUM = 2;
|
||||
if (insts_to_delete.size() <= MIN_BOUNDSCHECKS_NUM) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Skip small group of BoundsChecks";
|
||||
continue;
|
||||
}
|
||||
// Try to replace more than 2 bounds checks to deoptimization in loop
|
||||
auto save_state = FindSaveState(insts_to_delete);
|
||||
if (save_state == nullptr) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "SaveState isn't founded";
|
||||
continue;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Replace group of BoundsChecks on deoptimization in loop";
|
||||
auto insert_after = len_array->IsDominate(save_state) ? save_state : len_array;
|
||||
// Create deoptimize if max_index >= len_array
|
||||
InsertDeoptimization(ConditionCode::CC_GE, index, max, len_array, save_state, insert_after);
|
||||
// Create deoptimize if min_index < 0
|
||||
auto zero_const = GetGraph()->FindOrCreateConstant(0);
|
||||
InsertDeoptimization(ConditionCode::CC_LT, index, min, zero_const, save_state, insert_after);
|
||||
for (const auto &inst : insts_to_delete) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Delete group of BoundsChecks";
|
||||
ReplaceUsersAndRemoveCheck(inst, inst->GetInput(1).GetInst());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM) << "Finish ReplaceBoundsCheckToDeoptimizationInLoop";
|
||||
}
|
||||
|
||||
void ChecksElimination::ReplaceCheckMustThrowByUnconditionalDeoptimize()
|
||||
{
|
||||
for (auto &inst : checks_must_throw_) {
|
||||
auto block = inst->GetBasicBlock();
|
||||
if (block != nullptr) {
|
||||
COMPILER_LOG(DEBUG, CHECKS_ELIM)
|
||||
<< "Replace check with id = " << inst->GetId() << " by uncondition deoptimize";
|
||||
block->ReplaceInstByDeoptimize(inst);
|
||||
SetApplied();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
@ -1,154 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_CHECKSELIMINATION_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_CHECKSELIMINATION_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/pass.h"
|
||||
|
||||
#include "object_type_check_elimination.h"
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/analysis/object_type_propagation.h"
|
||||
#include "optimizer/ir/graph_visitor.h"
|
||||
#include "checks_elimination.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
// parent_index->{Vector<bound_check>, max_val, min_val}
|
||||
using GroupedBoundsChecks = ArenaUnorderedMap<Inst *, std::tuple<InstVector, int64_t, int64_t>>;
|
||||
// loop->len_array->GroupedBoundsChecks
|
||||
using NotFullyRedundantBoundsCheck = ArenaDoubleUnorderedMap<Loop *, Inst *, GroupedBoundsChecks>;
|
||||
|
||||
// {savestate; lower upper; upper value; loop index; cond code}
|
||||
using LoopInfo = std::tuple<Inst *, Inst *, Inst *, Inst *, ConditionCode>;
|
||||
|
||||
// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
|
||||
class ChecksElimination : public Optimization, public GraphVisitor {
|
||||
using Optimization::Optimization;
|
||||
|
||||
public:
|
||||
explicit ChecksElimination(Graph *graph)
|
||||
: Optimization(graph),
|
||||
bounds_checks_(graph->GetLocalAllocator()->Adapter()),
|
||||
null_checks_(graph->GetLocalAllocator()->Adapter()),
|
||||
any_type_checks_(graph->GetLocalAllocator()->Adapter()),
|
||||
checks_must_throw_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
NO_MOVE_SEMANTIC(ChecksElimination);
|
||||
NO_COPY_SEMANTIC(ChecksElimination);
|
||||
~ChecksElimination() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "ChecksElimination";
|
||||
}
|
||||
|
||||
bool IsApplied() const
|
||||
{
|
||||
return is_applied_;
|
||||
}
|
||||
void SetApplied()
|
||||
{
|
||||
is_applied_ = true;
|
||||
}
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCompilerChecksElimination();
|
||||
}
|
||||
|
||||
void InvalidateAnalyses() override;
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
static void VisitNullCheck(GraphVisitor *v, Inst *inst);
|
||||
static void VisitDeoptimizeIf(GraphVisitor *v, Inst *inst);
|
||||
static void VisitNegativeCheck(GraphVisitor *v, Inst *inst);
|
||||
static void VisitZeroCheck(GraphVisitor *v, Inst *inst);
|
||||
static void VisitRefTypeCheck(GraphVisitor *v, Inst *inst);
|
||||
static void VisitBoundsCheck(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCheckCast(GraphVisitor *v, Inst *inst);
|
||||
static void VisitAnyTypeCheck(GraphVisitor *v, Inst *inst);
|
||||
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
private:
|
||||
void ReplaceUsersAndRemoveCheck(Inst *inst_del, Inst *inst_rep);
|
||||
void PushNewNullCheck(Inst *null_check)
|
||||
{
|
||||
null_checks_.push_back(null_check);
|
||||
}
|
||||
void PushNewAnyTypeCheck(Inst *any_type_check)
|
||||
{
|
||||
any_type_checks_.push_back(any_type_check);
|
||||
}
|
||||
|
||||
void PushNewCheckMustThrow(Inst *inst)
|
||||
{
|
||||
checks_must_throw_.push_back(inst);
|
||||
}
|
||||
|
||||
bool IsInstIncOrDec(Inst *inst);
|
||||
void InitItemForNewIndex(GroupedBoundsChecks *place, Inst *index, Inst *inst);
|
||||
void PushNewBoundsCheck(Loop *loop, Inst *len_array, Inst *index, Inst *inst);
|
||||
void TryRemoveDominatedNullChecks(Inst *inst, Inst *ref);
|
||||
template <Opcode opc>
|
||||
void TryRemoveDominatedChecks(Inst *inst);
|
||||
template <Opcode opc>
|
||||
void TryRemoveConsecutiveChecks(Inst *inst);
|
||||
template <Opcode opc>
|
||||
bool TryRemoveCheckByBounds(Inst *inst, Inst *input);
|
||||
template <Opcode opc>
|
||||
bool TryRemoveCheck(Inst *inst);
|
||||
|
||||
private:
|
||||
bool TryInsertDeoptimization(LoopInfo loop_info, Inst *len_array, int64_t max_add, int64_t max_sub);
|
||||
|
||||
Inst *InsertNewLenArray(Inst *len_array, Inst *ss);
|
||||
void ProcessingLoop(Loop *loop, ArenaUnorderedMap<Inst *, GroupedBoundsChecks> *lenarr_index_checks);
|
||||
void ProcessingGroupBoundsCheck(GroupedBoundsChecks *index_boundschecks, LoopInfo loop_info, Inst *len_array);
|
||||
void ReplaceBoundsCheckToDeoptimizationBeforeLoop();
|
||||
Inst *FindSaveState(const InstVector &insts_to_delete);
|
||||
void ReplaceBoundsCheckToDeoptimizationInLoop();
|
||||
void ReplaceNullCheckToDeoptimizationBeforeLoop();
|
||||
void ReplaceCheckMustThrowByUnconditionalDeoptimize();
|
||||
void MoveAnyTypeCheckOutOfLoop();
|
||||
|
||||
void InsertDeoptimization(ConditionCode cc, Inst *left, int64_t val, Inst *right, Inst *ss, Inst *insert_after);
|
||||
Inst *InsertDeoptimization(ConditionCode cc, Inst *left, Inst *right, Inst *ss, Inst *insert_after);
|
||||
|
||||
std::optional<LoopInfo> FindLoopInfo(Loop *loop);
|
||||
Inst *FindSaveState(Loop *loop);
|
||||
|
||||
private:
|
||||
NotFullyRedundantBoundsCheck bounds_checks_;
|
||||
InstVector null_checks_;
|
||||
InstVector any_type_checks_;
|
||||
InstVector checks_must_throw_;
|
||||
|
||||
bool is_applied_ {false};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_CHECKSELIMINATION_H_
|
File diff suppressed because it is too large
Load Diff
@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CONST_FOLDING_H
|
||||
#define CONST_FOLDING_H
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
bool ConstFoldingCast(Inst *inst);
|
||||
bool ConstFoldingNeg(Inst *inst);
|
||||
bool ConstFoldingAbs(Inst *inst);
|
||||
bool ConstFoldingNot(Inst *inst);
|
||||
bool ConstFoldingAdd(Inst *inst);
|
||||
bool ConstFoldingSub(Inst *inst);
|
||||
bool ConstFoldingMul(Inst *inst);
|
||||
bool ConstFoldingDiv(Inst *inst);
|
||||
bool ConstFoldingMin(Inst *inst);
|
||||
bool ConstFoldingMax(Inst *inst);
|
||||
bool ConstFoldingMod(Inst *inst);
|
||||
bool ConstFoldingShl(Inst *inst);
|
||||
bool ConstFoldingShr(Inst *inst);
|
||||
bool ConstFoldingAShr(Inst *inst);
|
||||
bool ConstFoldingAnd(Inst *inst);
|
||||
bool ConstFoldingOr(Inst *inst);
|
||||
bool ConstFoldingXor(Inst *inst);
|
||||
bool ConstFoldingCmp(Inst *inst);
|
||||
bool ConstFoldingCompare(Inst *inst);
|
||||
bool ConstFoldingSqrt(Inst *inst);
|
||||
|
||||
// NB: casting may be required to create constants of the desired array type for creating LiteralArrays (in Bytecode
|
||||
// Optimizer ConstArrayResolver pass) If a constant is used to initialize an array (is_literal_data == true), it must be
|
||||
// able to be cast to the appropriate types
|
||||
ConstantInst *ConstFoldingCastConst(Inst *inst, Inst *input, bool is_literal_data = false);
|
||||
|
||||
ConstantInst *ConstFoldingCreateIntConst(Inst *inst, uint64_t value, bool is_literal_data = false);
|
||||
} // namespace panda::compiler
|
||||
#endif // CONST_FOLDING_H
|
@ -1,240 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cse.h"
|
||||
#include "utils/logger.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "algorithm"
|
||||
#include "compiler_logger.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
void Cse::LocalCse()
|
||||
{
|
||||
deleted_insts_.clear();
|
||||
candidates_.clear();
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
candidates_.clear();
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (!IsLegalExp(inst)) {
|
||||
continue;
|
||||
}
|
||||
// match the insts in candidates
|
||||
Exp exp = GetExp(inst);
|
||||
if (AllNotIn(candidates_, inst)) {
|
||||
InstVector empty_tmp(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
empty_tmp.emplace_back(inst);
|
||||
candidates_.emplace(exp, std::move(empty_tmp));
|
||||
continue;
|
||||
}
|
||||
if (NotIn(candidates_, exp)) {
|
||||
exp = GetExpCommutative(inst);
|
||||
}
|
||||
|
||||
auto &cntainer = candidates_.at(exp);
|
||||
auto iter = std::find_if(cntainer.begin(), cntainer.end(), Finder(inst));
|
||||
if (iter != cntainer.end()) {
|
||||
inst->ReplaceUsers(*iter);
|
||||
deleted_insts_.emplace_back(inst);
|
||||
} else {
|
||||
cntainer.emplace_back(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
RemoveInstsIn(&deleted_insts_);
|
||||
}
|
||||
|
||||
void Cse::CollectTreeForest()
|
||||
{
|
||||
replace_pair_.clear();
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
if (bb->IsEndBlock()) {
|
||||
continue;
|
||||
}
|
||||
candidates_.clear();
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (!IsLegalExp(inst)) {
|
||||
continue;
|
||||
}
|
||||
auto exp = GetExp(inst);
|
||||
if (AllNotIn(candidates_, inst)) {
|
||||
InstVector vectmp(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
vectmp.emplace_back(inst);
|
||||
candidates_.emplace(exp, std::move(vectmp));
|
||||
} else if (!NotIn(candidates_, exp)) {
|
||||
candidates_.at(exp).emplace_back(inst);
|
||||
} else {
|
||||
candidates_.at(GetExpCommutative(inst)).emplace_back(inst);
|
||||
}
|
||||
}
|
||||
if (candidates_.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (auto domed : bb->GetDominatedBlocks()) {
|
||||
for (auto inst : domed->Insts()) {
|
||||
if (!IsLegalExp(inst) || AllNotIn(candidates_, inst)) {
|
||||
continue;
|
||||
}
|
||||
Exp exp = NotIn(candidates_, GetExp(inst)) ? GetExpCommutative(inst) : GetExp(inst);
|
||||
auto &cntainer = candidates_.at(exp);
|
||||
auto iter = std::find_if(cntainer.begin(), cntainer.end(), Finder(inst));
|
||||
if (iter != cntainer.end()) {
|
||||
replace_pair_.emplace_back(inst, *iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the tree-forest structure of replace_pair into star-forest structure
|
||||
*/
|
||||
void Cse::ConvertTreeForestToStarForest()
|
||||
{
|
||||
min_replace_star_.clear();
|
||||
for (auto rpair : replace_pair_) {
|
||||
Inst *temp = rpair.second;
|
||||
auto it = replace_pair_.begin();
|
||||
do {
|
||||
it = replace_pair_.begin();
|
||||
while (it != replace_pair_.end() && it->first != temp) {
|
||||
it++;
|
||||
}
|
||||
if (it != replace_pair_.end()) {
|
||||
temp = it->second;
|
||||
}
|
||||
} while (it != replace_pair_.end());
|
||||
|
||||
min_replace_star_.emplace_back(temp, rpair.first);
|
||||
}
|
||||
}
|
||||
|
||||
void Cse::EliminateAlongDomTree()
|
||||
{
|
||||
// Eliminate along Dominator tree (based on star structure)
|
||||
deleted_insts_.clear();
|
||||
for (auto pair : min_replace_star_) {
|
||||
pair.second->ReplaceUsers(pair.first);
|
||||
deleted_insts_.emplace_back(pair.second);
|
||||
}
|
||||
RemoveInstsIn(&deleted_insts_);
|
||||
}
|
||||
|
||||
void Cse::BuildSetOfPairs(BasicBlock *block)
|
||||
{
|
||||
same_exp_pair_.clear();
|
||||
auto bbl = block->GetPredsBlocks()[0];
|
||||
auto bbr = block->GetPredsBlocks()[1];
|
||||
for (auto instl : bbl->Insts()) {
|
||||
if (!IsLegalExp(instl) || InVector(deleted_insts_, instl)) {
|
||||
continue;
|
||||
}
|
||||
Exp expl = GetExp(instl);
|
||||
if (!NotIn(same_exp_pair_, expl)) {
|
||||
auto &pair = same_exp_pair_.at(expl);
|
||||
pair.first.emplace_back(instl);
|
||||
continue;
|
||||
}
|
||||
if (!AllNotIn(same_exp_pair_, instl)) {
|
||||
auto &pair = same_exp_pair_.at(GetExpCommutative(instl));
|
||||
pair.first.emplace_back(instl);
|
||||
continue;
|
||||
}
|
||||
for (auto instr : bbr->Insts()) {
|
||||
if (!IsLegalExp(instr) || (NotSameExp(GetExp(instr), expl) && NotSameExp(GetExpCommutative(instr), expl)) ||
|
||||
InVector(deleted_insts_, instr)) {
|
||||
continue;
|
||||
}
|
||||
if (!NotIn(same_exp_pair_, expl)) {
|
||||
auto &pair = same_exp_pair_.at(expl);
|
||||
pair.second.emplace_back(instr);
|
||||
continue;
|
||||
}
|
||||
InstVector emptyl(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
emptyl.emplace_back(instl);
|
||||
InstVector emptyr(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
emptyr.emplace_back(instr);
|
||||
same_exp_pair_.emplace(expl, std::make_pair(std::move(emptyl), std::move(emptyr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cse::DomTreeCse()
|
||||
{
|
||||
CollectTreeForest();
|
||||
ConvertTreeForestToStarForest();
|
||||
EliminateAlongDomTree();
|
||||
}
|
||||
|
||||
void Cse::GlobalCse()
|
||||
{
|
||||
deleted_insts_.clear();
|
||||
matched_tuple_.clear();
|
||||
static constexpr size_t TWO_PREDS = 2;
|
||||
// Find out redundant insts
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
if (bb->GetPredsBlocks().size() != TWO_PREDS) {
|
||||
continue;
|
||||
}
|
||||
BuildSetOfPairs(bb);
|
||||
if (same_exp_pair_.empty()) {
|
||||
continue;
|
||||
}
|
||||
// Build the set of matched insts
|
||||
for (auto inst : bb->Insts()) {
|
||||
if (!IsLegalExp(inst) || AllNotIn(same_exp_pair_, inst)) {
|
||||
continue;
|
||||
}
|
||||
Exp exp = NotIn(same_exp_pair_, GetExp(inst)) ? GetExpCommutative(inst) : GetExp(inst);
|
||||
auto &pair = same_exp_pair_.find(exp)->second;
|
||||
for (auto instl : pair.first) {
|
||||
if (!CanRepalce(instl, inst)) {
|
||||
continue;
|
||||
}
|
||||
auto iter = std::find_if(pair.second.begin(), pair.second.end(), Finder(inst));
|
||||
if (iter != pair.second.end()) {
|
||||
deleted_insts_.emplace_back(inst);
|
||||
auto lrpair = std::make_pair(instl, *iter);
|
||||
matched_tuple_.emplace_back(inst, std::move(lrpair));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add phi instruction
|
||||
matched_tuple_.shrink_to_fit();
|
||||
for (auto tuple : matched_tuple_) {
|
||||
auto inst = tuple.first;
|
||||
auto phi = GetGraph()->CreateInstPhi(inst->GetType(), inst->GetPc());
|
||||
inst->ReplaceUsers(phi);
|
||||
inst->GetBasicBlock()->AppendPhi(phi);
|
||||
auto &pair = tuple.second;
|
||||
phi->AppendInput(pair.first);
|
||||
phi->AppendInput(pair.second);
|
||||
}
|
||||
RemoveInstsIn(&deleted_insts_);
|
||||
}
|
||||
|
||||
bool Cse::RunImpl()
|
||||
{
|
||||
LocalCse();
|
||||
DomTreeCse();
|
||||
GlobalCse();
|
||||
if (!is_applied_) {
|
||||
COMPILER_LOG(DEBUG, CSE_OPT) << "Cse does not find duplicate insts";
|
||||
}
|
||||
return is_applied_;
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,283 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_CSE_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_CSE_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/ir/analysis.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/inst.h"
|
||||
#include "optimizer/pass.h"
|
||||
#include "utils/arena_containers.h"
|
||||
#include "vn.h"
|
||||
#include "compiler_logger.h"
|
||||
#include "utils/logger.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class Cse : public Optimization {
|
||||
using PairInsts = std::pair<Inst *, Inst *>;
|
||||
using PairVectorsInsts = std::pair<InstVector, InstVector>;
|
||||
|
||||
public:
|
||||
explicit Cse(Graph *graph)
|
||||
: Optimization(graph),
|
||||
replace_pair_(graph->GetLocalAllocator()->Adapter()),
|
||||
min_replace_star_(graph->GetLocalAllocator()->Adapter()),
|
||||
deleted_insts_(graph->GetLocalAllocator()->Adapter()),
|
||||
same_exp_pair_(graph->GetLocalAllocator()->Adapter()),
|
||||
matched_tuple_(graph->GetLocalAllocator()->Adapter()),
|
||||
candidates_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
NO_MOVE_SEMANTIC(Cse);
|
||||
NO_COPY_SEMANTIC(Cse);
|
||||
~Cse() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCompilerCse();
|
||||
}
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "Cse";
|
||||
}
|
||||
|
||||
private:
|
||||
void LocalCse();
|
||||
/*
|
||||
LocalCse eliminates the duplicate computations for every basic block.
|
||||
If there are two instructions whose inputs and opcodes and types are all the same,
|
||||
then we move users from second instruction to first instructions(first instruction
|
||||
is at front of the second), and then delete the second instruction.
|
||||
*/
|
||||
|
||||
void DomTreeCse();
|
||||
/*
|
||||
DomTreeCse eliminates the duplicate computations along dominator tree.
|
||||
If there are two instructions inst1 and inst2 with the same expression
|
||||
such that inst1's block dominates inst2's block, then we can move
|
||||
the users of inst2 into inst1, and then delete inst2. Here we make a
|
||||
convention that a block does not dominate itself. It can be viewed as
|
||||
a generation of LocalCse.
|
||||
*/
|
||||
|
||||
void GlobalCse();
|
||||
/*
|
||||
GlobalCse eliminates the redundant computations whose result can be obtained
|
||||
from its (two) predecessors. In this case we will use a new Phi instruction
|
||||
to take place of the redundant instruction. For example,
|
||||
__________________________________ ____________________________________
|
||||
|BB 3: ... | |BB 4: ... |
|
||||
| 6.u32 Add v0, v1 -> (v8) | | 9.u32 Add v0, v1 -> (v15) |
|
||||
| ... | | ... |
|
||||
|_________________________________| |___________________________________|
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
\____________________________________/
|
||||
|BB 5: ... |
|
||||
| 14.u32 Add v0, v1 -> (v20) |
|
||||
| ... |
|
||||
|____________________________________|
|
||||
|
||||
GlobalCse will add a new Phi instruction, to take place of inst 14., as follows:
|
||||
__________________________________ ____________________________________
|
||||
|BB 3: ... | |BB 4: ... |
|
||||
| 6.u32 Add v0, v1 -> (v8, v33p) | | 9.u32 Add v0, v1 -> (v15, v33p) |
|
||||
| ... | | ... |
|
||||
|_________________________________| |___________________________________|
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
__ \_____________________________________/___
|
||||
|BB 5: 33.u32 Phi v6(bb3), v9(bb4) -> (v20) |
|
||||
| ... |
|
||||
| ... |
|
||||
|_____________________________________________|
|
||||
*/
|
||||
struct Exp {
|
||||
Opcode opcode;
|
||||
DataType::Type type;
|
||||
Inst *inputl;
|
||||
Inst *inputr;
|
||||
};
|
||||
static inline bool NotSameExp(Exp exp1, Exp exp2)
|
||||
{
|
||||
return exp1.opcode != exp2.opcode || exp1.type != exp2.type || exp1.inputl != exp2.inputl ||
|
||||
exp1.inputr != exp2.inputr;
|
||||
}
|
||||
/* Exp is the key of the instruction.
|
||||
* We will use ArenaMap<Exp, ArenaVector<Inst*>>canditates to record the insts that have been visited.
|
||||
* The instructions that have the same Exp will be put in a ArenaVector whose key is Exp.
|
||||
* With such map, we can search duplicate computations more efficiently.
|
||||
*/
|
||||
|
||||
static Exp GetExp(Inst *inst)
|
||||
{
|
||||
ASSERT(IsLegalExp(inst));
|
||||
Exp exp = {inst->GetOpcode(), inst->GetType(), inst->GetDataFlowInput(inst->GetInput(0).GetInst()),
|
||||
inst->GetDataFlowInput(inst->GetInput(1).GetInst())};
|
||||
return exp;
|
||||
}
|
||||
|
||||
static Exp GetExpCommutative(Inst *inst)
|
||||
{
|
||||
ASSERT(IsLegalExp(inst));
|
||||
Exp exp = {inst->GetOpcode(), inst->GetType(), inst->GetDataFlowInput(inst->GetInput(1).GetInst()),
|
||||
inst->GetDataFlowInput(inst->GetInput(0).GetInst())};
|
||||
return exp;
|
||||
}
|
||||
|
||||
struct Cmpexp {
|
||||
bool operator()(Exp exp1, Exp exp2) const
|
||||
{
|
||||
if (exp1.opcode != exp2.opcode) {
|
||||
return static_cast<int>(exp1.opcode) < static_cast<int>(exp2.opcode);
|
||||
}
|
||||
if (exp1.type != exp2.type) {
|
||||
return exp1.type < exp2.type;
|
||||
}
|
||||
if (exp1.inputl != exp2.inputl) {
|
||||
return exp1.inputl->GetId() < exp2.inputl->GetId();
|
||||
}
|
||||
if (exp1.inputr != exp2.inputr) {
|
||||
return exp1.inputr->GetId() < exp2.inputr->GetId();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// We eliminate the duplicate expressions which contain one of the following binary operators. One can add other
|
||||
// operators if necessary.
|
||||
static bool IsLegalExp(Inst *inst)
|
||||
{
|
||||
if (inst->IsNotCseApplicable() || !inst->HasUsers()) {
|
||||
return false;
|
||||
}
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Sub:
|
||||
case Opcode::Mul:
|
||||
case Opcode::Div:
|
||||
case Opcode::Mod:
|
||||
case Opcode::Min:
|
||||
case Opcode::Max:
|
||||
case Opcode::Shl:
|
||||
case Opcode::Shr:
|
||||
case Opcode::AShr:
|
||||
case Opcode::And:
|
||||
case Opcode::Or:
|
||||
case Opcode::Xor:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCommutative(Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Mul:
|
||||
case Opcode::Min:
|
||||
case Opcode::Max:
|
||||
case Opcode::And:
|
||||
case Opcode::Or:
|
||||
case Opcode::Xor:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CanRepalce(Inst *dominst, Inst *inst)
|
||||
{
|
||||
return !HasOsrEntryBetween(dominst, inst);
|
||||
}
|
||||
|
||||
struct Finder {
|
||||
explicit Finder(Inst *inst) : instruction_(inst) {}
|
||||
bool operator()(Inst *instn) const
|
||||
{
|
||||
return CanRepalce(instn, instruction_);
|
||||
}
|
||||
|
||||
private:
|
||||
Inst *instruction_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
bool NotIn(const T &candidates, Exp exp)
|
||||
{
|
||||
return candidates.find(exp) == candidates.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AllNotIn(const T &candidates, Inst *inst)
|
||||
{
|
||||
Exp exp = GetExp(inst);
|
||||
Exp expc = GetExpCommutative(inst);
|
||||
return NotIn(candidates, exp) && (!IsCommutative(inst) || NotIn(candidates, expc));
|
||||
}
|
||||
|
||||
static bool InVector(const InstVector &dele, Inst *inst)
|
||||
{
|
||||
return std::find(dele.begin(), dele.end(), inst) != dele.end();
|
||||
}
|
||||
|
||||
void DeleteInstLog(Inst *inst)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, CSE_OPT) << " Cse deletes inst " << inst->GetId();
|
||||
GetGraph()->GetEventWriter().EventCse(inst->GetId(), inst->GetPc());
|
||||
}
|
||||
|
||||
void RemoveInstsIn(InstVector *deleted_insts)
|
||||
{
|
||||
// delete redundant insts
|
||||
if (deleted_insts->empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto inst : *deleted_insts) {
|
||||
DeleteInstLog(inst);
|
||||
auto bb = inst->GetBasicBlock();
|
||||
bb->RemoveInst(inst);
|
||||
}
|
||||
is_applied_ = true;
|
||||
}
|
||||
|
||||
void ConvertTreeForestToStarForest();
|
||||
void EliminateAlongDomTree();
|
||||
void CollectTreeForest();
|
||||
void BuildSetOfPairs(BasicBlock *block);
|
||||
|
||||
private:
|
||||
bool is_applied_ = false;
|
||||
ArenaVector<PairInsts> replace_pair_;
|
||||
ArenaVector<PairInsts> min_replace_star_;
|
||||
InstVector deleted_insts_;
|
||||
ArenaMap<Exp, PairVectorsInsts, Cmpexp> same_exp_pair_;
|
||||
ArenaVector<std::pair<Inst *, PairInsts>> matched_tuple_;
|
||||
ArenaMap<Exp, InstVector, Cmpexp> candidates_;
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_CSE_H_
|
File diff suppressed because it is too large
Load Diff
@ -1,222 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/optimizations/licm.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
Licm::Licm(Graph *graph, uint32_t hoist_limit)
|
||||
: Optimization(graph), HOIST_LIMIT(hoist_limit), hoistable_instructions_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
hoistable_instructions_.reserve(HOIST_LIMIT);
|
||||
}
|
||||
|
||||
// TODO (a.popov) Use `LoopTransform` base class similarly `LoopUnroll`, `LoopPeeling`
|
||||
bool Licm::RunImpl()
|
||||
{
|
||||
if (!GetGraph()->GetAnalysis<LoopAnalyzer>().IsValid()) {
|
||||
GetGraph()->GetAnalysis<LoopAnalyzer>().Run();
|
||||
}
|
||||
|
||||
ASSERT(GetGraph()->GetRootLoop() != nullptr);
|
||||
if (!GetGraph()->GetRootLoop()->GetInnerLoops().empty()) {
|
||||
marker_loop_exit_ = GetGraph()->NewMarker();
|
||||
MarkLoopExits(GetGraph(), marker_loop_exit_);
|
||||
for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
|
||||
LoopSearchDFS(loop);
|
||||
}
|
||||
GetGraph()->EraseMarker(marker_loop_exit_);
|
||||
}
|
||||
return (hoisted_inst_count_ != 0);
|
||||
}
|
||||
|
||||
void Licm::InvalidateAnalyses()
|
||||
{
|
||||
GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
|
||||
GetGraph()->InvalidateAnalysis<AliasAnalysis>();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if block is loop exit, exclude loop header
|
||||
*/
|
||||
bool Licm::IsBlockLoopExit(BasicBlock *block)
|
||||
{
|
||||
return block->IsMarked(marker_loop_exit_) && !block->IsLoopHeader();
|
||||
}
|
||||
|
||||
/*
|
||||
* Search loops in DFS order to visit inner loops firstly
|
||||
*/
|
||||
void Licm::LoopSearchDFS(Loop *loop)
|
||||
{
|
||||
ASSERT(loop != nullptr);
|
||||
for (auto inner_loop : loop->GetInnerLoops()) {
|
||||
LoopSearchDFS(inner_loop);
|
||||
}
|
||||
if (IsLoopVisited(*loop)) {
|
||||
VisitLoop(loop);
|
||||
}
|
||||
}
|
||||
|
||||
bool Licm::IsLoopVisited(const Loop &loop) const
|
||||
{
|
||||
if (loop.IsIrreducible()) {
|
||||
COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "Irreducible loop isn't visited, id = " << loop.GetId();
|
||||
return false;
|
||||
}
|
||||
if (loop.IsOsrLoop()) {
|
||||
COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "OSR entry isn't visited, loop id = " << loop.GetId();
|
||||
return false;
|
||||
}
|
||||
if (loop.IsTryCatchLoop()) {
|
||||
COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "Try-catch loop isn't visited, loop id = " << loop.GetId();
|
||||
return false;
|
||||
}
|
||||
if (hoisted_inst_count_ >= HOIST_LIMIT) {
|
||||
COMPILER_LOG(DEBUG, LICM_OPT) << "Limit is exceeded, loop isn't visited, id = " << loop.GetId();
|
||||
return false;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, LICM_OPT) << "Visit Loop, id = " << loop.GetId();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over all instructions in the loop and move hoistable instructions to the loop preheader
|
||||
*/
|
||||
void Licm::VisitLoop(Loop *loop)
|
||||
{
|
||||
auto marker_holder = MarkerHolder(GetGraph());
|
||||
marker_hoist_inst_ = marker_holder.GetMarker();
|
||||
hoistable_instructions_.clear();
|
||||
// Collect instruction, which can be hoisted
|
||||
for (auto block : loop->GetBlocks()) {
|
||||
ASSERT(block->GetLoop() == loop);
|
||||
for (auto inst : block->InstsSafe()) {
|
||||
if (IsInstHoistable(inst)) {
|
||||
if (hoisted_inst_count_ >= HOIST_LIMIT) {
|
||||
COMPILER_LOG(DEBUG, LICM_OPT)
|
||||
<< "Limit is exceeded, Can't hoist instruction with id = " << inst->GetId();
|
||||
continue;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, LICM_OPT) << "Hoist instruction with id = " << inst->GetId();
|
||||
hoistable_instructions_.push_back(inst);
|
||||
inst->SetMarker(marker_hoist_inst_);
|
||||
hoisted_inst_count_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto pre_header = loop->GetPreHeader();
|
||||
ASSERT(pre_header != nullptr);
|
||||
auto header = loop->GetHeader();
|
||||
if (pre_header->GetSuccsBlocks().size() > 1) {
|
||||
ASSERT(GetGraph()->IsAnalysisValid<DominatorsTree>());
|
||||
// Create new pre-header with single successor: loop header
|
||||
auto new_pre_header = pre_header->InsertNewBlockToSuccEdge(header);
|
||||
pre_header->GetLoop()->AppendBlock(new_pre_header);
|
||||
// Fix Dominators info
|
||||
auto &dom_tree = GetGraph()->GetAnalysis<DominatorsTree>();
|
||||
dom_tree.SetValid(true);
|
||||
ASSERT(header->GetDominator() == pre_header);
|
||||
pre_header->RemoveDominatedBlock(header);
|
||||
dom_tree.SetDomPair(new_pre_header, header);
|
||||
dom_tree.SetDomPair(pre_header, new_pre_header);
|
||||
// Change pre-header
|
||||
loop->SetPreHeader(new_pre_header);
|
||||
pre_header = new_pre_header;
|
||||
}
|
||||
// Find position to move: either add first instruction to the empty block or after the last instruction
|
||||
Inst *last_inst = pre_header->GetLastInst();
|
||||
// Move instructions
|
||||
for (auto inst : hoistable_instructions_) {
|
||||
inst->GetBasicBlock()->EraseInst(inst);
|
||||
if (last_inst == nullptr || last_inst->IsPhi()) {
|
||||
pre_header->AppendInst(inst);
|
||||
} else {
|
||||
ASSERT(last_inst->GetBasicBlock() == pre_header);
|
||||
last_inst->InsertAfter(inst);
|
||||
}
|
||||
GetGraph()->GetEventWriter().EventLicm(inst->GetId(), inst->GetPc(), loop->GetId(),
|
||||
pre_header->GetLoop()->GetId());
|
||||
last_inst = inst;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Instruction is not hoistable if it has input which is defined in the same loop and not mark as hoistable
|
||||
* Or if it has input which is not dominate instruction's loop preheader
|
||||
*/
|
||||
bool Licm::InstInputDominatesPreheader(Inst *inst)
|
||||
{
|
||||
auto inst_loop = inst->GetBasicBlock()->GetLoop();
|
||||
for (auto input : inst->GetInputs()) {
|
||||
auto input_loop = input.GetInst()->GetBasicBlock()->GetLoop();
|
||||
if (inst_loop == input_loop) {
|
||||
if (!input.GetInst()->IsMarked(marker_hoist_inst_) || !input.GetInst()->IsDominate(inst)) {
|
||||
return false;
|
||||
}
|
||||
} else if (inst_loop->GetOuterLoop() == input_loop->GetOuterLoop()) {
|
||||
if (!inst->GetBasicBlock()->IsDominate(inst_loop->GetPreHeader())) {
|
||||
return false;
|
||||
}
|
||||
} else if (!inst_loop->IsInside(input_loop)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hoistable instruction must dominate all uses in the loop
|
||||
*/
|
||||
static bool InstDominatesUsesInLoop(Inst *inst)
|
||||
{
|
||||
auto inst_loop = inst->GetBasicBlock()->GetLoop();
|
||||
for (auto &user : inst->GetUsers()) {
|
||||
if (user.GetInst()->GetBasicBlock()->GetLoop() == inst_loop && !inst->IsDominate(user.GetInst())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hoistable instruction must dominate all loop exits
|
||||
*/
|
||||
bool Licm::InstDominatesLoopExits(Inst *inst)
|
||||
{
|
||||
auto inst_loop = inst->GetBasicBlock()->GetLoop();
|
||||
for (auto block : inst_loop->GetBlocks()) {
|
||||
if (IsBlockLoopExit(block) && !inst->GetBasicBlock()->IsDominate(block)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if instruction can be moved to the loop preheader
|
||||
*/
|
||||
bool Licm::IsInstHoistable(Inst *inst)
|
||||
{
|
||||
ASSERT(!inst->IsPhi());
|
||||
return !inst->IsNotHoistable() && InstInputDominatesPreheader(inst) && InstDominatesUsesInLoop(inst) &&
|
||||
InstDominatesLoopExits(inst) && !GetGraph()->IsInstThrowable(inst);
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_LICM_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_LICM_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/pass.h"
|
||||
#include "compiler_options.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class Licm : public Optimization {
|
||||
public:
|
||||
Licm(Graph *graph, uint32_t hoist_limit);
|
||||
NO_MOVE_SEMANTIC(Licm);
|
||||
NO_COPY_SEMANTIC(Licm);
|
||||
~Licm() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCompilerLicm();
|
||||
}
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "LICM";
|
||||
}
|
||||
|
||||
void InvalidateAnalyses() override;
|
||||
|
||||
bool IsBlockLoopExit(BasicBlock *block);
|
||||
|
||||
private:
|
||||
bool IsLoopVisited(const Loop &loop) const;
|
||||
void VisitLoop(Loop *loop);
|
||||
bool IsInstHoistable(Inst *inst);
|
||||
void LoopSearchDFS(Loop *loop);
|
||||
bool InstDominatesLoopExits(Inst *inst);
|
||||
bool InstInputDominatesPreheader(Inst *inst);
|
||||
|
||||
private:
|
||||
const uint32_t HOIST_LIMIT {0};
|
||||
uint32_t hoisted_inst_count_ {0};
|
||||
Marker marker_loop_exit_ {UNDEF_MARKER};
|
||||
Marker marker_hoist_inst_ {UNDEF_MARKER};
|
||||
InstVector hoistable_instructions_;
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_LICM_H_
|
@ -1,258 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "locations_builder.h"
|
||||
#include "compiler/optimizer/code_generator/callconv.h"
|
||||
#include "compiler/optimizer/ir/graph.h"
|
||||
#include "compiler/optimizer/ir/locations.h"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOCATIONS_BUILDER(type) \
|
||||
template <Arch arch> \
|
||||
type LocationsBuilder<arch>
|
||||
|
||||
namespace panda::compiler {
|
||||
bool RunLocationsBuilder(Graph *graph)
|
||||
{
|
||||
if (graph->GetCallingConvention() == nullptr) {
|
||||
return true;
|
||||
}
|
||||
auto pinfo = graph->GetCallingConvention()->GetParameterInfo(0);
|
||||
switch (graph->GetArch()) {
|
||||
case Arch::X86_64: {
|
||||
return graph->RunPass<LocationsBuilder<Arch::X86_64>>(pinfo);
|
||||
}
|
||||
case Arch::AARCH64: {
|
||||
return graph->RunPass<LocationsBuilder<Arch::AARCH64>>(pinfo);
|
||||
}
|
||||
case Arch::AARCH32: {
|
||||
return graph->RunPass<LocationsBuilder<Arch::AARCH32>>(pinfo);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG(ERROR, COMPILER) << "Unknown target architecture";
|
||||
return false;
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER()::LocationsBuilder(Graph *graph, ParameterInfo *pinfo) : Optimization(graph), params_info_(pinfo)
|
||||
{
|
||||
if (graph->SupportManagedCode()) {
|
||||
/* We skip first parameter location in managed mode, because calling convention for Panda methods requires
|
||||
pointer to the called method as a first parameter. So we reserve one native parameter for it. */
|
||||
GetParameterInfo()->GetNextLocation(DataType::POINTER);
|
||||
}
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(const ArenaVector<BasicBlock *> &)::GetBlocksToVisit() const
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(bool)::RunImpl()
|
||||
{
|
||||
VisitGraph();
|
||||
return true;
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::ProcessManagedCall(Inst *inst, ParameterInfo *pinfo)
|
||||
{
|
||||
ArenaAllocator *allocator = GetGraph()->GetAllocator();
|
||||
LocationsInfo *locations = allocator->New<LocationsInfo>(allocator, inst);
|
||||
|
||||
/* Reserve first parameter for the callee method. It will be set by codegen. */
|
||||
if (pinfo == nullptr) {
|
||||
pinfo = GetResetParameterInfo();
|
||||
pinfo->GetNextLocation(GetWordType());
|
||||
}
|
||||
|
||||
size_t inputs_count = inst->GetInputsCount() - (inst->RequireState() ? 1 : 0);
|
||||
size_t stack_args = 0;
|
||||
for (size_t i = 0; i < inputs_count; i++) {
|
||||
ASSERT(inst->GetInputType(i) != DataType::NO_TYPE);
|
||||
auto param = pinfo->GetNextLocation(inst->GetInputType(i));
|
||||
locations->SetLocation(i, param);
|
||||
if (param.IsStackArgument()) {
|
||||
stack_args++;
|
||||
}
|
||||
}
|
||||
if (inst->IsIntrinsic() && stack_args > 0) {
|
||||
inst->CastToIntrinsic()->SetArgumentsOnStack();
|
||||
}
|
||||
locations->SetDstLocation(GetLocationForReturn(inst));
|
||||
GetGraph()->UpdateStackSlotsCount(stack_args);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitCallStatic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
if (inst->CastToCallStatic()->IsInlined()) {
|
||||
return;
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitUnresolvedCallStatic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
if (inst->CastToUnresolvedCallStatic()->IsInlined()) {
|
||||
return;
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitCallVirtual(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
if (inst->CastToCallVirtual()->IsInlined()) {
|
||||
return;
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitUnresolvedCallVirtual(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
if (inst->CastToUnresolvedCallVirtual()->IsInlined()) {
|
||||
return;
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitCallDynamic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
if (inst->CastToCallDynamic()->IsInlined()) {
|
||||
return;
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst);
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitCallIndirect(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
ArenaAllocator *allocator = static_cast<LocationsBuilder *>(visitor)->GetGraph()->GetAllocator();
|
||||
LocationsInfo *locations = allocator->New<LocationsInfo>(allocator, inst);
|
||||
auto params = static_cast<LocationsBuilder *>(visitor)->GetResetParameterInfo();
|
||||
|
||||
/* First input is an address of the memory to call. So it hasn't exact location and maybe be any register */
|
||||
locations->SetLocation(0, Location::RequireRegister());
|
||||
|
||||
/* Inputs, starting from 1, are the native call arguments */
|
||||
for (size_t i = 1; i < inst->GetInputsCount(); i++) {
|
||||
auto param = params->GetNextLocation(inst->GetInputType(i));
|
||||
locations->SetLocation(i, param);
|
||||
}
|
||||
locations->SetDstLocation(GetLocationForReturn(inst));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitCall(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
ArenaAllocator *allocator = static_cast<LocationsBuilder *>(visitor)->GetGraph()->GetAllocator();
|
||||
LocationsInfo *locations = allocator->New<LocationsInfo>(allocator, inst);
|
||||
auto params = static_cast<LocationsBuilder *>(visitor)->GetResetParameterInfo();
|
||||
|
||||
for (size_t i = 0; i < inst->GetInputsCount(); i++) {
|
||||
auto param = params->GetNextLocation(inst->GetInputType(i));
|
||||
locations->SetLocation(i, param);
|
||||
}
|
||||
locations->SetDstLocation(GetLocationForReturn(inst));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitIntrinsic(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
auto graph = static_cast<LocationsBuilder *>(visitor)->GetGraph();
|
||||
auto intrinsic = inst->CastToIntrinsic();
|
||||
if (intrinsic->IsNativeCall()) {
|
||||
auto pinfo = static_cast<LocationsBuilder *>(visitor)->GetResetParameterInfo();
|
||||
if (intrinsic->HasImms() && graph->SupportManagedCode()) {
|
||||
for ([[maybe_unused]] auto imm : intrinsic->GetImms()) {
|
||||
pinfo->GetNextLocation(DataType::INT32);
|
||||
}
|
||||
}
|
||||
static_cast<LocationsBuilder *>(visitor)->ProcessManagedCall(inst, pinfo);
|
||||
return;
|
||||
}
|
||||
|
||||
ArenaAllocator *allocator = static_cast<LocationsBuilder *>(visitor)->GetGraph()->GetAllocator();
|
||||
LocationsInfo *locations = allocator->New<LocationsInfo>(allocator, inst);
|
||||
auto inputs_count = inst->GetInputsCount() - (inst->RequireState() ? 1 : 0);
|
||||
for (size_t i = 0; i < inputs_count; i++) {
|
||||
locations->SetLocation(i, Location::RequireRegister());
|
||||
}
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitNewArray([[maybe_unused]] GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
inst->CastToNewArray()->SetLocation(0, Location::MakeRegister(GetTarget().GetParamRegId(0)));
|
||||
inst->CastToNewArray()->SetLocation(1, Location::MakeRegister(GetTarget().GetParamRegId(1)));
|
||||
inst->CastToNewArray()->SetDstLocation(GetLocationForReturn(inst));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitNewObject([[maybe_unused]] GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
inst->CastToNewObject()->SetLocation(0, Location::MakeRegister(GetTarget().GetParamRegId(0)));
|
||||
inst->CastToNewObject()->SetDstLocation(GetLocationForReturn(inst));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitParameter([[maybe_unused]] GraphVisitor *visitor, [[maybe_unused]] Inst *inst)
|
||||
{
|
||||
/* Currently we can't process parameters in the Locations Builder, because Parameter instructions may be removed
|
||||
* during optimizations pipeline. Thus, locations is set by IR builder before optimizations.
|
||||
* TODO(compiler): we need to move Parameters' locations here, thereby we get rid of arch structures in the Graph,
|
||||
* such as ParameterInfo, CallingConvention, etc.
|
||||
*/
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitReturn([[maybe_unused]] GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
auto return_reg =
|
||||
DataType::IsFloatType(inst->GetType()) ? GetTarget().GetReturnRegId() : GetTarget().GetReturnFpRegId();
|
||||
inst->CastToReturn()->SetLocation(0, Location::MakeRegister(return_reg));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitThrow([[maybe_unused]] GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
inst->CastToThrow()->SetLocation(0, Location::MakeRegister(GetTarget().GetParamRegId(0)));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitLiveOut([[maybe_unused]] GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
inst->CastToLiveOut()->SetLocation(0, Location::MakeRegister(inst->GetDstReg()));
|
||||
}
|
||||
|
||||
LOCATIONS_BUILDER(void)::VisitMultiArray(GraphVisitor *visitor, Inst *inst)
|
||||
{
|
||||
auto graph = static_cast<LocationsBuilder *>(visitor)->GetGraph();
|
||||
ArenaAllocator *allocator = graph->GetAllocator();
|
||||
LocationsInfo *locations = allocator->New<LocationsInfo>(allocator, inst);
|
||||
locations->SetLocation(0, Location::RequireRegister());
|
||||
|
||||
for (size_t i = 1; i < inst->GetInputsCount() - 1; i++) {
|
||||
locations->SetLocation(i, Location::MakeStackArgument(i - 1));
|
||||
}
|
||||
auto stack_args = inst->GetInputsCount() - 2U;
|
||||
graph->UpdateStackSlotsCount(RoundUp(stack_args, 2U));
|
||||
}
|
||||
|
||||
template <Arch arch>
|
||||
Location LocationsBuilder<arch>::GetLocationForReturn(Inst *inst)
|
||||
{
|
||||
auto return_reg =
|
||||
DataType::IsFloatType(inst->GetType()) ? GetTarget().GetReturnFpRegId() : GetTarget().GetReturnRegId();
|
||||
return Location::MakeRegister(return_reg, inst->GetType());
|
||||
}
|
||||
|
||||
template <Arch arch>
|
||||
ParameterInfo *LocationsBuilder<arch>::GetResetParameterInfo()
|
||||
{
|
||||
params_info_->Reset();
|
||||
return params_info_;
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef PANDA_LOCATIONS_BUILDER_H
|
||||
#define PANDA_LOCATIONS_BUILDER_H
|
||||
|
||||
#include "compiler/optimizer/ir/graph_visitor.h"
|
||||
#include "compiler/optimizer/pass.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class ParameterInfo;
|
||||
|
||||
template <Arch arch> // NOLINTNEXTLINE(fuchsia-multiple-inheritance)
|
||||
class LocationsBuilder : public GraphVisitor, public Optimization {
|
||||
public:
|
||||
LocationsBuilder(Graph *graph, ParameterInfo *pinfo);
|
||||
|
||||
bool RunImpl() override;
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "LocationsBuilder";
|
||||
}
|
||||
|
||||
bool ShouldDump() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override;
|
||||
|
||||
static constexpr Target GetTarget()
|
||||
{
|
||||
return Target(arch);
|
||||
}
|
||||
static constexpr DataType::Type GetWordType()
|
||||
{
|
||||
return Is64BitsArch(arch) ? DataType::UINT64 : DataType::UINT32;
|
||||
}
|
||||
|
||||
static Location GetLocationForReturn(Inst *inst);
|
||||
|
||||
static void VisitCallStatic(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitUnresolvedCallStatic(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCallVirtual(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitUnresolvedCallVirtual(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCallIndirect(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCall(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitCallDynamic(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitIntrinsic(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitParameter(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitReturn(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitThrow(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitNewArray(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitNewObject(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitLiveOut(GraphVisitor *visitor, Inst *inst);
|
||||
static void VisitMultiArray(GraphVisitor *visitor, Inst *inst);
|
||||
|
||||
void ProcessManagedCall(Inst *inst, ParameterInfo *pinfo = nullptr);
|
||||
|
||||
private:
|
||||
ParameterInfo *GetResetParameterInfo();
|
||||
ParameterInfo *GetParameterInfo()
|
||||
{
|
||||
return params_info_;
|
||||
}
|
||||
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
private:
|
||||
ParameterInfo *params_info_ {nullptr};
|
||||
};
|
||||
|
||||
bool RunLocationsBuilder(Graph *graph);
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // PANDA_LOCATIONS_BUILDER_H
|
File diff suppressed because it is too large
Load Diff
@ -42,343 +42,26 @@ public:
|
||||
}
|
||||
|
||||
void InvalidateAnalyses() override;
|
||||
|
||||
static constexpr uint8_t HALF_SIZE = 32;
|
||||
static constexpr uint8_t WORD_SIZE = 64;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Utility template classes aimed to simplify pattern matching over IR-graph.
|
||||
* Patterns are trees declared as a type using Any, UnaryOp, BinaryOp and Operand composition.
|
||||
* Then IR-subtree could be tested for matching by calling static Capture method.
|
||||
* To capture operands from matched subtree Operand<Index> should be used, where
|
||||
* Index is an operand's index within OperandsCapture.
|
||||
*
|
||||
* For example, suppose that we want to test if IR-subtree rooted by some Inst matches add or sub instruction
|
||||
* pattern:
|
||||
*
|
||||
* Inst* inst = ...;
|
||||
* using Predicate = Any<BinaryOp<Opcode::Add, Operand<0>, Operand<1>,
|
||||
* BinaryOp<Opcode::Sub, Operand<0>, Operand<1>>;
|
||||
* OperandsCapture<2> capture{};
|
||||
* bool is_add_or_sub = Predicate::Capture(inst, capture);
|
||||
*
|
||||
* If inst is a binary instruction with opcode Add or Sub then Capture will return `true`,
|
||||
* capture.Get(0) will return left inst's input and capture.Get(1) will return right inst's input.
|
||||
*/
|
||||
|
||||
// Flags altering matching behavior.
|
||||
enum Flags {
|
||||
NONE = 0,
|
||||
COMMUTATIVE = 1, // binary operation is commutative
|
||||
C = COMMUTATIVE,
|
||||
SINGLE_USER = 2, // operation must have only single user
|
||||
S = SINGLE_USER
|
||||
};
|
||||
|
||||
static bool IsSet(uint64_t flags, Flags flag)
|
||||
{
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
static bool IsNotSet(uint64_t flags, Flags flag)
|
||||
{
|
||||
return (flags & flag) == 0;
|
||||
}
|
||||
|
||||
template <size_t max_operands>
|
||||
class OperandsCapture {
|
||||
public:
|
||||
Inst *Get(size_t i)
|
||||
{
|
||||
ASSERT(i < max_operands);
|
||||
return operands_[i];
|
||||
}
|
||||
|
||||
void Set(size_t i, Inst *inst)
|
||||
{
|
||||
ASSERT(i < max_operands);
|
||||
operands_[i] = inst;
|
||||
}
|
||||
|
||||
// Returns true if all non-constant operands have the same common type (obtained using GetCommonType) as all
|
||||
// other operands.
|
||||
bool HaveCommonType() const
|
||||
{
|
||||
auto non_const_type = DataType::LAST;
|
||||
for (size_t i = 0; i < max_operands; i++) {
|
||||
if (operands_[i]->GetOpcode() != Opcode::Constant) {
|
||||
non_const_type = GetCommonType(operands_[i]->GetType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// all operands are constants
|
||||
if (non_const_type == DataType::LAST) {
|
||||
non_const_type = operands_[0]->GetType();
|
||||
}
|
||||
for (size_t i = 0; i < max_operands; i++) {
|
||||
if (operands_[i]->GetOpcode() == Opcode::Constant) {
|
||||
if (GetCommonType(operands_[i]->GetType()) != GetCommonType(non_const_type)) {
|
||||
return false;
|
||||
}
|
||||
} else if (non_const_type != GetCommonType(operands_[i]->GetType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Inst *, max_operands> operands_;
|
||||
};
|
||||
|
||||
template <size_t max_insts>
|
||||
class InstructionsCapture {
|
||||
public:
|
||||
void Add(Inst *inst)
|
||||
{
|
||||
ASSERT(current_idx_ < max_insts);
|
||||
insts_[current_idx_++] = inst;
|
||||
}
|
||||
|
||||
size_t GetCurrentIndex() const
|
||||
{
|
||||
return current_idx_;
|
||||
}
|
||||
|
||||
void SetCurrentIndex(size_t idx)
|
||||
{
|
||||
ASSERT(idx < max_insts);
|
||||
current_idx_ = idx;
|
||||
}
|
||||
|
||||
// Returns true if all non-constant operands have exactly the same type and all
|
||||
// constant arguments have the same common type (obtained using GetCommonType) as all other operands.
|
||||
bool HaveSameType() const
|
||||
{
|
||||
ASSERT(current_idx_ == max_insts);
|
||||
auto non_const_type = DataType::LAST;
|
||||
for (size_t i = 0; i < max_insts; i++) {
|
||||
if (insts_[i]->GetOpcode() != Opcode::Constant) {
|
||||
non_const_type = insts_[i]->GetType();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// all operands are constants
|
||||
if (non_const_type == DataType::LAST) {
|
||||
non_const_type = insts_[0]->GetType();
|
||||
}
|
||||
for (size_t i = 0; i < max_insts; i++) {
|
||||
if (insts_[i]->GetOpcode() == Opcode::Constant) {
|
||||
if (GetCommonType(insts_[i]->GetType()) != GetCommonType(non_const_type)) {
|
||||
return false;
|
||||
}
|
||||
} else if (non_const_type != insts_[i]->GetType()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
InstructionsCapture &ResetIndex()
|
||||
{
|
||||
current_idx_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Inst *, max_insts> insts_;
|
||||
size_t current_idx_;
|
||||
};
|
||||
|
||||
template <Opcode opcode, typename L, typename R, uint64_t flags = Flags::NONE>
|
||||
struct BinaryOp {
|
||||
template <size_t max_operands, size_t max_insts>
|
||||
static bool Capture(Inst *inst, OperandsCapture<max_operands> &args, InstructionsCapture<max_insts> &insts)
|
||||
{
|
||||
constexpr auto INPUTS_NUM = 2;
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
if (inst->GetOpcode() != opcode || inst->GetInputsCount() != INPUTS_NUM ||
|
||||
(IsSet(flags, Flags::SINGLE_USER) && !inst->HasSingleUser())) {
|
||||
return false;
|
||||
}
|
||||
if (L::Capture(inst->GetInput(0).GetInst(), args, insts) &&
|
||||
R::Capture(inst->GetInput(1).GetInst(), args, insts)) {
|
||||
insts.Add(inst);
|
||||
return true;
|
||||
}
|
||||
if (IsSet(flags, Flags::COMMUTATIVE) && L::Capture(inst->GetInput(1).GetInst(), args, insts) &&
|
||||
R::Capture(inst->GetInput(0).GetInst(), args, insts)) {
|
||||
insts.Add(inst);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <Opcode opcode, typename T, uint64_t flags = Flags::NONE>
|
||||
struct UnaryOp {
|
||||
template <size_t max_operands, size_t max_insts>
|
||||
static bool Capture(Inst *inst, OperandsCapture<max_operands> &args, InstructionsCapture<max_insts> &insts)
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||
bool matched = inst->GetOpcode() == opcode && inst->GetInputsCount() == 1 &&
|
||||
(IsNotSet(flags, Flags::SINGLE_USER) || inst->HasSingleUser()) &&
|
||||
T::Capture(inst->GetInput(0).GetInst(), args, insts);
|
||||
if (matched) {
|
||||
insts.Add(inst);
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t idx>
|
||||
struct Operand {
|
||||
template <size_t max_operands, size_t max_insts>
|
||||
static bool Capture(Inst *inst, OperandsCapture<max_operands> &args,
|
||||
[[maybe_unused]] InstructionsCapture<max_insts> &insts)
|
||||
{
|
||||
static_assert(idx < max_operands, "Operand's index should not exceed OperandsCapture size");
|
||||
|
||||
args.Set(idx, inst);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Args>
|
||||
struct AnyOf {
|
||||
template <size_t max_operands, size_t max_insts>
|
||||
static bool Capture(Inst *inst, OperandsCapture<max_operands> &args, InstructionsCapture<max_insts> &insts)
|
||||
{
|
||||
size_t inst_idx = insts.GetCurrentIndex();
|
||||
if (T::Capture(inst, args, insts)) {
|
||||
return true;
|
||||
}
|
||||
insts.SetCurrentIndex(inst_idx);
|
||||
return AnyOf<Args...>::Capture(inst, args, insts);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct AnyOf<T> {
|
||||
template <size_t max_operands, size_t max_insts>
|
||||
static bool Capture(Inst *inst, OperandsCapture<max_operands> &args, InstructionsCapture<max_insts> &insts)
|
||||
{
|
||||
return T::Capture(inst, args, insts);
|
||||
}
|
||||
};
|
||||
|
||||
using SRC0 = Operand<0>;
|
||||
using SRC1 = Operand<1>;
|
||||
using SRC2 = Operand<2U>;
|
||||
|
||||
template <typename L, typename R, uint64_t F = Flags::NONE>
|
||||
using ADD = BinaryOp<Opcode::Add, L, R, F | Flags::COMMUTATIVE>;
|
||||
template <typename L, typename R, uint64_t F = Flags::NONE>
|
||||
using SUB = BinaryOp<Opcode::Sub, L, R, F>;
|
||||
template <typename L, typename R, uint64_t F = Flags::NONE>
|
||||
using MUL = BinaryOp<Opcode::Mul, L, R, F | Flags::COMMUTATIVE>;
|
||||
template <typename T, uint64_t F = Flags::NONE>
|
||||
using NEG = UnaryOp<Opcode::Neg, T, F>;
|
||||
template <typename T, uint64_t F = Flags::SINGLE_USER>
|
||||
using NOT = UnaryOp<Opcode::Not, T, F>;
|
||||
template <typename T, uint64_t F = Flags::SINGLE_USER>
|
||||
using SHRI = UnaryOp<Opcode::ShrI, T, F>;
|
||||
template <typename T, uint64_t F = Flags::SINGLE_USER>
|
||||
using ASHRI = UnaryOp<Opcode::AShrI, T, F>;
|
||||
template <typename T, uint64_t F = Flags::SINGLE_USER>
|
||||
using SHLI = UnaryOp<Opcode::ShlI, T, F>;
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
static void VisitAdd([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSub([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitCast([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitCastValueToAnyType([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
|
||||
template <Opcode opc>
|
||||
static void VisitBitwiseBinaryOperation([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitOr(GraphVisitor *v, Inst *inst);
|
||||
static void VisitAnd(GraphVisitor *v, Inst *inst);
|
||||
static void VisitXor(GraphVisitor *v, Inst *inst);
|
||||
|
||||
static void VisitAndNot([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitXorNot([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitOrNot([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSaveState([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSafePoint([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSaveStateOsr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSaveStateDeoptimize([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitBoundsCheck([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadArray([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoadCompressedStringChar([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreArray([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitLoad([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitStore([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitReturn([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitShr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitAShr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitShl([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitIfImm([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMul([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitDiv([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMod([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitNeg([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitDeoptimizeIf(GraphVisitor *v, Inst *inst);
|
||||
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
|
||||
static void InsertInstruction(Inst *inst, Inst *new_inst)
|
||||
{
|
||||
inst->InsertBefore(new_inst);
|
||||
inst->ReplaceUsers(new_inst);
|
||||
new_inst->GetBasicBlock()->GetGraph()->GetEventWriter().EventLowering(GetOpcodeString(inst->GetOpcode()),
|
||||
inst->GetId(), inst->GetPc());
|
||||
COMPILER_LOG(DEBUG, LOWERING) << "Lowering is applied for " << GetOpcodeString(inst->GetOpcode());
|
||||
}
|
||||
|
||||
template <size_t max_operands>
|
||||
static void SetInputsAndInsertInstruction(OperandsCapture<max_operands> &operands, Inst *inst, Inst *new_inst);
|
||||
|
||||
static constexpr Opcode GetInstructionWithShiftedOperand(Opcode opcode);
|
||||
static constexpr Opcode GetInstructionWithInvertedOperand(Opcode opcode);
|
||||
static ShiftType GetShiftTypeByOpcode(Opcode opcode);
|
||||
static Inst *GetCheckInstAndGetConstInput(Inst *inst);
|
||||
static ShiftOpcode ConvertOpcode(Opcode new_opcode);
|
||||
|
||||
static void LowerMemInstScale(Inst *inst);
|
||||
static void LowerShift(Inst *inst);
|
||||
static bool ConstantFitsCompareImm(Inst *cst, uint32_t size, ConditionCode cc);
|
||||
static Inst *LowerAddSub(Inst *inst);
|
||||
template <Opcode opcode>
|
||||
static void LowerMulDivMod(Inst *inst);
|
||||
static Inst *LowerMultiplyAddSub(Inst *inst);
|
||||
static Inst *LowerNegateMultiply(Inst *inst);
|
||||
static void LowerLogicWithInvertedOperand(Inst *inst);
|
||||
static bool LowerCastValueToAnyTypeWithConst(Inst *inst);
|
||||
template <typename T, size_t max_operands>
|
||||
static Inst *LowerOperationWithShiftedOperand(Inst *inst, OperandsCapture<max_operands> &operands, Inst *shift_inst,
|
||||
Opcode new_opcode);
|
||||
template <Opcode opcode, bool is_commutative = true>
|
||||
static Inst *LowerBinaryOperationWithShiftedOperand(Inst *inst);
|
||||
template <Opcode opcode>
|
||||
static void LowerUnaryOperationWithShiftedOperand(Inst *inst);
|
||||
static Inst *LowerLogic(Inst *inst);
|
||||
template <typename LowLevelType>
|
||||
static void LowerConstArrayIndex(Inst *inst, Opcode low_level_opcode);
|
||||
static void LowerStateInst(SaveStateInst *save_state);
|
||||
static void LowerReturnInst(FixedInputsInst1 *ret);
|
||||
// We'd like to swap only to make second operand immediate
|
||||
static bool BetterToSwapCompareInputs(Inst *cmp);
|
||||
// Optimize order of input arguments for decreasing using accumulator (Bytecodeoptimizer only).
|
||||
static void OptimizeIfInput(compiler::Inst *if_inst);
|
||||
static void JoinFcmpInst(IfImmInst *inst, CmpInst *input);
|
||||
static void LowerIf(IfImmInst *inst);
|
||||
static void InPlaceLowerIfImm(IfImmInst *inst, Inst *input, Inst *cst, ConditionCode cc);
|
||||
static void LowerToDeoptimizeCompare(Inst *inst);
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
|
@ -1,597 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "compiler/optimizer/ir/analysis.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/inst.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/optimizations/lse.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
static std::string LogInst(Inst *inst)
|
||||
{
|
||||
return "v" + std::to_string(inst->GetId()) + " (" + GetOpcodeString(inst->GetOpcode()) + ")";
|
||||
}
|
||||
|
||||
class LseVisitor {
|
||||
public:
|
||||
explicit LseVisitor(Graph *graph, Lse::Heap *heap, Lse::PhiCands *phis)
|
||||
: aa_(graph->GetAnalysis<AliasAnalysis>()),
|
||||
heap_(*heap),
|
||||
phis_(*phis),
|
||||
eliminations_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
NO_MOVE_SEMANTIC(LseVisitor);
|
||||
NO_COPY_SEMANTIC(LseVisitor);
|
||||
~LseVisitor() = default;
|
||||
|
||||
void VisitStore(Inst *inst, Inst *val)
|
||||
{
|
||||
auto hvalue = GetHeapValue(inst);
|
||||
/* Value can be eliminated already */
|
||||
auto alive = val;
|
||||
auto eliminated = eliminations_.find(val);
|
||||
if (eliminated != eliminations_.end()) {
|
||||
alive = eliminated->second.val;
|
||||
}
|
||||
/* If store was assigned to VAL then we can eliminate the second assignment */
|
||||
if (hvalue != nullptr && hvalue->val == alive) {
|
||||
if (Lse::CanEliminateInstruction(inst)) {
|
||||
auto heap = *hvalue;
|
||||
if (eliminations_.find(heap.val) != eliminations_.end()) {
|
||||
heap = eliminations_[heap.val];
|
||||
}
|
||||
ASSERT(eliminations_.find(heap.val) == eliminations_.end());
|
||||
eliminations_[inst] = heap;
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " is eliminated because of " << LogInst(heap.origin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " updated heap with v" << alive->GetId();
|
||||
|
||||
/* Stores added to eliminations_ above aren't checked versus phis -> no double instruction elimination */
|
||||
UpdatePhis(inst);
|
||||
|
||||
auto &block_heap = heap_.at(inst->GetBasicBlock());
|
||||
/* Erase all aliased values, because they may be overwritten */
|
||||
for (auto heap_iter = block_heap.begin(), heap_last = block_heap.end(); heap_iter != heap_last;) {
|
||||
auto hinst = heap_iter->first;
|
||||
if (aa_.CheckInstAlias(inst, hinst) == NO_ALIAS) {
|
||||
heap_iter++;
|
||||
} else {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT)
|
||||
<< "\tDrop from heap { " << LogInst(hinst) << ", v" << heap_iter->second.val->GetId() << "}";
|
||||
heap_iter = block_heap.erase(heap_iter);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set value of the inst to VAL */
|
||||
block_heap[inst] = {inst, alive};
|
||||
}
|
||||
|
||||
void VisitLoad(Inst *inst)
|
||||
{
|
||||
/* If we have a heap value for this load instruction then we can eliminate it */
|
||||
auto hvalue = GetHeapValue(inst);
|
||||
if (hvalue != nullptr) {
|
||||
if (Lse::CanEliminateInstruction(inst)) {
|
||||
auto heap = *hvalue;
|
||||
if (eliminations_.find(heap.val) != eliminations_.end()) {
|
||||
heap = eliminations_[heap.val];
|
||||
}
|
||||
ASSERT(eliminations_.find(heap.val) == eliminations_.end());
|
||||
eliminations_[inst] = heap;
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " is eliminated because of " << LogInst(heap.origin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " updated heap with v" << inst->GetId();
|
||||
|
||||
/* Loads added to eliminations_ above are not checked versus phis -> no double instruction elimination */
|
||||
UpdatePhis(inst);
|
||||
|
||||
/* Otherwise set the value of instruction to itself and update MUST_ALIASes */
|
||||
heap_.at(inst->GetBasicBlock())[inst] = {inst, inst};
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely resets the accumulated state: heap and phi candidates.
|
||||
*/
|
||||
void InvalidateHeap(BasicBlock *block)
|
||||
{
|
||||
heap_.at(block).clear();
|
||||
auto loop = block->GetLoop();
|
||||
while (!loop->IsRoot()) {
|
||||
phis_.at(loop).clear();
|
||||
loop = loop->GetOuterLoop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset accumulated references that may be relocated during GC.
|
||||
* If SaveState is provided then references mentioned in SaveState are not reset.
|
||||
*/
|
||||
void InvalidateRefs(BasicBlock *block, SaveStateInst *ss)
|
||||
{
|
||||
auto &bheap = heap_.at(block);
|
||||
for (auto it = bheap.begin(); it != bheap.end();) {
|
||||
auto hvalue = it->second;
|
||||
if (IsRelocatableValue(hvalue.val, ss)) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT)
|
||||
<< "\tDrop from heap { " << LogInst(it->first) << ", v" << hvalue.val->GetId() << "}";
|
||||
it = bheap.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
auto loop = block->GetLoop();
|
||||
while (!loop->IsRoot()) {
|
||||
auto &cands = phis_.at(loop);
|
||||
for (auto it = cands.begin(); it != cands.end();) {
|
||||
auto cand = it->first;
|
||||
auto val = cand->IsStore() ? InstStoredValue(cand) : cand;
|
||||
if (IsRelocatableValue(val, ss)) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT)
|
||||
<< "\tDrop a phi-cand { " << LogInst(cand) << ", v" << val->GetId() << "}";
|
||||
it = cands.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
loop = loop->GetOuterLoop();
|
||||
}
|
||||
}
|
||||
|
||||
ArenaUnorderedMap<Inst *, struct Lse::HeapValue> &GetEliminations()
|
||||
{
|
||||
return eliminations_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add eliminations inside loops if there is no overwrites on backedges.
|
||||
*/
|
||||
void FinalizeLoops()
|
||||
{
|
||||
for (auto &[loop, phis] : phis_) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Finalizing loop #" << loop->GetId();
|
||||
for (auto &[cand, insts] : phis) {
|
||||
if (insts.empty()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Skipping phi candidate " << LogInst(cand) << " (no users)";
|
||||
continue;
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
for (auto inst : insts) {
|
||||
// One MAY_ALIASed or MUST_ALIASed store is enough to reject the candidate
|
||||
if (inst->IsStore()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT)
|
||||
<< "Skipping phi candidate " << LogInst(cand) << " because of store " << LogInst(inst);
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Processing phi candidate: " << LogInst(cand);
|
||||
for (auto inst : insts) {
|
||||
if (eliminations_.find(inst) != eliminations_.end() ||
|
||||
aa_.CheckInstAlias(cand, inst) != MUST_ALIAS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
struct Lse::HeapValue hvalue = {cand, cand->IsStore() ? InstStoredValue(cand) : cand};
|
||||
eliminations_[inst] = hvalue;
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " is replaced by " << LogInst(hvalue.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Fixing elimination list after backedge substitutions";
|
||||
for (auto &entry : eliminations_) {
|
||||
auto hvalue = entry.second;
|
||||
if (eliminations_.find(hvalue.val) == eliminations_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto initial = hvalue.val;
|
||||
while (eliminations_.find(hvalue.val) != eliminations_.end()) {
|
||||
auto elim_value = eliminations_[hvalue.val];
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "\t" << LogInst(hvalue.val) << " is eliminated. Trying to replace by "
|
||||
<< LogInst(elim_value.val);
|
||||
hvalue = elim_value;
|
||||
ASSERT_PRINT(initial != hvalue.val, "A cyclic elimination has been detected");
|
||||
}
|
||||
entry.second = hvalue;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return a MUST_ALIASed heap entry, nullptr if not present.
|
||||
*/
|
||||
const Lse::HeapValue *GetHeapValue(Inst *inst) const
|
||||
{
|
||||
auto &block_heap = heap_.at(inst->GetBasicBlock());
|
||||
for (auto &entry : block_heap) {
|
||||
if (aa_.CheckInstAlias(inst, entry.first) == MUST_ALIAS) {
|
||||
return &entry.second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update phi candidates with aliased accesses
|
||||
*/
|
||||
void UpdatePhis(Inst *inst)
|
||||
{
|
||||
Loop *loop = inst->GetBasicBlock()->GetLoop();
|
||||
|
||||
while (!loop->IsRoot()) {
|
||||
auto &phis = phis_.at(loop);
|
||||
for (auto &[mem, values] : phis) {
|
||||
if (aa_.CheckInstAlias(inst, mem) != NO_ALIAS) {
|
||||
values.push_back(inst);
|
||||
}
|
||||
}
|
||||
loop = loop->GetOuterLoop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a value can be relocated after save state
|
||||
*/
|
||||
bool IsRelocatableValue(Inst *val, SaveStateInst *ss)
|
||||
{
|
||||
// Skip primitive values on the heap
|
||||
if (val->GetType() != DataType::REFERENCE) {
|
||||
return false;
|
||||
}
|
||||
// If it is saved we can keep it
|
||||
for (size_t i = 0; ss != nullptr && i < ss->GetInputsCount(); ++i) {
|
||||
auto saved = ss->GetDataFlowInput(i);
|
||||
// input can be eliminated
|
||||
if (eliminations_.find(saved) != eliminations_.end()) {
|
||||
saved = eliminations_[saved].val;
|
||||
}
|
||||
if (saved == val) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
AliasAnalysis &aa_;
|
||||
Lse::Heap &heap_;
|
||||
/* Mem accesses that could be replaced with Phi */
|
||||
Lse::PhiCands &phis_;
|
||||
/* Map of instructions to be deleted with values to replace them with */
|
||||
ArenaUnorderedMap<Inst *, struct Lse::HeapValue> eliminations_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the instruction invalidates the whole heap
|
||||
*/
|
||||
static bool IsHeapInvalidatingInst(Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadStatic:
|
||||
return inst->CastToLoadStatic()->GetVolatile();
|
||||
case Opcode::LoadObject:
|
||||
return inst->CastToLoadObject()->GetVolatile();
|
||||
case Opcode::InitObject:
|
||||
case Opcode::InitClass:
|
||||
case Opcode::LoadAndInitClass:
|
||||
case Opcode::UnresolvedLoadAndInitClass:
|
||||
case Opcode::UnresolvedCallVirtual:
|
||||
case Opcode::UnresolvedCallStatic:
|
||||
case Opcode::UnresolvedLoadStatic:
|
||||
case Opcode::UnresolvedStoreStatic:
|
||||
case Opcode::UnresolvedLoadObject:
|
||||
return true;
|
||||
case Opcode::CallVirtual:
|
||||
return !inst->CastToCallVirtual()->IsInlined();
|
||||
case Opcode::CallStatic:
|
||||
return !inst->CastToCallStatic()->IsInlined();
|
||||
default:
|
||||
return inst->GetFlag(compiler::inst_flags::HEAP_INV);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if after instruction execution loaded references can be changed
|
||||
*/
|
||||
static bool IsGCInst(Inst *inst)
|
||||
{
|
||||
return inst->GetOpcode() == Opcode::SafePoint || inst->IsRuntimeCall();
|
||||
}
|
||||
|
||||
bool Lse::CanEliminateInstruction(Inst *inst)
|
||||
{
|
||||
if (inst->IsBarrier()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: a barrier";
|
||||
return false;
|
||||
}
|
||||
auto loop = inst->GetBasicBlock()->GetLoop();
|
||||
if (loop->IsIrreducible()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an irreducible loop";
|
||||
return false;
|
||||
}
|
||||
if (loop->IsOsrLoop()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an OSR loop";
|
||||
return false;
|
||||
}
|
||||
if (loop->IsTryCatchLoop()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an try-catch loop";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* While entering in the loop we put all heap values obtained from loads as phi candidates.
|
||||
* Further phi candidates would replace MUST_ALIAS accesses in the loop if no aliased stores were met.
|
||||
*/
|
||||
void Lse::MergeHeapValuesForLoop(BasicBlock *block, Heap *heap, PhiCands *phis)
|
||||
{
|
||||
ASSERT(block->IsLoopHeader());
|
||||
[[maybe_unused]] auto it = heap->emplace(block, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(it.second);
|
||||
auto loop = block->GetLoop();
|
||||
auto phit = phis->emplace(loop, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(phit.second);
|
||||
|
||||
// Do not eliminate anything in irreducible or osr loops
|
||||
if (loop->IsIrreducible() || loop->IsOsrLoop() || loop->IsTryCatchLoop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto preheader = loop->GetPreHeader();
|
||||
auto &preheader_heap = heap->at(preheader);
|
||||
|
||||
auto &block_phis = phit.first->second;
|
||||
|
||||
for (auto mem : preheader_heap) {
|
||||
block_phis.try_emplace(mem.second.origin, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(mem.first) << " is a phi cand for BB #" << block->GetId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge heap values for passed block from its direct predecessors.
|
||||
*/
|
||||
void Lse::MergeHeapValuesForBlock(BasicBlock *block, Heap *heap)
|
||||
{
|
||||
auto it = heap->emplace(block, GetGraph()->GetLocalAllocator()->Adapter());
|
||||
ASSERT(it.second);
|
||||
|
||||
auto &block_heap = it.first->second;
|
||||
/* Copy a heap of one of predecessors */
|
||||
auto preds = block->GetPredsBlocks();
|
||||
auto pred_it = preds.begin();
|
||||
if (pred_it != preds.end()) {
|
||||
block_heap.insert(heap->at(*pred_it).begin(), heap->at(*pred_it).end());
|
||||
pred_it++;
|
||||
}
|
||||
|
||||
/* Erase from the heap anything that disappeared or was changed in other predecessors */
|
||||
while (pred_it != preds.end()) {
|
||||
auto pred_heap = heap->at(*pred_it);
|
||||
auto heap_it = block_heap.begin();
|
||||
while (heap_it != block_heap.end()) {
|
||||
if (pred_heap.find(heap_it->first) == pred_heap.end() ||
|
||||
pred_heap[heap_it->first].val != heap_it->second.val) {
|
||||
heap_it = block_heap.erase(heap_it);
|
||||
} else {
|
||||
heap_it++;
|
||||
}
|
||||
}
|
||||
pred_it++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the elimination code in two letter format.
|
||||
*
|
||||
* The first letter describes a [L]oad or [S]tore that was eliminated.
|
||||
* The second letter describes the dominant [L]oad or [S]tore that is the
|
||||
* reason why instruction was eliminated.
|
||||
*/
|
||||
const char *Lse::GetEliminationCode(Inst *inst, Inst *origin)
|
||||
{
|
||||
ASSERT(inst->IsMemory() && origin->IsMemory());
|
||||
if (inst->IsLoad()) {
|
||||
if (origin->IsLoad()) {
|
||||
return "LL";
|
||||
}
|
||||
if (origin->IsStore()) {
|
||||
return "LS";
|
||||
}
|
||||
}
|
||||
if (inst->IsStore()) {
|
||||
if (origin->IsLoad()) {
|
||||
return "SL";
|
||||
}
|
||||
if (origin->IsStore()) {
|
||||
return "SS";
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
/**
|
||||
* In the codegen of bytecode optimizer, we don't have corresponding pandasm
|
||||
* for the IR `Cast` of with some pairs of input types and output types. So
|
||||
* in the bytecode optimizer mode, we need to avoid generating such `Cast` IR.
|
||||
* The following function gives the list of legal pairs of types.
|
||||
* This function should not be used in compiler mode.
|
||||
*/
|
||||
|
||||
static bool IsTypeLegalForCast(DataType::Type output, DataType::Type input)
|
||||
{
|
||||
ASSERT(output != input);
|
||||
switch (input) {
|
||||
case DataType::INT32:
|
||||
case DataType::INT64:
|
||||
case DataType::FLOAT64:
|
||||
switch (output) {
|
||||
case DataType::FLOAT64:
|
||||
case DataType::INT64:
|
||||
case DataType::UINT32:
|
||||
case DataType::INT32:
|
||||
case DataType::INT16:
|
||||
case DataType::UINT16:
|
||||
case DataType::INT8:
|
||||
case DataType::UINT8:
|
||||
case DataType::ANY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case DataType::REFERENCE:
|
||||
return output == DataType::ANY;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace inputs of INST with VALUE and delete this INST. If deletion led to
|
||||
* appearance of instruction that has no users delete this instruction too.
|
||||
*/
|
||||
void Lse::DeleteInstruction(Inst *inst, Inst *value)
|
||||
{
|
||||
// Have to cast a value to the type of eliminated inst. Actually required only for loads.
|
||||
if (inst->GetType() != value->GetType() && inst->HasUsers()) {
|
||||
ASSERT(inst->GetType() != DataType::REFERENCE && value->GetType() != DataType::REFERENCE);
|
||||
// We will do nothing in bytecode optimizer mode when the types are not legal for cast.
|
||||
if (GetGraph()->IsBytecodeOptimizer() && !IsTypeLegalForCast(inst->GetType(), value->GetType())) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was not eliminated: requires an inappropriate cast";
|
||||
return;
|
||||
}
|
||||
auto cast = GetGraph()->CreateInstCast(inst->GetType(), inst->GetPc());
|
||||
cast->SetOperandsType(value->GetType());
|
||||
cast->SetInput(0, value);
|
||||
inst->InsertAfter(cast);
|
||||
value = cast;
|
||||
}
|
||||
inst->ReplaceUsers(value);
|
||||
|
||||
ArenaQueue<Inst *> queue(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
queue.push(inst);
|
||||
while (!queue.empty()) {
|
||||
Inst *front_inst = queue.front();
|
||||
BasicBlock *block = front_inst->GetBasicBlock();
|
||||
queue.pop();
|
||||
|
||||
// Have been already deleted or could not be deleted
|
||||
if (block == nullptr || front_inst->HasUsers()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &input : front_inst->GetInputs()) {
|
||||
/* Delete only instructions that has no data flow impact */
|
||||
if (input.GetInst()->HasPseudoDestination()) {
|
||||
queue.push(input.GetInst());
|
||||
}
|
||||
}
|
||||
block->RemoveInst(front_inst);
|
||||
applied_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Lse::HasMonitor()
|
||||
{
|
||||
for (auto bb : GetGraph()->GetBlocksRPO()) {
|
||||
if (bb->GetMonitorBlock()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Lse::RunImpl()
|
||||
{
|
||||
if (HasMonitor()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Load-Store Elimination skipped: monitors";
|
||||
return false;
|
||||
}
|
||||
|
||||
Heap heap(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
PhiCands phis(GetGraph()->GetLocalAllocator()->Adapter());
|
||||
|
||||
GetGraph()->RunPass<LoopAnalyzer>();
|
||||
GetGraph()->RunPass<AliasAnalysis>();
|
||||
|
||||
LseVisitor visitor(GetGraph(), &heap, &phis);
|
||||
for (auto block : GetGraph()->GetBlocksRPO()) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Processing BB " << block->GetId();
|
||||
if (block->IsLoopHeader()) {
|
||||
MergeHeapValuesForLoop(block, &heap, &phis);
|
||||
} else {
|
||||
MergeHeapValuesForBlock(block, &heap);
|
||||
}
|
||||
|
||||
for (auto inst : block->Insts()) {
|
||||
if (IsHeapInvalidatingInst(inst)) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " invalidates heap";
|
||||
visitor.InvalidateHeap(block);
|
||||
} else if (IsGCInst(inst)) {
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " relocates refs";
|
||||
SaveStateInst *ss = inst->GetSaveState();
|
||||
if (inst->GetOpcode() == Opcode::SafePoint) {
|
||||
ss = inst->CastToSafePoint();
|
||||
}
|
||||
visitor.InvalidateRefs(block, ss);
|
||||
} else if (inst->IsLoad()) {
|
||||
visitor.VisitLoad(inst);
|
||||
} else if (inst->IsStore()) {
|
||||
visitor.VisitStore(inst, InstStoredValue(inst));
|
||||
}
|
||||
}
|
||||
}
|
||||
visitor.FinalizeLoops();
|
||||
|
||||
auto &eliminated = visitor.GetEliminations();
|
||||
for (auto elim : eliminated) {
|
||||
Inst *inst = elim.first;
|
||||
Inst *origin = elim.second.origin;
|
||||
Inst *value = elim.second.val;
|
||||
|
||||
ASSERT_DO(eliminated.find(value) == eliminated.end(),
|
||||
(std::cerr << "Instruction:\n", inst->Dump(&std::cerr),
|
||||
std::cerr << "is replaced by eliminated value:\n", value->Dump(&std::cerr)));
|
||||
|
||||
GetGraph()->GetEventWriter().EventLse(inst->GetId(), inst->GetPc(), origin->GetId(), origin->GetPc(),
|
||||
GetEliminationCode(inst, origin));
|
||||
DeleteInstruction(inst, value);
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, LSE_OPT) << "Load-Store Elimination complete";
|
||||
return applied_;
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,127 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_LSE_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_LSE_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/pass.h"
|
||||
#include "compiler_options.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
/**
|
||||
* Load Store Elimination (Lse) optimization is aimed to eliminate redundant
|
||||
* loads and store. It uses Alias Analysis to determine which memory
|
||||
* instructions are redundant. Lse has the heap representation in form of
|
||||
*
|
||||
* "Memory Instruction" -> "Value stored at location pointed by instruction"
|
||||
*
|
||||
* Generally, this optimization tries to:
|
||||
* 1) delete stores that attempt to store the same values that already have
|
||||
* been been stored by a previous store
|
||||
* 2) delete loads that attempt to load values that were previously loaded or
|
||||
* stored (optimization keep track what was stored previously)
|
||||
*
|
||||
* Traversing basic blocks in RPO optimization does the following with
|
||||
* instructions:
|
||||
* - if the instruction is a store and a stored value is equal to value from
|
||||
* heap for this store then this store can be eliminated.
|
||||
* - if the instruction is a store and a value from heap for this store is
|
||||
* absent or differs from new stored value then the new stored value is written
|
||||
* into heap. The values of memory instructions that MUST_ALIAS this store are
|
||||
* updated as well. All values in the heap that MAY_ALIAS this store
|
||||
* instruction are invalidated.
|
||||
* - if the instruction is a load and there is a value from the heap for this
|
||||
* load then this load can be eliminated.
|
||||
* - if the instruction is a load and there is no value from the heap for this
|
||||
* load then we update heap value for this load with the result of this load.
|
||||
* All instructions that MUST_ALIAS this load updated as well.
|
||||
* - if the instruction is a volatile load then the whole heap is invalidated.
|
||||
* - if the instruction is a call then the whole heap is invalidated.
|
||||
*
|
||||
* Instructions that invalidate heap are enumerated in IsHeapInvalidatingInst.
|
||||
* Instructions that cannot be eliminated are presented in
|
||||
* CanEliminateInstruction.
|
||||
*
|
||||
* Another noticeable points:
|
||||
* - Heap is invalided at loop headers basic blocks because values can be
|
||||
* overwritten by using back edges.
|
||||
* - Heap for basic block is obtained by merging heaps from predecessors. If
|
||||
* heap value conflicts among predecessors it is not added.
|
||||
* - Instructions are deleted at the end of pass. If a deleted instruction is
|
||||
* the cause that another instruction now without users, this instruction is
|
||||
* deleted as well. This process continues recursively.
|
||||
*
|
||||
* Loops are handled in the following way:
|
||||
* - on the loop header we record all loads from preheader's heap. They are
|
||||
* potential memory accesses that can be used to eliminated accesses inside the
|
||||
* loop. We call them phi-candidates (in future they can be used to reuse
|
||||
* stores inside the loop).
|
||||
* - visiting any memory access inside a loop we check the aliasing with
|
||||
* phi-candidates and record aliased ones to a corresponding candidate.
|
||||
* - all phi-candidates of a loop (and of all outer loops of this loop) are
|
||||
* invalidated if any of instructions that invalidate heap have been met in
|
||||
* this loop.
|
||||
* - after actual deletion of collected accesses for elimination we iterate
|
||||
* over candidates with aliased accesses. If any of aliased accesses for a
|
||||
* candidate is a store, we do nothing. If among aliased accesses only loads,
|
||||
* we simply replace MUST_ALIASed loads with the corresponding candidate.
|
||||
*/
|
||||
class Lse : public Optimization {
|
||||
using Optimization::Optimization;
|
||||
|
||||
public:
|
||||
struct HeapValue {
|
||||
Inst *origin; // The instruction the value comes from
|
||||
Inst *val; // The value itself
|
||||
};
|
||||
|
||||
using Heap = ArenaDoubleUnorderedMap<BasicBlock *, Inst *, struct HeapValue>;
|
||||
using PhiCands = ArenaDoubleUnorderedMap<Loop *, Inst *, InstVector>;
|
||||
|
||||
explicit Lse(Graph *graph) : Optimization(graph) {};
|
||||
|
||||
NO_MOVE_SEMANTIC(Lse);
|
||||
NO_COPY_SEMANTIC(Lse);
|
||||
~Lse() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
bool IsEnable() const override
|
||||
{
|
||||
return options.IsCompilerLse();
|
||||
}
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "LSE";
|
||||
}
|
||||
|
||||
static bool CanEliminateInstruction(Inst *inst);
|
||||
|
||||
private:
|
||||
void MergeHeapValuesForLoop(BasicBlock *block, Heap *heap, PhiCands *phis);
|
||||
void MergeHeapValuesForBlock(BasicBlock *block, Heap *heap);
|
||||
const char *GetEliminationCode(Inst *inst, Inst *origin);
|
||||
void DeleteInstruction(Inst *inst, Inst *value);
|
||||
bool HasMonitor();
|
||||
|
||||
private:
|
||||
bool applied_ {false};
|
||||
};
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_LSE_H_
|
@ -14,699 +14,15 @@
|
||||
*/
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/ir/graph_visitor.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/inst.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/optimizations/memory_coalescing.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
/**
|
||||
* Basic analysis for variables used in loops. It works as follows:
|
||||
* 1) Identify variables that are derived from another variables and their difference (AddI, SubI supported).
|
||||
* 2) Based on previous step reveal loop variables and their iteration increment if possible.
|
||||
*/
|
||||
class VariableAnalysis {
|
||||
public:
|
||||
struct BaseVariable {
|
||||
int64_t initial;
|
||||
int64_t step;
|
||||
};
|
||||
struct DerivedVariable {
|
||||
Inst *base;
|
||||
int64_t diff;
|
||||
};
|
||||
|
||||
explicit VariableAnalysis(Graph *graph)
|
||||
: base_(graph->GetLocalAllocator()->Adapter()), derived_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
for (auto block : graph->GetBlocksRPO()) {
|
||||
for (auto inst : block->AllInsts()) {
|
||||
if (GetCommonType(inst->GetType()) == DataType::INT64) {
|
||||
AddUsers(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto loop : graph->GetRootLoop()->GetInnerLoops()) {
|
||||
if (loop->IsIrreducible()) {
|
||||
continue;
|
||||
}
|
||||
auto header = loop->GetHeader();
|
||||
for (auto phi : header->PhiInsts()) {
|
||||
constexpr auto INPUTS_COUNT = 2;
|
||||
if (phi->GetInputsCount() != INPUTS_COUNT || GetCommonType(phi->GetType()) != DataType::INT64) {
|
||||
continue;
|
||||
}
|
||||
auto var = phi->CastToPhi();
|
||||
Inst *initial = var->GetPhiInput(var->GetPhiInputBb(0));
|
||||
Inst *update = var->GetPhiInput(var->GetPhiInputBb(1));
|
||||
if (var->GetPhiInputBb(0) != loop->GetPreHeader()) {
|
||||
std::swap(initial, update);
|
||||
}
|
||||
|
||||
if (!initial->IsConst()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (derived_.find(update) != derived_.end()) {
|
||||
auto init_val = static_cast<int64_t>(initial->CastToConstant()->GetIntValue());
|
||||
base_[var] = {init_val, derived_[update].diff};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING) << "Evolution variables:";
|
||||
for (auto entry : base_) {
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING)
|
||||
<< "v" << entry.first->GetId() << " = {" << entry.second.initial << ", " << entry.second.step << "}";
|
||||
}
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING) << "Loop variables:";
|
||||
for (auto entry : derived_) {
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING)
|
||||
<< "v" << entry.first->GetId() << " = v" << entry.second.base->GetId() << " + " << entry.second.diff;
|
||||
}
|
||||
}
|
||||
|
||||
DEFAULT_MOVE_SEMANTIC(VariableAnalysis);
|
||||
DEFAULT_COPY_SEMANTIC(VariableAnalysis);
|
||||
~VariableAnalysis() = default;
|
||||
|
||||
bool IsAnalyzed(Inst *inst) const
|
||||
{
|
||||
return derived_.find(inst) != derived_.end();
|
||||
}
|
||||
|
||||
Inst *GetBase(Inst *inst) const
|
||||
{
|
||||
return derived_.at(inst).base;
|
||||
}
|
||||
|
||||
int64_t GetInitial(Inst *inst) const
|
||||
{
|
||||
auto var = derived_.at(inst);
|
||||
return base_.at(var.base).initial + var.diff;
|
||||
}
|
||||
|
||||
int64_t GetDiff(Inst *inst) const
|
||||
{
|
||||
return derived_.at(inst).diff;
|
||||
}
|
||||
|
||||
int64_t GetStep(Inst *inst) const
|
||||
{
|
||||
return base_.at(derived_.at(inst).base).step;
|
||||
}
|
||||
|
||||
bool IsEvoluted(Inst *inst) const
|
||||
{
|
||||
return derived_.at(inst).base->IsPhi();
|
||||
}
|
||||
|
||||
bool HasKnownEvolution(Inst *inst) const
|
||||
{
|
||||
Inst *base = derived_.at(inst).base;
|
||||
return base->IsPhi() && base_.find(base) != base_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Add derived variables if we can deduce the change from INST
|
||||
*/
|
||||
void AddUsers(Inst *inst)
|
||||
{
|
||||
auto acc = 0;
|
||||
auto base = inst;
|
||||
if (derived_.find(inst) != derived_.end()) {
|
||||
acc += derived_[inst].diff;
|
||||
base = derived_[inst].base;
|
||||
} else {
|
||||
derived_[inst] = {inst, 0};
|
||||
}
|
||||
for (auto &user : inst->GetUsers()) {
|
||||
auto uinst = user.GetInst();
|
||||
ASSERT(uinst->IsPhi() || derived_.find(uinst) == derived_.end());
|
||||
switch (uinst->GetOpcode()) {
|
||||
case Opcode::AddI: {
|
||||
auto val = static_cast<int64_t>(uinst->CastToAddI()->GetImm());
|
||||
derived_[uinst] = {base, acc + val};
|
||||
break;
|
||||
}
|
||||
case Opcode::SubI: {
|
||||
auto val = static_cast<int64_t>(uinst->CastToSubI()->GetImm());
|
||||
derived_[uinst] = {base, acc - val};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ArenaUnorderedMap<Inst *, struct BaseVariable> base_;
|
||||
ArenaUnorderedMap<Inst *, struct DerivedVariable> derived_;
|
||||
};
|
||||
|
||||
/**
|
||||
* The visitor collects pairs of memory instructions that can be coalesced.
|
||||
* It operates in scope of basic block. During observation of instructions we
|
||||
* collect memory instructions in one common queue of candidates that can be merged.
|
||||
*
|
||||
* Candidate is marked as invalid in the following conditions:
|
||||
* - it has been paired already
|
||||
* - it is a store and SaveState has been met
|
||||
* - a BARRIER or CAN_TROW instruction has been met
|
||||
*
|
||||
* To pair valid array accesses:
|
||||
* - check that accesses happen on the consecutive indices of the same array
|
||||
* - find the lowest position the dominator access can be sunk
|
||||
* - find the highest position the dominatee access can be hoisted
|
||||
* - if highest position dominates lowest position the coalescing is possible
|
||||
*/
|
||||
class PairCollectorVisitor : public GraphVisitor {
|
||||
public:
|
||||
explicit PairCollectorVisitor(Graph *graph, const AliasAnalysis &aliases, const VariableAnalysis &vars, Marker mrk,
|
||||
bool aligned)
|
||||
: aligned_only_(aligned),
|
||||
mrk_invalid_(mrk),
|
||||
graph_(graph),
|
||||
aliases_(aliases),
|
||||
vars_(vars),
|
||||
pairs_(graph->GetLocalAllocator()->Adapter()),
|
||||
candidates_(graph->GetLocalAllocator()->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return graph_->GetBlocksRPO();
|
||||
}
|
||||
|
||||
NO_MOVE_SEMANTIC(PairCollectorVisitor);
|
||||
NO_COPY_SEMANTIC(PairCollectorVisitor);
|
||||
~PairCollectorVisitor() override = default;
|
||||
|
||||
static void VisitLoadArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<PairCollectorVisitor *>(v)->HandleArrayAccess(inst);
|
||||
}
|
||||
|
||||
static void VisitStoreArray(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<PairCollectorVisitor *>(v)->HandleArrayAccess(inst);
|
||||
}
|
||||
|
||||
static void VisitLoadArrayI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<PairCollectorVisitor *>(v)->HandleArrayAccessI(inst);
|
||||
}
|
||||
|
||||
static void VisitStoreArrayI(GraphVisitor *v, Inst *inst)
|
||||
{
|
||||
static_cast<PairCollectorVisitor *>(v)->HandleArrayAccessI(inst);
|
||||
}
|
||||
|
||||
void VisitDefault(Inst *inst) override
|
||||
{
|
||||
if (inst->CanThrow()) {
|
||||
candidates_.clear();
|
||||
return;
|
||||
}
|
||||
if (inst->IsMemory()) {
|
||||
candidates_.push_back(inst);
|
||||
return;
|
||||
}
|
||||
if (inst->IsBarrier()) {
|
||||
candidates_.clear();
|
||||
return;
|
||||
}
|
||||
// Do not move stores and ref loads over SaveState
|
||||
// TODO(ekudriashov): re-consider logic after implementing SaveState input fixing
|
||||
if (inst->IsSaveState()) {
|
||||
InvalidateAllStoresAndRefLoads();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
candidates_.clear();
|
||||
}
|
||||
|
||||
ArenaVector<MemoryCoalescing::CoalescedPair> &GetPairs()
|
||||
{
|
||||
return pairs_;
|
||||
}
|
||||
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
private:
|
||||
void InvalidateAllStoresAndRefLoads()
|
||||
{
|
||||
for (auto cand : candidates_) {
|
||||
if (cand->IsStore() || (cand->IsLoad() && cand->GetType() == DataType::REFERENCE)) {
|
||||
cand->SetMarker(mrk_invalid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the highest instructions that INST can be inserted after (in scope of basic block).
|
||||
* Consider aliased memory accesses and volatile operations. CHECK_CFG enables the check of INST inputs
|
||||
* as well.
|
||||
*/
|
||||
Inst *FindUpperInsertAfter(Inst *inst, Inst *bound, bool check_cfg)
|
||||
{
|
||||
ASSERT(bound != nullptr);
|
||||
auto upper_after = bound;
|
||||
// We do not move higher than bound
|
||||
auto lower_input = upper_after;
|
||||
if (check_cfg) {
|
||||
// Update upper bound according to def-use chains
|
||||
for (auto &input_item : inst->GetInputs()) {
|
||||
auto input = input_item.GetInst();
|
||||
if (input->GetBasicBlock() == inst->GetBasicBlock() && lower_input->IsPrecedingInSameBlock(input)) {
|
||||
ASSERT(input->IsPrecedingInSameBlock(inst));
|
||||
lower_input = input;
|
||||
}
|
||||
}
|
||||
upper_after = lower_input;
|
||||
}
|
||||
|
||||
auto bound_it = std::find(candidates_.rbegin(), candidates_.rend(), bound);
|
||||
ASSERT(bound_it != candidates_.rend());
|
||||
for (auto it = candidates_.rbegin(); it != bound_it; it++) {
|
||||
auto cand = *it;
|
||||
if (check_cfg && cand->IsPrecedingInSameBlock(lower_input)) {
|
||||
return lower_input;
|
||||
}
|
||||
// Can't hoist over aliased store
|
||||
if (cand->IsStore() && aliases_.CheckInstAlias(inst, cand) != NO_ALIAS) {
|
||||
return cand;
|
||||
}
|
||||
// Can't hoist over volatile load
|
||||
if (cand->IsLoad() && IsVolatileMemInst(cand)) {
|
||||
return cand;
|
||||
}
|
||||
}
|
||||
return upper_after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest instructions that INST can be inserted after (in scope of basic block).
|
||||
* Consider aliased memory accesses and volatile operations. CHECK_CFG enables the check of INST users
|
||||
* as well.
|
||||
*/
|
||||
Inst *FindLowerInsertAfter(Inst *inst, Inst *bound, bool check_cfg = true)
|
||||
{
|
||||
ASSERT(bound != nullptr);
|
||||
auto lower_after = bound->GetPrev();
|
||||
// We do not move lower than bound
|
||||
auto upper_user = lower_after;
|
||||
ASSERT(upper_user != nullptr);
|
||||
if (check_cfg) {
|
||||
// Update lower bound according to def-use chains
|
||||
for (auto &user_item : inst->GetUsers()) {
|
||||
auto user = user_item.GetInst();
|
||||
if (!user->IsPhi() && user->GetBasicBlock() == inst->GetBasicBlock() &&
|
||||
user->IsPrecedingInSameBlock(upper_user)) {
|
||||
ASSERT(inst->IsPrecedingInSameBlock(user));
|
||||
upper_user = user->GetPrev();
|
||||
ASSERT(upper_user != nullptr);
|
||||
}
|
||||
}
|
||||
lower_after = upper_user;
|
||||
}
|
||||
|
||||
auto inst_it = std::find(candidates_.begin(), candidates_.end(), inst);
|
||||
ASSERT(inst_it != candidates_.end());
|
||||
for (auto it = inst_it + 1; it != candidates_.end(); it++) {
|
||||
auto cand = *it;
|
||||
if (check_cfg && upper_user->IsPrecedingInSameBlock(cand)) {
|
||||
return upper_user;
|
||||
}
|
||||
// Can't lower over aliased store
|
||||
if (cand->IsStore() && aliases_.CheckInstAlias(inst, cand) != NO_ALIAS) {
|
||||
ASSERT(cand->GetPrev() != nullptr);
|
||||
return cand->GetPrev();
|
||||
}
|
||||
// Can't lower over volatile store
|
||||
if (cand->IsStore() && IsVolatileMemInst(cand)) {
|
||||
ASSERT(cand->GetPrev() != nullptr);
|
||||
return cand->GetPrev();
|
||||
}
|
||||
}
|
||||
return lower_after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pair if a difference between indices equals to one. The first in pair is with lower index.
|
||||
*/
|
||||
bool TryAddCoalescedPair(Inst *inst, int64_t inst_idx, Inst *cand, int64_t cand_idx)
|
||||
{
|
||||
Inst *first = nullptr;
|
||||
Inst *second = nullptr;
|
||||
Inst *insert_after = nullptr;
|
||||
if (inst_idx == cand_idx - 1) {
|
||||
first = inst;
|
||||
second = cand;
|
||||
} else if (cand_idx == inst_idx - 1) {
|
||||
first = cand;
|
||||
second = inst;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
ASSERT(inst->IsMemory() && cand->IsMemory());
|
||||
ASSERT(inst->GetOpcode() == cand->GetOpcode());
|
||||
ASSERT(inst != cand && cand->IsPrecedingInSameBlock(inst));
|
||||
Inst *cand_lower_after = nullptr;
|
||||
Inst *inst_upper_after = nullptr;
|
||||
if (first->IsLoad()) {
|
||||
// Consider dominance of load users
|
||||
bool check_cfg = true;
|
||||
cand_lower_after = FindLowerInsertAfter(cand, inst, check_cfg);
|
||||
// Do not need index if v0[v1] preceeds v0[v1 + 1] because v1 + 1 is not used in paired load.
|
||||
check_cfg = second->IsPrecedingInSameBlock(first);
|
||||
inst_upper_after = FindUpperInsertAfter(inst, cand, check_cfg);
|
||||
} else if (first->IsStore()) {
|
||||
// Store instructions do not have users. Don't check them
|
||||
bool check_cfg = false;
|
||||
cand_lower_after = FindLowerInsertAfter(cand, inst, check_cfg);
|
||||
// Should check that stored value is ready
|
||||
check_cfg = true;
|
||||
inst_upper_after = FindUpperInsertAfter(inst, cand, check_cfg);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// No intersection in reordering ranges
|
||||
if (!inst_upper_after->IsPrecedingInSameBlock(cand_lower_after)) {
|
||||
return false;
|
||||
}
|
||||
if (cand->IsPrecedingInSameBlock(inst_upper_after)) {
|
||||
insert_after = inst_upper_after;
|
||||
} else {
|
||||
insert_after = cand;
|
||||
}
|
||||
|
||||
first->SetMarker(mrk_invalid_);
|
||||
second->SetMarker(mrk_invalid_);
|
||||
pairs_.push_back({first, second, insert_after});
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleArrayAccessI(Inst *inst)
|
||||
{
|
||||
Inst *obj = inst->GetDataFlowInput(inst->GetInput(0).GetInst());
|
||||
uint64_t idx = GetInstImm(inst);
|
||||
if (!MemoryCoalescing::AcceptedType(inst->GetType())) {
|
||||
candidates_.push_back(inst);
|
||||
return;
|
||||
}
|
||||
/* Last candidates more likely to be coalesced */
|
||||
for (auto iter = candidates_.rbegin(); iter != candidates_.rend(); iter++) {
|
||||
auto cand = *iter;
|
||||
/* Skip not interesting candidates */
|
||||
if (cand->IsMarked(mrk_invalid_) || cand->GetOpcode() != inst->GetOpcode()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Inst *cand_obj = cand->GetDataFlowInput(cand->GetInput(0).GetInst());
|
||||
/* Array objects must alias each other */
|
||||
if (aliases_.CheckRefAlias(obj, cand_obj) != MUST_ALIAS) {
|
||||
continue;
|
||||
}
|
||||
/* The difference between indices should be equal to one */
|
||||
uint64_t cand_idx = GetInstImm(cand);
|
||||
/* To keep alignment the lowest index should be even */
|
||||
if (aligned_only_ && ((idx < cand_idx && (idx & 1U) != 0) || (cand_idx < idx && (cand_idx & 1U) != 0))) {
|
||||
continue;
|
||||
}
|
||||
if (TryAddCoalescedPair(inst, idx, cand, cand_idx)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
candidates_.push_back(inst);
|
||||
}
|
||||
|
||||
void HandleArrayAccess(Inst *inst)
|
||||
{
|
||||
Inst *obj = inst->GetDataFlowInput(inst->GetInput(0).GetInst());
|
||||
Inst *idx = inst->GetDataFlowInput(inst->GetInput(1).GetInst());
|
||||
if (!vars_.IsAnalyzed(idx) || !MemoryCoalescing::AcceptedType(inst->GetType())) {
|
||||
candidates_.push_back(inst);
|
||||
return;
|
||||
}
|
||||
/* Last candidates more likely to be coalesced */
|
||||
for (auto iter = candidates_.rbegin(); iter != candidates_.rend(); iter++) {
|
||||
auto cand = *iter;
|
||||
/* Skip not interesting candidates */
|
||||
if (cand->IsMarked(mrk_invalid_) || cand->GetOpcode() != inst->GetOpcode()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Inst *cand_obj = cand->GetDataFlowInput(cand->GetInput(0).GetInst());
|
||||
auto cand_idx = cand->GetDataFlowInput(cand->GetInput(1).GetInst());
|
||||
/* We need to have info about candidate's index and array objects must alias each other */
|
||||
if (!vars_.IsAnalyzed(cand_idx) || aliases_.CheckRefAlias(obj, cand_obj) != MUST_ALIAS) {
|
||||
continue;
|
||||
}
|
||||
if (vars_.HasKnownEvolution(idx) && vars_.HasKnownEvolution(cand_idx)) {
|
||||
/* Accesses inside loop */
|
||||
auto idx_step = vars_.GetStep(idx);
|
||||
auto idx_initial = vars_.GetInitial(idx);
|
||||
auto cand_step = vars_.GetStep(cand_idx);
|
||||
auto cand_initial = vars_.GetInitial(cand_idx);
|
||||
/* Indices should be incremented at the same value and their
|
||||
increment should be even to hold alignment */
|
||||
if (idx_step != cand_step) {
|
||||
continue;
|
||||
}
|
||||
/* To keep alignment we need to have even step and even lowest initial */
|
||||
constexpr auto IMM_2 = 2;
|
||||
if (aligned_only_ && idx_step % IMM_2 != 0 &&
|
||||
((idx_initial < cand_initial && idx_initial % IMM_2 != 0) ||
|
||||
(cand_initial < idx_initial && cand_initial % IMM_2 != 0))) {
|
||||
continue;
|
||||
}
|
||||
if (TryAddCoalescedPair(inst, idx_initial, cand, cand_initial)) {
|
||||
break;
|
||||
}
|
||||
} else if (!aligned_only_ && !vars_.HasKnownEvolution(idx) && !vars_.HasKnownEvolution(cand_idx)) {
|
||||
/* Accesses outside loop */
|
||||
if (vars_.GetBase(idx) != vars_.GetBase(cand_idx)) {
|
||||
continue;
|
||||
}
|
||||
if (TryAddCoalescedPair(inst, vars_.GetDiff(idx), cand, vars_.GetDiff(cand_idx))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidates_.push_back(inst);
|
||||
}
|
||||
|
||||
uint64_t GetInstImm(Inst *inst)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::LoadArrayI:
|
||||
return inst->CastToLoadArrayI()->GetImm();
|
||||
case Opcode::StoreArrayI:
|
||||
return inst->CastToStoreArrayI()->GetImm();
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool aligned_only_;
|
||||
Marker mrk_invalid_;
|
||||
Graph *graph_ {nullptr};
|
||||
const AliasAnalysis &aliases_;
|
||||
const VariableAnalysis &vars_;
|
||||
ArenaVector<MemoryCoalescing::CoalescedPair> pairs_;
|
||||
InstVector candidates_;
|
||||
};
|
||||
|
||||
/**
|
||||
* This optimization coalesces two loads (stores) that read (write) values from (to) the consecutive memory into
|
||||
* a single operation.
|
||||
*
|
||||
* 1) If we have two memory instruction that can be coalesced then we are trying to find a position for
|
||||
* coalesced operation. If it is possible, the memory operations are coalesced and skipped otherwise.
|
||||
* 2) The instruction of Aarch64 requires memory address alignment. For arrays
|
||||
* it means we can coalesce only accesses that starts from even index.
|
||||
* 3) The implemented coalescing for arrays supposes there is no volatile array element accesses.
|
||||
*/
|
||||
bool MemoryCoalescing::RunImpl()
|
||||
{
|
||||
if (GetGraph()->GetArch() != Arch::AARCH64) {
|
||||
COMPILER_LOG(INFO, MEMORY_COALESCING) << "Skipping Memory Coalescing for unsupported architecture";
|
||||
return false;
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<DominatorsTree>();
|
||||
GetGraph()->RunPass<LoopAnalyzer>();
|
||||
GetGraph()->RunPass<AliasAnalysis>();
|
||||
|
||||
VariableAnalysis variables(GetGraph());
|
||||
auto &aliases = GetGraph()->GetValidAnalysis<AliasAnalysis>();
|
||||
Marker mrk = GetGraph()->NewMarker();
|
||||
PairCollectorVisitor collector(GetGraph(), aliases, variables, mrk, aligned_only_);
|
||||
for (auto block : GetGraph()->GetBlocksRPO()) {
|
||||
collector.VisitBlock(block);
|
||||
collector.Reset();
|
||||
}
|
||||
GetGraph()->EraseMarker(mrk);
|
||||
|
||||
ReplacePairs(collector.GetPairs());
|
||||
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING) << "Memory Coalescing complete";
|
||||
return !collector.GetPairs().empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a paired load (store) and then inserts it at specified place.
|
||||
* After all insertions remove single loads (stores). Separation is needed
|
||||
* because specified place could be an instruction that would be paired and it
|
||||
* cannot be removed on the fly.
|
||||
*/
|
||||
void MemoryCoalescing::ReplacePairs(ArenaVector<CoalescedPair> const &pairs)
|
||||
{
|
||||
for (auto pair : pairs) {
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING)
|
||||
<< "Accesses that may be coalesced: v" << pair.first->GetId() << " v" << pair.second->GetId();
|
||||
}
|
||||
|
||||
for (auto pair : pairs) {
|
||||
ASSERT(pair.first->GetType() == pair.second->GetType());
|
||||
Inst *paired = nullptr;
|
||||
switch (pair.first->GetOpcode()) {
|
||||
case Opcode::LoadArray:
|
||||
paired = ReplaceLoadArray(pair.first, pair.second, pair.after_inst);
|
||||
break;
|
||||
case Opcode::LoadArrayI:
|
||||
paired = ReplaceLoadArrayI(pair.first, pair.second, pair.after_inst);
|
||||
break;
|
||||
case Opcode::StoreArray:
|
||||
paired = ReplaceStoreArray(pair.first, pair.second, pair.after_inst);
|
||||
break;
|
||||
case Opcode::StoreArrayI:
|
||||
paired = ReplaceStoreArrayI(pair.first, pair.second, pair.after_inst);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
COMPILER_LOG(DEBUG, MEMORY_COALESCING)
|
||||
<< "Coalescing of {v" << pair.first->GetId() << " v" << pair.second->GetId() << "} is successful";
|
||||
GetGraph()->GetEventWriter().EventMemoryCoalescing(pair.first->GetId(), pair.first->GetPc(),
|
||||
pair.second->GetId(), pair.second->GetPc(), paired->GetId(),
|
||||
paired->IsStore() ? "Store" : "Load");
|
||||
}
|
||||
|
||||
for (auto pair : pairs) {
|
||||
auto bb = pair.first->GetBasicBlock();
|
||||
bb->RemoveInst(pair.first);
|
||||
bb->RemoveInst(pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCoalescing::ReplaceLoadByPair(Inst *load, Inst *paired_load, int32_t dst_idx)
|
||||
{
|
||||
auto pair_getter = GetGraph()->CreateInstLoadPairPart(dst_idx);
|
||||
pair_getter->SetInput(0, paired_load);
|
||||
pair_getter->SetType(load->GetType());
|
||||
load->ReplaceUsers(pair_getter);
|
||||
paired_load->InsertAfter(pair_getter);
|
||||
}
|
||||
|
||||
Inst *MemoryCoalescing::ReplaceLoadArray(Inst *first, Inst *second, Inst *insert_after)
|
||||
{
|
||||
ASSERT(first->GetOpcode() == Opcode::LoadArray);
|
||||
ASSERT(second->GetOpcode() == Opcode::LoadArray);
|
||||
|
||||
auto pload = GetGraph()->CreateInstLoadArrayPair();
|
||||
pload->CastToLoadArrayPair()->SetNeedBarrier(first->CastToLoadArray()->GetNeedBarrier() ||
|
||||
second->CastToLoadArray()->GetNeedBarrier());
|
||||
pload->SetInput(0, first->GetInput(0).GetInst());
|
||||
pload->SetInput(1, first->GetInput(1).GetInst());
|
||||
pload->SetType(first->GetType());
|
||||
insert_after->InsertAfter(pload);
|
||||
if (first->CanThrow() || second->CanThrow()) {
|
||||
pload->SetFlag(compiler::inst_flags::CAN_THROW);
|
||||
}
|
||||
|
||||
ReplaceLoadByPair(second, pload, 1);
|
||||
ReplaceLoadByPair(first, pload, 0);
|
||||
|
||||
return pload;
|
||||
}
|
||||
|
||||
Inst *MemoryCoalescing::ReplaceLoadArrayI(Inst *first, Inst *second, Inst *insert_after)
|
||||
{
|
||||
ASSERT(first->GetOpcode() == Opcode::LoadArrayI);
|
||||
ASSERT(second->GetOpcode() == Opcode::LoadArrayI);
|
||||
|
||||
auto pload = GetGraph()->CreateInstLoadArrayPairI(first->CastToLoadArrayI()->GetImm());
|
||||
pload->CastToLoadArrayPairI()->SetNeedBarrier(first->CastToLoadArrayI()->GetNeedBarrier() ||
|
||||
second->CastToLoadArrayI()->GetNeedBarrier());
|
||||
pload->SetInput(0, first->GetInput(0).GetInst());
|
||||
pload->SetType(first->GetType());
|
||||
insert_after->InsertAfter(pload);
|
||||
if (first->CanThrow() || second->CanThrow()) {
|
||||
pload->SetFlag(compiler::inst_flags::CAN_THROW);
|
||||
}
|
||||
|
||||
ReplaceLoadByPair(second, pload, 1);
|
||||
ReplaceLoadByPair(first, pload, 0);
|
||||
|
||||
return pload;
|
||||
}
|
||||
|
||||
Inst *MemoryCoalescing::ReplaceStoreArray(Inst *first, Inst *second, Inst *insert_after)
|
||||
{
|
||||
ASSERT(first->GetOpcode() == Opcode::StoreArray);
|
||||
ASSERT(second->GetOpcode() == Opcode::StoreArray);
|
||||
|
||||
auto pstore = GetGraph()->CreateInstStoreArrayPair();
|
||||
pstore->CastToStoreArrayPair()->SetNeedBarrier(first->CastToStoreArray()->GetNeedBarrier() ||
|
||||
second->CastToStoreArray()->GetNeedBarrier());
|
||||
pstore->SetInput(0, first->GetInput(0).GetInst());
|
||||
pstore->SetInput(1, first->CastToStoreArray()->GetIndex());
|
||||
constexpr auto IMM_2 = 2;
|
||||
pstore->SetInput(IMM_2, first->CastToStoreArray()->GetStoredValue());
|
||||
constexpr auto IMM_3 = 3;
|
||||
pstore->SetInput(IMM_3, second->CastToStoreArray()->GetStoredValue());
|
||||
pstore->SetType(first->GetType());
|
||||
insert_after->InsertAfter(pstore);
|
||||
if (first->CanThrow() || second->CanThrow()) {
|
||||
pstore->SetFlag(compiler::inst_flags::CAN_THROW);
|
||||
}
|
||||
|
||||
return pstore;
|
||||
}
|
||||
|
||||
Inst *MemoryCoalescing::ReplaceStoreArrayI(Inst *first, Inst *second, Inst *insert_after)
|
||||
{
|
||||
ASSERT(first->GetOpcode() == Opcode::StoreArrayI);
|
||||
ASSERT(second->GetOpcode() == Opcode::StoreArrayI);
|
||||
|
||||
auto pstore = GetGraph()->CreateInstStoreArrayPairI(first->CastToStoreArrayI()->GetImm());
|
||||
pstore->CastToStoreArrayPairI()->SetNeedBarrier(first->CastToStoreArrayI()->GetNeedBarrier() ||
|
||||
second->CastToStoreArrayI()->GetNeedBarrier());
|
||||
pstore->SetInput(0, first->GetInput(0).GetInst());
|
||||
pstore->SetInput(1, first->CastToStoreArrayI()->GetStoredValue());
|
||||
constexpr auto IMM_2 = 2;
|
||||
pstore->SetInput(IMM_2, second->CastToStoreArrayI()->GetStoredValue());
|
||||
pstore->SetType(first->GetType());
|
||||
insert_after->InsertAfter(pstore);
|
||||
if (first->CanThrow() || second->CanThrow()) {
|
||||
pstore->SetFlag(compiler::inst_flags::CAN_THROW);
|
||||
}
|
||||
|
||||
return pstore;
|
||||
return true;
|
||||
}
|
||||
} // namespace panda::compiler
|
||||
|
@ -22,17 +22,7 @@
|
||||
|
||||
namespace panda::compiler {
|
||||
class MemoryCoalescing : public Optimization {
|
||||
using Optimization::Optimization;
|
||||
|
||||
public:
|
||||
struct CoalescedPair {
|
||||
Inst *first;
|
||||
Inst *second;
|
||||
Inst *after_inst;
|
||||
};
|
||||
|
||||
explicit MemoryCoalescing(Graph *graph, bool aligned = true) : Optimization(graph), aligned_only_(aligned) {}
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
bool IsEnable() const override
|
||||
@ -68,18 +58,6 @@ public:
|
||||
NO_MOVE_SEMANTIC(MemoryCoalescing);
|
||||
NO_COPY_SEMANTIC(MemoryCoalescing);
|
||||
~MemoryCoalescing() override = default;
|
||||
|
||||
private:
|
||||
void ReplacePairs(ArenaVector<CoalescedPair> const &pairs);
|
||||
void ReplaceLoadByPair(Inst *load, Inst *paired_load, int32_t dst_idx);
|
||||
|
||||
Inst *ReplaceLoadArray(Inst *first, Inst *second, Inst *insert_after);
|
||||
Inst *ReplaceLoadArrayI(Inst *first, Inst *second, Inst *insert_after);
|
||||
Inst *ReplaceStoreArray(Inst *first, Inst *second, Inst *insert_after);
|
||||
Inst *ReplaceStoreArrayI(Inst *first, Inst *second, Inst *insert_after);
|
||||
|
||||
private:
|
||||
bool aligned_only_;
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,202 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_PEEPHOLES_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_PEEPHOLES_H_
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/pass.h"
|
||||
|
||||
#include "compiler_logger.h"
|
||||
#include "optimizer/ir/analysis.h"
|
||||
#include "optimizer/ir/graph_visitor.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
|
||||
class Peepholes : public Optimization, public GraphVisitor {
|
||||
using Optimization::Optimization;
|
||||
|
||||
public:
|
||||
explicit Peepholes(Graph *graph) : Optimization(graph) {}
|
||||
|
||||
NO_MOVE_SEMANTIC(Peepholes);
|
||||
NO_COPY_SEMANTIC(Peepholes);
|
||||
~Peepholes() override = default;
|
||||
|
||||
bool RunImpl() override;
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "Peepholes";
|
||||
}
|
||||
|
||||
bool IsApplied() const
|
||||
{
|
||||
return is_applied_;
|
||||
}
|
||||
|
||||
void InvalidateAnalyses() override;
|
||||
|
||||
const ArenaVector<BasicBlock *> &GetBlocksToVisit() const override
|
||||
{
|
||||
return GetGraph()->GetBlocksRPO();
|
||||
}
|
||||
|
||||
static void VisitSafePoint([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitNeg([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitAbs([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitNot([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitAdd([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSub([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMulOneConst([[maybe_unused]] GraphVisitor *v, Inst *inst, Inst *input0, Inst *input1);
|
||||
static void VisitMul([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitDiv([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMin([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMax([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitMod([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitShl([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitShr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitAShr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitAnd([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitOr([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitXor([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitCmp(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCompare([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitIf(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCast([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitLenArray(GraphVisitor *v, Inst *inst);
|
||||
static void VisitPhi([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitSqrt([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitIsInstance(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCastValueToAnyType(GraphVisitor *v, Inst *inst);
|
||||
static void VisitCompareAnyType(GraphVisitor *v, Inst *inst);
|
||||
static void VisitStore([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreObject([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
static void VisitStoreStatic([[maybe_unused]] GraphVisitor *v, Inst *inst);
|
||||
|
||||
#include "optimizer/ir/visitor.inc"
|
||||
|
||||
private:
|
||||
void SetIsApplied(Inst *inst, bool altFormat = false, const char *file = nullptr, int line = 0)
|
||||
{
|
||||
is_applied_ = true;
|
||||
if (altFormat) {
|
||||
COMPILER_LOG(DEBUG, PEEPHOLE) << "Peephole (" << file << ":" << line << ") is applied for " << *inst;
|
||||
} else {
|
||||
COMPILER_LOG(DEBUG, PEEPHOLE) << "Peephole is applied for " << GetOpcodeString(inst->GetOpcode());
|
||||
}
|
||||
inst->GetBasicBlock()->GetGraph()->GetEventWriter().EventPeephole(GetOpcodeString(inst->GetOpcode()),
|
||||
inst->GetId(), inst->GetPc());
|
||||
if (options.IsCompilerDumpPeepholes()) {
|
||||
std::string name(GetPassName());
|
||||
name += "_" + std::to_string(apply_index_);
|
||||
GetGraph()->GetPassManager()->DumpGraph(name.c_str());
|
||||
apply_index_++;
|
||||
}
|
||||
}
|
||||
|
||||
// This function check that we can merge two Phi instructions in one basic block.
|
||||
static bool IsPhiUnionPossible(PhiInst *phi1, PhiInst *phi2);
|
||||
|
||||
// Get power of 2
|
||||
// if n not power of 2 return -1;
|
||||
static int64_t GetPowerOfTwo(uint64_t n);
|
||||
|
||||
// Create new instruction instead of current inst
|
||||
static Inst *CreateAndInsertInst(Opcode new_opc, Inst *curr_inst, Inst *input0, Inst *input1 = nullptr);
|
||||
|
||||
// Try put constant in second input
|
||||
void TrySwapInputs(Inst *inst);
|
||||
|
||||
// Try to remove redundant cast
|
||||
void TrySimplifyCastToOperandsType(Inst *inst);
|
||||
void TrySimplifyShifts(Inst *inst);
|
||||
bool TrySimplifyAddSubWithConstInput(Inst *inst);
|
||||
template <Opcode opc, int idx>
|
||||
void TrySimplifyAddSub(Inst *inst, Inst *input0, Inst *input1);
|
||||
bool TrySimplifyAddSubAdd(Inst *inst, Inst *input0, Inst *input1);
|
||||
bool TrySimplifyAddSubSub(Inst *inst, Inst *input0, Inst *input1);
|
||||
bool TrySimplifySubAddAdd(Inst *inst, Inst *input0, Inst *input1);
|
||||
bool TrySimplifyShlShlAdd(Inst *inst);
|
||||
bool TryReassociateShlShlAddSub(Inst *inst);
|
||||
void TrySimplifyNeg(Inst *inst);
|
||||
void TryReplaceDivByShrAndAshr(Inst *inst, uint64_t unsigned_val, Inst *input0);
|
||||
void TryReplaceDivByShift(Inst *inst);
|
||||
bool TrySimplifyCompareCaseInputInv(Inst *inst, Inst *input);
|
||||
bool TrySimplifyCompareWithBoolInput(Inst *inst);
|
||||
bool TrySimplifyCmpCompareWithZero(Inst *inst);
|
||||
bool TrySimplifyTestEqualInputs(Inst *inst);
|
||||
static bool TrySimplifyCompareAndZero(Inst *inst);
|
||||
static bool TrySimplifyCompareAnyType(Inst *inst);
|
||||
static bool TrySimplifyCompareLenArrayWithZero(Inst *inst);
|
||||
// Try to combine constants when arithmetic operations with constants are repeated
|
||||
template <typename T>
|
||||
static bool TryCombineConst(Inst *inst, ConstantInst *cnst1, T combine);
|
||||
static bool TryCombineAddSubConst(Inst *inst, ConstantInst *cnst1);
|
||||
static bool TryCombineShiftConst(Inst *inst, ConstantInst *cnst1);
|
||||
static bool TryCombineMulConst(Inst *inst, ConstantInst *cnst1);
|
||||
|
||||
static bool GetInputsOfCompareWithConst(const Inst *inst, Inst **input, ConstantInst **constInput,
|
||||
bool *inputsSwapped);
|
||||
|
||||
// Table for eliminating 'Compare <CC> <INPUT>, <CONST>'
|
||||
enum InputCode {
|
||||
INPUT_TRUE, // Result of compare is TRUE whatever the INPUT is
|
||||
INPUT_ORIG, // Result of compare is equal to the INPUT itself
|
||||
INPUT_INV, // Result of compare is a negation of the INPUT
|
||||
INPUT_FALSE, // Result of compare is FALSE whatever the INPUT is
|
||||
INPUT_UNDEF // Result of compare is undefined
|
||||
};
|
||||
// clang-format off
|
||||
static constexpr std::array<std::array<InputCode, 4>, CC_LAST + 1> VALUES = {{
|
||||
/* CONST < 0 CONST == 0 CONST == 1 CONST > 1 CC */
|
||||
{INPUT_FALSE, INPUT_INV, INPUT_ORIG, INPUT_FALSE}, /* CC_EQ */
|
||||
{INPUT_TRUE, INPUT_ORIG, INPUT_INV, INPUT_TRUE}, /* CC_NE */
|
||||
{INPUT_FALSE, INPUT_FALSE, INPUT_INV, INPUT_TRUE}, /* CC_LT */
|
||||
{INPUT_FALSE, INPUT_INV, INPUT_TRUE, INPUT_TRUE}, /* CC_LE */
|
||||
{INPUT_TRUE, INPUT_ORIG, INPUT_FALSE, INPUT_FALSE}, /* CC_GT */
|
||||
{INPUT_TRUE, INPUT_TRUE, INPUT_ORIG, INPUT_FALSE}, /* CC_GE */
|
||||
/* For unsigned comparison 'CONST < 0' equals to 'CONST > 1' */
|
||||
{INPUT_TRUE, INPUT_FALSE, INPUT_INV, INPUT_TRUE}, /* CC_B */
|
||||
{INPUT_TRUE, INPUT_INV, INPUT_TRUE, INPUT_TRUE}, /* CC_BE */
|
||||
{INPUT_FALSE, INPUT_ORIG, INPUT_FALSE, INPUT_FALSE}, /* CC_A */
|
||||
{INPUT_FALSE, INPUT_TRUE, INPUT_ORIG, INPUT_FALSE} /* CC_AE */
|
||||
}};
|
||||
// clang-format on
|
||||
static InputCode GetInputCode(const ConstantInst *inst, ConditionCode cc)
|
||||
{
|
||||
if (inst->GetType() == DataType::INT32) {
|
||||
auto i = std::min<int32_t>(std::max<int32_t>(-1, static_cast<int32_t>(inst->GetInt32Value())), 2U) + 1;
|
||||
return VALUES[cc][i];
|
||||
}
|
||||
if (inst->GetType() == DataType::INT64) {
|
||||
auto i = std::min<int64_t>(std::max<int64_t>(-1, static_cast<int64_t>(inst->GetIntValue())), 2U) + 1;
|
||||
return VALUES[cc][i];
|
||||
}
|
||||
UNREACHABLE();
|
||||
return InputCode::INPUT_UNDEF;
|
||||
}
|
||||
template <typename T>
|
||||
static void EliminateInstPrecedingStore(GraphVisitor *v, Inst *inst);
|
||||
|
||||
private:
|
||||
// Each peephole application has own unique index, it will be used in peepholes dumps
|
||||
uint32_t apply_index_ {0};
|
||||
|
||||
bool is_applied_ {false};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_PEEPHOLES_H_
|
@ -17,58 +17,14 @@
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/optimizations/cleanup.h"
|
||||
#include "reg_alloc_graph_coloring.h"
|
||||
#include "reg_alloc_linear_scan.h"
|
||||
#include "reg_alloc_resolver.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
static constexpr size_t INST_LIMIT_FOR_GRAPH_COLORING = 5000;
|
||||
|
||||
bool IsGraphColoringEnable(const Graph *graph)
|
||||
{
|
||||
if (graph->GetArch() == Arch::AARCH32 || !graph->IsAotMode() || !options.IsCompilerAotRa()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t inst_count = 0;
|
||||
for (auto bb : graph->GetBlocksRPO()) {
|
||||
for ([[maybe_unused]] auto inst : bb->AllInsts()) {
|
||||
++inst_count;
|
||||
}
|
||||
}
|
||||
return inst_count < INST_LIMIT_FOR_GRAPH_COLORING;
|
||||
}
|
||||
|
||||
bool ShouldSkipAllocation(Graph *graph)
|
||||
{
|
||||
#ifndef PANDA_TARGET_WINDOWS
|
||||
// Parameters spill-fills are empty
|
||||
return graph->GetCallingConvention() == nullptr && !graph->IsBytecodeOptimizer();
|
||||
#else
|
||||
return !graph->IsBytecodeOptimizer();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RegAlloc(Graph *graph)
|
||||
{
|
||||
if (ShouldSkipAllocation(graph)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
graph->RunPass<Cleanup>();
|
||||
|
||||
if (graph->IsBytecodeOptimizer()) {
|
||||
RegAllocResolver(graph).ResolveCatchPhis();
|
||||
return graph->RunPass<RegAllocGraphColoring>(VIRTUAL_FRAME_SIZE);
|
||||
}
|
||||
|
||||
bool ra_passed = false;
|
||||
if (IsGraphColoringEnable(graph)) {
|
||||
ra_passed = graph->RunPass<RegAllocGraphColoring>();
|
||||
}
|
||||
|
||||
if (!ra_passed) {
|
||||
ra_passed = graph->RunPass<RegAllocLinearScan>();
|
||||
}
|
||||
return ra_passed;
|
||||
RegAllocResolver(graph).ResolveCatchPhis();
|
||||
return graph->RunPass<RegAllocGraphColoring>(VIRTUAL_FRAME_SIZE);
|
||||
}
|
||||
} // namespace panda::compiler
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include "optimizer/ir/datatype.h"
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/optimizations/locations_builder.h"
|
||||
#include "split_resolver.h"
|
||||
#include "spill_fills_resolver.h"
|
||||
#include "reg_alloc_resolver.h"
|
||||
@ -135,12 +134,6 @@ bool RegAllocBase::Resolve()
|
||||
SpillFillsResolver(GetGraph()).Run();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (!GetGraph()->IsBytecodeOptimizer() && options.IsCompilerVerifyRegalloc() &&
|
||||
!GetGraph()->RunPass<RegAllocVerifier>()) {
|
||||
LOG(FATAL, COMPILER) << "Regalloc verification failed";
|
||||
}
|
||||
#endif // NDEBUG
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,565 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "reg_alloc_linear_scan.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/ir/basicblock.h"
|
||||
#include "optimizer/ir/datatype.h"
|
||||
#include "optimizer/ir/graph.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
static constexpr auto MAX_LIFE_NUMBER = std::numeric_limits<LifeNumber>::max();
|
||||
|
||||
/**
|
||||
* Add interval in sorted order
|
||||
*/
|
||||
static void AddInterval(LifeIntervals *interval, InstructionsIntervals *dest)
|
||||
{
|
||||
auto cmp = [](LifeIntervals *lhs, LifeIntervals *rhs) { return lhs->GetBegin() >= rhs->GetBegin(); };
|
||||
|
||||
if (dest->empty()) {
|
||||
dest->push_back(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = dest->end();
|
||||
--iter;
|
||||
while (true) {
|
||||
if (cmp(interval, *iter)) {
|
||||
dest->insert(++iter, interval);
|
||||
break;
|
||||
}
|
||||
if (iter == dest->begin()) {
|
||||
dest->push_front(interval);
|
||||
break;
|
||||
}
|
||||
--iter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use graph's registers masks and `MAX_NUM_STACK_SLOTS` stack slots
|
||||
*/
|
||||
RegAllocLinearScan::RegAllocLinearScan(Graph *graph)
|
||||
: RegAllocBase(graph),
|
||||
working_intervals_(graph->GetLocalAllocator()),
|
||||
regs_use_positions_(graph->GetLocalAllocator()->Adapter()),
|
||||
general_intervals_(graph->GetLocalAllocator()),
|
||||
vector_intervals_(graph->GetLocalAllocator()),
|
||||
reg_map_(graph->GetLocalAllocator()),
|
||||
remat_constants_(!graph->IsBytecodeOptimizer() && options.IsCompilerRematConst())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Use dynamic general registers mask (without vector regs) and zero stack slots
|
||||
*/
|
||||
RegAllocLinearScan::RegAllocLinearScan(Graph *graph, [[maybe_unused]] EmptyRegMask mask)
|
||||
: RegAllocBase(graph, VIRTUAL_FRAME_SIZE),
|
||||
working_intervals_(graph->GetLocalAllocator()),
|
||||
regs_use_positions_(graph->GetLocalAllocator()->Adapter()),
|
||||
general_intervals_(graph->GetLocalAllocator()),
|
||||
vector_intervals_(graph->GetLocalAllocator()),
|
||||
reg_map_(graph->GetLocalAllocator()),
|
||||
remat_constants_(!graph->IsBytecodeOptimizer() && options.IsCompilerRematConst())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate registers
|
||||
*/
|
||||
bool RegAllocLinearScan::Allocate()
|
||||
{
|
||||
AssignLocations<false>();
|
||||
AssignLocations<true>();
|
||||
return success_;
|
||||
}
|
||||
|
||||
void RegAllocLinearScan::InitIntervals()
|
||||
{
|
||||
GetIntervals<false>().fixed.resize(MAX_NUM_REGS);
|
||||
GetIntervals<true>().fixed.resize(MAX_NUM_VREGS);
|
||||
ReserveTempRegisters();
|
||||
}
|
||||
|
||||
void RegAllocLinearScan::PrepareInterval(LifeIntervals *interval)
|
||||
{
|
||||
bool is_fp = DataType::IsFloatType(interval->GetType());
|
||||
auto &intervals = is_fp ? GetIntervals<true>() : GetIntervals<false>();
|
||||
|
||||
if (interval->IsPhysical()) {
|
||||
ASSERT(intervals.fixed[interval->GetReg()] == nullptr);
|
||||
intervals.fixed[interval->GetReg()] = interval;
|
||||
return;
|
||||
}
|
||||
|
||||
if (interval->NoDest() || interval->GetInst()->GetDstCount() > 1U || interval->GetReg() == ACC_REG_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interval->IsPreassigned() && interval->GetReg() == GetGraph()->GetZeroReg()) {
|
||||
ASSERT(interval->GetReg() != INVALID_REG);
|
||||
return;
|
||||
}
|
||||
|
||||
AddInterval(interval, &intervals.regular);
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::AssignLocations()
|
||||
{
|
||||
auto ®ular_intervals = GetIntervals<is_fp>().regular;
|
||||
if (regular_intervals.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-magic-numbers,readability-braces-around-statements,bugprone-suspicious-semicolon)
|
||||
if constexpr (is_fp) {
|
||||
GetGraph()->SetHasFloatRegs();
|
||||
}
|
||||
|
||||
working_intervals_.Clear();
|
||||
auto arch = GetGraph()->GetArch();
|
||||
size_t priority = arch != Arch::NONE ? GetFirstCalleeReg(arch, is_fp) : 0;
|
||||
reg_map_.SetMask(GetLocationMask<is_fp>(), priority);
|
||||
regs_use_positions_.resize(reg_map_.GetAvailableRegsCount());
|
||||
|
||||
AddFixedIntervalsToWorkingIntervals<is_fp>();
|
||||
PreprocessPreassignedIntervals<is_fp>();
|
||||
while (!regular_intervals.empty() && success_) {
|
||||
ExpireIntervals<is_fp>(regular_intervals.front()->GetBegin());
|
||||
WalkIntervals<is_fp>();
|
||||
}
|
||||
RemapRegistersIntervals();
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::PreprocessPreassignedIntervals()
|
||||
{
|
||||
for (auto &interval : GetIntervals<is_fp>().regular) {
|
||||
if (!interval->IsPreassigned() || interval->IsSplitSibling() || interval->GetReg() == ACC_REG_ID) {
|
||||
continue;
|
||||
}
|
||||
interval->SetPreassignedReg(reg_map_.CodegenToRegallocReg(interval->GetReg()));
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Preassigned interval " << interval->template ToString<true>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free registers from expired intervals
|
||||
*/
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::ExpireIntervals(LifeNumber current_position)
|
||||
{
|
||||
IterateIntervalsWithErasion(working_intervals_.active, [this, current_position](const auto &interval) {
|
||||
if (!interval->HasReg() || interval->GetEnd() <= current_position) {
|
||||
working_intervals_.handled.push_back(interval);
|
||||
return true;
|
||||
}
|
||||
if (!interval->SplitCover(current_position)) {
|
||||
AddInterval(interval, &working_intervals_.inactive);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
IterateIntervalsWithErasion(working_intervals_.inactive, [this, current_position](const auto &interval) {
|
||||
if (!interval->HasReg() || interval->GetEnd() <= current_position) {
|
||||
working_intervals_.handled.push_back(interval);
|
||||
return true;
|
||||
}
|
||||
if (interval->SplitCover(current_position)) {
|
||||
AddInterval(interval, &working_intervals_.active);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
IterateIntervalsWithErasion(working_intervals_.stack, [this, current_position](const auto &interval) {
|
||||
if (interval->GetEnd() <= current_position) {
|
||||
GetStackMask().Reset(interval->GetLocation().GetValue());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Working intervals processing
|
||||
*/
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::WalkIntervals()
|
||||
{
|
||||
auto current_interval = GetIntervals<is_fp>().regular.front();
|
||||
GetIntervals<is_fp>().regular.pop_front();
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "----------------";
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Process interval " << current_interval->template ToString<true>();
|
||||
|
||||
// Parameter that was passed in the stack slot: split its interval before first use
|
||||
if (current_interval->GetLocation().IsStackParameter()) {
|
||||
ASSERT(current_interval->GetInst()->IsParameter());
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Interval was defined in the stack parameter slot";
|
||||
if (GetGraph()->IsDynamicMethod()) {
|
||||
AssignStackSlot(current_interval);
|
||||
}
|
||||
auto next_use = current_interval->GetNextUsage(current_interval->GetBegin() + 1U);
|
||||
SplitBeforeUse<is_fp>(current_interval, next_use);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_interval->HasReg()) {
|
||||
if (TryToAssignRegister<is_fp>(current_interval)) {
|
||||
COMPILER_LOG(DEBUG, REGALLOC)
|
||||
<< current_interval->GetLocation().ToString(GetGraph()->GetArch()) << " was assigned to the interval "
|
||||
<< current_interval->template ToString<true>();
|
||||
} else {
|
||||
COMPILER_LOG(ERROR, REGALLOC) << "There are no available registers";
|
||||
success_ = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ASSERT(current_interval->IsPreassigned());
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Interval has preassigned "
|
||||
<< current_interval->GetLocation().ToString(GetGraph()->GetArch());
|
||||
if (!IsIntervalRegFree(current_interval, current_interval->GetReg())) {
|
||||
SplitAndSpill<is_fp>(&working_intervals_.active, current_interval);
|
||||
SplitAndSpill<is_fp>(&working_intervals_.inactive, current_interval);
|
||||
}
|
||||
}
|
||||
HandleFixedIntervalIntersection<is_fp>(current_interval);
|
||||
AddInterval(current_interval, &working_intervals_.active);
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
bool RegAllocLinearScan::TryToAssignRegister(LifeIntervals *current_interval)
|
||||
{
|
||||
auto reg = GetSuitableRegister(current_interval);
|
||||
if (reg != INVALID_REG) {
|
||||
current_interval->SetReg(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to assign blocked register
|
||||
auto [blocked_reg, next_blocked_use] = GetBlockedRegister(current_interval);
|
||||
auto next_use = current_interval->GetNextUsage(current_interval->GetBegin());
|
||||
|
||||
// Spill current interval if its first use later than use of blocked register
|
||||
if (blocked_reg != INVALID_REG && next_blocked_use < next_use) {
|
||||
SplitBeforeUse<is_fp>(current_interval, next_use);
|
||||
AssignStackSlot(current_interval);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocked register that will be used in the next position mustn't be reassigned
|
||||
if (blocked_reg == INVALID_REG || next_blocked_use < current_interval->GetBegin() + LIFE_NUMBER_GAP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_interval->SetReg(blocked_reg);
|
||||
SplitAndSpill<is_fp>(&working_intervals_.active, current_interval);
|
||||
SplitAndSpill<is_fp>(&working_intervals_.inactive, current_interval);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::SplitAndSpill(const InstructionsIntervals *intervals, const LifeIntervals *current_interval)
|
||||
{
|
||||
for (auto interval : *intervals) {
|
||||
if (interval->GetReg() != current_interval->GetReg() ||
|
||||
interval->GetFirstIntersectionWith(current_interval) == INVALID_LIFE_NUMBER) {
|
||||
continue;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Found active interval: " << interval->template ToString<true>();
|
||||
SplitActiveInterval<is_fp>(interval, current_interval->GetBegin());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split interval with assigned 'reg' into 3 parts:
|
||||
* [interval|split|split_next]
|
||||
*
|
||||
* 'interval' - holds assigned register;
|
||||
* 'split' - stack slot is assigned to it;
|
||||
* 'split_next' - added to the queue for future assignment;
|
||||
*/
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::SplitActiveInterval(LifeIntervals *interval, LifeNumber split_pos)
|
||||
{
|
||||
auto prev_use_pos = interval->GetPrevUsage(split_pos);
|
||||
auto next_use_pos = interval->GetNextUsage(split_pos + 1U);
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Prev use position: " << std::to_string(prev_use_pos)
|
||||
<< ", Next use position: " << std::to_string(next_use_pos);
|
||||
auto split = interval;
|
||||
if (prev_use_pos == INVALID_LIFE_NUMBER) {
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Spill the whole interval " << interval->template ToString<true>();
|
||||
interval->ClearLocation();
|
||||
} else {
|
||||
auto split_position = (split_pos % 2U == 1) ? split_pos : split_pos - 1;
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Split interval " << interval->template ToString<true>() << " at position "
|
||||
<< static_cast<int>(split_position);
|
||||
|
||||
split = interval->SplitAt(split_position, GetGraph()->GetAllocator());
|
||||
}
|
||||
SplitBeforeUse<is_fp>(split, next_use_pos);
|
||||
AssignStackSlot(split);
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::AddToQueue(LifeIntervals *interval)
|
||||
{
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Add to the queue: " << interval->template ToString<true>();
|
||||
AddInterval(interval, &GetIntervals<is_fp>().regular);
|
||||
}
|
||||
|
||||
Register RegAllocLinearScan::GetSuitableRegister(const LifeIntervals *current_interval)
|
||||
{
|
||||
// First of all, try to assign register using hint
|
||||
auto &use_table = GetGraph()->GetAnalysis<LivenessAnalyzer>().GetUseTable();
|
||||
auto hint_reg = use_table.GetNextUseOnFixedLocation(current_interval->GetInst(), current_interval->GetBegin());
|
||||
if (hint_reg != INVALID_REG) {
|
||||
auto reg = reg_map_.CodegenToRegallocReg(hint_reg);
|
||||
if (reg_map_.IsRegAvailable(reg, GetGraph()->GetArch()) && IsIntervalRegFree(current_interval, reg)) {
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Hint-register is available";
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
// If hint doesn't exist or hint-register not available, try to assign any free register
|
||||
return GetFreeRegister(current_interval);
|
||||
}
|
||||
|
||||
Register RegAllocLinearScan::GetFreeRegister(const LifeIntervals *current_interval)
|
||||
{
|
||||
std::fill(regs_use_positions_.begin(), regs_use_positions_.end(), MAX_LIFE_NUMBER);
|
||||
|
||||
auto set_fixed_usage = [this, ¤t_interval](const auto &interval, LifeNumber intersection) {
|
||||
// If intersection is equal to the current_interval's begin
|
||||
// than it means that current_interval is a call and fixed interval's range was
|
||||
// created for it.
|
||||
// Do not disable fixed register for a call.
|
||||
if (intersection == current_interval->GetBegin()) {
|
||||
return;
|
||||
}
|
||||
regs_use_positions_[interval->GetReg()] = intersection;
|
||||
};
|
||||
auto set_inactive_usage = [this](const auto &interval, LifeNumber intersection) {
|
||||
auto ®_use = regs_use_positions_[interval->GetReg()];
|
||||
reg_use = std::min<size_t>(intersection, reg_use);
|
||||
};
|
||||
|
||||
EnumerateIntersectedIntervals(working_intervals_.fixed, current_interval, set_fixed_usage);
|
||||
EnumerateIntersectedIntervals(working_intervals_.inactive, current_interval, set_inactive_usage);
|
||||
EnumerateIntervals(working_intervals_.active,
|
||||
[this](const auto &interval) { regs_use_positions_[interval->GetReg()] = 0; });
|
||||
|
||||
// Select register with max position
|
||||
auto it = std::max_element(regs_use_positions_.cbegin(), regs_use_positions_.cend());
|
||||
// Register is free if it's available for the whole interval
|
||||
return (*it >= current_interval->GetEnd()) ? std::distance(regs_use_positions_.cbegin(), it) : INVALID_REG;
|
||||
}
|
||||
|
||||
// Return blocked register and its next use position
|
||||
std::pair<Register, LifeNumber> RegAllocLinearScan::GetBlockedRegister(const LifeIntervals *current_interval)
|
||||
{
|
||||
// Using blocked registers is impossible in the `BytecodeOptimizer` mode
|
||||
if (GetGraph()->IsBytecodeOptimizer()) {
|
||||
return {INVALID_REG, INVALID_LIFE_NUMBER};
|
||||
}
|
||||
|
||||
std::fill(regs_use_positions_.begin(), regs_use_positions_.end(), MAX_LIFE_NUMBER);
|
||||
|
||||
auto set_fixed_usage = [this, current_interval](const auto &interval, LifeNumber intersection) {
|
||||
// If intersection is equal to the current_interval's begin
|
||||
// than it means that current_interval is a call and fixed interval's range was
|
||||
// created for it.
|
||||
// Do not disable fixed register for a call.
|
||||
if (intersection == current_interval->GetBegin()) {
|
||||
return;
|
||||
}
|
||||
regs_use_positions_[interval->GetReg()] = intersection;
|
||||
};
|
||||
auto set_inactive_usage = [this](const auto &interval, LifeNumber intersection) {
|
||||
auto ®_use = regs_use_positions_[interval->GetReg()];
|
||||
reg_use = std::min<size_t>(interval->GetNextUsage(intersection), reg_use);
|
||||
};
|
||||
|
||||
EnumerateIntersectedIntervals(working_intervals_.fixed, current_interval, set_fixed_usage);
|
||||
EnumerateIntersectedIntervals(working_intervals_.inactive, current_interval, set_inactive_usage);
|
||||
EnumerateIntervals(working_intervals_.active, [this, ¤t_interval](const auto &interval) {
|
||||
auto ®_use = regs_use_positions_[interval->GetReg()];
|
||||
reg_use = std::min<size_t>(interval->GetNextUsage(current_interval->GetBegin()), reg_use);
|
||||
});
|
||||
|
||||
// Two pseudo-users ot multi-output instruction must not have equal register
|
||||
auto current_inst = current_interval->GetInst();
|
||||
if (IsPseudoUserOfMultiOutput(current_inst) && IsPseudoUserOfMultiOutput(current_inst->GetPrev())) {
|
||||
auto prev_inst = current_inst->GetPrev();
|
||||
auto interval = GetGraph()->GetAnalysis<LivenessAnalyzer>().GetInstLifeIntervals(prev_inst);
|
||||
regs_use_positions_[interval->GetReg()] = 0;
|
||||
}
|
||||
// Select register with max position
|
||||
auto it = std::max_element(regs_use_positions_.cbegin(), regs_use_positions_.cend());
|
||||
auto reg = std::distance(regs_use_positions_.cbegin(), it);
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Selected blocked r" << static_cast<int>(reg) << " with use next use position "
|
||||
<< *it;
|
||||
return {reg, regs_use_positions_[reg]};
|
||||
}
|
||||
|
||||
bool RegAllocLinearScan::IsIntervalRegFree(const LifeIntervals *current_interval, Register reg) const
|
||||
{
|
||||
for (auto interval : working_intervals_.fixed) {
|
||||
if (interval == nullptr || interval->GetReg() != reg) {
|
||||
continue;
|
||||
}
|
||||
if (interval->GetFirstIntersectionWith(current_interval) < current_interval->GetBegin() + LIFE_NUMBER_GAP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto interval : working_intervals_.inactive) {
|
||||
if (interval->GetReg() == reg && interval->GetFirstIntersectionWith(current_interval) != INVALID_LIFE_NUMBER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto interval : working_intervals_.active) {
|
||||
if (interval->GetReg() == reg) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegAllocLinearScan::AssignStackSlot(LifeIntervals *interval)
|
||||
{
|
||||
ASSERT(!interval->GetLocation().IsStack());
|
||||
if (remat_constants_ && interval->GetInst()->IsConst()) {
|
||||
auto imm_slot = GetGraph()->AddSpilledConstant(interval->GetInst()->CastToConstant());
|
||||
if (imm_slot != INVALID_IMM_TABLE_SLOT) {
|
||||
interval->SetLocation(Location::MakeConstant(imm_slot));
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << interval->GetLocation().ToString(GetGraph()->GetArch())
|
||||
<< " was assigned to the interval " << interval->template ToString<true>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto slot = GetNextStackSlot(interval);
|
||||
if (slot != INVALID_STACK_SLOT) {
|
||||
interval->SetLocation(Location::MakeStackSlot(slot));
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << interval->GetLocation().ToString(GetGraph()->GetArch())
|
||||
<< " was assigned to the interval " << interval->template ToString<true>();
|
||||
working_intervals_.stack.push_back(interval);
|
||||
} else {
|
||||
COMPILER_LOG(ERROR, REGALLOC) << "There are no available stack slots";
|
||||
success_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegAllocLinearScan::RemapRegallocReg(LifeIntervals *interval)
|
||||
{
|
||||
if (interval->HasReg()) {
|
||||
auto reg = interval->GetReg();
|
||||
interval->SetReg(reg_map_.RegallocToCodegenReg(reg));
|
||||
}
|
||||
}
|
||||
|
||||
void RegAllocLinearScan::RemapRegistersIntervals()
|
||||
{
|
||||
for (auto interval : working_intervals_.handled) {
|
||||
RemapRegallocReg(interval);
|
||||
}
|
||||
for (auto interval : working_intervals_.active) {
|
||||
RemapRegallocReg(interval);
|
||||
}
|
||||
for (auto interval : working_intervals_.inactive) {
|
||||
RemapRegallocReg(interval);
|
||||
}
|
||||
for (auto interval : working_intervals_.fixed) {
|
||||
if (interval != nullptr) {
|
||||
RemapRegallocReg(interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::AddFixedIntervalsToWorkingIntervals()
|
||||
{
|
||||
working_intervals_.fixed.resize(GetLocationMask<is_fp>().GetSize());
|
||||
// remap registers for fixed intervals and add it to working intervals
|
||||
for (auto fixed_interval : GetIntervals<is_fp>().fixed) {
|
||||
if (fixed_interval == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto reg = reg_map_.CodegenToRegallocReg(fixed_interval->GetReg());
|
||||
fixed_interval->SetReg(reg);
|
||||
working_intervals_.fixed[reg] = fixed_interval;
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Fixed interval for r" << static_cast<int>(fixed_interval->GetReg()) << ": "
|
||||
<< fixed_interval->template ToString<true>();
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::HandleFixedIntervalIntersection(LifeIntervals *current_interval)
|
||||
{
|
||||
if (!current_interval->HasReg()) {
|
||||
return;
|
||||
}
|
||||
auto reg = current_interval->GetReg();
|
||||
if (reg >= working_intervals_.fixed.size() || working_intervals_.fixed[reg] == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto fixed_interval = working_intervals_.fixed[reg];
|
||||
auto intersection = current_interval->GetFirstIntersectionWith(fixed_interval);
|
||||
if (intersection == current_interval->GetBegin()) {
|
||||
// Current interval can intersect fixed interval at the beginning of its live range
|
||||
// only if it's a call and fixed interval's range was created for it.
|
||||
// Try to find first intersection excluding the range blocking registers during a call.
|
||||
intersection = current_interval->GetFirstIntersectionWith(fixed_interval, intersection + 1U);
|
||||
}
|
||||
if (intersection == INVALID_LIFE_NUMBER) {
|
||||
return;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Intersection with fixed interval at: " << std::to_string(intersection);
|
||||
|
||||
auto &use_table = GetGraph()->GetAnalysis<LivenessAnalyzer>().GetUseTable();
|
||||
if (use_table.HasUseOnFixedLocation(current_interval->GetInst(), intersection)) {
|
||||
// Instruction is used at intersection position: spilt before that use
|
||||
SplitBeforeUse<is_fp>(current_interval, intersection);
|
||||
return;
|
||||
}
|
||||
|
||||
auto last_use_before = current_interval->GetLastUsageBefore(intersection);
|
||||
if (last_use_before != INVALID_LIFE_NUMBER) {
|
||||
// Split after the last use before intesection
|
||||
SplitBeforeUse<is_fp>(current_interval, last_use_before + LIFE_NUMBER_GAP);
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no use before intersection, split after intesection add splitted-interval to the queue
|
||||
auto next_use = current_interval->GetNextUsage(intersection);
|
||||
current_interval->ClearLocation();
|
||||
SplitBeforeUse<is_fp>(current_interval, next_use);
|
||||
AssignStackSlot(current_interval);
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void RegAllocLinearScan::SplitBeforeUse(LifeIntervals *current_interval, LifeNumber use_pos)
|
||||
{
|
||||
if (use_pos == INVALID_LIFE_NUMBER) {
|
||||
return;
|
||||
}
|
||||
COMPILER_LOG(DEBUG, REGALLOC) << "Split at " << std::to_string(use_pos - 1);
|
||||
auto split = current_interval->SplitAt(use_pos - 1, GetGraph()->GetAllocator());
|
||||
AddToQueue<is_fp>(split);
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
@ -1,195 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_OPTIMIZER_OPTIMIZATIONS_REG_ALLOC_LINEAR_SCAN_H_
|
||||
#define COMPILER_OPTIMIZER_OPTIMIZATIONS_REG_ALLOC_LINEAR_SCAN_H_
|
||||
|
||||
#include "utils/arena_containers.h"
|
||||
#include "optimizer/analysis/liveness_analyzer.h"
|
||||
#include "optimizer/code_generator/registers_description.h"
|
||||
#include "reg_alloc_base.h"
|
||||
#include "reg_map.h"
|
||||
#include "compiler_logger.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class BasicBlock;
|
||||
class Graph;
|
||||
class LifeIntervals;
|
||||
|
||||
using InstructionsIntervals = ArenaList<LifeIntervals *>;
|
||||
using EmptyRegMask = std::bitset<0>;
|
||||
|
||||
/**
|
||||
* Algorithm is based on paper "Linear Scan Register Allocation" by Christian Wimmer
|
||||
*
|
||||
* LinearScan works with instructions lifetime intervals, computed by the LivenessAnalyzer.
|
||||
* It scans forward through the intervals, ordered by increasing starting point and assign registers while the number of
|
||||
* the active intervals at the point is less then the number of available registers. When there are no available
|
||||
* registers for some interval, LinearScan assigns one of the blocked ones. Previous holder of this blocked register is
|
||||
* spilled to the memory at this point. Blocked register to be assigned is selected according to the usage information,
|
||||
* LinearScan selects register with the most distant use point from the current one.
|
||||
*/
|
||||
class RegAllocLinearScan : public RegAllocBase {
|
||||
struct WorkingIntervals {
|
||||
explicit WorkingIntervals(ArenaAllocator *allocator)
|
||||
: active(allocator->Adapter()),
|
||||
inactive(allocator->Adapter()),
|
||||
stack(allocator->Adapter()),
|
||||
handled(allocator->Adapter()),
|
||||
fixed(allocator->Adapter())
|
||||
{
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
active.clear();
|
||||
inactive.clear();
|
||||
stack.clear();
|
||||
handled.clear();
|
||||
fixed.clear();
|
||||
}
|
||||
|
||||
InstructionsIntervals active; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
InstructionsIntervals inactive; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
InstructionsIntervals stack; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
InstructionsIntervals handled; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
ArenaVector<LifeIntervals *> fixed; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
};
|
||||
|
||||
struct PendingIntervals {
|
||||
explicit PendingIntervals(ArenaAllocator *allocator)
|
||||
: regular(allocator->Adapter()), fixed(allocator->Adapter())
|
||||
{
|
||||
}
|
||||
InstructionsIntervals regular; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
ArenaVector<LifeIntervals *> fixed; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
};
|
||||
|
||||
public:
|
||||
explicit RegAllocLinearScan(Graph *graph);
|
||||
RegAllocLinearScan(Graph *graph, [[maybe_unused]] EmptyRegMask mask);
|
||||
~RegAllocLinearScan() override = default;
|
||||
NO_MOVE_SEMANTIC(RegAllocLinearScan);
|
||||
NO_COPY_SEMANTIC(RegAllocLinearScan);
|
||||
|
||||
const char *GetPassName() const override
|
||||
{
|
||||
return "RegAllocLinearScan";
|
||||
}
|
||||
|
||||
bool AbortIfFailed() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Allocate() override;
|
||||
void InitIntervals() override;
|
||||
void PrepareInterval(LifeIntervals *interval) override;
|
||||
|
||||
private:
|
||||
template <bool is_fp>
|
||||
const LocationMask &GetLocationMask() const
|
||||
{
|
||||
return is_fp ? GetVRegMask() : GetRegMask();
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
PendingIntervals &GetIntervals()
|
||||
{
|
||||
return is_fp ? vector_intervals_ : general_intervals_;
|
||||
}
|
||||
|
||||
template <bool is_fp>
|
||||
void AssignLocations();
|
||||
template <bool is_fp>
|
||||
void PreprocessPreassignedIntervals();
|
||||
template <bool is_fp>
|
||||
void ExpireIntervals(LifeNumber current_position);
|
||||
template <bool is_fp>
|
||||
void WalkIntervals();
|
||||
template <bool is_fp>
|
||||
bool TryToAssignRegister(LifeIntervals *current_interval);
|
||||
template <bool is_fp>
|
||||
void SplitAndSpill(const InstructionsIntervals *intervals, const LifeIntervals *current_interval);
|
||||
template <bool is_fp>
|
||||
void SplitActiveInterval(LifeIntervals *interval, LifeNumber split_pos);
|
||||
template <bool is_fp>
|
||||
void AddToQueue(LifeIntervals *interval);
|
||||
template <bool is_fp>
|
||||
void SplitBeforeUse(LifeIntervals *current_interval, LifeNumber use_pos);
|
||||
bool IsIntervalRegFree(const LifeIntervals *current_interval, Register reg) const;
|
||||
void AssignStackSlot(LifeIntervals *interval);
|
||||
void RemapRegallocReg(LifeIntervals *interval);
|
||||
void RemapRegistersIntervals();
|
||||
template <bool is_fp>
|
||||
void AddFixedIntervalsToWorkingIntervals();
|
||||
template <bool is_fp>
|
||||
void HandleFixedIntervalIntersection(LifeIntervals *current_interval);
|
||||
|
||||
Register GetSuitableRegister(const LifeIntervals *current_interval);
|
||||
Register GetFreeRegister(const LifeIntervals *current_interval);
|
||||
std::pair<Register, LifeNumber> GetBlockedRegister(const LifeIntervals *current_interval);
|
||||
|
||||
template <class T, class Callback>
|
||||
void IterateIntervalsWithErasion(T &intervals, const Callback &callback) const
|
||||
{
|
||||
for (auto it = intervals.begin(); it != intervals.end();) {
|
||||
auto interval = *it;
|
||||
if (callback(interval)) {
|
||||
it = intervals.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class Callback>
|
||||
void EnumerateIntervals(const T &intervals, const Callback &callback) const
|
||||
{
|
||||
for (const auto &interval : intervals) {
|
||||
if (interval == nullptr || interval->GetReg() >= reg_map_.GetAvailableRegsCount()) {
|
||||
continue;
|
||||
}
|
||||
callback(interval);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class Callback>
|
||||
void EnumerateIntersectedIntervals(const T &intervals, const LifeIntervals *current, const Callback &callback) const
|
||||
{
|
||||
for (const auto &interval : intervals) {
|
||||
if (interval == nullptr || interval->GetReg() >= reg_map_.GetAvailableRegsCount()) {
|
||||
continue;
|
||||
}
|
||||
auto intersection = interval->GetFirstIntersectionWith(current);
|
||||
if (intersection != INVALID_LIFE_NUMBER) {
|
||||
callback(interval, intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
WorkingIntervals working_intervals_;
|
||||
ArenaVector<LifeNumber> regs_use_positions_;
|
||||
PendingIntervals general_intervals_;
|
||||
PendingIntervals vector_intervals_;
|
||||
RegisterMap reg_map_;
|
||||
bool remat_constants_;
|
||||
bool success_ {true};
|
||||
};
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_OPTIMIZATIONS_REG_ALLOC_LINEAR_SCAN_H_
|
@ -23,54 +23,6 @@
|
||||
namespace panda::compiler {
|
||||
class BasicBlock;
|
||||
|
||||
inline bool IsNotCommutativeInst(Inst *inst)
|
||||
{
|
||||
return !inst->IsCommutative() || DataType::IsFloatType(inst->GetType());
|
||||
}
|
||||
|
||||
static bool AddClassInst(Inst *inst, VnObject *obj)
|
||||
{
|
||||
if (inst->GetOpcode() != Opcode::LoadAndInitClass && inst->GetOpcode() != Opcode::InitClass &&
|
||||
inst->GetOpcode() != Opcode::LoadClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
obj->Add(static_cast<uint32_t>(Opcode::InitClass));
|
||||
auto class_inst = static_cast<ClassInst *>(inst);
|
||||
auto klass = class_inst->GetClass();
|
||||
if (klass == nullptr) {
|
||||
obj->Add(class_inst->GetTypeId());
|
||||
} else {
|
||||
obj->Add(reinterpret_cast<uint64_t>(klass));
|
||||
}
|
||||
inst->SetVnObject(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AddCommutativeInst(Inst *inst, VnObject *obj)
|
||||
{
|
||||
if (IsNotCommutativeInst(inst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[maybe_unused]] constexpr auto INPUTS_NUM = 2;
|
||||
ASSERT(inst->GetInputsCount() == INPUTS_NUM);
|
||||
auto input0 = inst->GetDataFlowInput(inst->GetInput(0).GetInst());
|
||||
auto input1 = inst->GetDataFlowInput(inst->GetInput(1).GetInst());
|
||||
ASSERT(input0->GetVN() != INVALID_VN);
|
||||
ASSERT(input1->GetVN() != INVALID_VN);
|
||||
if (input0->GetId() > input1->GetId()) {
|
||||
obj->Add(input0->GetVN());
|
||||
obj->Add(input1->GetVN());
|
||||
} else {
|
||||
obj->Add(input1->GetVN());
|
||||
obj->Add(input0->GetVN());
|
||||
}
|
||||
|
||||
inst->SetVnObject(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void AddSpecialTraits(Inst *inst, VnObject *obj)
|
||||
{
|
||||
switch (inst->GetOpcode()) {
|
||||
@ -91,27 +43,13 @@ static void AddSpecialTraits(Inst *inst, VnObject *obj)
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsIrreducibleClassInst(Inst *inst, Inst *equiv_inst)
|
||||
{
|
||||
return equiv_inst->GetOpcode() == Opcode::LoadClass &&
|
||||
(inst->GetOpcode() == Opcode::InitClass || inst->GetOpcode() == Opcode::LoadAndInitClass);
|
||||
}
|
||||
|
||||
void VnObject::Add(Inst *inst)
|
||||
{
|
||||
if (AddClassInst(inst, this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Add(static_cast<uint32_t>(inst->GetOpcode()));
|
||||
Add(static_cast<uint32_t>(inst->GetType()));
|
||||
|
||||
AddSpecialTraits(inst, this);
|
||||
|
||||
if (AddCommutativeInst(inst, this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto input : inst->GetInputs()) {
|
||||
auto input_inst = inst->GetDataFlowInput(input.GetInst());
|
||||
auto vn = input_inst->GetVN();
|
||||
@ -173,22 +111,12 @@ bool ValNum::TryToApplyCse(Inst *inst, InstVector *equiv_insts)
|
||||
auto block = inst->GetBasicBlock();
|
||||
for (auto equiv_inst : *equiv_insts) {
|
||||
COMPILER_LOG(DEBUG, VN_OPT) << " Equivalent instructions are found, id " << equiv_inst->GetId();
|
||||
if ((block == equiv_inst->GetBasicBlock() ||
|
||||
(equiv_inst->IsDominate(inst) && !HasOsrEntryBetween(equiv_inst, inst))) &&
|
||||
!IsIrreducibleClassInst(inst, equiv_inst)) {
|
||||
if (block == equiv_inst->GetBasicBlock() ||
|
||||
(equiv_inst->IsDominate(inst) && !HasOsrEntryBetween(equiv_inst, inst))) {
|
||||
COMPILER_LOG(DEBUG, VN_OPT) << " CSE is applied for inst with id " << inst->GetId();
|
||||
GetGraph()->GetEventWriter().EventGvn(inst->GetId(), inst->GetPc(), equiv_inst->GetId(),
|
||||
equiv_inst->GetPc());
|
||||
inst->ReplaceUsers(equiv_inst);
|
||||
// TODO (ekudriashov): need more detailed analysis with Unresolved cases
|
||||
if (equiv_inst->GetOpcode() == Opcode::InitClass &&
|
||||
(inst->GetOpcode() == Opcode::LoadClass || inst->GetOpcode() == Opcode::LoadAndInitClass)) {
|
||||
equiv_inst->SetOpcode(Opcode::LoadAndInitClass);
|
||||
equiv_inst->SetType(DataType::REFERENCE);
|
||||
}
|
||||
|
||||
// IsInstance, InitClass, LoadClass and LoadAndInitClass have attribute NO_DCE, so they can't be removed by
|
||||
// DCE pass But we can remove the instructions after VN because there is dominate equal instruction
|
||||
inst->ClearFlag(compiler::inst_flags::NO_DCE);
|
||||
cse_is_appied_ = true;
|
||||
return true;
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/ir/graph_checker.h"
|
||||
#include "optimizer/ir/visualizer_printer.h"
|
||||
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
@ -29,9 +28,7 @@
|
||||
#include "optimizer/analysis/liveness_analyzer.h"
|
||||
#include "optimizer/analysis/live_registers.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/analysis/monitor_analysis.h"
|
||||
#include "optimizer/analysis/object_type_propagation.h"
|
||||
#include "optimizer/analysis/reg_alloc_verifier.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/analysis/types_analysis.h"
|
||||
#include "optimizer/optimizations/cleanup.h"
|
||||
@ -126,29 +123,6 @@ void PassManager::DumpLifeIntervals([[maybe_unused]] const char *pass_name)
|
||||
GetGraph()->GetAnalysis<LivenessAnalyzer>().DumpLifeIntervals(strm);
|
||||
#endif // ENABLE_IR_DUMP && !PANDA_TARGET_MACOS
|
||||
}
|
||||
void PassManager::InitialDumpVisualizerGraph()
|
||||
{
|
||||
#if defined(ENABLE_IR_DUMP) && !defined(PANDA_TARGET_MACOS)
|
||||
std::ofstream strm(GetFileName());
|
||||
strm << "begin_compilation\n";
|
||||
strm << " name \"" << GetGraph()->GetRuntime()->GetClassNameFromMethod(GetGraph()->GetMethod()) << "_"
|
||||
<< GetGraph()->GetRuntime()->GetMethodName(GetGraph()->GetMethod()) << "\"\n";
|
||||
strm << " method \"" << GetGraph()->GetRuntime()->GetClassNameFromMethod(GetGraph()->GetMethod()) << "_"
|
||||
<< GetGraph()->GetRuntime()->GetMethodName(GetGraph()->GetMethod()) << "\"\n";
|
||||
strm << " date " << std::time(nullptr) << "\n";
|
||||
strm << "end_compilation\n";
|
||||
strm.close();
|
||||
#endif // ENABLE_IR_DUMP && !PANDA_TARGET_MACOS
|
||||
}
|
||||
|
||||
void PassManager::DumpVisualizerGraph([[maybe_unused]] const char *pass_name)
|
||||
{
|
||||
#if defined(ENABLE_IR_DUMP) && !defined(PANDA_TARGET_MACOS)
|
||||
std::ofstream strm(GetFileName(), std::ios::app);
|
||||
VisualizerPrinter(GetGraph(), &strm, pass_name).Print();
|
||||
strm.close();
|
||||
#endif // ENABLE_IR_DUMP && !PANDA_TARGET_MACOS
|
||||
}
|
||||
|
||||
bool PassManager::RunPass(Pass *pass, size_t local_mem_size_before_pass)
|
||||
{
|
||||
@ -197,10 +171,6 @@ bool PassManager::RunPass(Pass *pass, size_t local_mem_size_before_pass)
|
||||
}
|
||||
}
|
||||
|
||||
if (options.IsCompilerVisualizerDump() && pass->ShouldDump()) {
|
||||
DumpVisualizerGraph(pass->GetPassName());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool checker_enabled = options.IsCompilerCheckGraph();
|
||||
if (options.IsCompilerCheckFinal()) {
|
||||
|
@ -35,10 +35,8 @@ class DominatorsTree;
|
||||
class Rpo;
|
||||
class LinearOrder;
|
||||
class BoundsAnalysis;
|
||||
class MonitorAnalysis;
|
||||
// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
|
||||
class ObjectTypePropagation;
|
||||
class RegAllocVerifier;
|
||||
class TypesAnalysis;
|
||||
|
||||
namespace details {
|
||||
@ -78,7 +76,7 @@ public:
|
||||
|
||||
using PredefinedAnalyses =
|
||||
PassTypeList<LivenessAnalyzer, LoopAnalyzer, AliasAnalysis, DominatorsTree, Rpo, LinearOrder, BoundsAnalysis,
|
||||
MonitorAnalysis, LiveRegisters, ObjectTypePropagation, RegAllocVerifier, TypesAnalysis>;
|
||||
LiveRegisters, ObjectTypePropagation, TypesAnalysis>;
|
||||
} // namespace details
|
||||
|
||||
class PassManager {
|
||||
@ -122,8 +120,6 @@ public:
|
||||
std::string GetFileName(const char *pass_name = nullptr, const std::string &suffix = ".cfg");
|
||||
void DumpGraph(const char *pass_name);
|
||||
void DumpLifeIntervals(const char *pass_name);
|
||||
void InitialDumpVisualizerGraph();
|
||||
void DumpVisualizerGraph(const char *pass_name);
|
||||
|
||||
Graph *GetGraph()
|
||||
{
|
||||
|
@ -201,50 +201,6 @@ int64_t InstBuilder::GetInstructionJumpOffset(const BytecodeInstruction* inst) {
|
||||
return INVALID_OFFSET;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-size)
|
||||
bool IrBuilderInliningAnalysis::IsSuitableForInline(const BytecodeInstruction* inst) {
|
||||
switch(inst->GetOpcode()) {
|
||||
% runtime_insts = [/^(div|mod)u?[2i]?/, /^call/, /^(f?)(ld|st|len|new)(arr|obj)/, /throw/, /^initobj/, /^checkcast/, /^ldstatic/, /^ststatic/, /^lda.type/, /^lda.str/, /^lda.const/, /^isinstance/, /^builtin/, /^monitor/]
|
||||
% Panda::instructions.each do |inst|
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>:
|
||||
% if runtime_insts.any? { |x| x.match?(inst.mnemonic) }
|
||||
has_runtime_calls_ = true;
|
||||
return !options.IsCompilerInlineSimpleOnly();
|
||||
% else
|
||||
return true;
|
||||
% end
|
||||
% end
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-size)
|
||||
bool IrBuilderExternalInliningAnalysis::IsSuitableForInline(const BytecodeInstruction* inst) {
|
||||
switch(inst->GetOpcode()) {
|
||||
% acceptable_insts = [/^(f?)mov/, /^(f?)lda/, /^(f?)sta/, /^return/, /^(f?)add/, /^(f?)sub/, /^(f?)mul/, /^(x?)or/, /^and/, /^(f?)neg/, /^not/, /^(a?)shr/, /^shl/, /^inc/, /^(u|f)?cmp/, /^[ifu][813264]{1,2}to[ifu][813264]{1,2}$/]
|
||||
% object_insts = [/^ldobj/, /^stobj/]
|
||||
% Panda::instructions.each do |inst|
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>:
|
||||
% if acceptable_insts.any? { |x| x.match?(inst.mnemonic) }
|
||||
% if runtime_insts.any? { |x| x.match?(inst.mnemonic) }
|
||||
return false;
|
||||
% else
|
||||
return true;
|
||||
% end
|
||||
% elsif object_insts.any? { |x| x.match?(inst.mnemonic) }
|
||||
return true;
|
||||
% elsif inst.jump?
|
||||
return true;
|
||||
% else
|
||||
return false;
|
||||
% end
|
||||
% end
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(vpukhov): Move this logic from core into plugins/ecmascript
|
||||
// Currently we support two strategies for building IR from ecma.* instructions:
|
||||
// 1) Each ecma.* instruction is translated to a corresponding intrinsic call.
|
||||
@ -267,11 +223,7 @@ void InstBuilder::BuildEcma([[maybe_unused]] const BytecodeInstruction* bc_inst)
|
||||
if (GetGraph()->IsBytecodeOptimizer()) {
|
||||
BuildEcmaAsIntrinsics(bc_inst);
|
||||
} else {
|
||||
if (options.IsCompilerInlineFullIntrinsics()) {
|
||||
BuildEcmaFromIrtoc(bc_inst);
|
||||
} else {
|
||||
BuildEcmaAsIntrinsics<true>(bc_inst);
|
||||
}
|
||||
BuildEcmaAsIntrinsics<true>(bc_inst);
|
||||
}
|
||||
% elsif inst.compilable?
|
||||
// +compilable, -inlinable: ecma.* -> intrinsics for all scenarios:
|
||||
@ -462,73 +414,4 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-size)
|
||||
void InstBuilder::BuildEcmaFromIrtoc([[maybe_unused]] const BytecodeInstruction* bc_inst)
|
||||
{
|
||||
#ifdef ENABLE_BYTECODE_OPT
|
||||
ASSERT(!GetGraph()->IsBytecodeOptimizer()); // Not applicable for optimizing bytecode
|
||||
switch (bc_inst->GetOpcode()) {
|
||||
% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
|
||||
% format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase
|
||||
% vreg_index = 0
|
||||
% imm_index = 0
|
||||
% id_index = 0
|
||||
% inputs = []
|
||||
%
|
||||
% # Corner case, no inlinable IRtoC handler found:
|
||||
% unless inst.inlinable?
|
||||
case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
|
||||
UNREACHABLE(); // Inlinable IRtoC handler is not implemented yet
|
||||
break;
|
||||
}
|
||||
% next
|
||||
% end
|
||||
%
|
||||
% # Generate code for inlining IRtoC handler:
|
||||
case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
|
||||
% inst.operands.each do |param|
|
||||
% if param.reg?
|
||||
auto vreg<%= vreg_index %> = GetDefinition(bc_inst->GetVReg<<%= format %>, <%= vreg_index %>>());
|
||||
% inputs.push("vreg#{vreg_index}")
|
||||
% vreg_index = vreg_index + 1
|
||||
% elsif param.imm?
|
||||
auto imm<%= imm_index %>_payload = static_cast<uint32_t>(bc_inst->GetImm<<%= format %>, <%= imm_index %>>());
|
||||
auto imm<%= imm_index %> = GetGraph()->FindOrCreateConstant(imm<%= imm_index %>_payload);
|
||||
% inputs.push("imm#{imm_index}")
|
||||
% imm_index = imm_index + 1
|
||||
% elsif param.id?
|
||||
auto id<%= id_index %>_payload = static_cast<uint32_t>(bc_inst->template GetId<<%= format %>>().AsIndex());
|
||||
auto id<%= id_index %> = GetGraph()->FindOrCreateConstant(id<%= id_index %>_payload);
|
||||
% inputs.push("id#{id_index}")
|
||||
% id_index = id_index + 1
|
||||
% else
|
||||
% abort 'Unexpected param type'
|
||||
% end
|
||||
% end
|
||||
% if inst.acc.include?('in')
|
||||
auto acc = GetDefinitionAcc(); // According to ecma.* convention, always goes last
|
||||
% inputs.push('acc')
|
||||
% end
|
||||
% if inst.properties.include?('use_ic')
|
||||
% inputs.push('GetGraph()->FindOrCreateConstant(GetPc(bc_inst->GetAddress()))')
|
||||
% end
|
||||
% builder = 'Build' + inst.opcode.split('_')[0..1].map do |_| _.capitalize end.join()
|
||||
[[maybe_unused]] auto inst = <%= builder %>(GetGraph(), this, ¤t_bb_, <%= inputs.empty? ? '' : inputs.join(', ') + ', ' %>GetPc(bc_inst->GetAddress()), visited_block_marker_);
|
||||
% if inst.acc.include?('out')
|
||||
UpdateDefinitionAcc(inst);
|
||||
% end
|
||||
SyncWithGraph();
|
||||
break;
|
||||
}
|
||||
% end
|
||||
default: {
|
||||
failed_ = true;
|
||||
LOG(ERROR, COMPILER) << "Unknown ecma.* opcode: " << static_cast<int>(bc_inst->GetOpcode());
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
@ -14,84 +14,3 @@
|
||||
*/
|
||||
|
||||
// Autogenerated file -- DO NOT EDIT!
|
||||
|
||||
inline bool NeedSafePointAfterIntrinsic(RuntimeInterface::IntrinsicId intrinsic) {
|
||||
switch (intrinsic) // NOLINT(hicpp-multiway-paths-covered)
|
||||
{
|
||||
% Compiler::intrinsics.select {|intrinsic| intrinsic.safepoint_after_call}.each do |intrinsic|
|
||||
case panda::compiler::RuntimeInterface::IntrinsicId::<%= intrinsic.entrypoint_name %>:
|
||||
return true;
|
||||
% end
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_virtual>
|
||||
void InstBuilder::AddArgNullcheckIfNeeded([[maybe_unused]] RuntimeInterface::IntrinsicId intrinsic,
|
||||
[[maybe_unused]] Inst *inst, [[maybe_unused]] Inst *save_state,
|
||||
[[maybe_unused]] size_t bc_addr)
|
||||
{
|
||||
static_assert(is_virtual, "It is not implemented for static call");
|
||||
% if Compiler::intrinsics.any? {|intrinsic| !intrinsic.need_nullcheck.empty?}
|
||||
constexpr int arg_offset = is_virtual ? 1 : 0;
|
||||
// NOLINTNEXTLINE(hicpp-multiway-paths-covered)
|
||||
switch (intrinsic)
|
||||
{
|
||||
% Compiler::intrinsics.select {|intrinsic| !intrinsic.need_nullcheck.empty?}.each do |intrinsic|
|
||||
case panda::compiler::RuntimeInterface::IntrinsicId::<%= intrinsic.entrypoint_name %>:
|
||||
% intrinsic.need_nullcheck.each do |arg_num|
|
||||
{
|
||||
auto null_check = graph_->CreateInstNullCheck(DataType::REFERENCE, bc_addr);
|
||||
null_check->SetInput(0, inst->GetInput(arg_offset + <%= arg_num %>).GetInst());
|
||||
null_check->SetInput(1, save_state);
|
||||
inst->SetInput(arg_offset + <%= arg_num %>, null_check);
|
||||
AddInstruction(null_check);
|
||||
}
|
||||
% end
|
||||
break;
|
||||
% end
|
||||
default:
|
||||
break;
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-definitions-in-headers)
|
||||
inline bool IsVirtual([[maybe_unused]] RuntimeInterface::IntrinsicId intrinsic)
|
||||
{
|
||||
% if Compiler::intrinsics.any? {|intrinsic| !intrinsic.static}
|
||||
switch (intrinsic)
|
||||
{
|
||||
% Compiler::intrinsics.select {|intrinsic| intrinsic.has_impl? && !intrinsic.static}.each do |intrinsic|
|
||||
case panda::compiler::RuntimeInterface::IntrinsicId::<%= intrinsic.entrypoint_name %>:
|
||||
return true;
|
||||
% end
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
% else
|
||||
return false;
|
||||
% end
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-definitions-in-headers)
|
||||
void InstBuilder::BuildVirtualCallIntrinsic([[maybe_unused]] const BytecodeInstruction *bc_inst,
|
||||
[[maybe_unused]] bool is_range, [[maybe_unused]] bool acc_read)
|
||||
{
|
||||
% if Compiler::ext_intrinsic_spaces.any?
|
||||
auto method_index = bc_inst->GetId(0).AsIndex();
|
||||
auto method_id = GetRuntime()->ResolveMethodIndex(GetMethod(), method_index);
|
||||
auto method = GetRuntime()->GetMethodById(GetMethod(), method_id);
|
||||
auto intrinsic_id = GetRuntime()->GetIntrinsicId(method);
|
||||
// NOLINTNEXTLINE(hicpp-multiway-paths-covered)
|
||||
switch (intrinsic_id) {
|
||||
#include "intrinsics_ir_build_virtual_call.inl"
|
||||
default: {
|
||||
% end
|
||||
return BuildDefaultVirtualCallIntrinsic(bc_inst, is_range, acc_read); // not supported case
|
||||
% if Compiler::ext_intrinsic_spaces.any?
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
@ -17,9 +17,10 @@
|
||||
#define COMPILER_OPTIMIZER_TEMPLATES_IR_DYN_BASE_TYPES_H
|
||||
|
||||
#include "compiler/optimizer/ir/datatype.h"
|
||||
#include "compiler/optimizer/code_generator/encode.h"
|
||||
#include "source_languages.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
% Common::plugins.each_value do |plugin_opts|
|
||||
% next unless plugin_opts["compiler_base_types"]
|
||||
% next unless plugin_opts["compiler_base_types"]["header_path_implementation_codegen"]
|
||||
@ -33,13 +34,6 @@
|
||||
% end
|
||||
|
||||
namespace panda::compiler {
|
||||
|
||||
class CompareAnyTypeInst;
|
||||
class CastAnyTypeValueInst;
|
||||
class CastValueToAnyTypeInst;
|
||||
class AnyTypeCheckInst;
|
||||
class EncodeVisitor;
|
||||
|
||||
inline AnyBaseType NumericDataTypeToAnyType([[maybe_unused]] panda::compiler::DataType::Type type,
|
||||
panda::compiler::SourceLanguage language) {
|
||||
ASSERT(type == panda::compiler::DataType::Type::UINT8 || type == panda::compiler::DataType::Type::INT8 ||
|
||||
@ -155,63 +149,6 @@ inline const char *AnyTypeTypeToString(AnyBaseType any_type)
|
||||
return ANYBASETYPE_NAMES[idx];
|
||||
}
|
||||
|
||||
inline bool TryCompareAnyTypePluginGen([[maybe_unused]] const panda::compiler::CompareAnyTypeInst *cati,
|
||||
[[maybe_unused]] panda::compiler::EncodeVisitor *enc) {
|
||||
|
||||
% Common::plugins.each_value do |plugin_opts|
|
||||
% next unless plugin_opts["compiler_base_types"]
|
||||
% next unless plugin_opts["compiler_base_types"]["func_compare_implementation_codegen"]
|
||||
if (<%= plugin_opts["compiler_base_types"]["func_compare_implementation_codegen"] %>(cati, enc)) {
|
||||
return true; // NOLINT(readability-simplify-boolean-expr)
|
||||
}
|
||||
% end
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TryCastAnyTypeValuePluginGen([[maybe_unused]] const panda::compiler::CastAnyTypeValueInst *cati,
|
||||
[[maybe_unused]] panda::compiler::EncodeVisitor *enc) {
|
||||
|
||||
% Common::plugins.each_value do |plugin_opts|
|
||||
% next unless plugin_opts["compiler_base_types"]
|
||||
% next unless plugin_opts["compiler_base_types"]["func_cast_implementation_codegen"]
|
||||
if (<%= plugin_opts["compiler_base_types"]["func_cast_implementation_codegen"] %>(cati, enc)) {
|
||||
return true; // NOLINT(readability-simplify-boolean-expr)
|
||||
}
|
||||
% end
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TryCastValueToAnyTypePluginGen([[maybe_unused]] const panda::compiler::CastValueToAnyTypeInst *cvai,
|
||||
[[maybe_unused]] panda::compiler::EncodeVisitor *enc) {
|
||||
|
||||
% Common::plugins.each_value do |plugin_opts|
|
||||
% next unless plugin_opts["compiler_base_types"]
|
||||
% next unless plugin_opts["compiler_base_types"]["func_cast_to_any_implementation_codegen"]
|
||||
if (<%= plugin_opts["compiler_base_types"]["func_cast_to_any_implementation_codegen"] %>(cvai, enc)) {
|
||||
return true; // NOLINT(readability-simplify-boolean-expr)
|
||||
}
|
||||
% end
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TryAnyTypeCheckPluginGen([[maybe_unused]] const panda::compiler::AnyTypeCheckInst *check_inst,
|
||||
[[maybe_unused]] panda::compiler::EncodeVisitor *enc,
|
||||
[[maybe_unused]] LabelHolder::LabelId id) {
|
||||
|
||||
% Common::plugins.each_value do |plugin_opts|
|
||||
% next unless plugin_opts["compiler_base_types"]
|
||||
% next unless plugin_opts["compiler_base_types"]["func_any_type_check_implementation_codegen"]
|
||||
if (<%= plugin_opts["compiler_base_types"]["func_any_type_check_implementation_codegen"] %>(check_inst, enc, id)) {
|
||||
return true; // NOLINT(readability-simplify-boolean-expr)
|
||||
}
|
||||
% end
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
||||
|
||||
#endif // COMPILER_OPTIMIZER_TEMPLATES_IR_DYN_BASE_TYPES_H
|
||||
|
@ -24,37 +24,28 @@
|
||||
#include "optimizer/code_generator/method_properties.h"
|
||||
#include "optimizer/ir/graph.h"
|
||||
#include "optimizer/ir/graph_checker.h"
|
||||
#include "optimizer/ir/visualizer_printer.h"
|
||||
#include "optimizer/analysis/alias_analysis.h"
|
||||
#include "optimizer/analysis/bounds_analysis.h"
|
||||
#include "optimizer/analysis/dominators_tree.h"
|
||||
#include "optimizer/analysis/linear_order.h"
|
||||
#include "optimizer/analysis/loop_analyzer.h"
|
||||
#include "optimizer/analysis/monitor_analysis.h"
|
||||
#include "optimizer/analysis/rpo.h"
|
||||
#include "optimizer/optimizations/balance_expressions.h"
|
||||
#include "optimizer/optimizations/branch_elimination.h"
|
||||
#include "optimizer/optimizations/checks_elimination.h"
|
||||
#include "optimizer/optimizations/code_sink.h"
|
||||
#include "optimizer/optimizations/deoptimize_elimination.h"
|
||||
#include "optimizer/optimizations/cleanup.h"
|
||||
#include "optimizer/optimizations/if_conversion.h"
|
||||
#include "optimizer/optimizations/inlining.h"
|
||||
#include "optimizer/optimizations/licm.h"
|
||||
#include "optimizer/optimizations/loop_peeling.h"
|
||||
#include "optimizer/optimizations/loop_unroll.h"
|
||||
#include "optimizer/optimizations/lowering.h"
|
||||
#include "optimizer/optimizations/lse.h"
|
||||
#include "optimizer/optimizations/memory_barriers.h"
|
||||
#include "optimizer/optimizations/memory_coalescing.h"
|
||||
#include "optimizer/optimizations/peepholes.h"
|
||||
#include "optimizer/optimizations/redundant_loop_elimination.h"
|
||||
#include "optimizer/optimizations/regalloc/reg_alloc.h"
|
||||
#include "optimizer/optimizations/scheduler.h"
|
||||
#include "optimizer/optimizations/try_catch_resolving.h"
|
||||
#include "optimizer/optimizations/types_resolving.h"
|
||||
#include "optimizer/optimizations/vn.h"
|
||||
#include "optimizer/optimizations/cse.h"
|
||||
#include "optimizer/optimizations/move_constants.h"
|
||||
#include "optimizer/optimizations/adjust_arefs.h"
|
||||
|
||||
@ -70,11 +61,6 @@ static inline bool RunCodegenPass(Graph *graph)
|
||||
|
||||
bool RunOptimizations(Graph *graph)
|
||||
{
|
||||
#if !defined(NDEBUG) && !defined(PANDA_TARGET_MOBILE)
|
||||
if (options.IsCompilerVisualizerDump()) {
|
||||
graph->GetPassManager()->InitialDumpVisualizerGraph();
|
||||
}
|
||||
#endif // NDEBUG && PANDA_TARGET_MOBILE
|
||||
auto finalizer = [graph](void * /* unused */) { graph->GetPassManager()->Finalize(); };
|
||||
std::unique_ptr<void, decltype(finalizer)> pp(&finalizer, finalizer);
|
||||
|
||||
@ -96,10 +82,6 @@ bool RunOptimizations(Graph *graph)
|
||||
graph->RunPass<Inlining>();
|
||||
}
|
||||
graph->RunPass<TryCatchResolving>();
|
||||
if (!graph->RunPass<MonitorAnalysis>()) {
|
||||
LOG(WARNING, COMPILER) << "Compiler detected incorrect monitor policy";
|
||||
return false;
|
||||
}
|
||||
graph->RunPass<Peepholes>();
|
||||
graph->RunPass<BranchElimination>();
|
||||
graph->RunPass<ValNum>();
|
||||
@ -122,7 +104,6 @@ bool RunOptimizations(Graph *graph)
|
||||
if (graph->IsAotMode()) {
|
||||
graph->RunPass<Cse>();
|
||||
}
|
||||
graph->RunPass<ChecksElimination>();
|
||||
graph->RunPass<LoopUnroll>(options.GetCompilerLoopUnrollInstLimit(), options.GetCompilerLoopUnrollFactor());
|
||||
graph->RunPass<BalanceExpressions>();
|
||||
if (graph->RunPass<Peepholes>()) {
|
||||
@ -155,10 +136,6 @@ bool RunOptimizations(Graph *graph)
|
||||
// catch-handlers; After supporting catch-handlers' compilation, this pass can be run in the optimizing mode
|
||||
// only.
|
||||
graph->RunPass<TryCatchResolving>();
|
||||
if (!graph->RunPass<MonitorAnalysis>()) {
|
||||
LOG(WARNING, COMPILER) << "Compiler detected incorrect monitor policy";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool fatal_on_err = !options.IsCompilerAllowBackendFailures();
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,728 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "unit_test.h"
|
||||
#include "optimizer/optimizations/cse.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class CSETest : public GraphTest {
|
||||
public:
|
||||
CSETest() : is_cse_enable_default_(options.IsCompilerCse())
|
||||
{
|
||||
options.SetCompilerCse(true);
|
||||
}
|
||||
|
||||
~CSETest()
|
||||
{
|
||||
options.SetCompilerCse(is_cse_enable_default_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_cse_enable_default_;
|
||||
};
|
||||
|
||||
TEST_F(CSETest, CSETestApply1)
|
||||
{
|
||||
// Remove duplicate arithmetic instructions in one basicblock
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(6, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(7, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(10, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(11, Opcode::Div).f64().Inputs(3, 2);
|
||||
INST(12, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(13, Opcode::Add).u64().Inputs(0, 1);
|
||||
|
||||
INST(14, Opcode::Mod).u64().Inputs(0, 1);
|
||||
INST(15, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(16, Opcode::Max).u64().Inputs(0, 1);
|
||||
INST(17, Opcode::Shl).u64().Inputs(0, 1);
|
||||
INST(18, Opcode::Shr).u64().Inputs(0, 1);
|
||||
INST(19, Opcode::AShr).u64().Inputs(0, 1);
|
||||
INST(20, Opcode::And).b().Inputs(0, 1);
|
||||
INST(21, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(22, Opcode::Xor).b().Inputs(0, 1);
|
||||
|
||||
INST(23, Opcode::Mod).u64().Inputs(0, 1);
|
||||
INST(24, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(25, Opcode::Max).u64().Inputs(0, 1);
|
||||
INST(26, Opcode::Shl).u64().Inputs(0, 1);
|
||||
INST(27, Opcode::Shr).u64().Inputs(0, 1);
|
||||
INST(28, Opcode::AShr).u64().Inputs(0, 1);
|
||||
INST(29, Opcode::And).b().Inputs(0, 1);
|
||||
INST(30, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(31, Opcode::Xor).b().Inputs(0, 1);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(32, Opcode::CallStatic)
|
||||
.b()
|
||||
.InputsAutoType(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
|
||||
29, 30, 31, 35);
|
||||
INST(33, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// delete insts 10, 11, 12, 13, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(6, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(7, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(14, Opcode::Mod).u64().Inputs(0, 1);
|
||||
INST(15, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(16, Opcode::Max).u64().Inputs(0, 1);
|
||||
INST(17, Opcode::Shl).u64().Inputs(0, 1);
|
||||
INST(18, Opcode::Shr).u64().Inputs(0, 1);
|
||||
INST(19, Opcode::AShr).u64().Inputs(0, 1);
|
||||
INST(20, Opcode::And).b().Inputs(0, 1);
|
||||
INST(21, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(22, Opcode::Xor).b().Inputs(0, 1);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(32, Opcode::CallStatic)
|
||||
.b()
|
||||
.InputsAutoType(6, 7, 8, 9, 7, 9, 8, 6, 14, 15, 16, 17, 18, 19, 20, 21, 22, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 35);
|
||||
INST(33, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<Cse>();
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestApply2)
|
||||
{
|
||||
// Remove duplicate arithmetic instructions along dominator tree
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Add).s32().Inputs(0, 1);
|
||||
INST(7, Opcode::Sub).s32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::Div).f64().Inputs(3, 2);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::CallStatic).b().InputsAutoType(6, 7, 8, 9, 35);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(12, Opcode::Add).s32().Inputs(0, 1);
|
||||
INST(13, Opcode::Add).s32().Inputs(12, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(14, Opcode::Sub).s32().Inputs(1, 0);
|
||||
INST(15, Opcode::Sub).s32().Inputs(14, 1);
|
||||
INST(16, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(17, Opcode::Mul).f32().Inputs(16, 5);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(18, Opcode::Div).f64().Inputs(3, 2);
|
||||
INST(19, Opcode::Div).f64().Inputs(18, 3);
|
||||
INST(20, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Insts 12, 14, 16, 18
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Add).s32().Inputs(0, 1);
|
||||
INST(7, Opcode::Sub).s32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::Div).f64().Inputs(3, 2);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::CallStatic).b().InputsAutoType(6, 7, 8, 9, 35);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(13, Opcode::Add).s32().Inputs(6, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(15, Opcode::Sub).s32().Inputs(7, 1);
|
||||
INST(17, Opcode::Mul).f32().Inputs(8, 5);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(19, Opcode::Div).f64().Inputs(9, 3);
|
||||
INST(20, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Cse>());
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestApply3)
|
||||
{
|
||||
// use Phi to replace the arithmetic instruction which has appeared in the two preds of its block.
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::UINT64).Inputs(0, 1);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(6);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(8, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(9, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(10, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(11, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(22, Opcode::Add).u64().Inputs(8, 1);
|
||||
INST(23, Opcode::Sub).u32().Inputs(9, 0);
|
||||
INST(24, Opcode::Mul).f32().Inputs(10, 5);
|
||||
INST(25, Opcode::Div).f64().Inputs(11, 2);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(12, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(13, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(14, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(15, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(26, Opcode::Add).u64().Inputs(12, 1);
|
||||
INST(27, Opcode::Sub).u32().Inputs(13, 0);
|
||||
INST(28, Opcode::Mul).f32().Inputs(14, 5);
|
||||
INST(29, Opcode::Div).f64().Inputs(15, 2);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(16, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(17, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(18, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(19, Opcode::Div).f64().Inputs(3, 2);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(16, 17, 18, 19, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Phi 30, 31, 32, 33; Delete Inst 16, 17, 18, 19
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::UINT64).Inputs(0, 1);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(6);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(8, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(9, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(10, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(11, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(22, Opcode::Add).u64().Inputs(8, 1);
|
||||
INST(23, Opcode::Sub).u32().Inputs(9, 0);
|
||||
INST(24, Opcode::Mul).f32().Inputs(10, 5);
|
||||
INST(25, Opcode::Div).f64().Inputs(11, 2);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(12, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(13, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(14, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(15, Opcode::Div).f64().Inputs(3, 2);
|
||||
|
||||
INST(26, Opcode::Add).u64().Inputs(12, 1);
|
||||
INST(27, Opcode::Sub).u32().Inputs(13, 0);
|
||||
INST(28, Opcode::Mul).f32().Inputs(14, 5);
|
||||
INST(29, Opcode::Div).f64().Inputs(15, 2);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(30, Opcode::Phi).u64().Inputs({{3, 8}, {4, 12}});
|
||||
INST(31, Opcode::Phi).u32().Inputs({{3, 9}, {4, 13}});
|
||||
INST(32, Opcode::Phi).f32().Inputs({{3, 10}, {4, 14}});
|
||||
INST(33, Opcode::Phi).f64().Inputs({{3, 11}, {4, 15}});
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(30, 31, 32, 33, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Cse>());
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestApply4)
|
||||
{
|
||||
// Remove duplicate arithmetic instructions in one basicblock (commutative)
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(6, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(7, Opcode::Mul).f32().Inputs(4, 5);
|
||||
|
||||
INST(8, Opcode::Mul).f32().Inputs(5, 4);
|
||||
INST(9, Opcode::Add).u64().Inputs(1, 0);
|
||||
|
||||
INST(10, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(11, Opcode::Max).u64().Inputs(0, 1);
|
||||
INST(12, Opcode::And).b().Inputs(0, 1);
|
||||
INST(13, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(14, Opcode::Xor).b().Inputs(0, 1);
|
||||
|
||||
INST(15, Opcode::Min).u64().Inputs(1, 0);
|
||||
INST(16, Opcode::Max).u64().Inputs(1, 0);
|
||||
INST(17, Opcode::And).b().Inputs(1, 0);
|
||||
INST(18, Opcode::Or).b().Inputs(1, 0);
|
||||
INST(19, Opcode::Xor).b().Inputs(1, 0);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// delete insts 8, 9, 15, 16, 17, 18, 19
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(6, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(7, Opcode::Mul).f32().Inputs(4, 5);
|
||||
|
||||
INST(10, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(11, Opcode::Max).u64().Inputs(0, 1);
|
||||
INST(12, Opcode::And).b().Inputs(0, 1);
|
||||
INST(13, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(14, Opcode::Xor).b().Inputs(0, 1);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(6, 7, 7, 6, 10, 11, 12, 13, 14, 10, 11, 12, 13, 14, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<Cse>();
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestApply5)
|
||||
{
|
||||
// Remove duplicate arithmetic instructions along dominator tree(commutative)
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Add).s32().Inputs(0, 1);
|
||||
INST(7, Opcode::Max).s32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::And).b().Inputs(0, 1);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::CallStatic).b().InputsAutoType(6, 7, 8, 9, 35);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(12, Opcode::Add).s32().Inputs(1, 0);
|
||||
INST(13, Opcode::Add).s32().Inputs(12, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(14, Opcode::Max).s32().Inputs(0, 1);
|
||||
INST(15, Opcode::Sub).s32().Inputs(14, 1);
|
||||
INST(16, Opcode::Mul).f32().Inputs(5, 4);
|
||||
INST(17, Opcode::Mul).f32().Inputs(16, 5);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(18, Opcode::And).b().Inputs(1, 0);
|
||||
INST(19, Opcode::Xor).b().Inputs(18, 1);
|
||||
INST(20, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Insts 12, 14, 16, 18
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Add).s32().Inputs(0, 1);
|
||||
INST(7, Opcode::Max).s32().Inputs(1, 0);
|
||||
INST(8, Opcode::Mul).f32().Inputs(4, 5);
|
||||
INST(9, Opcode::And).b().Inputs(0, 1);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(21, Opcode::CallStatic).b().InputsAutoType(6, 7, 8, 9, 35);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(13, Opcode::Add).s32().Inputs(6, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(15, Opcode::Sub).s32().Inputs(7, 1);
|
||||
INST(17, Opcode::Mul).f32().Inputs(8, 5);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(19, Opcode::Xor).b().Inputs(9, 1);
|
||||
INST(20, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Cse>());
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestApply6)
|
||||
{
|
||||
// use Phi to replace the arithmetic instruction which has appeared in the two preds of its block (commutative).
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).u64();
|
||||
PARAMETER(3, 3).u64();
|
||||
PARAMETER(4, 4).b();
|
||||
PARAMETER(5, 5).b();
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::UINT64).Inputs(0, 1);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(6);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(8, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(9, Opcode::Min).u64().Inputs(1, 0);
|
||||
INST(10, Opcode::Xor).b().Inputs(4, 5);
|
||||
INST(11, Opcode::Max).u64().Inputs(3, 2);
|
||||
|
||||
INST(22, Opcode::Or).b().Inputs(8, 1);
|
||||
INST(23, Opcode::Min).u64().Inputs(9, 0);
|
||||
INST(24, Opcode::Xor).b().Inputs(10, 5);
|
||||
INST(25, Opcode::Max).u64().Inputs(11, 2);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(12, Opcode::Or).b().Inputs(1, 0);
|
||||
INST(13, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(14, Opcode::Xor).b().Inputs(5, 4);
|
||||
INST(15, Opcode::Max).u64().Inputs(2, 3);
|
||||
|
||||
INST(26, Opcode::Or).b().Inputs(12, 1);
|
||||
INST(27, Opcode::Min).u64().Inputs(13, 0);
|
||||
INST(28, Opcode::Xor).b().Inputs(14, 5);
|
||||
INST(29, Opcode::Max).u64().Inputs(15, 2);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(16, Opcode::Or).b().Inputs(1, 0);
|
||||
INST(17, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(18, Opcode::Xor).b().Inputs(5, 4);
|
||||
INST(19, Opcode::Max).u64().Inputs(2, 3);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(16, 17, 18, 19, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Phi 30, 31, 32, 33; Delete Inst 16, 17, 18, 19
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).u64();
|
||||
PARAMETER(3, 3).u64();
|
||||
PARAMETER(4, 4).b();
|
||||
PARAMETER(5, 5).b();
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::UINT64).Inputs(0, 1);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(6);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(8, Opcode::Or).b().Inputs(0, 1);
|
||||
INST(9, Opcode::Min).u64().Inputs(1, 0);
|
||||
INST(10, Opcode::Xor).b().Inputs(4, 5);
|
||||
INST(11, Opcode::Max).u64().Inputs(3, 2);
|
||||
|
||||
INST(22, Opcode::Or).b().Inputs(8, 1);
|
||||
INST(23, Opcode::Min).u64().Inputs(9, 0);
|
||||
INST(24, Opcode::Xor).b().Inputs(10, 5);
|
||||
INST(25, Opcode::Max).u64().Inputs(11, 2);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(12, Opcode::Or).b().Inputs(1, 0);
|
||||
INST(13, Opcode::Min).u64().Inputs(0, 1);
|
||||
INST(14, Opcode::Xor).b().Inputs(5, 4);
|
||||
INST(15, Opcode::Max).u64().Inputs(2, 3);
|
||||
|
||||
INST(26, Opcode::Or).b().Inputs(12, 1);
|
||||
INST(27, Opcode::Min).u64().Inputs(13, 0);
|
||||
INST(28, Opcode::Xor).b().Inputs(14, 5);
|
||||
INST(29, Opcode::Max).u64().Inputs(15, 2);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(30, Opcode::Phi).b().Inputs({{3, 8}, {4, 12}});
|
||||
INST(31, Opcode::Phi).u64().Inputs({{3, 9}, {4, 13}});
|
||||
INST(32, Opcode::Phi).b().Inputs({{3, 10}, {4, 14}});
|
||||
INST(33, Opcode::Phi).u64().Inputs({{3, 11}, {4, 15}});
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(20, Opcode::CallStatic).b().InputsAutoType(30, 31, 32, 33, 35);
|
||||
INST(21, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Cse>());
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestNotApply1)
|
||||
{
|
||||
// Instructions has different type, inputs, opcodes
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
PARAMETER(6, 6).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(7, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(8, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(9, Opcode::Mul).f32().Inputs(4, 5);
|
||||
|
||||
INST(10, Opcode::Sub).u64().Inputs(0, 1); // different opcode
|
||||
INST(11, Opcode::Sub).u64().Inputs(1, 0); // different type
|
||||
INST(12, Opcode::Mul).f32().Inputs(4, 6); // different inputs
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(13, Opcode::CallStatic).b().InputsAutoType(7, 8, 9, 10, 11, 12, 35);
|
||||
INST(14, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
// graph does not change
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).f64();
|
||||
PARAMETER(3, 3).f64();
|
||||
PARAMETER(4, 4).f32();
|
||||
PARAMETER(5, 5).f32();
|
||||
PARAMETER(6, 6).f32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(7, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(8, Opcode::Sub).u32().Inputs(1, 0);
|
||||
INST(9, Opcode::Mul).f32().Inputs(4, 5);
|
||||
|
||||
INST(10, Opcode::Sub).u64().Inputs(0, 1);
|
||||
INST(11, Opcode::Sub).u64().Inputs(1, 0);
|
||||
INST(12, Opcode::Mul).f32().Inputs(4, 6);
|
||||
INST(35, Opcode::SaveState).NoVregs();
|
||||
INST(13, Opcode::CallStatic).b().InputsAutoType(7, 8, 9, 10, 11, 12, 35);
|
||||
INST(14, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_FALSE(GetGraph()->RunPass<Cse>());
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
|
||||
TEST_F(CSETest, CSETestNotApply2)
|
||||
{
|
||||
// Instructions in different blocks between which there is no dominating relationship.
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).s32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(3, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(4, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_GE).Imm(0).Inputs(3);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(5, Opcode::Add).s32().Inputs(1, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 5, 6)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(1, 0);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_GE).Imm(0).Inputs(6);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(6, 5)
|
||||
{
|
||||
INST(8, Opcode::Add).s32().Inputs(1, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(9, Opcode::Phi).s32().Inputs({{4, 5}, {3, 2}, {6, 8}});
|
||||
INST(10, Opcode::Return).s32().Inputs(9);
|
||||
}
|
||||
}
|
||||
|
||||
// Graph does not change
|
||||
Graph *graph_csed = CreateEmptyGraph();
|
||||
GRAPH(graph_csed)
|
||||
{
|
||||
PARAMETER(0, 0).s32();
|
||||
PARAMETER(1, 1).s32();
|
||||
PARAMETER(2, 2).s32();
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(3, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(0, 1);
|
||||
INST(4, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_GE).Imm(0).Inputs(3);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(5, Opcode::Add).s32().Inputs(1, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 5, 6)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().SrcType(DataType::Type::INT32).Inputs(1, 0);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_GE).Imm(0).Inputs(6);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(6, 5)
|
||||
{
|
||||
INST(8, Opcode::Add).s32().Inputs(1, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(9, Opcode::Phi).s32().Inputs({{4, 5}, {3, 2}, {6, 8}});
|
||||
INST(10, Opcode::Return).s32().Inputs(9);
|
||||
}
|
||||
}
|
||||
|
||||
GraphChecker(GetGraph()).Check();
|
||||
ASSERT_FALSE(GetGraph()->RunPass<Cse>());
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph_csed));
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,550 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "optimizer/optimizations/inlining.h"
|
||||
#include "unit_test.h"
|
||||
#include "utils/logger.h"
|
||||
#include "compiler_logger.h"
|
||||
#include "events/events.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class InliningTest : public AsmTest {
|
||||
public:
|
||||
InliningTest()
|
||||
{
|
||||
options.SetCompilerInlining(true);
|
||||
}
|
||||
};
|
||||
|
||||
struct ScopeEvents {
|
||||
ScopeEvents()
|
||||
{
|
||||
Events::Create<Events::MEMORY>();
|
||||
}
|
||||
~ScopeEvents()
|
||||
{
|
||||
Events::Destroy();
|
||||
}
|
||||
};
|
||||
|
||||
auto PopulateInstructionMap(Graph *graph)
|
||||
{
|
||||
ArenaUnorderedMap<Opcode, ArenaVector<Inst *>> res(graph->GetLocalAllocator()->Adapter());
|
||||
for (auto bb : *graph) {
|
||||
for (auto inst : bb->Insts()) {
|
||||
auto it = res.try_emplace(inst->GetOpcode(), graph->GetLocalAllocator()->Adapter());
|
||||
it.first->second.push_back(inst);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, Simple)
|
||||
{
|
||||
auto source = R"(
|
||||
.function u1 main(u32 a0, u32 a1){
|
||||
movi v0, 1
|
||||
movi v1, 2
|
||||
or a0, v1
|
||||
lda a0
|
||||
add a1, v0
|
||||
lda a1
|
||||
call.short inline, a0, a1
|
||||
return
|
||||
}
|
||||
|
||||
.function u32 inline(u32 a0, u32 a1){
|
||||
movi v2, 2023
|
||||
movi v3, 2
|
||||
add a0, v2
|
||||
sta v4
|
||||
sub a1, v3
|
||||
mul2 v4
|
||||
return
|
||||
}
|
||||
)";
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 1U);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, TwoMethods)
|
||||
{
|
||||
auto source = R"(
|
||||
.function u1 main(u32 a0, u32 a1){
|
||||
movi v0, 1
|
||||
movi v1, 2
|
||||
or a0, v1
|
||||
lda a0
|
||||
add a1, v0
|
||||
lda a1
|
||||
call.short inline1, a0, a1
|
||||
sta v2
|
||||
call.short inline2, v2, a1
|
||||
return
|
||||
}
|
||||
|
||||
.function u32 inline1(u32 a0, u32 a1){
|
||||
movi v2, 2023
|
||||
movi v3, 2
|
||||
add a0, v2
|
||||
sta v4
|
||||
sub a1, v3
|
||||
mul2 v4
|
||||
return
|
||||
}
|
||||
|
||||
.function u32 inline2(u32 a0, u32 a1){
|
||||
movi v2, 2023
|
||||
movi v3, 2
|
||||
add a0, v2
|
||||
sta v4
|
||||
sub a1, v3
|
||||
mul2 v4
|
||||
return
|
||||
}
|
||||
)";
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 2U);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, MultipleReturns)
|
||||
{
|
||||
auto source = R"(
|
||||
.function f64 main(f64 a0, f64 a1){
|
||||
call.short inline, a0, a1
|
||||
return.64
|
||||
}
|
||||
|
||||
.function f64 inline(f64 a0){
|
||||
fldai.64 0.0
|
||||
fcmpl.64 a0
|
||||
jlez label
|
||||
lda.64 a0
|
||||
fneg.64
|
||||
return.64
|
||||
label:
|
||||
lda.64 a0
|
||||
return.64
|
||||
})";
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 1U);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, WithCalls)
|
||||
{
|
||||
auto source = R"(
|
||||
.function u32 main(u32 a0, u32 a1){
|
||||
sub a0, a1
|
||||
sta a0
|
||||
call.short inline, a0, a0
|
||||
addi 1
|
||||
return
|
||||
}
|
||||
.function u32 inline(u32 a0){
|
||||
lda a0
|
||||
not
|
||||
jlez label
|
||||
sta a0
|
||||
call.short fn, a0, a0
|
||||
return
|
||||
label:
|
||||
lda a0
|
||||
return
|
||||
}
|
||||
.function u32 fn(u32 a0){
|
||||
ldai 14
|
||||
sub2 a0
|
||||
return
|
||||
}
|
||||
)";
|
||||
options.SetCompilerInlineSimpleOnly(false);
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 2U);
|
||||
auto inst_map = PopulateInstructionMap(GetGraph());
|
||||
ASSERT_EQ(inst_map.at(Opcode::ReturnInlined).size(), 1U);
|
||||
auto &call_insts = inst_map.at(Opcode::CallStatic);
|
||||
ASSERT_EQ(call_insts.size(), 1U);
|
||||
ASSERT_TRUE(static_cast<CallInst *>(call_insts[0])->IsInlined());
|
||||
ASSERT_TRUE(inst_map.at(Opcode::ReturnInlined)[0]->GetInput(0).GetInst()->GetOpcode() == Opcode::SaveState);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, WithLoop)
|
||||
{
|
||||
auto source = R"(
|
||||
.function u32 main(u32 a0, u32 a1){
|
||||
sub a0, a1
|
||||
sta a0
|
||||
call.short inline, a0, a0
|
||||
addi 1
|
||||
return
|
||||
}
|
||||
.function u32 inline(u32 a0){
|
||||
lda a0
|
||||
not
|
||||
loop:
|
||||
jlez exit
|
||||
subi 1
|
||||
jmp loop
|
||||
exit:
|
||||
return
|
||||
}
|
||||
)";
|
||||
options.SetCompilerInlineSimpleOnly(false);
|
||||
auto use_safepoint_global = options.IsCompilerUseSafepoint();
|
||||
options.SetCompilerUseSafepoint(true);
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_FALSE(GetGraph()->HasLoop());
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 1U);
|
||||
auto inst_map = PopulateInstructionMap(GetGraph());
|
||||
ASSERT_EQ(inst_map.at(Opcode::ReturnInlined).size(), 1U);
|
||||
ASSERT_EQ(inst_map.at(Opcode::CallStatic).size(), 1U);
|
||||
ASSERT_TRUE(GetGraph()->RunPass<LoopAnalyzer>());
|
||||
ASSERT_TRUE(GetGraph()->HasLoop());
|
||||
options.SetCompilerUseSafepoint(use_safepoint_global);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, WithChecks)
|
||||
{
|
||||
auto source = R"(
|
||||
.function u32 main(u32 a0, u32 a1){
|
||||
sub a0, a1
|
||||
sta a0
|
||||
call.short inline, a0, a0
|
||||
addi 1
|
||||
return
|
||||
}
|
||||
.function u32 inline(u32 a0){
|
||||
ldai 123
|
||||
div2 a0
|
||||
return
|
||||
}
|
||||
)";
|
||||
options.SetCompilerInlineSimpleOnly(false);
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
ASSERT_EQ(GetGraph()->GetPassManager()->GetStatistics()->GetInlinedMethods(), 1U);
|
||||
auto inst_map = PopulateInstructionMap(GetGraph());
|
||||
ASSERT_EQ(inst_map.at(Opcode::ReturnInlined).size(), 1U);
|
||||
ASSERT_EQ(inst_map.at(Opcode::CallStatic).size(), 1U);
|
||||
ASSERT_TRUE(static_cast<CallInst *>(inst_map.at(Opcode::CallStatic)[0])->IsInlined());
|
||||
ASSERT_TRUE(inst_map.at(Opcode::ReturnInlined)[0]->GetInput(0).GetInst()->GetOpcode() == Opcode::SaveState);
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, InliningMethodWithDivu2)
|
||||
{
|
||||
auto source = R"(.function u1 main() {
|
||||
ldai.64 0
|
||||
movi.64 v0, 0x8000000000000000
|
||||
divu2.64 v0
|
||||
movi.64 v0, 0
|
||||
ucmp.64 v0
|
||||
# check positive
|
||||
movi v0, 0 # expected result
|
||||
jne v0, exit_failure
|
||||
ldai 0 # passed
|
||||
return
|
||||
exit_failure:
|
||||
ldai 1 # failed
|
||||
return
|
||||
}
|
||||
#
|
||||
.function u1 main_exitcode_wrapper() {
|
||||
call main
|
||||
jeqz wrapper_exit_positive
|
||||
ldai 81
|
||||
return
|
||||
wrapper_exit_positive:
|
||||
ldai 80
|
||||
return
|
||||
}
|
||||
)";
|
||||
ASSERT_TRUE(ParseToGraph(source, "main_exitcode_wrapper"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto inst_map = PopulateInstructionMap(GetGraph());
|
||||
ASSERT_EQ(inst_map.at(Opcode::ReturnInlined).size(), 1U);
|
||||
auto &call_insts = inst_map.at(Opcode::CallStatic);
|
||||
ASSERT_EQ(call_insts.size(), 1U);
|
||||
ASSERT_TRUE(static_cast<CallInst *>(call_insts[0])->IsInlined());
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, InlineVoidFunc)
|
||||
{
|
||||
auto source = R"(.function u1 main() {
|
||||
initobj.short Test.ctor
|
||||
sta.obj v0
|
||||
movi v1, 1
|
||||
movi v2, 2
|
||||
call.range run, v0
|
||||
return.void
|
||||
}
|
||||
.record Test {
|
||||
i32 res
|
||||
}
|
||||
|
||||
.function void run(Test a0, i32 a1, i32 a2) {
|
||||
mov v0, a1
|
||||
lda v0
|
||||
jltz label_0
|
||||
mov.obj v0, a0
|
||||
mov.obj v1, v0
|
||||
ldobj v1, Test.res
|
||||
sta v1
|
||||
mov v2, a1
|
||||
mov v3, a2
|
||||
add v3, v2
|
||||
sta v2
|
||||
add v2, v1
|
||||
sta v1
|
||||
lda v1
|
||||
stobj v0, Test.res
|
||||
return.void
|
||||
label_0: return.void
|
||||
}
|
||||
|
||||
.function void Test.ctor(Test a0) <ctor> {
|
||||
mov.obj v0, a0
|
||||
movi v1, 5
|
||||
lda v1
|
||||
stobj v0, Test.res
|
||||
return.void
|
||||
}
|
||||
)";
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto inst_map = PopulateInstructionMap(GetGraph());
|
||||
ASSERT_EQ(inst_map.at(Opcode::ReturnInlined).size(), 2U);
|
||||
auto &call_insts = inst_map.at(Opcode::CallStatic);
|
||||
ASSERT_EQ(call_insts.size(), 2U);
|
||||
ASSERT_TRUE(static_cast<CallInst *>(call_insts[0])->IsInlined());
|
||||
ASSERT_TRUE(static_cast<CallInst *>(call_insts[1])->IsInlined());
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, VirtualProvenByNewobj)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.record Obj {}
|
||||
|
||||
.function i32 Obj.foo(Obj a0) {
|
||||
ldai 42
|
||||
return
|
||||
}
|
||||
|
||||
.function u1 main() {
|
||||
newobj v0, Obj
|
||||
call.virt Obj.foo, v0
|
||||
return
|
||||
}
|
||||
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 1);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::main");
|
||||
ASSERT_EQ(inline_events[0]->callee, "Obj::foo");
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, VirtualProvenByNewArray)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.record Obj {}
|
||||
|
||||
.function i32 Obj.foo(Obj a0) {
|
||||
ldai 42
|
||||
return
|
||||
}
|
||||
|
||||
.function u1 main() {
|
||||
movi v0, 4
|
||||
newarr v0, v0, Obj[]
|
||||
ldai 1
|
||||
ldarr.obj v0
|
||||
sta.obj v1
|
||||
call.virt Obj.foo, v1
|
||||
return
|
||||
}
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
options.SetCompilerInlining(true);
|
||||
CompilerLogger::Init({"inlining"});
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 1);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::main");
|
||||
ASSERT_EQ(inline_events[0]->callee, "Obj::foo");
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, VirtualProvenByInitobj)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.record Obj {}
|
||||
|
||||
.function void Obj.ctor(Obj a0) <ctor> {
|
||||
return.void
|
||||
}
|
||||
|
||||
.function i32 Obj.foo(Obj a0) {
|
||||
ldai 42
|
||||
return
|
||||
}
|
||||
|
||||
.function u1 main() {
|
||||
initobj.short Obj.ctor
|
||||
sta.obj v0
|
||||
call.virt Obj.foo, v0
|
||||
return
|
||||
}
|
||||
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "main"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 2);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::main");
|
||||
ASSERT_EQ(inline_events[0]->callee, "Obj::.ctor");
|
||||
ASSERT_EQ(inline_events[1]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[1]->caller, "_GLOBAL::main");
|
||||
ASSERT_EQ(inline_events[1]->callee, "Obj::foo");
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, InliningToInfLoop)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.function u32 foo_inf(i32 a0){
|
||||
loop:
|
||||
inci a0, 1
|
||||
call foo_throw
|
||||
jmp loop
|
||||
}
|
||||
|
||||
.function u32 foo_throw() {
|
||||
ldai 42
|
||||
return
|
||||
}
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "foo_inf"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 1);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::foo_inf");
|
||||
ASSERT_EQ(inline_events[0]->callee, "_GLOBAL::foo_throw");
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, DontInlineAlwaysThrow)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.record E {}
|
||||
|
||||
.function u32 foo(i32 a0) {
|
||||
call foo_throw
|
||||
return
|
||||
}
|
||||
|
||||
.function u32 foo_throw() {
|
||||
newobj v0, E
|
||||
throw v0
|
||||
}
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "foo"));
|
||||
ASSERT_FALSE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 1);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::UNSUITABLE);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::foo");
|
||||
ASSERT_EQ(inline_events[0]->callee, "_GLOBAL::foo_throw");
|
||||
}
|
||||
|
||||
TEST_F(InliningTest, DontInlineIntoThrowBlock)
|
||||
{
|
||||
#ifndef PANDA_EVENTS_ENABLED
|
||||
return;
|
||||
#endif
|
||||
auto source = R"(
|
||||
.record E {}
|
||||
|
||||
.function u32 foo(i32 a0) {
|
||||
lda a0
|
||||
jeqz nothrow
|
||||
call foo_get_obj
|
||||
sta.obj v0
|
||||
throw v0
|
||||
nothrow:
|
||||
call foo_get_int
|
||||
return
|
||||
}
|
||||
|
||||
.function E foo_get_obj() {
|
||||
newobj v0, E
|
||||
lda.obj v0
|
||||
return.obj
|
||||
}
|
||||
|
||||
.function i32 foo_get_int() {
|
||||
ldai 42
|
||||
return
|
||||
}
|
||||
)";
|
||||
ScopeEvents scope_events;
|
||||
|
||||
ASSERT_TRUE(ParseToGraph(source, "foo"));
|
||||
ASSERT_TRUE(GetGraph()->RunPass<Inlining>());
|
||||
auto events = Events::CastTo<Events::MEMORY>();
|
||||
auto inline_events = events->Select<events::EventsMemory::InlineEvent>();
|
||||
ASSERT_EQ(inline_events.size(), 1);
|
||||
ASSERT_EQ(inline_events[0]->result, events::InlineResult::SUCCESS);
|
||||
ASSERT_EQ(inline_events[0]->caller, "_GLOBAL::foo");
|
||||
ASSERT_EQ(inline_events[0]->callee, "_GLOBAL::foo_get_int");
|
||||
}
|
||||
} // namespace panda::compiler
|
@ -1,304 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "unit_test.h"
|
||||
#include "optimizer/optimizations/licm.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class LicmTest : public GraphTest {
|
||||
public:
|
||||
static constexpr uint32_t HOST_LIMIT = 8;
|
||||
};
|
||||
|
||||
/*
|
||||
* Test Graph:
|
||||
* [0]
|
||||
* |
|
||||
* v
|
||||
* /-----[2]--------\
|
||||
* | ^ |
|
||||
* | | |
|
||||
* | [6] |
|
||||
* | ^ |
|
||||
* | | |
|
||||
* | [5]------->|
|
||||
* | ^ |
|
||||
* | | |
|
||||
* | [4] |
|
||||
* | ^ |
|
||||
* | | |
|
||||
* \---->[3] |
|
||||
* | |
|
||||
* v |
|
||||
* [exit]<-----/
|
||||
*/
|
||||
|
||||
TEST_F(LicmTest, LoopExits)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
BASIC_BLOCK(2, 3, 7)
|
||||
{
|
||||
INST(2, Opcode::Compare).b().Inputs(0, 1);
|
||||
INST(3, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(2);
|
||||
}
|
||||
BASIC_BLOCK(3, 4, 7)
|
||||
{
|
||||
INST(4, Opcode::Compare).b().Inputs(0, 1);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
BASIC_BLOCK(4, 5) {}
|
||||
BASIC_BLOCK(5, 6, 7)
|
||||
{
|
||||
INST(6, Opcode::Compare).b().Inputs(0, 1);
|
||||
INST(7, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(6);
|
||||
}
|
||||
BASIC_BLOCK(6, 2) {}
|
||||
BASIC_BLOCK(7, -1)
|
||||
{
|
||||
INST(8, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
auto licm = Licm(GetGraph(), HOST_LIMIT);
|
||||
licm.RunImpl();
|
||||
|
||||
ASSERT_TRUE(licm.IsBlockLoopExit(&BB(3)));
|
||||
ASSERT_TRUE(licm.IsBlockLoopExit(&BB(5)));
|
||||
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(0)));
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(1)));
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(2)));
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(4)));
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(6)));
|
||||
ASSERT_FALSE(licm.IsBlockLoopExit(&BB(7)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test Graph:
|
||||
* [0]
|
||||
* |
|
||||
* v
|
||||
* /-----[2]<----\
|
||||
* | | |
|
||||
* | v |
|
||||
* | [3]-----/
|
||||
* |
|
||||
* \---->[4]
|
||||
* |
|
||||
* v
|
||||
* [exit]
|
||||
*/
|
||||
TEST_F(LicmTest, OneLoop)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
CONSTANT(0, 1);
|
||||
CONSTANT(1, 10);
|
||||
CONSTANT(2, 20);
|
||||
CONSTANT(12, 1);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(3, Opcode::Phi).u64().Inputs({{0, 0}, {3, 7}});
|
||||
INST(4, Opcode::Phi).u64().Inputs({{0, 1}, {3, 8}});
|
||||
INST(5, Opcode::Compare).b().Inputs(4, 0);
|
||||
INST(6, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(5);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 2)
|
||||
{
|
||||
INST(7, Opcode::Mul).u64().Inputs(3, 4);
|
||||
INST(13, Opcode::Mul).u64().Inputs(12, 0);
|
||||
INST(8, Opcode::Sub).u64().Inputs(4, 13);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(10, Opcode::Add).u64().Inputs(2, 3);
|
||||
INST(20, Opcode::SaveState).NoVregs();
|
||||
INST(15, Opcode::CallStatic).u64().InputsAutoType(3, 4, 20);
|
||||
INST(11, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<Licm>(HOST_LIMIT);
|
||||
ASSERT_EQ(INS(13).GetBasicBlock(), BB(3).GetLoop()->GetPreHeader());
|
||||
ASSERT_EQ(INS(7).GetBasicBlock(), &BB(3));
|
||||
ASSERT_EQ(INS(8).GetBasicBlock(), &BB(3));
|
||||
|
||||
GraphChecker(GetGraph()).Check();
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO (a.popov) Improve Licm to support this test with updated DF: `INST(19, Opcode::Phi).u64().Inputs(1, 18)`
|
||||
*
|
||||
* Test Graph:
|
||||
*
|
||||
* [0]
|
||||
* |
|
||||
* v
|
||||
* [2]<----------\
|
||||
* | |
|
||||
* v |
|
||||
* /-----[3]<----\ |
|
||||
* | | | |
|
||||
* | v | |
|
||||
* | [4]-----/ [6]
|
||||
* | |
|
||||
* \---->[5]-----------/
|
||||
* |
|
||||
* v
|
||||
* [exit]
|
||||
*/
|
||||
TEST_F(LicmTest, DISABLED_TwoLoops)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 10).u64();
|
||||
PARAMETER(3, 100).u64();
|
||||
BASIC_BLOCK(2, 3) {}
|
||||
BASIC_BLOCK(3, 4, 5)
|
||||
{
|
||||
INST(5, Opcode::Phi).u64().Inputs(2, 9);
|
||||
INST(6, Opcode::Phi).u64().Inputs(3, 11);
|
||||
INST(19, Opcode::Phi).u64().Inputs(1, 18);
|
||||
INST(7, Opcode::Compare).b().Inputs(5, 6);
|
||||
INST(8, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(7);
|
||||
}
|
||||
BASIC_BLOCK(4, 3)
|
||||
{
|
||||
INST(9, Opcode::Mul).u64().Inputs(5, 1);
|
||||
INST(10, Opcode::Mul).u64().Inputs(1, 2);
|
||||
INST(18, Opcode::Mul).u64().Inputs(10, 10);
|
||||
INST(11, Opcode::Sub).u64().Inputs(6, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(5, 6, 7)
|
||||
{
|
||||
INST(13, Opcode::Compare).b().Inputs(6, 1);
|
||||
INST(14, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(13);
|
||||
}
|
||||
BASIC_BLOCK(6, 2) {}
|
||||
BASIC_BLOCK(7, -1)
|
||||
{
|
||||
INST(16, Opcode::Add).u64().Inputs(19, 0);
|
||||
INST(17, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<Licm>(HOST_LIMIT);
|
||||
ASSERT_EQ(INS(10).GetBasicBlock(), BB(2).GetLoop()->GetPreHeader());
|
||||
ASSERT_EQ(INS(18).GetBasicBlock(), BB(2).GetLoop()->GetPreHeader());
|
||||
ASSERT_EQ(INS(9).GetBasicBlock(), &BB(4));
|
||||
ASSERT_EQ(INS(11).GetBasicBlock(), &BB(4));
|
||||
GraphChecker(GetGraph()).Check();
|
||||
}
|
||||
|
||||
TEST_F(LicmTest, EmptyPreHeaderWithPhi)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 1);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::UINT64).CC(CC_NE).Imm(0).Inputs(0);
|
||||
}
|
||||
BASIC_BLOCK(3, 4) {}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(6, Opcode::Phi).u64().Inputs(0, 1);
|
||||
}
|
||||
BASIC_BLOCK(5, 5, 6)
|
||||
{
|
||||
INST(7, Opcode::Phi).u64().Inputs(0, 8);
|
||||
INST(8, Opcode::Add).u64().Inputs(7, 1);
|
||||
INST(9, Opcode::Mul).u64().Inputs(6, 6);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::UINT64).CC(CC_LT).Inputs(8, 9);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(6, -1)
|
||||
{
|
||||
INST(12, Opcode::Return).u64().Inputs(8);
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<Licm>(HOST_LIMIT);
|
||||
ASSERT_EQ(INS(9).GetBasicBlock(), BB(5).GetLoop()->GetPreHeader());
|
||||
}
|
||||
|
||||
TEST_F(LicmTest, PreHeaderWithIf)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 1);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::UINT64).CC(CC_NE).Imm(0).Inputs(0);
|
||||
}
|
||||
BASIC_BLOCK(3, 3, 4)
|
||||
{
|
||||
INST(7, Opcode::Phi).u64().Inputs(0, 8);
|
||||
INST(8, Opcode::Add).u64().Inputs(7, 1);
|
||||
INST(9, Opcode::Mul).u64().Inputs(0, 0);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::UINT64).CC(CC_LT).Inputs(8, 9);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(12, Opcode::Phi).u64().Inputs(0, 8);
|
||||
INST(13, Opcode::Return).u64().Inputs(12);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<Licm>(HOST_LIMIT);
|
||||
|
||||
auto graph = CreateEmptyGraph();
|
||||
GRAPH(graph)
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 1);
|
||||
|
||||
BASIC_BLOCK(2, 5, 4)
|
||||
{
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::UINT64).CC(CC_NE).Imm(0).Inputs(0);
|
||||
}
|
||||
BASIC_BLOCK(5, 3)
|
||||
{
|
||||
INST(9, Opcode::Mul).u64().Inputs(0, 0);
|
||||
}
|
||||
BASIC_BLOCK(3, 3, 4)
|
||||
{
|
||||
INST(7, Opcode::Phi).u64().Inputs(0, 8);
|
||||
INST(8, Opcode::Add).u64().Inputs(7, 1);
|
||||
INST(10, Opcode::Compare).b().SrcType(DataType::Type::UINT64).CC(CC_LT).Inputs(8, 9);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(12, Opcode::Phi).u64().Inputs(0, 8);
|
||||
INST(13, Opcode::Return).u64().Inputs(12);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph));
|
||||
}
|
||||
} // namespace panda::compiler
|
File diff suppressed because it is too large
Load Diff
@ -1,386 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "unit_test.h"
|
||||
#include "optimizer/analysis/monitor_analysis.h"
|
||||
|
||||
namespace panda::compiler {
|
||||
class MonitorAnalysisTest : public GraphTest {
|
||||
};
|
||||
|
||||
TEST_F(MonitorAnalysisTest, OneMonitorForOneBlock)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(4, Opcode::SaveState).Inputs(0).SrcVregs({0});
|
||||
INST(1, Opcode::Monitor).v0id().Entry().Inputs(0, 4);
|
||||
INST(5, Opcode::SaveState).Inputs(0).SrcVregs({0});
|
||||
INST(2, Opcode::Monitor).v0id().Exit().Inputs(0, 5);
|
||||
INST(3, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_TRUE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
EXPECT_TRUE(BB(2).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorBlock());
|
||||
}
|
||||
|
||||
TEST_F(MonitorAnalysisTest, OneMonitorForSeveralBlocks)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
CONSTANT(2, 10);
|
||||
CONSTANT(3, 2);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(11, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(4, Opcode::Monitor).v0id().Entry().Inputs(1, 11);
|
||||
INST(5, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(6, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(5);
|
||||
}
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
INST(7, Opcode::Mul).u64().Inputs(0, 3);
|
||||
}
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(8, Opcode::Phi).u64().Inputs({{2, 0}, {3, 7}});
|
||||
INST(12, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(9, Opcode::Monitor).v0id().Exit().Inputs(1, 12);
|
||||
INST(10, Opcode::Return).u64().Inputs(8);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_TRUE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
EXPECT_TRUE(BB(2).GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(BB(2).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(3).GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(BB(3).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(3).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(4).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorBlock());
|
||||
}
|
||||
|
||||
TEST_F(MonitorAnalysisTest, TwoMonitorForSeveralBlocks)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
PARAMETER(2, 3).ref();
|
||||
CONSTANT(3, 10);
|
||||
CONSTANT(4, 2);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(14, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(5, Opcode::Monitor).v0id().Entry().Inputs(1, 14);
|
||||
INST(15, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(6, Opcode::Monitor).v0id().Entry().Inputs(2, 15);
|
||||
INST(16, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(7, Opcode::Monitor).v0id().Exit().Inputs(1, 16);
|
||||
INST(8, Opcode::Compare).b().Inputs(0, 3);
|
||||
INST(9, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(8);
|
||||
}
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
INST(10, Opcode::Mul).u64().Inputs(0, 4);
|
||||
}
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(11, Opcode::Phi).u64().Inputs({{2, 0}, {3, 10}});
|
||||
INST(17, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(12, Opcode::Monitor).v0id().Exit().Inputs(2, 17);
|
||||
INST(13, Opcode::Return).u64().Inputs(11);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_TRUE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
EXPECT_TRUE(BB(2).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(3).GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(BB(3).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(3).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(4).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorBlock());
|
||||
}
|
||||
|
||||
TEST_F(MonitorAnalysisTest, OneEntryMonitorAndTwoExitMonitors)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
CONSTANT(2, 10);
|
||||
CONSTANT(3, 2);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(12, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(4, Opcode::Monitor).v0id().Entry().Inputs(1, 12);
|
||||
INST(5, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(6, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(5);
|
||||
}
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(13, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(7, Opcode::Monitor).v0id().Exit().Inputs(1, 13);
|
||||
INST(8, Opcode::Mul).u64().Inputs(0, 3);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(14, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(9, Opcode::Monitor).v0id().Exit().Inputs(1, 14);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(10, Opcode::Phi).u64().Inputs({{4, 0}, {3, 8}});
|
||||
INST(11, Opcode::Return).u64().Inputs(10);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_TRUE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
EXPECT_TRUE(BB(2).GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(BB(2).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(2).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(3).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(3).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(3).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(4).GetMonitorEntryBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorExitBlock());
|
||||
EXPECT_TRUE(BB(4).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(BB(5).GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(BB(5).GetMonitorExitBlock());
|
||||
EXPECT_FALSE(BB(5).GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetStartBlock()->GetMonitorBlock());
|
||||
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorEntryBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorExitBlock());
|
||||
EXPECT_FALSE(GetGraph()->GetEndBlock()->GetMonitorBlock());
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernal case:
|
||||
* bb 2
|
||||
* | \
|
||||
* | bb 3
|
||||
* | MonitorEntry
|
||||
* | /
|
||||
* bb 4 - Conditional is equal to bb 2
|
||||
* | \
|
||||
* | bb 5
|
||||
* | MonitorExit
|
||||
* | /
|
||||
* bb 6
|
||||
*
|
||||
* The monitor analysis marks all blocks (excluding bb 2) as BlockMonitor
|
||||
*/
|
||||
TEST_F(MonitorAnalysisTest, KernalCase)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
CONSTANT(2, 10);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(4, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
INST(6, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(7, Opcode::Monitor).v0id().Entry().Inputs(1, 6);
|
||||
}
|
||||
BASIC_BLOCK(4, 5, 6)
|
||||
{
|
||||
INST(10, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
|
||||
}
|
||||
BASIC_BLOCK(5, 6)
|
||||
{
|
||||
INST(12, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(13, Opcode::Monitor).v0id().Exit().Inputs(1, 12);
|
||||
}
|
||||
BASIC_BLOCK(6, -1)
|
||||
{
|
||||
INST(15, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_FALSE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
}
|
||||
|
||||
/*
|
||||
* bb 2
|
||||
* | \
|
||||
* | bb 3
|
||||
* | MonitorEntry
|
||||
* | /
|
||||
* bb 4
|
||||
* |
|
||||
* bb 5
|
||||
* MonitorExit
|
||||
* |
|
||||
* bb 6
|
||||
*
|
||||
* MonitorAnalysis must return false because
|
||||
* - MonitorEntry is optional
|
||||
* - MonitorExit is not optional
|
||||
*/
|
||||
TEST_F(MonitorAnalysisTest, InconsistentMonitorsNumberCase1)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
CONSTANT(2, 10);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(4, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
INST(6, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(7, Opcode::Monitor).v0id().Entry().Inputs(1, 6);
|
||||
}
|
||||
BASIC_BLOCK(4, 5) {}
|
||||
BASIC_BLOCK(5, 6)
|
||||
{
|
||||
INST(12, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(13, Opcode::Monitor).v0id().Exit().Inputs(1, 12);
|
||||
}
|
||||
BASIC_BLOCK(6, -1)
|
||||
{
|
||||
INST(15, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_FALSE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
}
|
||||
|
||||
/*
|
||||
* bb 2
|
||||
* MonitorEntry
|
||||
* | \
|
||||
* | bb 3
|
||||
* | MonitorExit
|
||||
* | /
|
||||
* bb 4
|
||||
* MonitorExit
|
||||
* |
|
||||
* bb 5
|
||||
*
|
||||
* MonitorAnalysis must return false because two Exits can happen for the single monitor
|
||||
*/
|
||||
TEST_F(MonitorAnalysisTest, InconsistentMonitorsNumberCase2)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).u64();
|
||||
PARAMETER(1, 2).ref();
|
||||
CONSTANT(2, 10);
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(3, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(14, Opcode::Monitor).v0id().Entry().Inputs(1, 3);
|
||||
INST(4, Opcode::Compare).b().Inputs(0, 2);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
BASIC_BLOCK(3, 4)
|
||||
{
|
||||
INST(6, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(7, Opcode::Monitor).v0id().Exit().Inputs(1, 6);
|
||||
}
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(8, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(9, Opcode::Monitor).v0id().Exit().Inputs(1, 8);
|
||||
}
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(15, Opcode::ReturnVoid);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_FALSE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
}
|
||||
|
||||
/*
|
||||
* MonitorAnalysis should be Ok about Monitor Entry/Exit conunter mismatch when Throw happens within
|
||||
* the synchronized block
|
||||
*/
|
||||
TEST_F(MonitorAnalysisTest, MonitorAndThrow)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 1).ref();
|
||||
PARAMETER(1, 2).ref();
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(4, Opcode::Monitor).v0id().Entry().Inputs(1, 3);
|
||||
INST(5, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(6, Opcode::Throw).Inputs(0, 5);
|
||||
}
|
||||
}
|
||||
GetGraph()->RunPass<MonitorAnalysis>();
|
||||
EXPECT_TRUE(GetGraph()->IsAnalysisValid<MonitorAnalysis>());
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,439 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "unit_test.h"
|
||||
#include "optimizer/analysis/reg_alloc_verifier.h"
|
||||
#include "optimizer/optimizations/regalloc/reg_alloc_linear_scan.h"
|
||||
#include "optimizer/optimizations/regalloc/spill_fills_resolver.h"
|
||||
namespace panda::compiler {
|
||||
class RegAllocVerifierTest : public GraphTest {
|
||||
public:
|
||||
RegAllocVerifierTest() : default_verify_option_(options.IsCompilerVerifyRegalloc())
|
||||
{
|
||||
// Avoid fatal errors in the negative-tests
|
||||
options.SetCompilerVerifyRegalloc(false);
|
||||
}
|
||||
|
||||
~RegAllocVerifierTest()
|
||||
{
|
||||
options.SetCompilerVerifyRegalloc(default_verify_option_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool default_verify_option_ {};
|
||||
};
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyBranchelessCode)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(3, Opcode::Mul).u64().Inputs(0, 2);
|
||||
INST(4, Opcode::Return).u64().Inputs(3);
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyBranching)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 42);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(2, Opcode::Compare).b().Inputs(0, 1);
|
||||
INST(3, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(2);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 5)
|
||||
{
|
||||
INST(4, Opcode::Mul).u64().Inputs(0, 0);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, 5)
|
||||
{
|
||||
INST(5, Opcode::Mul).u64().Inputs(1, 1);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(5, -1)
|
||||
{
|
||||
INST(6, Opcode::Phi).u64().Inputs(4, 5);
|
||||
INST(7, Opcode::Return).u64().Inputs(6);
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyLoop)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 1);
|
||||
|
||||
BASIC_BLOCK(2, 3, 4)
|
||||
{
|
||||
INST(2, Opcode::Phi).u64().Inputs(0, 7);
|
||||
INST(3, Opcode::Phi).u64().Inputs(1, 6);
|
||||
INST(4, Opcode::Compare).b().Inputs(2, 1);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, 2)
|
||||
{
|
||||
INST(6, Opcode::Add).u64().Inputs(3, 0);
|
||||
INST(7, Opcode::SubI).u64().Imm(1).Inputs(2);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(4, -1)
|
||||
{
|
||||
INST(8, Opcode::Return).u64().Inputs(3);
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifySingleBlockLoop)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
CONSTANT(1, 0);
|
||||
|
||||
BASIC_BLOCK(2, 2, 3)
|
||||
{
|
||||
INST(2, Opcode::Phi).u64().Inputs(0, 3);
|
||||
INST(3, Opcode::SubI).u64().Imm(1).Inputs(2);
|
||||
INST(4, Opcode::Compare).b().Inputs(3, 1);
|
||||
INST(5, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(4);
|
||||
}
|
||||
|
||||
BASIC_BLOCK(3, -1)
|
||||
{
|
||||
INST(6, Opcode::Return).u64().Inputs(3);
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, LoadPair)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(1, Opcode::SaveState).Inputs(0).SrcVregs({0});
|
||||
INST(2, Opcode::NullCheck).ref().Inputs(0, 1);
|
||||
INST(3, Opcode::LoadArrayPairI).s64().Inputs(2).Imm(0x0);
|
||||
INST(4, Opcode::LoadPairPart).s64().Inputs(3).Imm(0x0);
|
||||
INST(5, Opcode::LoadPairPart).s64().Inputs(3).Imm(0x1);
|
||||
INST(6, Opcode::SaveState).Inputs(2, 4, 5).SrcVregs({2, 4, 5});
|
||||
INST(7, Opcode::CallStatic).v0id().InputsAutoType(4, 5, 6);
|
||||
INST(8, Opcode::Add).s64().Inputs(4, 5);
|
||||
INST(9, Opcode::Return).s64().Inputs(8);
|
||||
}
|
||||
}
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, TestVoidMethodCall)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(1, Opcode::SaveState).Inputs(0).SrcVregs({0});
|
||||
INST(2, Opcode::CallStatic).v0id().InputsAutoType(0, 1);
|
||||
INST(3, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, ZeroReg)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).ref();
|
||||
CONSTANT(1, 0);
|
||||
CONSTANT(2, nullptr);
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::SaveState).Inputs(0, 1, 2).SrcVregs({0, 1, 2});
|
||||
INST(4, Opcode::NullCheck).ref().Inputs(0, 3);
|
||||
INST(5, Opcode::StoreObject).u64().Inputs(4, 1);
|
||||
INST(6, Opcode::StoreObject).ref().Inputs(4, 2);
|
||||
INST(7, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, TooManyParameters)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
for (int i = 0; i < 32; i++) {
|
||||
PARAMETER(i, i).u64();
|
||||
}
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
for (int i = 0; i < 32; i++) {
|
||||
INST(32 + i, Opcode::Add).u64().Inputs(i, 32 + i - 1);
|
||||
}
|
||||
INST(64, Opcode::Return).u64().Inputs(63);
|
||||
}
|
||||
}
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, ReorderSpillFill)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
CONSTANT(0, 1);
|
||||
CONSTANT(1, 2);
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::SpillFill);
|
||||
INST(3, Opcode::Add).u64().Inputs(0, 1);
|
||||
INST(4, Opcode::Return).u64().Inputs(3);
|
||||
}
|
||||
}
|
||||
|
||||
INS(0).SetDstReg(0);
|
||||
INS(1).SetDstReg(1);
|
||||
INS(3).SetSrcReg(0, 1);
|
||||
INS(3).SetSrcReg(1, 0);
|
||||
INS(3).SetDstReg(0);
|
||||
INS(4).SetSrcReg(0, 0);
|
||||
|
||||
auto sf = INS(2).CastToSpillFill();
|
||||
// swap 0 and 1: should be 0 -> tmp, 1 -> 0, tmp -> 1, but we intentionally reorder it
|
||||
sf->AddMove(1, 0, DataType::UINT64);
|
||||
sf->AddMove(0, 16, DataType::UINT64);
|
||||
sf->AddMove(16, 1, DataType::UINT64);
|
||||
|
||||
ArenaVector<bool> regs = ArenaVector<bool>(std::max(MAX_NUM_REGS, MAX_NUM_VREGS), false, GetAllocator()->Adapter());
|
||||
GetGraph()->InitUsedRegs<DataType::INT64>(®s);
|
||||
GetGraph()->InitUsedRegs<DataType::FLOAT64>(®s);
|
||||
ASSERT_FALSE(RegAllocVerifier(GetGraph(), false).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, UseIncorrectInputReg)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::Mul).u64().Inputs(0, 1);
|
||||
INST(3, Opcode::Return).u64().Inputs(2);
|
||||
}
|
||||
}
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
INS(3).SetSrcReg(0, 15);
|
||||
ASSERT_FALSE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, CallSpillFillReordering)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u32();
|
||||
PARAMETER(1, 1).u32();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
|
||||
INST(3, Opcode::CallStatic).InputsAutoType(1, 0, 2).v0id();
|
||||
INST(4, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
// Manually allocate registers to ensure that parameters 0 and 1 will be swapped
|
||||
// at CallStatic call site. That swap requires additional resolving move.
|
||||
INS(0).CastToParameter()->SetLocationData(
|
||||
{LocationType::REGISTER, LocationType::REGISTER, 1, 1, DataType::Type::INT32});
|
||||
INS(0).SetDstReg(1);
|
||||
|
||||
INS(1).CastToParameter()->SetLocationData(
|
||||
{LocationType::REGISTER, LocationType::REGISTER, 2, 2, DataType::Type::INT32});
|
||||
INS(1).SetDstReg(2);
|
||||
|
||||
ArenaVector<bool> regs = ArenaVector<bool>(std::max(MAX_NUM_REGS, MAX_NUM_VREGS), false, GetAllocator()->Adapter());
|
||||
GetGraph()->InitUsedRegs<DataType::INT64>(®s);
|
||||
GetGraph()->InitUsedRegs<DataType::FLOAT64>(®s);
|
||||
|
||||
RegAllocLinearScan(GetGraph()).Run();
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph(), false).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyIndirectCall)
|
||||
{
|
||||
if (GetGraph()->GetArch() == Arch::AARCH32) {
|
||||
// TODO: Support spill/fills in entrypoints frame and enable test
|
||||
GTEST_SKIP();
|
||||
}
|
||||
GetGraph()->SetMode(GraphMode::FastPath());
|
||||
auto ptr_type = Is64BitsArch(GetGraph()->GetArch()) ? DataType::Type::UINT64 : DataType::Type::UINT32;
|
||||
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).type(ptr_type);
|
||||
PARAMETER(1, 1).u64();
|
||||
PARAMETER(2, 2).u64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(3, Opcode::CallIndirect).InputsAutoType(0, 1, 2).u64();
|
||||
INST(4, Opcode::Return).Inputs(3).u64();
|
||||
}
|
||||
}
|
||||
|
||||
GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
// Inputs overlapping: Input #2 has fixed location r1, Input #0 has dst r1
|
||||
// Failed verification is correct for now. Fix it.
|
||||
ASSERT_FALSE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyInlinedCall)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
PARAMETER(0, 0).u64();
|
||||
PARAMETER(1, 1).u64();
|
||||
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
INST(2, Opcode::Add).Inputs(0, 1).u64();
|
||||
INST(3, Opcode::Mul).Inputs(0, 2).u64();
|
||||
INST(4, Opcode::SaveState).Inputs(0, 1, 2, 3).SrcVregs({0, 1, 2, 3});
|
||||
INST(5, Opcode::CallStatic).Inlined().InputsAutoType(3, 2, 1, 0, 4).u64();
|
||||
INST(6, Opcode::ReturnInlined).Inputs(4).v0id();
|
||||
INST(7, Opcode::ReturnVoid).v0id();
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(INS(5).GetDstReg(), INVALID_REG);
|
||||
ASSERT_TRUE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
TEST_F(RegAllocVerifierTest, VerifyEmptyStartBlock)
|
||||
{
|
||||
GRAPH(GetGraph())
|
||||
{
|
||||
BASIC_BLOCK(2, -1)
|
||||
{
|
||||
CONSTANT(0, 0xABCDABCDABCDABCDLL);
|
||||
INST(1, Opcode::SaveState).Inputs(0).SrcVregs({0});
|
||||
INST(2, Opcode::CallStatic).InputsAutoType(0, 1).u64();
|
||||
INST(3, Opcode::Mul).Inputs(0, 2).u64();
|
||||
INST(4, Opcode::Return).Inputs(3).u64();
|
||||
}
|
||||
}
|
||||
|
||||
auto result = GetGraph()->RunPass<RegAllocLinearScan>();
|
||||
if (GetGraph()->GetCallingConvention() == nullptr) {
|
||||
ASSERT_FALSE(result);
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE(result);
|
||||
// set incorrect regs
|
||||
INS(3).SetDstReg(INS(3).GetDstReg() + 1);
|
||||
ASSERT_FALSE(RegAllocVerifier(GetGraph()).Run());
|
||||
}
|
||||
|
||||
} // namespace panda::compiler
|
@ -34,7 +34,6 @@ Second, we need to support the transfer of information between optimizations.
|
||||
* [Memory Coalescing](../compiler/docs/memory_coalescing_doc.md)
|
||||
* [Peepholes](../compiler/docs/peephole_doc.md)
|
||||
* [Redundant Loop Elimination](../compiler/docs/redundant_loop_elimination_doc.md)
|
||||
* [Register allocator](../compiler/docs/reg_alloc_linear_scan_doc.md)
|
||||
* [Scheduler](../compiler/docs/scheduler_doc.md)
|
||||
* [Value Numbering](../compiler/docs/vn_doc.md)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user