Files
2026-07-13 12:40:42 +08:00

170 lines
5.9 KiB
C++

// Copyright (c) 2024 CINN Authors. All Rights Reserved.
//
// 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 "paddle/cinn/optim/schedule_block_dce.h"
#include "paddle/cinn/ir/ir_mutator.h"
#include "paddle/cinn/ir/ir_printer.h"
#include "paddle/cinn/optim/replace_var_with_expr.h"
namespace cinn {
namespace optim {
struct ScheduleBlockDCE : public ir::IRMutator<Expr*> {
explicit ScheduleBlockDCE(const std::vector<std::string>& output_names)
: output_names_(output_names.begin(), output_names.end()) {}
void operator()(ir::Expr* expr) {
UpdateDeadScheduleBlocks(*expr);
while (!dead_schedule_block_names_.empty()) {
Visit(expr);
UpdateDeadScheduleBlocks(*expr);
}
}
private:
void Visit(ir::Expr* expr) { ir::IRMutator<>::Visit(expr, expr); }
void Visit(const ir::Block* op, Expr* expr) override {
auto* node = expr->As<ir::Block>();
PADDLE_ENFORCE_NOT_NULL(node,
::common::errors::InvalidArgument(
"Sorry, but expr->As node is nullptr"));
for (auto& stmt : node->stmts) {
IRMutator::Visit(&stmt, &stmt);
}
std::unordered_set<int> need_remove_ids;
for (int i = 0; i < node->stmts.size(); ++i) {
if (IsDeadScheduleBlock(node->stmts[i]) || IsEmptyBlock(node->stmts[i])) {
need_remove_ids.insert(i);
}
}
if (!need_remove_ids.empty()) {
node->stmts = [&] {
std::vector<ir::Expr> new_stmts;
for (int i = 0; i < node->stmts.size(); ++i) {
if (need_remove_ids.count(i) == 0) {
new_stmts.push_back(node->stmts[i]);
}
}
return new_stmts;
}();
}
}
void Visit(const ir::IfThenElse* op, Expr* expr) override {
auto* node = expr->As<ir::IfThenElse>();
PADDLE_ENFORCE_NOT_NULL(node,
::common::errors::InvalidArgument(
"Sorry, but node expr->As is nullptr"));
IRMutator::Visit(&node->true_case, &node->true_case);
if (node->false_case.defined()) {
IRMutator::Visit(&node->false_case, &node->false_case);
}
if (IsEmptyIf(op)) {
*expr = ir::Block::Make({});
}
}
void Visit(const ir::For* op, Expr* expr) override {
auto* node = expr->As<ir::For>();
PADDLE_ENFORCE_NOT_NULL(node,
::common::errors::InvalidArgument(
"Sorry, but node expr->As is nullptr"));
IRMutator::Visit(&(node->body), &(node->body));
if (IsEmptyBlock(op->body)) {
*expr = ir::Block::Make({});
}
}
bool IsEmptyBlock(const ir::Expr& expr) {
const auto* block_node = expr.As<ir::Block>();
if (block_node == nullptr) return false;
for (const auto& stmt : block_node->stmts) {
if (!IsEmptyBlock(stmt)) return false;
}
return true;
}
bool IsEmptyIf(const ir::IfThenElse* node) {
if (node->false_case.defined()) {
return IsEmptyBlock(node->true_case) && IsEmptyBlock(node->false_case);
}
return IsEmptyBlock(node->true_case);
}
bool IsDeadScheduleBlock(const ir::Expr& expr) {
const auto* sbr = expr.As<ir::ScheduleBlockRealize>();
return sbr != nullptr &&
dead_schedule_block_names_.count(
sbr->schedule_block.As<ir::ScheduleBlock>()->name) > 0;
}
void UpdateDeadScheduleBlocks(const ir::Expr& expr) {
dead_schedule_block_names_.clear();
std::unordered_set<std::string> load_buffer_names;
std::unordered_set<std::string> load_tensor_names;
auto InsertLoadTensorAndBufferNames = [&](const ir::Expr* x) -> bool {
if (const ir::Load* load = x->As<ir::Load>()) {
load_buffer_names.insert(load->tensor.as_tensor()->buffer->name);
load_tensor_names.insert(load->tensor.as_tensor()->name);
}
return false;
};
ir::ir_utils::CollectIRNodes(expr, InsertLoadTensorAndBufferNames);
auto IsShareBufferWithLoadedTensor =
[&](const ir::_Tensor_* tensor) -> bool {
return load_buffer_names.count(tensor->buffer->name) > 0;
};
auto IsLoadedTensor = [&](const ir::_Tensor_* tensor) -> bool {
return load_tensor_names.count(tensor->name) > 0;
};
auto IsOutputTensor = [&](const ir::_Tensor_* tensor) -> bool {
return output_names_.count(tensor->name) > 0;
};
auto IsDeadStore = [&](const ir::Store* store) -> bool {
const ir::_Tensor_* tensor = store->tensor.as_tensor();
return !IsOutputTensor(tensor) && !IsLoadedTensor(tensor) &&
!IsShareBufferWithLoadedTensor(tensor);
};
auto InsertDeadStoreName = [&](const ir::Expr* x) -> bool {
const ir::Store* store = x->As<ir::Store>();
if (store != nullptr && IsDeadStore(store)) {
VLOG(6) << "Find dead schedule block: "
<< store->tensor.as_tensor()->name;
dead_schedule_block_names_.insert(store->tensor.as_tensor()->name);
}
return false;
};
ir::ir_utils::CollectIRNodes(expr, InsertDeadStoreName);
}
private:
std::unordered_set<std::string> dead_schedule_block_names_;
std::unordered_set<std::string> output_names_;
};
void EliminateDeadScheduleBlock(Expr* e,
const std::vector<std::string>& output_names) {
VLOG(6) << "Start EliminateDeadScheduleBlock" << *e;
ScheduleBlockDCE eliminator(output_names);
eliminator(e);
VLOG(6) << "End EliminateDeadScheduleBlock: " << *e;
}
} // namespace optim
} // namespace cinn