chore: 代码彻底清理——删除死代码、旧备份、生成文件

删除:
- warning/ src.bak.full/ llvm/ docs/superpowers/ (95MB+ 磁盘释放)
- Inline.cpp (621行,从未正确集成)
- PassManagerModule 死类 + 3个未使用pass声明 (PassManager.h)
- RunStrengthReduction/RunLoopUnrolling/RunLoopFission (LoopInfo.cpp 805行)
- FindInductionVars/AllUsesInLoop/IsSRLoopInvariantValue 死辅助函数
- 生成文件: Error.txt results.csv/json time_opt.txt settings.json
更新 .gitignore 防止未来污染
lzk
lzkk 4 days ago
parent 39e4dada13
commit 5d43539290

19
.gitignore vendored

@ -87,8 +87,27 @@ test_fail/
# Local test & build
# =========================
2026test/
2026test_results/
build_clang/
build_debug/
build_ubsan/
build_eval/
test5.sh
# =========================
# Tooling
# =========================
.claude/
.claudeignore
.codegraph/
.count_tmp.s
# =========================
# Generated reports
# =========================
results.csv
results.json
Error.txt
time_opt.txt
settings.json
指令数基线.md

@ -10,66 +10,12 @@ namespace ir {
void RunMem2Reg(Module& module);
void RunLICM(Module* module);
void RunStrengthReduction(Module* module);
void RunLoopUnrolling(Module* module);
void RunLoopFission(Module* module);
void RunConstFold(Module& module);
void RunConstProp(Module& module);
void RunDCE(Module& module);
void RunCFGSimplify(Module& module);
void RunCSE(Module& module);
class PassManagerModule {
public:
explicit PassManagerModule(Module* module) : module_(module) {}
void Run() {
if (!module_) {
return;
}
RunMem2Reg(*module_);
RunLICM(module_);
RunStrengthReduction(module_);
RunLoopFission(module_);
RunLoopUnrolling(module_);
bool changed = true;
int max_iterations = 10;
int iterations = 0;
while (changed && iterations < max_iterations) {
changed = false;
iterations++;
auto before = SerializeModule(*module_);
RunConstFold(*module_);
RunConstProp(*module_);
RunCFGSimplify(*module_);
RunCSE(*module_);
RunDCE(*module_);
auto after = SerializeModule(*module_);
changed = (before != after);
}
}
private:
std::string SerializeModule(const Module& module) {
std::ostringstream oss;
IRPrinter printer;
printer.Print(module, oss);
return oss.str();
}
Module* module_;
};
class PassManager {
public:
PassManager() = default;
@ -105,8 +51,6 @@ class PassManager {
changed = (before.str() != after.str());
}
}
private:
};
} // namespace ir

@ -592,935 +592,10 @@ namespace ir
int step_sign = 1;
};
std::vector<InductionVar> FindInductionVars(const LoopInfo *loop)
{
std::vector<InductionVar> inductions;
for (auto &inst : loop->header->GetInstructions())
{
auto *phi = dynamic_cast<PhiInst *>(inst.get());
if (!phi)
continue;
if (!phi->GetType()->IsInt32())
continue;
if (phi->GetNumOperands() < 4 || phi->GetNumOperands() % 2 != 0)
continue;
Value *outside_val = nullptr;
BasicBlock *outside_block = nullptr;
Value *common_step = nullptr;
int common_step_sign = 0;
Instruction *step_inst = nullptr;
BasicBlock *latch_block = nullptr;
bool valid = true;
for (size_t i = 0; i < phi->GetNumOperands(); i += 2)
{
auto *val = phi->GetOperand(i);
auto *bb = dynamic_cast<BasicBlock *>(phi->GetOperand(i + 1));
if (!bb)
{
valid = false;
break;
}
if (loop->blocks.count(bb) == 0)
{
if (outside_val != nullptr)
{
valid = false;
break;
}
outside_val = val;
outside_block = bb;
}
else
{
auto *bin_inst = dynamic_cast<BinaryInst *>(val);
if (!bin_inst)
{
valid = false;
break;
}
Value *step = nullptr;
int step_sign = 0;
if (bin_inst->GetOpcode() == Opcode::Add)
{
if (bin_inst->GetLhs() == phi)
{
step = bin_inst->GetRhs();
step_sign = 1;
}
else if (bin_inst->GetRhs() == phi)
{
step = bin_inst->GetLhs();
step_sign = 1;
}
}
else if (bin_inst->GetOpcode() == Opcode::Sub)
{
if (bin_inst->GetLhs() == phi)
{
step = bin_inst->GetRhs();
step_sign = -1;
}
}
if (!step || step_sign == 0)
{
valid = false;
break;
}
if (!dynamic_cast<ConstantInt *>(step))
{
valid = false;
break;
}
if (common_step_sign == 0)
{
common_step = step;
common_step_sign = step_sign;
step_inst = bin_inst;
latch_block = bb;
}
else
{
auto *prev_const = dynamic_cast<ConstantInt *>(common_step);
auto *cur_const = dynamic_cast<ConstantInt *>(step);
if (!prev_const || !cur_const ||
prev_const->GetValue() * common_step_sign !=
cur_const->GetValue() * step_sign)
{
valid = false;
break;
}
}
}
}
if (!valid || !outside_val || common_step_sign == 0 || !step_inst || !latch_block)
continue;
InductionVar iv;
iv.phi = phi;
iv.init_val = outside_val;
iv.init_block = outside_block;
iv.step_val = common_step;
iv.step_inst = step_inst;
iv.latch_block = latch_block;
iv.step_sign = common_step_sign;
inductions.push_back(iv);
if (kDebugSR)
{
std::cerr << "[SR] Found induction var: " << phi->GetName()
<< " init=" << (outside_val ? outside_val->GetName() : "null")
<< " step=" << (common_step_sign > 0 ? "+" : "-") << common_step->GetName()
<< " operands=" << phi->GetNumOperands() << std::endl;
}
}
return inductions;
}
bool IsSRLoopInvariantValue(Value *val, const std::set<BasicBlock *> &loop_blocks)
{
if (!val)
return false;
if (dynamic_cast<ConstantInt *>(val))
return true;
if (dynamic_cast<ConstantFloat *>(val))
return true;
if (dynamic_cast<Argument *>(val))
return true;
if (dynamic_cast<GlobalValue *>(val))
return true;
auto *inst = dynamic_cast<Instruction *>(val);
if (!inst)
return false;
auto *parent = inst->GetParent();
if (parent && loop_blocks.count(parent) == 0)
return true;
return false;
}
bool AllUsesInLoop(Value *val, const std::set<BasicBlock *> &loop_blocks)
{
for (auto &use : val->GetUses())
{
auto *user = use.GetUser();
if (!user)
continue;
auto *inst = dynamic_cast<Instruction *>(user);
if (!inst)
continue;
auto *parent = inst->GetParent();
if (!parent || loop_blocks.count(parent) == 0)
return false;
}
return true;
}
void RunStrengthReductionOnFunction(Function *func, Module *module)
{
int block_count = func->GetBlocks().size();
if (block_count > 500)
return;
auto loops = FindLoops(func);
if (loops.empty())
return;
auto &ctx = module->GetContext();
for (auto &loop : loops)
{
if (!loop->preheader)
continue;
auto inductions = FindInductionVars(loop.get());
if (inductions.empty())
continue;
for (auto &iv : inductions)
{
std::vector<std::pair<BinaryInst *, Value *>> muls_to_reduce;
for (auto *bb : loop->blocks)
{
for (auto &inst : bb->GetInstructions())
{
auto *mul = dynamic_cast<BinaryInst *>(inst.get());
if (!mul || mul->GetOpcode() != Opcode::Mul)
continue;
if (mul == iv.step_inst)
continue;
Value *other = nullptr;
if (mul->GetLhs() == iv.phi)
{
other = mul->GetRhs();
}
else if (mul->GetRhs() == iv.phi)
{
other = mul->GetLhs();
}
else
{
continue;
}
if (other == iv.phi)
continue;
if (!IsSRLoopInvariantValue(other, loop->blocks))
continue;
if (!AllUsesInLoop(mul, loop->blocks))
continue;
muls_to_reduce.push_back(std::make_pair(mul, other));
}
}
for (auto &entry : muls_to_reduce)
{
auto *mul = entry.first;
auto *k = entry.second;
auto *derived_phi = loop->header->Prepend<PhiInst>(iv.phi->GetType(), ctx.NextTemp());
Value *init_val = nullptr;
auto *init_const = dynamic_cast<ConstantInt *>(iv.init_val);
if (init_const && init_const->GetValue() == 0)
{
init_val = ctx.GetConstInt(0);
}
else
{
auto init_mul = std::make_unique<BinaryInst>(
Opcode::Mul, iv.init_val->GetType(), iv.init_val, k, ctx.NextTemp());
init_val = init_mul.get();
loop->preheader->InsertInstructionBeforeTerminator(std::move(init_mul));
}
Value *increment = nullptr;
auto *step_const = dynamic_cast<ConstantInt *>(iv.step_val);
int step_v = step_const ? step_const->GetValue() : 0;
int effective_step = step_v * iv.step_sign;
if (effective_step == 1)
{
increment = k;
}
else if (effective_step == -1)
{
auto neg = std::make_unique<BinaryInst>(
Opcode::Sub, k->GetType(), ctx.GetConstInt(0), k, ctx.NextTemp());
increment = neg.get();
loop->preheader->InsertInstructionBeforeTerminator(std::move(neg));
}
else
{
auto *step_for_mul = ctx.GetConstInt(effective_step);
auto step_mul = std::make_unique<BinaryInst>(
Opcode::Mul, k->GetType(), step_for_mul, k, ctx.NextTemp());
increment = step_mul.get();
loop->preheader->InsertInstructionBeforeTerminator(std::move(step_mul));
}
derived_phi->AddOperand(init_val);
derived_phi->AddOperand(iv.init_block);
for (size_t i = 0; i < iv.phi->GetNumOperands(); i += 2)
{
auto *bb = dynamic_cast<BasicBlock *>(iv.phi->GetOperand(i + 1));
if (!bb)
continue;
if (loop->blocks.count(bb) == 0)
continue;
auto update_add = std::make_unique<BinaryInst>(
Opcode::Add, derived_phi->GetType(), derived_phi, increment, ctx.NextTemp());
auto *update_add_ptr = update_add.get();
bb->InsertInstructionBeforeTerminator(std::move(update_add));
derived_phi->AddOperand(update_add_ptr);
derived_phi->AddOperand(bb);
}
mul->ReplaceAllUsesWith(derived_phi);
if (kDebugSR)
{
std::cerr << "[SR] Replaced " << mul->GetName() << " = mul "
<< iv.phi->GetName() << ", " << k->GetName()
<< " with derived phi " << derived_phi->GetName() << std::endl;
}
}
}
}
}
std::unique_ptr<Instruction> CloneInstruction(Instruction *inst, Context &ctx,
std::unordered_map<Value *, Value *> &value_map)
{
Opcode op = inst->GetOpcode();
switch (op)
{
case Opcode::Add:
case Opcode::Sub:
case Opcode::Mul:
case Opcode::Div:
case Opcode::Mod:
case Opcode::Eq:
case Opcode::Ne:
case Opcode::Lt:
case Opcode::Le:
case Opcode::Gt:
case Opcode::Ge:
{
auto *bin = static_cast<BinaryInst *>(inst);
auto *lhs_orig = bin->GetLhs();
auto *rhs_orig = bin->GetRhs();
auto *lhs = value_map.count(lhs_orig) ? value_map[lhs_orig] : lhs_orig;
auto *rhs = value_map.count(rhs_orig) ? value_map[rhs_orig] : rhs_orig;
auto cloned = std::make_unique<BinaryInst>(op, inst->GetType(), lhs, rhs, ctx.NextTemp());
value_map[inst] = cloned.get();
return cloned;
}
case Opcode::SIToFP:
case Opcode::FPToSI:
case Opcode::ZExt:
{
auto *cast = static_cast<CastInst *>(inst);
auto *operand_orig = cast->GetOperandValue();
auto *operand = value_map.count(operand_orig) ? value_map[operand_orig] : operand_orig;
auto cloned = std::make_unique<CastInst>(op, inst->GetType(), operand, ctx.NextTemp());
value_map[inst] = cloned.get();
return cloned;
}
case Opcode::Load:
{
auto *load = static_cast<LoadInst *>(inst);
auto *ptr_orig = load->GetPtr();
auto *ptr = value_map.count(ptr_orig) ? value_map[ptr_orig] : ptr_orig;
auto cloned = std::make_unique<LoadInst>(inst->GetType(), ptr, ctx.NextTemp());
value_map[inst] = cloned.get();
return cloned;
}
case Opcode::Store:
{
auto *store = static_cast<StoreInst *>(inst);
auto *val_orig = store->GetValue();
auto *ptr_orig = store->GetPtr();
auto *val = value_map.count(val_orig) ? value_map[val_orig] : val_orig;
auto *ptr = value_map.count(ptr_orig) ? value_map[ptr_orig] : ptr_orig;
auto cloned = std::make_unique<StoreInst>(Type::GetVoidType(), val, ptr);
value_map[inst] = cloned.get();
return cloned;
}
case Opcode::GEP:
{
auto *gep = static_cast<GetElementPtrInst *>(inst);
auto *base_orig = gep->GetBasePtr();
auto *idx_orig = gep->GetIndex();
auto *base = value_map.count(base_orig) ? value_map[base_orig] : base_orig;
auto *idx = value_map.count(idx_orig) ? value_map[idx_orig] : idx_orig;
auto cloned = std::make_unique<GetElementPtrInst>(inst->GetType(), base, idx, ctx.NextTemp());
value_map[inst] = cloned.get();
return cloned;
}
case Opcode::Call:
{
auto *call = static_cast<CallInst *>(inst);
auto *callee = call->GetCallee();
std::vector<Value *> args;
for (size_t i = 0; i < call->GetNumArgs(); ++i)
{
auto *arg_orig = call->GetArg(i);
auto *arg = value_map.count(arg_orig) ? value_map[arg_orig] : arg_orig;
args.push_back(arg);
}
auto cloned = std::make_unique<CallInst>(inst->GetType(), callee, args, ctx.NextTemp());
value_map[inst] = cloned.get();
return cloned;
}
default:
return nullptr;
}
}
void RunLoopUnrollingOnFunction(Function *func, Module *module)
{
int block_count = func->GetBlocks().size();
if (block_count > 500)
return;
auto loops = FindLoops(func);
if (loops.empty())
return;
auto &ctx = module->GetContext();
const int unroll_factor = 2;
for (auto &loop : loops)
{
if (!loop->preheader)
continue;
if (loop->blocks.size() != 1)
continue;
auto *header = loop->header;
if (header != *loop->blocks.begin())
continue;
auto inductions = FindInductionVars(loop.get());
if (inductions.size() != 1)
continue;
auto &iv = inductions[0];
if (iv.step_sign != 1)
continue;
auto *step_const = dynamic_cast<ConstantInt *>(iv.step_val);
if (!step_const)
continue;
int step_v = step_const->GetValue();
if (step_v <= 0)
continue;
bool has_phi_other_than_iv = false;
bool has_call = false;
int body_inst_count = 0;
for (auto &inst : header->GetInstructions())
{
if (inst->IsTerminator())
continue;
auto *phi = dynamic_cast<PhiInst *>(inst.get());
if (phi)
{
if (phi != iv.phi)
has_phi_other_than_iv = true;
continue;
}
if (inst->GetOpcode() == Opcode::Call)
has_call = true;
body_inst_count++;
}
if (has_phi_other_than_iv)
continue;
if (has_call)
continue;
if (body_inst_count == 0 || body_inst_count > 50)
continue;
auto *cond_br = dynamic_cast<CondBranchInst *>(header->GetInstructions().back().get());
if (!cond_br)
continue;
auto header_succs = GetSuccessors(header);
if (header_succs.size() != 2)
continue;
bool branches_to_self = false;
for (auto *s : header_succs)
{
if (s == header)
branches_to_self = true;
}
if (!branches_to_self)
continue;
if (kDebugLU)
{
std::cerr << "[LU] Unrolling loop " << header->GetName()
<< " factor=" << unroll_factor
<< " iv=" << iv.phi->GetName()
<< " step=" << step_v
<< " body_insts=" << body_inst_count << std::endl;
}
std::vector<Instruction *> body_inst_ptrs;
for (auto &inst : header->GetInstructions())
{
if (inst->IsTerminator())
continue;
if (dynamic_cast<PhiInst *>(inst.get()))
continue;
body_inst_ptrs.push_back(inst.get());
}
std::vector<std::unique_ptr<Instruction>> owned_body;
std::unordered_map<Value *, Value *> orig_to_copy0;
for (auto *ptr : body_inst_ptrs)
{
auto owned = header->TakeInstruction(ptr);
if (owned)
{
orig_to_copy0[ptr] = ptr;
owned_body.push_back(std::move(owned));
}
}
for (int copy = 1; copy < unroll_factor; ++copy)
{
std::unordered_map<Value *, Value *> remap;
for (auto &inst : owned_body)
{
if (inst.get() == iv.step_inst)
{
auto *orig_step = static_cast<BinaryInst *>(iv.step_inst);
Value *lhs_orig = orig_step->GetLhs();
Value *rhs_orig = orig_step->GetRhs();
Value *lhs = orig_to_copy0.count(lhs_orig) ? orig_to_copy0[lhs_orig] : lhs_orig;
Value *rhs = rhs_orig;
if (copy == 1)
{
auto new_step = std::make_unique<BinaryInst>(
Opcode::Add, iv.phi->GetType(), lhs, rhs, ctx.NextTemp());
remap[iv.step_inst] = new_step.get();
orig_to_copy0[iv.step_inst] = new_step.get();
header->InsertInstructionBeforeTerminator(std::move(new_step));
}
continue;
}
auto cloned = CloneInstruction(inst.get(), ctx, orig_to_copy0);
if (cloned)
{
remap[inst.get()] = cloned.get();
orig_to_copy0[inst.get()] = cloned.get();
header->InsertInstructionBeforeTerminator(std::move(cloned));
}
}
}
for (auto &inst : owned_body)
{
inst->SetParent(nullptr);
header->InsertInstructionBeforeTerminator(std::move(inst));
}
auto old_step_owned = header->TakeInstruction(iv.step_inst);
auto *new_step_const = ctx.GetConstInt(step_v * unroll_factor);
auto new_step = std::make_unique<BinaryInst>(
Opcode::Add, iv.phi->GetType(), iv.phi, new_step_const, ctx.NextTemp());
auto *new_step_ptr = new_step.get();
header->InsertInstructionBeforeTerminator(std::move(new_step));
for (size_t i = 0; i < iv.phi->GetNumOperands(); i += 2)
{
auto *val = iv.phi->GetOperand(i);
if (val == iv.step_inst)
{
iv.phi->SetOperand(i, new_step_ptr);
}
}
if (kDebugLU)
{
std::cerr << "[LU] Unrolled loop " << header->GetName()
<< " factor=" << unroll_factor
<< " new_step=" << new_step_ptr->GetName() << std::endl;
}
}
}
void CollectDepChain(Instruction *inst, std::set<Instruction *> &chain,
const std::set<Instruction *> &all_body)
{
if (!inst || chain.count(inst))
return;
chain.insert(inst);
for (size_t i = 0; i < inst->GetNumOperands(); ++i)
{
auto *op = inst->GetOperand(i);
if (!op)
continue;
auto *op_inst = dynamic_cast<Instruction *>(op);
if (op_inst && all_body.count(op_inst))
{
CollectDepChain(op_inst, chain, all_body);
}
}
}
void RunLoopFissionOnFunction(Function *func, Module *module)
{
int block_count = func->GetBlocks().size();
if (block_count > 500)
return;
auto loops = FindLoops(func);
if (loops.empty())
return;
auto &ctx = module->GetContext();
for (auto &loop : loops)
{
if (!loop->preheader)
continue;
if (loop->blocks.size() != 1)
continue;
auto *header = loop->header;
if (header != *loop->blocks.begin())
continue;
auto inductions = FindInductionVars(loop.get());
if (inductions.size() != 1)
continue;
auto &iv = inductions[0];
if (iv.step_sign != 1)
continue;
auto *step_const = dynamic_cast<ConstantInt *>(iv.step_val);
if (!step_const)
continue;
int step_v = step_const->GetValue();
if (step_v <= 0)
continue;
auto *cond_br = dynamic_cast<CondBranchInst *>(header->GetInstructions().back().get());
if (!cond_br)
continue;
auto header_succs = GetSuccessors(header);
if (header_succs.size() != 2)
continue;
bool branches_to_self = false;
BasicBlock *exit_block = nullptr;
for (auto *s : header_succs)
{
if (s == header)
branches_to_self = true;
else
exit_block = s;
}
if (!branches_to_self || !exit_block)
continue;
std::vector<StoreInst *> store_insts;
std::set<Instruction *> all_body;
std::vector<Instruction *> body_order;
for (auto &inst : header->GetInstructions())
{
if (inst->IsTerminator())
continue;
if (dynamic_cast<PhiInst *>(inst.get()))
continue;
all_body.insert(inst.get());
body_order.push_back(inst.get());
auto *store = dynamic_cast<StoreInst *>(inst.get());
if (store)
store_insts.push_back(store);
}
if (store_insts.size() < 2)
continue;
bool has_call = false;
for (auto *inst : all_body)
{
if (inst->GetOpcode() == Opcode::Call)
{
has_call = true;
break;
}
}
if (has_call)
continue;
std::set<Value *> store_ptrs;
for (auto *store : store_insts)
{
store_ptrs.insert(store->GetPtr());
}
std::vector<std::set<Instruction *>> store_chains;
for (auto *store : store_insts)
{
std::set<Instruction *> chain;
CollectDepChain(store, chain, all_body);
store_chains.push_back(std::move(chain));
}
std::vector<int> group_id(store_insts.size(), -1);
int num_groups = 0;
for (size_t i = 0; i < store_insts.size(); ++i)
{
if (group_id[i] >= 0)
continue;
group_id[i] = num_groups;
for (size_t j = i + 1; j < store_insts.size(); ++j)
{
if (group_id[j] >= 0)
continue;
bool has_overlap = false;
for (auto *inst : store_chains[i])
{
if (store_chains[j].count(inst))
{
has_overlap = true;
break;
}
}
if (has_overlap)
{
group_id[j] = num_groups;
}
}
num_groups++;
}
if (num_groups < 2)
continue;
if (kDebugLF)
{
std::cerr << "[LF] Loop " << header->GetName()
<< " has " << store_insts.size() << " stores in "
<< num_groups << " groups" << std::endl;
}
std::vector<std::set<Instruction *>> group_insts(num_groups);
for (size_t i = 0; i < store_insts.size(); ++i)
{
int gid = group_id[i];
for (auto *inst : store_chains[i])
{
group_insts[gid].insert(inst);
}
}
for (auto *inst : all_body)
{
bool found = false;
for (auto &group : group_insts)
{
if (group.count(inst))
{
found = true;
break;
}
}
if (!found)
{
group_insts[0].insert(inst);
}
}
std::vector<std::vector<Instruction *>> group_ordered(num_groups);
for (auto *inst : body_order)
{
for (int g = 0; g < num_groups; ++g)
{
if (group_insts[g].count(inst))
{
group_ordered[g].push_back(inst);
break;
}
}
}
std::vector<Instruction *> first_group_insts = group_ordered[0];
std::vector<Instruction *> other_group_insts;
for (int g = 1; g < num_groups; ++g)
{
for (auto *inst : group_ordered[g])
{
other_group_insts.push_back(inst);
}
}
if (other_group_insts.empty())
continue;
bool first_group_stores_to_array = false;
for (auto *inst : first_group_insts)
{
auto *store = dynamic_cast<StoreInst *>(inst);
if (store)
{
auto *ptr = store->GetPtr();
if (ptr && dynamic_cast<GetElementPtrInst *>(ptr))
{
first_group_stores_to_array = true;
break;
}
}
}
bool other_group_stores_to_array = false;
for (auto *inst : other_group_insts)
{
auto *store = dynamic_cast<StoreInst *>(inst);
if (store)
{
auto *ptr = store->GetPtr();
if (ptr && dynamic_cast<GetElementPtrInst *>(ptr))
{
other_group_stores_to_array = true;
break;
}
}
}
if (!first_group_stores_to_array || !other_group_stores_to_array)
continue;
if (kDebugLF)
{
std::cerr << "[LF] Fissioning loop " << header->GetName()
<< " into 2 loops (first=" << first_group_insts.size()
<< " insts, other=" << other_group_insts.size() << " insts)" << std::endl;
}
auto *new_header = func->CreateBlock(header->GetName() + std::string(".fission"));
auto *new_phi = new_header->Prepend<PhiInst>(iv.phi->GetType(), ctx.NextTemp());
new_phi->AddOperand(iv.init_val);
new_phi->AddOperand(iv.init_block);
std::unordered_map<Value *, Value *> value_map;
value_map[iv.phi] = new_phi;
for (auto *inst : other_group_insts)
{
auto cloned = CloneInstruction(inst, ctx, value_map);
if (cloned)
{
value_map[inst] = cloned.get();
new_header->InsertInstructionBeforeTerminator(std::move(cloned));
}
}
auto *new_step_const = ctx.GetConstInt(step_v);
auto new_step = std::make_unique<BinaryInst>(
Opcode::Add, new_phi->GetType(), new_phi, new_step_const, ctx.NextTemp());
auto *new_step_ptr = new_step.get();
new_header->InsertInstructionBeforeTerminator(std::move(new_step));
new_phi->AddOperand(new_step_ptr);
new_phi->AddOperand(new_header);
auto *cond_inst = dynamic_cast<BinaryInst *>(cond_br->GetCond());
Value *new_cond = nullptr;
if (cond_inst)
{
auto *lhs_orig = cond_inst->GetLhs();
auto *rhs_orig = cond_inst->GetRhs();
auto *lhs = value_map.count(lhs_orig) ? value_map[lhs_orig] : lhs_orig;
auto *rhs = value_map.count(rhs_orig) ? value_map[rhs_orig] : rhs_orig;
auto new_cmp = std::make_unique<BinaryInst>(
cond_inst->GetOpcode(), cond_inst->GetType(), lhs, rhs, ctx.NextTemp());
new_cond = new_cmp.get();
new_header->InsertInstructionBeforeTerminator(std::move(new_cmp));
}
if (!new_cond)
new_cond = cond_br->GetCond();
auto new_br = std::make_unique<CondBranchInst>(
Type::GetVoidType(), new_cond, new_header, exit_block);
new_header->InsertInstructionBeforeTerminator(std::move(new_br));
for (auto *inst : other_group_insts)
{
auto owned = header->TakeInstruction(inst);
}
auto *old_br = dynamic_cast<CondBranchInst *>(header->GetInstructions().back().get());
if (old_br)
{
auto old_br_owned = header->TakeInstruction(old_br);
auto new_orig_br = std::make_unique<CondBranchInst>(
Type::GetVoidType(), old_br->GetCond(), new_header, exit_block);
header->InsertInstructionBeforeTerminator(std::move(new_orig_br));
}
if (kDebugLF)
{
std::cerr << "[LF] Created fission loop " << new_header->GetName()
<< " with " << other_group_insts.size() << " instructions" << std::endl;
}
}
}
}
} // namespace
void RunLICM(Module *module)
{
@ -1536,46 +611,4 @@ namespace ir
}
}
void RunStrengthReduction(Module *module)
{
if (!module)
return;
for (auto &func : module->GetFunctions())
{
if (func && !func->IsExternal())
{
RunStrengthReductionOnFunction(func.get(), module);
}
}
}
void RunLoopUnrolling(Module *module)
{
if (!module)
return;
for (auto &func : module->GetFunctions())
{
if (func && !func->IsExternal())
{
RunLoopUnrollingOnFunction(func.get(), module);
}
}
}
void RunLoopFission(Module *module)
{
if (!module)
return;
for (auto &func : module->GetFunctions())
{
if (func && !func->IsExternal())
{
RunLoopFissionOnFunction(func.get(), module);
}
}
}
}
} // namespace ir

@ -1,621 +0,0 @@
#include "ir/IR.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace ir {
namespace {
constexpr bool kDebugInline = false;
constexpr int kMaxInlineSize = 200;
constexpr int kMaxMultiBlockInlineSize = 50;
bool IsRecursive(Function* func) {
if (!func || func->IsExternal()) return true;
for (auto& bb : func->GetBlocks()) {
for (auto& inst : bb->GetInstructions()) {
if (auto* call = dynamic_cast<CallInst*>(inst.get())) {
if (call->GetCallee() == func) return true;
}
}
}
return false;
}
int CountInstructions(Function* func) {
int count = 0;
for (auto& bb : func->GetBlocks()) {
count += bb->GetInstructions().size();
}
return count;
}
Value* MapValue(Value* v, const std::unordered_map<Value*, Value*>& value_map) {
auto it = value_map.find(v);
if (it != value_map.end()) return it->second;
return v;
}
void CloneInstruction(Instruction* inst,
const std::unordered_map<Value*, Value*>& value_map,
std::vector<std::unique_ptr<Instruction>>& out) {
std::unique_ptr<Instruction> cloned;
switch (inst->GetOpcode()) {
case Opcode::Add:
case Opcode::Sub:
case Opcode::Mul:
case Opcode::Div:
case Opcode::Mod:
case Opcode::Eq:
case Opcode::Ne:
case Opcode::Lt:
case Opcode::Le:
case Opcode::Gt:
case Opcode::Ge: {
auto* bin = static_cast<BinaryInst*>(inst);
Value* lhs = MapValue(bin->GetLhs(), value_map);
Value* rhs = MapValue(bin->GetRhs(), value_map);
cloned = std::make_unique<BinaryInst>(
inst->GetOpcode(), inst->GetType(), lhs, rhs,
inst->GetName() + ".inl");
break;
}
case Opcode::SIToFP:
case Opcode::FPToSI:
case Opcode::ZExt: {
auto* cast = static_cast<CastInst*>(inst);
Value* operand = MapValue(cast->GetOperandValue(), value_map);
cloned = std::make_unique<CastInst>(
inst->GetOpcode(), inst->GetType(), operand,
inst->GetName() + ".inl");
break;
}
case Opcode::Load: {
auto* load = static_cast<LoadInst*>(inst);
Value* ptr = MapValue(load->GetPtr(), value_map);
cloned = std::make_unique<LoadInst>(
load->GetType(), ptr, inst->GetName() + ".inl");
break;
}
case Opcode::Store: {
auto* store = static_cast<StoreInst*>(inst);
Value* val = MapValue(store->GetValue(), value_map);
Value* ptr = MapValue(store->GetPtr(), value_map);
cloned = std::make_unique<StoreInst>(Type::GetVoidType(), val, ptr);
break;
}
case Opcode::GEP: {
auto* gep = static_cast<GetElementPtrInst*>(inst);
Value* base = MapValue(gep->GetBasePtr(), value_map);
Value* index = MapValue(gep->GetIndex(), value_map);
cloned = std::make_unique<GetElementPtrInst>(
gep->GetType(), base, index, inst->GetName() + ".inl");
break;
}
case Opcode::Call: {
auto* orig_call = static_cast<CallInst*>(inst);
Function* callee_func = orig_call->GetCallee();
std::vector<Value*> args;
for (size_t i = 0; i < orig_call->GetNumArgs(); ++i) {
args.push_back(MapValue(orig_call->GetArg(i), value_map));
}
cloned = std::make_unique<CallInst>(
orig_call->GetType(), callee_func, args,
inst->GetName() + ".inl");
break;
}
case Opcode::Alloca: {
auto* alloca_inst = static_cast<AllocaInst*>(inst);
if (alloca_inst->IsArrayAlloca()) {
Value* count = MapValue(alloca_inst->GetCount(), value_map);
cloned = std::make_unique<AllocaInst>(
alloca_inst->GetElementType(),
alloca_inst->GetName() + ".inl", count);
} else {
cloned = std::make_unique<AllocaInst>(
alloca_inst->GetElementType(),
alloca_inst->GetName() + ".inl");
}
break;
}
default:
break;
}
if (cloned) {
out.push_back(std::move(cloned));
}
}
bool InlineCall(CallInst* call, Function* callee, Function* caller,
BasicBlock* call_bb, Module* module) {
if (kDebugInline) {
std::cerr << "[Inline] Inlining " << callee->GetName()
<< " (" << callee->GetBlocks().size() << " blocks)"
<< " into " << caller->GetName() << std::endl;
}
bool is_single_block = (callee->GetBlocks().size() == 1);
std::unordered_map<Value*, Value*> value_map;
for (auto& gv : module->GetGlobals()) {
value_map[gv.get()] = gv.get();
}
for (auto& other_func : module->GetFunctions()) {
value_map[other_func.get()] = other_func.get();
}
for (auto& arg : caller->GetParams()) {
value_map[arg.get()] = arg.get();
}
{
auto& blocks = caller->GetBlocks();
for (size_t bi = 0; bi < blocks.size(); ++bi) {
auto& insts = blocks[bi]->GetInstructions();
for (size_t ii = 0; ii < insts.size(); ++ii) {
value_map[insts[ii].get()] = insts[ii].get();
}
}
}
for (size_t i = 0; i < callee->GetParams().size(); ++i) {
auto* formal_arg = callee->GetParams()[i].get();
auto* actual_arg = call->GetArg(i);
value_map[formal_arg] = actual_arg;
}
auto& call_bb_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
call_bb->GetInstructions());
size_t call_idx = 0;
for (size_t i = 0; i < call_bb_insts.size(); ++i) {
if (call_bb_insts[i].get() == call) {
call_idx = i;
break;
}
}
if (is_single_block) {
auto* callee_entry = callee->GetEntry();
Value* return_value = nullptr;
std::vector<std::unique_ptr<Instruction>> cloned_insts;
std::vector<std::unique_ptr<Instruction>> alloca_insts;
for (auto& inst : callee_entry->GetInstructions()) {
if (inst->GetOpcode() == Opcode::Alloca) {
std::vector<std::unique_ptr<Instruction>> tmp;
CloneInstruction(inst.get(), value_map, tmp);
if (!tmp.empty()) {
value_map[inst.get()] = tmp.back().get();
alloca_insts.push_back(std::move(tmp.back()));
}
continue;
}
if (inst->GetOpcode() == Opcode::Ret) {
auto* ret_inst = static_cast<ReturnInst*>(inst.get());
if (ret_inst->HasValue()) {
return_value = MapValue(ret_inst->GetValue(), value_map);
}
continue;
}
std::vector<std::unique_ptr<Instruction>> tmp;
CloneInstruction(inst.get(), value_map, tmp);
if (!tmp.empty()) {
value_map[inst.get()] = tmp.back().get();
cloned_insts.push_back(std::move(tmp.back()));
}
}
if (return_value) {
call->ReplaceAllUsesWith(return_value);
} else if (!call->GetType()->IsVoid()) {
call->ReplaceAllUsesWith(module->GetContext().GetConstInt(0));
}
auto* entry_bb = caller->GetEntry();
auto& entry_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
entry_bb->GetInstructions());
size_t alloca_insert_pos = 0;
for (size_t i = 0; i < entry_insts.size(); ++i) {
if (entry_insts[i]->GetOpcode() == Opcode::Alloca) {
alloca_insert_pos = i + 1;
} else {
break;
}
}
for (auto& alloca : alloca_insts) {
alloca->SetParent(entry_bb);
entry_insts.insert(entry_insts.begin() + alloca_insert_pos, std::move(alloca));
alloca_insert_pos++;
}
size_t insert_pos = call_idx;
for (auto& cloned : cloned_insts) {
cloned->SetParent(call_bb);
call_bb_insts.insert(call_bb_insts.begin() + insert_pos, std::move(cloned));
insert_pos++;
}
for (size_t i = 0; i < call_bb_insts.size(); ++i) {
if (call_bb_insts[i].get() == call) {
for (size_t oi = 0; oi < call->GetNumOperands(); ++oi) {
auto* op = call->GetOperand(oi);
if (auto* op_inst = dynamic_cast<Instruction*>(op)) {
op_inst->RemoveUse(call, oi);
}
}
call_bb_insts.erase(call_bb_insts.begin() + i);
break;
}
}
return true;
}
// === Multi-block inlining ===
// 1. Create after_bb: move instructions after call from call_bb to after_bb
BasicBlock* after_bb = caller->CreateBlock(call_bb->GetName() + ".after");
std::vector<std::unique_ptr<Instruction>> after_insts;
for (size_t i = call_idx + 1; i < call_bb_insts.size(); ++i) {
after_insts.push_back(std::move(call_bb_insts[i]));
}
call_bb_insts.resize(call_idx + 1);
for (auto& inst : after_insts) {
inst->SetParent(after_bb);
after_bb->GetMutablePredecessors();
}
auto& after_bb_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
after_bb->GetInstructions());
for (auto& inst : after_insts) {
after_bb_insts.push_back(std::move(inst));
}
// 1b. Fix phi nodes: any phi that had call_bb as predecessor should now use after_bb
for (auto& bb : caller->GetBlocks()) {
for (auto& inst : bb->GetInstructions()) {
if (inst->GetOpcode() != Opcode::Phi) break;
auto* phi = static_cast<PhiInst*>(inst.get());
size_t num_ops = phi->GetNumOperands();
for (size_t i = 0; i + 1 < num_ops; i += 2) {
auto* bb_ptr = dynamic_cast<BasicBlock*>(phi->GetOperand(i + 1));
if (bb_ptr == call_bb) {
phi->SetOperand(i + 1, after_bb);
}
}
}
}
// 2. Create cloned blocks for callee
std::unordered_map<BasicBlock*, BasicBlock*> bb_map;
std::vector<BasicBlock*> cloned_bbs;
for (auto& bb : callee->GetBlocks()) {
BasicBlock* cloned_bb = caller->CreateBlock(bb->GetName() + ".inl");
bb_map[bb.get()] = cloned_bb;
cloned_bbs.push_back(cloned_bb);
}
BasicBlock* cloned_entry = bb_map[callee->GetEntry()];
// 2b. Reorder blocks: move cloned blocks and after_bb right after call_bb
// IMPORTANT: after_bb must come AFTER all cloned blocks, because
// after_bb may use values defined in the cloned blocks (e.g., call results
// from nested inlines). The lowering processes blocks in order, so values
// must be defined before they are used.
{
auto& blocks = const_cast<std::vector<std::unique_ptr<BasicBlock>>&>(caller->GetBlocks());
std::vector<size_t> move_indices;
for (auto* cb : cloned_bbs) {
for (size_t i = 0; i < blocks.size(); ++i) {
if (blocks[i].get() == cb) { move_indices.push_back(i); break; }
}
}
for (size_t i = 0; i < blocks.size(); ++i) {
if (blocks[i].get() == after_bb) { move_indices.push_back(i); break; }
}
size_t call_bb_idx = 0;
for (size_t i = 0; i < blocks.size(); ++i) {
if (blocks[i].get() == call_bb) { call_bb_idx = i; break; }
}
std::vector<std::unique_ptr<BasicBlock>> extracted;
for (auto idx : move_indices) {
extracted.push_back(std::move(blocks[idx]));
}
size_t insert_pos = call_bb_idx + 1;
for (auto& b : extracted) {
blocks.insert(blocks.begin() + insert_pos, std::move(b));
insert_pos++;
}
blocks.erase(std::remove_if(blocks.begin(), blocks.end(),
[](const std::unique_ptr<BasicBlock>& b) { return b == nullptr; }),
blocks.end());
}
// 4. Create alloca for return value (if non-void)
AllocaInst* ret_alloca = nullptr;
bool has_return = !call->GetType()->IsVoid();
if (has_return) {
auto* entry_bb = caller->GetEntry();
auto& entry_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
entry_bb->GetInstructions());
auto alloca = std::make_unique<AllocaInst>(call->GetType(), "__ret.inl");
alloca->SetParent(entry_bb);
ret_alloca = static_cast<AllocaInst*>(alloca.get());
size_t alloca_insert_pos = 0;
for (size_t i = 0; i < entry_insts.size(); ++i) {
if (entry_insts[i]->GetOpcode() == Opcode::Alloca) {
alloca_insert_pos = i + 1;
} else {
break;
}
}
entry_insts.insert(entry_insts.begin() + alloca_insert_pos, std::move(alloca));
}
// 5. Clone all instructions from callee blocks into cloned blocks
// Pass 1: Create cloned instructions with original operands, build value_map
std::vector<std::unique_ptr<Instruction>> alloca_insts;
std::vector<std::pair<Instruction*, Instruction*>> remap_list;
for (auto& bb : callee->GetBlocks()) {
BasicBlock* cloned_bb = bb_map[bb.get()];
auto& cloned_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
cloned_bb->GetInstructions());
for (auto& inst : bb->GetInstructions()) {
if (inst->GetOpcode() == Opcode::Alloca) {
std::vector<std::unique_ptr<Instruction>> tmp;
CloneInstruction(inst.get(), value_map, tmp);
if (!tmp.empty()) {
value_map[inst.get()] = tmp.back().get();
alloca_insts.push_back(std::move(tmp.back()));
}
continue;
}
if (inst->GetOpcode() == Opcode::Phi) {
auto* phi = static_cast<PhiInst*>(inst.get());
auto new_phi = std::make_unique<PhiInst>(phi->GetType(), phi->GetName() + ".inl");
new_phi->SetParent(cloned_bb);
value_map[inst.get()] = new_phi.get();
cloned_insts.push_back(std::move(new_phi));
continue;
}
if (inst->IsTerminator()) continue;
std::vector<std::unique_ptr<Instruction>> tmp;
CloneInstruction(inst.get(), value_map, tmp);
if (!tmp.empty()) {
tmp.back()->SetParent(cloned_bb);
value_map[inst.get()] = tmp.back().get();
remap_list.push_back({inst.get(), tmp.back().get()});
cloned_insts.push_back(std::move(tmp.back()));
}
}
}
// Pass 1b: Remap operands of cloned instructions now that value_map is complete
for (auto& [orig, cloned] : remap_list) {
for (size_t i = 0; i < orig->GetNumOperands(); ++i) {
Value* orig_op = orig->GetOperand(i);
Value* mapped = MapValue(orig_op, value_map);
if (mapped != orig_op) {
cloned->SetOperand(i, mapped);
}
}
}
// Pass 2: fill phi operands and handle terminators
for (auto& bb : callee->GetBlocks()) {
BasicBlock* cloned_bb = bb_map[bb.get()];
auto& cloned_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
cloned_bb->GetInstructions());
for (auto& inst : bb->GetInstructions()) {
if (inst->GetOpcode() == Opcode::Phi) {
auto* orig_phi = static_cast<PhiInst*>(inst.get());
auto* cloned_phi = static_cast<PhiInst*>(value_map[orig_phi]);
if (!cloned_phi) continue;
for (size_t i = 0; i < orig_phi->GetNumOperands(); i += 2) {
Value* val = MapValue(orig_phi->GetOperand(i), value_map);
auto* orig_pred = static_cast<BasicBlock*>(orig_phi->GetOperand(i + 1));
auto pred_it = bb_map.find(orig_pred);
BasicBlock* pred = (pred_it != bb_map.end()) ? pred_it->second : orig_pred;
cloned_phi->AddOperand(val);
cloned_phi->AddOperand(pred);
}
continue;
}
if (inst->GetOpcode() == Opcode::Ret) {
auto* ret_inst = static_cast<ReturnInst*>(inst.get());
if (ret_inst->HasValue() && has_return) {
Value* ret_val = MapValue(ret_inst->GetValue(), value_map);
auto store = std::make_unique<StoreInst>(
Type::GetVoidType(), ret_val, ret_alloca);
store->SetParent(cloned_bb);
cloned_insts.push_back(std::move(store));
}
auto br = std::make_unique<BranchInst>(Type::GetVoidType(), after_bb);
br->SetParent(cloned_bb);
cloned_insts.push_back(std::move(br));
continue;
}
if (inst->GetOpcode() == Opcode::Br) {
auto* br = static_cast<BranchInst*>(inst.get());
auto it = bb_map.find(br->GetTarget());
BasicBlock* target = (it != bb_map.end()) ? it->second : br->GetTarget();
auto new_br = std::make_unique<BranchInst>(Type::GetVoidType(), target);
new_br->SetParent(cloned_bb);
cloned_insts.push_back(std::move(new_br));
continue;
}
if (inst->GetOpcode() == Opcode::CondBr) {
auto* cbr = static_cast<CondBranchInst*>(inst.get());
Value* cond = MapValue(cbr->GetCond(), value_map);
auto true_it = bb_map.find(cbr->GetTrueTarget());
BasicBlock* true_target = (true_it != bb_map.end()) ? true_it->second : cbr->GetTrueTarget();
auto false_it = bb_map.find(cbr->GetFalseTarget());
BasicBlock* false_target = (false_it != bb_map.end()) ? false_it->second : cbr->GetFalseTarget();
auto new_cbr = std::make_unique<CondBranchInst>(
Type::GetVoidType(), cond, true_target, false_target);
new_cbr->SetParent(cloned_bb);
cloned_insts.push_back(std::move(new_cbr));
continue;
}
}
}
// 7. Insert alloca_insts into caller entry
{
auto* entry_bb = caller->GetEntry();
auto& entry_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(
entry_bb->GetInstructions());
size_t alloca_insert_pos = 0;
for (size_t i = 0; i < entry_insts.size(); ++i) {
if (entry_insts[i]->GetOpcode() == Opcode::Alloca) {
alloca_insert_pos = i + 1;
} else {
break;
}
}
for (auto& alloca : alloca_insts) {
alloca->SetParent(entry_bb);
entry_insts.insert(entry_insts.begin() + alloca_insert_pos, std::move(alloca));
alloca_insert_pos++;
}
}
// 8-9. Handle return value and remove call
auto call_type = call->GetType();
if (has_return) {
auto load_ret = std::make_unique<LoadInst>(
call_type, ret_alloca, "__ret.load.inl");
load_ret->SetParent(after_bb);
Value* ret_val = load_ret.get();
after_bb_insts.insert(after_bb_insts.begin(), std::move(load_ret));
call->ReplaceAllUsesWith(ret_val);
} else {
call->ReplaceAllUsesWith(module->GetContext().GetConstInt(0));
}
// Remove the call and add branch to cloned_entry
for (size_t i = 0; i < call_bb_insts.size(); ++i) {
if (call_bb_insts[i].get() == call) {
for (size_t oi = 0; oi < call->GetNumOperands(); ++oi) {
auto* op = call->GetOperand(oi);
if (auto* op_inst = dynamic_cast<Instruction*>(op)) {
op_inst->RemoveUse(call, oi);
}
}
call_bb_insts.erase(call_bb_insts.begin() + i);
break;
}
}
auto br_to_entry = std::make_unique<BranchInst>(Type::GetVoidType(), cloned_entry);
br_to_entry->SetParent(call_bb);
call_bb_insts.push_back(std::move(br_to_entry));
if (kDebugInline) {
std::cerr << "[Inline] Done inlining " << callee->GetName() << std::endl;
}
return true;
}
} // namespace
void RunInline(Module* module) {
if (!module) return;
std::unordered_map<std::string, int> func_sizes;
std::unordered_set<std::string> recursive_funcs;
for (auto& func : module->GetFunctions()) {
if (func->IsExternal()) continue;
func_sizes[func->GetName()] = CountInstructions(func.get());
if (IsRecursive(func.get())) {
recursive_funcs.insert(func->GetName());
}
}
struct InlineSite {
CallInst* call;
Function* caller;
BasicBlock* call_bb;
};
std::vector<InlineSite> inline_sites;
for (auto& caller : module->GetFunctions()) {
if (caller->IsExternal()) continue;
for (auto& bb : caller->GetBlocks()) {
for (auto& inst : bb->GetInstructions()) {
auto* call = dynamic_cast<CallInst*>(inst.get());
if (!call) continue;
auto* callee = call->GetCallee();
if (!callee) continue;
if (callee->IsExternal()) continue;
if (recursive_funcs.count(callee->GetName())) continue;
auto size_it = func_sizes.find(callee->GetName());
int callee_size = (size_it != func_sizes.end()) ? size_it->second : 9999;
if (callee_size > kMaxInlineSize) continue;
if (callee == caller.get()) continue;
if (callee->GetBlocks().size() > 1 && callee_size > kMaxMultiBlockInlineSize) continue;
inline_sites.push_back({call, caller.get(), bb.get()});
}
}
}
for (auto& site : inline_sites) {
auto* callee = site.call->GetCallee();
if (!callee) continue;
bool still_valid = false;
BasicBlock* actual_bb = nullptr;
for (auto& bb : site.caller->GetBlocks()) {
for (auto& inst : bb->GetInstructions()) {
if (inst.get() == site.call) {
still_valid = true;
actual_bb = bb.get();
break;
}
}
if (still_valid) break;
}
if (!still_valid) continue;
InlineCall(site.call, callee, site.caller, actual_bb, module);
}
}
} // namespace ir
Loading…
Cancel
Save