[midend-SCCP]删除User的析构函数,usedelete增加逻辑通知inst所使用的value移除对应的use关系(一般在这之前会替换使用inst的uses为其他值),TODO:仍然存在bug需要调试
This commit is contained in:
@ -14,6 +14,62 @@
|
||||
|
||||
Mem2Reg 遍的主要目标是将那些不必要的、只用于局部标量变量的内存分配 (alloca 指令) 消除,并将这些变量的值转换为 SSA 形式。这有助于减少内存访问,提高代码效率,并为后续的优化创造更好的条件。
|
||||
|
||||
通过Mem2Reg理解删除指令时对use关系的维护:
|
||||
|
||||
在 `Mem2Reg` 优化遍中,当 `load` 和 `store` 指令被删除时,其 `use` 关系(即它们作为操作数与其他 `Value` 对象之间的连接)的正确消除是一个关键问题,尤其涉及到 `AllocaInst`。
|
||||
|
||||
结合您提供的 `Mem2RegContext::renameVariables` 代码和我们之前讨论的 `usedelete` 逻辑,下面是 `use` 关系如何被正确消除的详细过程:
|
||||
|
||||
### 问题回顾:`Use` 关系的双向性
|
||||
|
||||
在您的 IR 设计中,`Use` 对象扮演着连接 `User`(使用者,如 `LoadInst`)和 `Value`(被使用者,如 `AllocaInst`)的双向角色:
|
||||
|
||||
* 一个 `User` 持有对其操作数 `Value` 的 `Use` 对象(通过 `User::operands` 列表)。
|
||||
* 一个 `Value` 持有所有使用它的 `User` 的 `Use` 对象(通过 `Value::uses` 列表)。
|
||||
|
||||
原始问题是:当一个 `LoadInst` 或 `StoreInst` 被删除时,如果不对其作为操作数与 `AllocaInst` 之间的 `Use` 关系进行明确清理,`AllocaInst` 的 `uses` 列表中就会留下指向已删除 `LoadInst` / `StoreInst` 的 `Use` 对象,导致内部的 `User*` 指针悬空,在后续访问时引发 `segmentation fault`。
|
||||
|
||||
### `Mem2Reg` 中 `load`/`store` 指令的删除行为
|
||||
|
||||
在 `Mem2RegContext::renameVariables` 函数中,`load` 和 `store` 指令被处理时,其行为如下:
|
||||
|
||||
1. **处理 `LoadInst`:**
|
||||
当找到一个指向可提升 `AllocaInst` 的 `LoadInst` 时,其用途会被 `replaceAllUsesWith(allocaToValueStackMap[alloca].top())` 替换。这意味着任何原本使用 `LoadInst` 本身计算结果的指令,现在都直接使用 SSA 值栈顶部的 `Value`。
|
||||
**重点:** 这一步处理的是 `LoadInst` 作为**被使用的值 (Value)** 时,其 `uses` 列表的清理。即,将 `LoadInst` 的所有使用者重定向到新的 SSA 值,并把这些 `Use` 对象从 `LoadInst` 的 `uses` 列表中移除。
|
||||
|
||||
2. **处理 `StoreInst`:**
|
||||
当找到一个指向可提升 `AllocaInst` 的 `StoreInst` 时,`StoreInst` 存储的值会被压入值栈。`StoreInst` 本身并不产生可被其他指令直接使用的值(其类型是 `void`),所以它没有 `uses` 列表需要替换。
|
||||
**重点:** `StoreInst` 的主要作用是更新内存状态,在 SSA 形式下,它被移除后需要清理它作为**使用者 (User)** 时的操作数关系。
|
||||
|
||||
在这两种情况下,一旦 `load` 或 `store` 指令的 SSA 转换完成,它们都会通过 `instIter = SysYIROptUtils::usedelete(instIter)` 被显式删除。
|
||||
|
||||
### `SysYIROptUtils::usedelete` 如何正确消除 `Use` 关系
|
||||
|
||||
关键在于对 `SysYIROptUtils::usedelete` 函数的修改,使其在删除指令时,同时处理该指令作为 `User` 和 `Value` 的两种 `Use` 关系:
|
||||
|
||||
1. **清理指令作为 `Value` 时的 `uses` 列表 (由 `replaceAllUsesWith` 完成):**
|
||||
在 `usedelete` 函数中,`inst->replaceAllUsesWith(UndefinedValue::get(inst->getType()))` 的调用至关重要。这确保了:
|
||||
* 如果被删除的 `Instruction`(例如 `LoadInst`)产生了结果值并被其他指令使用,所有这些使用者都会被重定向到 `UndefinedValue`(或者 `Mem2Reg` 中具体的 SSA 值)。
|
||||
* 这个过程会遍历 `LoadInst` 的 `uses` 列表,并将这些 `Use` 对象从 `LoadInst` 的 `uses` 列表中移除。这意味着 `LoadInst` 自己不再被任何其他指令使用。
|
||||
|
||||
2. **清理指令作为 `User` 时其操作数的 `uses` 列表 (由 `RemoveUserOperandUses` 完成):**
|
||||
这是您提出的、并已集成到 `usedelete` 中的关键改进点。对于一个被删除的 `Instruction`(它同时也是 `User`),我们需要清理它**自己使用的操作数**所维护的 `use` 关系。
|
||||
* 例如,`LoadInst %op1` 使用了 `%op1`(一个 `AllocaInst`)。当 `LoadInst` 被删除时,`AllocaInst` 的 `uses` 列表中有一个 `Use` 对象指向这个 `LoadInst`。
|
||||
* `RemoveUserOperandUses` 函数会遍历被删除 `User`(即 `LoadInst` 或 `StoreInst`)的 `operands` 列表。
|
||||
* 对于 `operands` 列表中的每个 `std::shared_ptr<Use> use_ptr`,它会获取 `Use` 对象内部指向的 `Value`(例如 `AllocaInst*`),然后调用 `value->removeUse(use_ptr)`。
|
||||
* 这个 `removeUse` 调用会负责将 `use_ptr` 从 `AllocaInst` 的 `uses` 列表中删除。
|
||||
|
||||
### 总结
|
||||
|
||||
通过在 `SysYIROptUtils::usedelete` 中同时执行这两个步骤:
|
||||
|
||||
* `replaceAllUsesWith`:处理被删除指令**作为结果被使用**时的 `use` 关系。
|
||||
* `RemoveUserOperandUses`:处理被删除指令**作为使用者(User)时,其操作数**的 `use` 关系。
|
||||
|
||||
这就确保了当 `Mem2Reg` 遍历并删除 `load` 和 `store` 指令时,无论是它们作为 `Value` 的使用者,还是它们作为 `User` 的操作数,所有相关的 `Use` 对象都能被正确地从 `Value` 的 `uses` 列表中移除,从而避免了悬空指针和后续的 `segmentation fault`。
|
||||
|
||||
最后,当所有指向某个 `AllocaInst` 的 `load` 和 `store` 指令都被移除后,`AllocaInst` 的 `uses` 列表将变得干净(只包含 Phi 指令,如果它们在 SSA 转换中需要保留 Alloca 作为操作数),这时在 `Mem2RegContext::cleanup()` 阶段,`SysYIROptUtils::usedelete(alloca)` 就可以安全地删除 `AllocaInst` 本身了。
|
||||
|
||||
## Reg2Mem
|
||||
|
||||
我们的Reg2Mem 遍的主要目标是作为 Mem2Reg 的一种逆操作,但更具体是解决后端无法识别 PhiInst 指令的问题。主要的速录是将函数参数和 PhiInst 指令的结果从 SSA 形式转换回内存形式,通过插入 alloca、load 和 store 指令来实现。其他非 Phi 的指令结果将保持 SSA 形式。
|
||||
|
||||
@ -626,21 +626,21 @@ class User : public Value {
|
||||
explicit User(Type *type, const std::string &name = "") : Value(type, name) {}
|
||||
|
||||
public:
|
||||
~User() override {
|
||||
// 当 User 对象被销毁时(例如,LoadInst 或 StoreInst 被删除时),
|
||||
// 它必须通知它所使用的所有 Value,将对应的 Use 关系从它们的 uses 列表中移除。
|
||||
// 这样可以防止 Value 的 uses 列表中出现悬空的 Use 对象。
|
||||
for (const auto &use_ptr : operands) {
|
||||
// 确保 use_ptr 非空,并且其内部指向的 Value* 也非空
|
||||
// (虽然通常情况下不会为空,但为了健壮性考虑)
|
||||
if (use_ptr && use_ptr->getValue()) {
|
||||
use_ptr->getValue()->removeUse(use_ptr);
|
||||
}
|
||||
}
|
||||
// operands 向量本身是 std::vector<std::shared_ptr<Use>>,
|
||||
// 在此析构函数结束后,operands 向量会被销毁,其内部的 shared_ptr 也会被释放,
|
||||
// 如果 shared_ptr 引用计数降为0,Use 对象本身也会被销毁。
|
||||
}
|
||||
// ~User() override {
|
||||
// // 当 User 对象被销毁时(例如,LoadInst 或 StoreInst 被删除时),
|
||||
// // 它必须通知它所使用的所有 Value,将对应的 Use 关系从它们的 uses 列表中移除。
|
||||
// // 这样可以防止 Value 的 uses 列表中出现悬空的 Use 对象。
|
||||
// for (const auto &use_ptr : operands) {
|
||||
// // 确保 use_ptr 非空,并且其内部指向的 Value* 也非空
|
||||
// // (虽然通常情况下不会为空,但为了健壮性考虑)
|
||||
// if (use_ptr && use_ptr->getValue()) {
|
||||
// use_ptr->getValue()->removeUse(use_ptr);
|
||||
// }
|
||||
// }
|
||||
// // operands 向量本身是 std::vector<std::shared_ptr<Use>>,
|
||||
// // 在此析构函数结束后,operands 向量会被销毁,其内部的 shared_ptr 也会被释放,
|
||||
// // 如果 shared_ptr 引用计数降为0,Use 对象本身也会被销毁。
|
||||
// }
|
||||
unsigned getNumOperands() const { return operands.size(); } ///< 获取操作数数量
|
||||
auto operand_begin() const { return operands.begin(); } ///< 返回操作数列表的开头迭代器
|
||||
auto operand_end() const { return operands.end(); } ///< 返回操作数列表的结尾迭代器
|
||||
|
||||
@ -23,38 +23,76 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static void RemoveUserOperandUses(User *user) {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历 User 的 operands 列表。
|
||||
// 由于 operands 是 protected 成员,我们需要一个临时方法来访问它,
|
||||
// 或者在 User 类中添加一个 friend 声明。
|
||||
// 假设 User 内部有一个像 getOperands() 这样的公共方法返回 operands 的引用,
|
||||
// 或者将 SysYIROptUtils 声明为 User 的 friend。
|
||||
// 为了示例,我将假设可以直接访问 user->operands 或通过一个getter。
|
||||
// 如果无法直接访问,请在 IR.h 的 User 类中添加:
|
||||
// public: const std::vector<std::shared_ptr<Use>>& getOperands() const { return operands; }
|
||||
|
||||
// 迭代 copies of shared_ptr to avoid issues if removeUse modifies the list
|
||||
// (though remove should handle it, iterating a copy is safer or reverse iteration).
|
||||
// Since we'll clear the vector at the end, iterating forward is fine.
|
||||
for (const auto& use_ptr : user->getOperands()) { // 假设 getOperands() 可用
|
||||
if (use_ptr) {
|
||||
Value *val = use_ptr->getValue(); // 获取 Use 指向的 Value (如 AllocaInst)
|
||||
if (val) {
|
||||
val->removeUse(use_ptr); // 通知 Value 从其 uses 列表中移除此 Use 关系
|
||||
}
|
||||
}
|
||||
}
|
||||
// 清空 User 的 operands 向量。这会递减 User 持有的 shared_ptr<Use> 的引用计数。
|
||||
// 当引用计数降为 0 时,Use 对象本身将被销毁。
|
||||
// User::operands.clear(); // 这个步骤会在 Instruction 的析构函数中自动完成,因为它是 vector 成员
|
||||
// 或者我们可以在 User::removeOperand 方法中确保 Use 对象从 operands 中移除。
|
||||
// 实际上,只要 Value::removeUse(use_ptr) 被调用了,
|
||||
// 当 Instruction 所在的 unique_ptr 销毁时,它的 operands vector 也会被销毁。
|
||||
// 所以这里不需要显式 clear()
|
||||
}
|
||||
static void usedelete(Instruction *inst) {
|
||||
assert(inst && "Instruction to delete cannot be null.");
|
||||
BasicBlock *parentBlock = inst->getParent();
|
||||
assert(parentBlock && "Instruction must have a parent BasicBlock to be deleted.");
|
||||
|
||||
// 直接在此处执行 replaceAllUsesWith
|
||||
// 步骤1: 处理所有使用者,将他们从使用 inst 变为使用 UndefinedValue
|
||||
// 这将清理 inst 作为 Value 时的 uses 列表
|
||||
if (!inst->getUses().empty()) {
|
||||
if(DEBUG) {
|
||||
std::cout << "Replacing all uses of instruction " << inst->getName() << " with UndefinedValue." << std::endl;
|
||||
}
|
||||
// 替换所有使用该指令的地方为未定义值
|
||||
inst->replaceAllUsesWith(UndefinedValue::get(inst->getType()));
|
||||
inst->replaceAllUsesWith(UndefinedValue::get(inst->getType()));
|
||||
}
|
||||
// 物理删除指令
|
||||
inst->getParent()->removeInst(inst);
|
||||
}
|
||||
|
||||
// 步骤2: 清理 inst 作为 User 时的操作数关系
|
||||
// 通知 inst 所使用的所有 Value (如 AllocaInst),移除对应的 Use 关系。
|
||||
// 这里的 inst 实际上是一个 User*,所以可以安全地向下转型。
|
||||
RemoveUserOperandUses(static_cast<User*>(inst));
|
||||
|
||||
// 步骤3: 物理删除指令
|
||||
// 这会导致 Instruction 对象的 unique_ptr 销毁,从而调用其析构函数链。
|
||||
parentBlock->removeInst(inst);
|
||||
}
|
||||
|
||||
static BasicBlock::iterator usedelete(BasicBlock::iterator inst_it) {
|
||||
Instruction *inst_to_delete = inst_it->get();
|
||||
BasicBlock *parentBlock = inst_to_delete->getParent();
|
||||
assert(parentBlock && "Instruction must have a parent BasicBlock for iterator deletion.");
|
||||
|
||||
// 直接在此处执行 replaceAllUsesWith
|
||||
// 步骤1: 处理所有使用者
|
||||
if (!inst_to_delete->getUses().empty()) {
|
||||
if(DEBUG) {
|
||||
std::cout << "Replacing all uses of instruction " << inst_to_delete->getName() << " with UndefinedValue." << std::endl;
|
||||
}
|
||||
inst_to_delete->replaceAllUsesWith(UndefinedValue::get(inst_to_delete->getType()));
|
||||
inst_to_delete->replaceAllUsesWith(UndefinedValue::get(inst_to_delete->getType()));
|
||||
}
|
||||
// 物理删除指令
|
||||
|
||||
// 步骤2: 清理操作数关系
|
||||
RemoveUserOperandUses(static_cast<User*>(inst_to_delete));
|
||||
|
||||
// 步骤3: 物理删除指令并返回下一个迭代器
|
||||
return parentBlock->removeInst(inst_it);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否是全局变量
|
||||
static bool isGlobal(Value *val) {
|
||||
|
||||
Reference in New Issue
Block a user