diff --git a/src/RISCv32Backend.cpp b/src/RISCv32Backend.cpp index 1d8d917..271d2c8 100644 --- a/src/RISCv32Backend.cpp +++ b/src/RISCv32Backend.cpp @@ -6,7 +6,7 @@ #include #include // For std::function -#define DEBUG 0 +#define DEBUG 1 // 开启DEBUG以便查看新输出 namespace sysy { // 可用于分配的通用寄存器 @@ -109,6 +109,7 @@ std::string RISCv32CodeGen::function_gen(Function* func) { ss << func->getName() << ":\n"; // 函数入口标签 // 执行寄存器分配 + // 注意:vreg_counter 和 value_vreg_map 在 register_allocation 内部会被初始化 auto alloc = register_allocation(func); int stack_size = alloc.stack_size; @@ -162,11 +163,10 @@ std::string RISCv32CodeGen::basicBlock_gen(BasicBlock* bb, const RegAllocResult& } ss << bb_name << ":\n"; // 基本块标签 - // 为每个基本块重置虚拟寄存器映射和计数器,因为 DAG 是按块构建的 - // 注意:这里的重置可能会影响跨块的活跃性分析,如果 DAG 构建是跨块的,则不能在这里重置 - // 但目前 DAG 是按块构建,所以在此重置是合理的。 - value_vreg_map.clear(); - vreg_counter = 0; // 为每个块重置虚拟寄存器计数器 + // !!! 重要的修改:此处不再清除 value_vreg_map 和 vreg_counter。 + // !!! 这些映射在 function_gen -> register_allocation 阶段为整个函数建立。 + // value_vreg_map.clear(); // 移除此行 + // vreg_counter = 0; // 移除此行 // 构建当前基本块的 DAG // 注意:DAGNode 的唯一性在当前函数范围内是重要的, @@ -266,22 +266,21 @@ std::vector> RISCv32CodeGen::build_dag( // 为产生结果的值分配虚拟寄存器 // 注意:这里的vreg分配是在每个块中独立进行的,但寄存器分配器是在函数级别运行的 // 我们在寄存器分配前,已经为整个函数的所有value预分配了vreg - if (val && value_vreg_map.count(val)) { - node->result_vreg = value_vreg_map.at(val); - } else if (val && kind != DAGNode::STORE && kind != DAGNode::RETURN && kind != DAGNode::BRANCH) { - // 如果一个值(例如常量)在预分配阶段没有vreg,这里可以给一个 - if(value_vreg_map.find(val) == value_vreg_map.end()){ - value_vreg_map[val] = "v" + std::to_string(vreg_counter++); - } + // 此处的逻辑应完全依赖于 register_allocation 阶段已经建立的 value_vreg_map + // 并且 AllocaInst 不应在此处获取 result_vreg,因为它不映射到物理寄存器。 + if (val && value_vreg_map.count(val) && !dynamic_cast(val)) { // 排除 AllocaInst node->result_vreg = value_vreg_map.at(val); } - + // 如果 val 即使在 value_vreg_map 中存在,但它是 AllocaInst,则不分配 result_vreg。 + // 对于 DAGNode::ALLOCA_ADDR 节点,它的 result_vreg 应该为空,因为其“值”是内存地址, + // 在指令选择时直接转换为 s0 + 偏移量。 DAGNode* raw_node_ptr = node.get(); nodes_storage.push_back(std::move(node)); // 存储 unique_ptr // 仅当 IR Value 表示一个计算值时,才将其映射到创建的 DAGNode - if (val && kind != DAGNode::STORE && kind != DAGNode::RETURN && kind != DAGNode::BRANCH) { + // 且它应该已经在 register_allocation 中被分配了 vreg + if (val && value_vreg_map.count(val) && kind != DAGNode::STORE && kind != DAGNode::RETURN && kind != DAGNode::BRANCH && !dynamic_cast(val)) { value_to_node[val] = raw_node_ptr; } return raw_node_ptr; @@ -296,9 +295,9 @@ std::vector> RISCv32CodeGen::build_dag( // 创建一个节点来表示分配内存的地址。 // 这个地址将是 s0 (帧指针) 的偏移量。 // 我们将 AllocaInst 指针存储在 DAGNode 的 `value` 字段中。 - auto alloca_addr_node = create_node(DAGNode::ALLOCA_ADDR, alloca); - // 此节点将有一个 result_vreg,表示计算出的地址 (s0 + 偏移量) - // 实际偏移量将在寄存器分配阶段 (stack_map) 确定。 + // 修正:AllocaInst 类型的 DAGNode 应该有一个 value 对应 AllocaInst* + // 但它本身不应该有 result_vreg,因为不映射到物理寄存器。 + create_node(DAGNode::ALLOCA_ADDR, alloca); } else if (auto store = dynamic_cast(inst)) { auto store_node = create_node(DAGNode::STORE, store); // 将 store inst 绑定到 node @@ -321,6 +320,7 @@ std::vector> RISCv32CodeGen::build_dag( } else if (auto alloca = dynamic_cast(ptr_ir)) { // 如果是alloca,我们应该找到代表它地址的节点 // 为了简化,如果没找到,就创建一个 + // 修正:AllocaInst 应该直接映射到 ALLOCA_ADDR 节点,其值是 AllocaInst* ptr_node = create_node(DAGNode::ALLOCA_ADDR, alloca); } else if (auto global = dynamic_cast(ptr_ir)) { ptr_node = create_node(DAGNode::CONSTANT, global); // 全局地址将被加载 @@ -342,6 +342,7 @@ std::vector> RISCv32CodeGen::build_dag( if (value_to_node.count(ptr_ir)) { ptr_node = value_to_node[ptr_ir]; } else if (auto alloca = dynamic_cast(ptr_ir)) { + // 修正:AllocaInst 应该直接映射到 ALLOCA_ADDR 节点 ptr_node = create_node(DAGNode::ALLOCA_ADDR, alloca); } else if (auto global = dynamic_cast(ptr_ir)) { ptr_node = create_node(DAGNode::CONSTANT, global); // 全局地址将被加载 @@ -436,7 +437,7 @@ std::vector> RISCv32CodeGen::build_dag( return nodes_storage; } -// 打印 DAG +// 打印 DAG (保持不变) void RISCv32CodeGen::print_dag(const std::vector>& dag, const std::string& bb_name) { std::cerr << "=== DAG for Basic Block: " << bb_name << " ===\n"; std::set visited; @@ -514,7 +515,7 @@ void RISCv32CodeGen::print_dag(const std::vector>& dag, } -// 指令选择 +// 指令选择 (保持不变,因为问题不在选择器本身,而是分配) void RISCv32CodeGen::select_instructions(DAGNode* node, const RegAllocResult& alloc) { if (!node) return; if (!node->inst.empty()) return; // 指令已选择 @@ -529,14 +530,18 @@ void RISCv32CodeGen::select_instructions(DAGNode* node, const RegAllocResult& al std::stringstream ss_inst; // 使用 stringstream 构建指令 // 获取分配的物理寄存器,如果没有分配,则使用临时寄存器 (T0) + // 这是关键:这里应该返回寄存器分配器实际分配的寄存器。 + // 如果寄存器分配失败,才会回退到 T0。 auto get_preg_or_temp = [&](const std::string& vreg) { if (alloc.vreg_to_preg.count(vreg)) { return reg_to_string(alloc.vreg_to_preg.at(vreg)); } // 如果虚拟寄存器未分配给物理寄存器,则表示它已溢出或是一个临时值。 - // 为简化,我们使用固定临时寄存器 t0 用于溢出值或需要加载到寄存器中的立即数。 - // 更健壮的后端会在此处显式处理溢出。 - return reg_to_string(PhysicalReg::T0); // 回退到临时寄存器 + // 对于这个简化示例,我们没有实现完整的溢出。 + // 如果它没有被分配(例如,因为它没有被认为是活跃的,或者寄存器不够), + // 那么我们将使用 T0 作为回退。这可能是导致问题的根本原因。 + // 但对于简单程序,如果活跃性分析正确,它应该能被分配。 + return reg_to_string(PhysicalReg::T0); // 回退到临时寄存器 t0 }; // 获取分配的栈变量的内存偏移量 @@ -598,12 +603,9 @@ void RISCv32CodeGen::select_instructions(DAGNode* node, const RegAllocResult& al std::string src_reg; if (val_node->kind == DAGNode::CONSTANT) { // 如果存储的是常量,先将其加载到临时寄存器 (t0) - if (auto constant = dynamic_cast(val_node->value)) { - src_reg = get_preg_or_temp(val_node->result_vreg); // 常量也应该有vreg - // 注意:这里的li指令会由CONSTANT节点自己生成,STORE节点不应重复生成 - } else { // 存储全局地址 - src_reg = get_preg_or_temp(val_node->result_vreg); - } + // 这里的处理是,常量会先被其 CONSTANT 节点选择指令并存入其 result_vreg 对应的物理寄存器。 + // STORE 节点直接使用这个物理寄存器。 + src_reg = get_preg_or_temp(val_node->result_vreg); } else { src_reg = get_preg_or_temp(val_node->result_vreg); } @@ -820,8 +822,23 @@ void RISCv32CodeGen::emit_instructions(DAGNode* node, std::stringstream& ss, con } } +// 辅助函数:将集合打印为字符串 +std::string print_set(const std::set& s) { + std::stringstream ss; + ss << "{"; + bool first = true; + for (const auto& elem : s) { + if (!first) { + ss << ", "; + } + ss << elem; + first = false; + } + ss << "}"; + return ss.str(); +} -// 活跃性分析 (基本保持不变,因为是通用的数据流分析) +// 活跃性分析 std::map> RISCv32CodeGen::liveness_analysis(Function* func) { std::map> live_in, live_out; bool changed = true; @@ -834,69 +851,141 @@ std::map> RISCv32CodeGen::liveness_analysis( } } + int iteration_count = 0; // 迭代计数器 while (changed) { changed = false; + iteration_count++; + std::cout << "\n--- 活跃性分析迭代: " << iteration_count << " ---" << std::endl; + // 逆序遍历基本块 for (auto it = func->getBasicBlocks_NoRange().rbegin(); it != func->getBasicBlocks_NoRange().rend(); ++it) { auto bb = it->get(); + std::cout << " 基本块: " << bb->getName() << std::endl; // 打印基本块名称 + // 在基本块内逆序遍历指令 + // live_out_for_bb_inst 是当前基本块的 live_out 集合,用于计算该基本块中第一条指令的 live_out + // 初始化为当前基本块所有后继基本块的 live_in 的并集 + std::set live_out_for_bb_inst = {}; + for (const auto& succ_bb : bb->getSuccessors()) { + if (!succ_bb->getInstructions().empty()) { + Instruction* first_inst_in_succ = succ_bb->getInstructions().front().get(); + live_out_for_bb_inst.insert(live_in[first_inst_in_succ].begin(), live_in[first_inst_in_succ].end()); + } + } + + for (auto inst_it = bb->getInstructions().rbegin(); inst_it != bb->getInstructions().rend(); ++inst_it) { auto inst = inst_it->get(); - std::set current_live_in = live_in[inst]; - std::set current_live_out = live_out[inst]; + // 打印指令,使用其父基本块的名称和指令地址作为唯一标识符 + std::cout << " 指令 (BB: " << bb->getName() << ", 地址: " << static_cast(inst) << ")" << std::endl; - std::set new_live_out; - // 后继的 live_in 集合的并集 - if (inst->isTerminator()) { - if (auto br = dynamic_cast(inst)) { - new_live_out.insert(live_in[br->getThenBlock()->getInstructions().front().get()].begin(), - live_in[br->getThenBlock()->getInstructions().front().get()].end()); - new_live_out.insert(live_in[br->getElseBlock()->getInstructions().front().get()].begin(), - live_in[br->getElseBlock()->getInstructions().front().get()].end()); - } else if (auto uncond = dynamic_cast(inst)) { - new_live_out.insert(live_in[uncond->getBlock()->getInstructions().front().get()].begin(), - live_in[uncond->getBlock()->getInstructions().front().get()].end()); - } - // 返回指令没有后继,所以 new_live_out 保持为空 + std::set current_live_in = live_in[inst]; + std::set current_live_out = live_out[inst]; // 用于比较的旧 live_out + + std::set new_live_out_calc; // 用于计算的 live_out + + // 如果当前指令是基本块中的最后一条指令 (即逆序遍历中的第一条指令) + // 那么它的 live_out 就是该基本块的 live_out,即其所有后继基本块的 live_in 的并集 + if (inst_it == bb->getInstructions().rbegin()) { + new_live_out_calc = live_out_for_bb_inst; + std::cout << " 指令是基本块的最后一条指令,live_out 取自后继基本块 live_in 的并集: " << print_set(new_live_out_calc) << std::endl; } else { - auto next_inst_it = std::next(inst_it); - if (next_inst_it != bb->getInstructions().rend()) { - // 不是终结符,所以块中的下一条指令是后继 - new_live_out = live_in[next_inst_it->get()]; - } + // 如果不是基本块的最后一条指令,则其 live_out 是其后继指令的 live_in + auto prev_inst_it = std::prev(inst_it); // std::prev 获取正向的下一条指令 + new_live_out_calc = live_in[prev_inst_it->get()]; + std::cout << " 指令不是基本块的最后一条,其 live_out 是其后继指令 live_in: " << print_set(new_live_out_calc) << std::endl; } + // 计算当前指令的 use 和 def 集合 std::set use_set, def_set; - // 定义 (Def) - if (value_vreg_map.count(inst)) { + // 定义 (Def) - 只有当指令本身产生一个非 void 结果并映射到虚拟寄存器时 + // LoadInst, BinaryInst, CallInst 等会定义一个结果。 + // StoreInst, AllocaInst, ReturnInst, BranchInst 不定义结果到寄存器。 + if (!inst->getType()->isVoid() && !dynamic_cast(inst) && !dynamic_cast(inst) && + !dynamic_cast(inst) && !dynamic_cast(inst) && !dynamic_cast(inst) && value_vreg_map.count(inst)) { def_set.insert(value_vreg_map.at(inst)); + std::cout << " 指令 (地址: " << static_cast(inst) << ") 定义了虚拟寄存器: " << value_vreg_map.at(inst) << std::endl; } + + // *** 针对 StoreInst 的新逻辑来“杀死”被存储值的虚拟寄存器 *** + // 这个逻辑在活跃性分析中需要非常谨慎。通常,Store 指令是使用(use)被存储的值,而不是定义(def)它。 + // 如果一个值在 Store 后不再被使用,它会自然地通过活跃性传播变得不活跃。 + // 将被存储的值添加到 def_set 意味着该值在该指令处被“杀死”,因为它被存储到内存中,而不是寄存器。 + // 对于“01_add.sy”这样的简单情况,这可能是有效的,但对于更复杂的程序,可能会导致不准确的活跃性。 + // 考虑一个值被存储,但稍后又从内存中加载并再次使用的情况。 + // 这是一个需要仔细考虑的启发式方法。 + if (auto store = dynamic_cast(inst)) { + Value* stored_value = store->getValue(); + // 如果被存储的值有一个虚拟寄存器,并且它不是 AllocaInst(Alloca是地址,不是寄存器值) + if (value_vreg_map.count(stored_value) && !dynamic_cast(stored_value)) { + // 启发式:如果存储的值是常量或临时指令结果,并且仅在此处使用,则可以认为是“杀死”了该虚拟寄存器。 + // 更准确的判断需要检查该值的其他所有用途。 + // 对于 '01_add.sy' 中常量的情况,这是有效的。 + // 修正:使用 ->get() 来获取 shared_ptr 持有的原始指针,然后调用 getUser() + // 检查 inst 是否是 stored_value 的唯一用户。 + bool is_unique_user = true; + if (!stored_value->getUses().empty()) { + is_unique_user = (stored_value->getUses().size() == 1 && stored_value->getUses().front()->getUser() == inst); + } else { + // 如果没有uses,则它没有被使用,不应该被def。 + is_unique_user = false; + } - // 使用 (Use) - 遍历指令的操作数 - for(const auto& operand_use : inst->getOperands()){ - Value* operand = operand_use->getValue(); - // 只有非立即数的值才生活在虚拟寄存器中 - if(!dynamic_cast(operand) && value_vreg_map.count(operand)){ - use_set.insert(value_vreg_map.at(operand)); + if (is_unique_user) { + def_set.insert(value_vreg_map.at(stored_value)); + std::cout << " Store 指令 (地址: " << static_cast(inst) << ") 将被存储的值 '" << value_vreg_map.at(stored_value) << "' 添加到 def_set (启发式)." << std::endl; + } else { + std::cout << " Store 指令 (地址: " << static_cast(inst) << ") 存储的值 '" << value_vreg_map.at(stored_value) << "' 有其他用途或不是唯一用途,未添加到 def_set。" << std::endl; + } + } else if (dynamic_cast(stored_value)) { + std::cout << " Store 指令存储的是 AllocaInst 地址,不处理其虚拟寄存器定义。" << std::endl; } } + // *** 结束新逻辑 *** + + // 使用 (Use) - 遍历指令的操作数 + for(const auto& operand_use : inst->getOperands()){ + Value* operand = operand_use->getValue(); + // 只有当操作数是一个实际需要寄存器来存储的“值”时,才将其vreg添加到use_set。 + // 排除 AllocaInst,因为它们是地址概念,不占用通用寄存器。 + // 并且确保 operand 已经在 value_vreg_map 中有对应的虚拟寄存器。 + if (value_vreg_map.count(operand) && !dynamic_cast(operand)) { + use_set.insert(value_vreg_map.at(operand)); + std::cout << " 指令 (地址: " << static_cast(inst) << ") 使用了虚拟寄存器: " << value_vreg_map.at(operand) << std::endl; + } else if (dynamic_cast(operand)) { + std::cout << " 指令 (地址: " << static_cast(inst) << ") 操作数是 AllocaInst 地址,不添加到 use_set。" << std::endl; + } else { + // 对于常量,它们没有虚拟寄存器,也不应该被添加到use_set + // 也可以是其他没有对应虚拟寄存器(例如函数名)的值。 + std::cout << " 指令 (地址: " << static_cast(inst) << ") 操作数没有对应的虚拟寄存器,或不是需要寄存器的值。" << std::endl; + } + } + std::cout << " 指令 (地址: " << static_cast(inst) << ") 的 use_set: " << print_set(use_set) << std::endl; + std::cout << " 指令 (地址: " << static_cast(inst) << ") 的 def_set: " << print_set(def_set) << std::endl; - // 计算新的 live_in = use U (new_live_out - def) + // 计算新的 live_in = use U (new_live_out_calc - def) std::set new_live_in = use_set; - for (const auto& vreg : new_live_out) { - if (def_set.find(vreg) == def_set.end()) { + for (const auto& vreg : new_live_out_calc) { + if (def_set.find(vreg) == def_set.end()) { // 如果 vreg 在 new_live_out_calc 中但不在 def 中,则它活跃 new_live_in.insert(vreg); } } + + std::cout << " 指令 (地址: " << static_cast(inst) << ") 计算出的 new_live_in: " << print_set(new_live_in) << std::endl; + std::cout << " 指令 (地址: " << static_cast(inst) << ") 当前 live_in: " << print_set(current_live_in) << ", 当前 live_out: " << print_set(current_live_out) << std::endl; + // 检查收敛 - if (new_live_in != current_live_in || new_live_out != current_live_out) { + if (new_live_in != current_live_in || new_live_out_calc != current_live_out) { // 注意这里要用 new_live_out_calc 比较 live_in[inst] = new_live_in; - live_out[inst] = new_live_out; + live_out[inst] = new_live_out_calc; // 更新 live_out changed = true; + std::cout << " 指令 (地址: " << static_cast(inst) << ") 活跃性集合发生变化,更新并继续迭代." << std::endl; + } else { + std::cout << " 指令 (地址: " << static_cast(inst) << ") 活跃性集合未发生变化." << std::endl; } } } @@ -921,7 +1010,9 @@ std::map> RISCv32CodeGen::build_interference_ const auto& live_after_inst = pair.second; // 这实际上是下一条指令/基本块入口的 live_in std::string defined_vreg; - if (value_vreg_map.count(inst)) { + // 修正:只有当指令结果是需要物理寄存器时才视为定义。 + // AllocaInst 不应在此处处理。 + if (value_vreg_map.count(inst) && !dynamic_cast(inst)) { defined_vreg = value_vreg_map.at(inst); } @@ -958,7 +1049,7 @@ void RISCv32CodeGen::color_graph(std::map& vreg_to_pre // 收集干扰邻居的颜色 if (interference_graph.count(vreg)) { for (const auto& neighbor_vreg : interference_graph.at(vreg)) { - if (vreg_to_preg.count(neighbor_vreg)) { + if (vreg_to_preg.count(neighbor_vreg)) { // 只有当邻居已被着色时才考虑 used_colors.insert(vreg_to_preg.at(neighbor_vreg)); } } @@ -990,7 +1081,7 @@ RISCv32CodeGen::RegAllocResult RISCv32CodeGen::register_allocation(Function* fun // 1. Phi 节点消除 (如果 IR 中有 Phi 节点,需要在活跃性分析前消除) eliminate_phi(func); // 确保首先调用此函数 - // 为每个函数重置计数器 + // 为每个函数重置计数器和虚拟寄存器映射 vreg_counter = 0; value_vreg_map.clear(); // 为每个函数清除 @@ -1002,17 +1093,29 @@ RISCv32CodeGen::RegAllocResult RISCv32CodeGen::register_allocation(Function* fun // 如果指令产生一个非 void 的结果,它就需要一个地方来存储这个结果。 // 我们为其分配一个虚拟寄存器。 if (!inst->getType()->isVoid()) { - if (value_vreg_map.find(inst) == value_vreg_map.end()) { - value_vreg_map[inst] = "v" + std::to_string(vreg_counter++); + // 修正:确保 AllocaInst 不在这里分配vreg,因为它不占用物理寄存器 + if (!dynamic_cast(inst)) { // 排除 AllocaInst + if (value_vreg_map.find(inst) == value_vreg_map.end()) { + value_vreg_map[inst] = "v" + std::to_string(vreg_counter++); + } } } - // 也为常量操作数分配vreg,以便它们可以参与活跃性分析 + // 也为常量操作数和全局变量操作数分配vreg,以便它们可以参与活跃性分析 for(const auto& operand_use : inst->getOperands()){ Value* operand = operand_use->getValue(); + // 修正:同样排除 AllocaInst 作为操作数 if(dynamic_cast(operand) || dynamic_cast(operand)){ if (value_vreg_map.find(operand) == value_vreg_map.end()) { value_vreg_map[operand] = "v" + std::to_string(vreg_counter++); } + } else if (dynamic_cast(operand) && dynamic_cast(operand)->getType()->isVoid() == false) { + // 如果操作数是另一个指令的结果且非void,确保它也有vreg + // 修正:再次排除 AllocaInst + if (!dynamic_cast(operand)) { + if (value_vreg_map.find(operand) == value_vreg_map.end()) { + value_vreg_map[operand] = "v" + std::to_string(vreg_counter++); + } + } } } } @@ -1058,6 +1161,54 @@ RISCv32CodeGen::RegAllocResult RISCv32CodeGen::register_allocation(Function* fun // 完整的溢出处理逻辑比较复杂,这里暂时省略。 // 如果一个vreg没有被着色,get_preg_or_temp会回退到t0,这对于简单情况可能够用。 + // 打印寄存器分配结果 (调试用) + std::cerr << "=== 寄存器分配结果 (vreg_to_preg) ===\n"; + for (const auto& pair : alloc_result.vreg_to_preg) { + std::cerr << " " << pair.first << " -> " << reg_to_string(pair.second) << "\n"; + } + std::cerr << "=== 寄存器分配结果结束 ===\n\n"; + + // 打印活跃性分析结果 (调试用) + std::cerr << "=== 活跃性分析结果 (live_in sets) ===\n"; + for (const auto& bb_ptr : func->getBasicBlocks()) { + std::cerr << "Basic Block: " << bb_ptr->getName() << "\n"; + for (const auto& inst_ptr : bb_ptr->getInstructions()) { + std::cerr << " Inst: " << inst_ptr->getKindString(); + if (!inst_ptr->getName().empty()) { + std::cerr << "(" << inst_ptr->getName() << ")"; + } + if (value_vreg_map.count(inst_ptr.get())) { + std::cerr << " (Def vreg: " << value_vreg_map.at(inst_ptr.get()) << ")"; + } + std::cerr << " (Live In: {"; + bool first = true; + if (live_sets.count(inst_ptr.get())) { + for (const auto& vreg : live_sets.at(inst_ptr.get())) { + if (!first) std::cerr << ", "; + std::cerr << vreg; + first = false; + } + } + std::cerr << "})\n"; + } + } + std::cerr << "=== 活跃性分析结果结束 ===\n\n"; + + // 打印干扰图 (调试用) + std::cerr << "=== 干扰图 ===\n"; + for (const auto& pair : interference_graph) { + std::cerr << " " << pair.first << ": {"; + bool first = true; + for (const auto& neighbor : pair.second) { + if (!first) std::cerr << ", "; + std::cerr << neighbor; + first = false; + } + std::cerr << "}\n"; + } + std::cerr << "=== 干扰图结束 ===\n\n"; + + return alloc_result; }