在GCC IPA阶段实现结构体拆分Pass时,如何定位并修改目标GIMPLE代码
在GCC IPA阶段实现结构体拆分Pass时,如何定位并修改目标GIMPLE代码
看起来你已经找对了方向,但卡在了定位目标GIMPLE序列和替换逻辑上。我来帮你梳理下具体的思路和实现细节:
首先,我们得先拆解清楚你要处理的GIMPLE模式:
原来的r->node[i].state在GIMPLE里是一连串的赋值:
_1 = r->node; // 取reg里的node指针 _2 = (long unsigned int) i; _3 = _2 * 16; // 16是原struct node的大小 _4 = _1 + _3; // 计算第i个node的地址 _5 = _4->state; // 取该node的state成员
而你要把它改成:
_1 = r->node; _2 = _1->state; // 取node里的state指针 _3 = (long unsigned int) i; _4 = _3 * 8; // 8是unsigned long long的大小 _5 = _2 + _4; // 计算第i个state的地址 _6 = *_5; // 取值
一、精准定位目标语句
你当前只找到了r->node的赋值,但真正的突破口是访问struct node的state成员的COMPONENT_REF语句(也就是上面的_5 = _4->state;)。具体步骤:
- 先拿到
struct node里state成员的tree节点:
tree state_field = lookup_field(desire_type, "state"); if (!state_field) return 0;
- 在遍历GIMPLE语句时,筛选出访问该成员的赋值语句:
// 遍历stmt的循环内 if (gimple_code(stmt) == GIMPLE_ASSIGN) { if (gimple_assign_rhs_code(stmt) == COMPONENT_REF) { tree rhs_comp = gimple_assign_rhs1(stmt); tree comp_field = TREE_OPERAND(rhs_comp, 1); // 验证是不是我们要处理的state成员,且父类型是原struct node if (comp_field == state_field && TREE_TYPE(TREE_OPERAND(rhs_comp, 0)) == desire_type) { // 找到了目标语句!接下来追踪它的来源链 gimple *target_stmt = stmt; tree ptr_to_node = TREE_OPERAND(rhs_comp, 0); // 对应上面的_4 this->process_array_access(target_stmt, ptr_to_node, func); } } }
二、回溯数组访问的计算链
找到_4->state后,需要确认_4是数组索引计算的结果(_1 + i*sizeof(node))。可以写个辅助函数验证:
bool pass_struct_split::is_array_index(tree var, tree *base_ptr, tree *index_var, tree struct_type) { gimple *def_stmt = SSA_NAME_DEF_STMT(var); if (!def_stmt || gimple_code(def_stmt) != GIMPLE_ASSIGN) return false; // 检查是不是指针加法:base + offset if (gimple_assign_rhs_code(def_stmt) != POINTER_PLUS_EXPR) return false; tree base = gimple_assign_rhs1(def_stmt); tree offset = gimple_assign_rhs2(def_stmt); // 检查offset是不是索引乘以原结构体大小 gimple *offset_def = SSA_NAME_DEF_STMT(offset); if (!offset_def || gimple_assign_rhs_code(offset_def) != MULT_EXPR) return false; tree idx = gimple_assign_rhs1(offset_def); tree size_cst = gimple_assign_rhs2(offset_def); // 验证大小是否匹配原struct node的字节数 if (TREE_INT_CST_LOW(size_cst) != int_size_in_bytes(struct_type)) return false; // 验证base是不是原struct node的指针 if (TREE_TYPE(base) != build_pointer_type(struct_type)) return false; *base_ptr = base; *index_var = idx; return true; }
调用这个函数后,就能拿到base_ptr(对应_1,即r->node)和index_var(对应i),确认这是我们要处理的数组访问场景。
三、替换GIMPLE代码序列
确认目标后,就可以删除旧的语句,插入新的逻辑:
void pass_struct_split::process_array_access(gimple *target_stmt, tree ptr_to_node, function *func) { tree base_ptr, index_var; if (!is_array_index(ptr_to_node, &base_ptr, &index_var, desire_type)) return; gimple_stmt_iterator gsi = gsi_for_stmt(target_stmt); tree lhs = gimple_assign_lhs(target_stmt); // 回溯并删除旧的计算语句:cast i -> mult -> ptr add -> target stmt gimple *ptr_add_stmt = SSA_NAME_DEF_STMT(ptr_to_node); tree offset_var = gimple_assign_rhs2(ptr_add_stmt); gimple *mult_stmt = SSA_NAME_DEF_STMT(offset_var); tree cast_var = gimple_assign_rhs1(mult_stmt); gimple *cast_stmt = SSA_NAME_DEF_STMT(cast_var); // 从后往前删,避免迭代器失效 gsi_remove(&gsi, true); gsi = gsi_for_stmt(ptr_add_stmt); gsi_remove(&gsi, true); gsi = gsi_for_stmt(mult_stmt); gsi_remove(&gsi, true); gsi = gsi_for_stmt(cast_stmt); gsi_remove(&gsi, true); // 插入新的语句序列 tree state_ptr_type = build_pointer_type(TREE_TYPE(state_field)); // 1. 取node里的state指针 tree new_state_ptr = make_ssa_name(state_ptr_type, NULL); gimple *comp_stmt = gimple_build_assign(new_state_ptr, COMPONENT_REF, base_ptr, state_field); gsi_insert_after(&gsi, comp_stmt, GSI_NEW_STMT); gsi_next(&gsi); // 2. 转换索引类型 tree cast_type = unsigned_long_type_node; tree new_cast = make_ssa_name(cast_type, NULL); gimple *new_cast_stmt = gimple_build_assign(new_cast, NOP_EXPR, index_var, cast_type); gsi_insert_after(&gsi, new_cast_stmt, GSI_NEW_STMT); gsi_next(&gsi); // 3. 计算索引偏移 tree elem_size = build_int_cst(cast_type, int_size_in_bytes(TREE_TYPE(state_field))); tree new_mult = make_ssa_name(cast_type, NULL); gimple *new_mult_stmt = gimple_build_assign(new_mult, MULT_EXPR, new_cast, elem_size); gsi_insert_after(&gsi, new_mult_stmt, GSI_NEW_STMT); gsi_next(&gsi); // 4. 指针加法 tree new_ptr_add = make_ssa_name(build_pointer_type(TREE_TYPE(state_field)), NULL); gimple *new_ptr_stmt = gimple_build_assign(new_ptr_add, POINTER_PLUS_EXPR, new_state_ptr, new_mult); gsi_insert_after(&gsi, new_ptr_stmt, GSI_NEW_STMT); gsi_next(&gsi); // 5. 取值并赋值给原来的lhs gimple *deref_stmt = gimple_build_assign(lhs, INDIRECT_REF, new_ptr_add); gsi_insert_after(&gsi, deref_stmt, GSI_NEW_STMT); }
四、额外注意事项
- SSA名字管理:所有新变量要用
make_ssa_name创建,确保类型匹配,避免SSA形式出错。 - IPA阶段的跨函数处理:如果你的结构体在多个函数中被使用,要在IPA阶段同步处理所有引用,比如其他函数里的
malloc(sizeof(struct node)),需要改成适配拆分后的结构体的分配逻辑。 - 边界场景测试:比如结构体的直接声明、传递、返回等情况,都要验证你的Pass是否能正确处理。
- 利用GCC内置工具:GCC提供了大量tree/gimple辅助函数,比如
int_size_in_bytes、SSA_NAME_DEF_STMT,要充分利用这些工具简化逻辑。
备注:内容来源于stack exchange,提问作者Dbettkk




