#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int plugin_is_GPL_compatible; #ifndef SPSLR_OFFSETOF #define SPSLR_OFFSETOF "__spslr_offsetof_" /* with suffix */ #endif #ifndef UNSPEC_SPSLR_OFFSETOF #define UNSPEC_SPSLR_OFFSETOF 1042 #endif // Recognize __attribute__((spslr)) on structures and track their layout struct Member { using OFF = unsigned long; using SIZE = unsigned long; static constexpr int FLAG_DANGERZONE = 1; OFF offset = 0; SIZE size = 0; int flags = 0; }; struct Target { using UID = unsigned long; UID uid; std::map members; void log_member(const Member& member); const Member* get_member(Member::OFF offset) const; }; struct TargetTree { tree t; Target::UID uid; }; static Target::UID next_target_uid = 0; static std::unordered_map targets; static std::list target_trees; static std::unordered_set target_markings; static bool find_target_log(tree t, Target::UID& uid) { for (const TargetTree& tt : target_trees) { if (lang_hooks.types_compatible_p(tt.t, t)) { uid = tt.uid; return true; } } return false; } void Target::log_member(const Member& member) { Member tmp_member; tmp_member.offset = member.offset; tmp_member.size = (member.size == 0 ? 1 : member.size); tmp_member.flags = (member.size == 0 ? Member::FLAG_DANGERZONE : 0) | member.flags; // Overlaps are dangerous -> remove and integrate into member auto overlap_end = members.lower_bound(tmp_member.offset + tmp_member.size); for (auto it = std::make_reverse_iterator(overlap_end); it != members.rend();) { const Member& existing_member = it->second; if (existing_member.offset + existing_member.size <= tmp_member.offset) break; Member::OFF combined_end = std::max(tmp_member.offset + tmp_member.size, existing_member.offset + existing_member.size); Member::OFF combined_offset = std::min(tmp_member.offset, existing_member.offset); Member::SIZE combined_size = combined_end - combined_offset; tmp_member.flags |= (existing_member.flags | Member::FLAG_DANGERZONE); tmp_member.offset = combined_offset; tmp_member.size = combined_size; // Erase overlapping member auto tmp_forward = std::prev(it.base()); tmp_forward = members.erase(tmp_forward); it = std::make_reverse_iterator(tmp_forward); } members.emplace(tmp_member.offset, tmp_member); } const Member* Target::get_member(Member::OFF offset) const { auto it = members.find(offset); if (it == members.end()) return nullptr; return &it->second; } static void log_target(tree node) { node = TYPE_MAIN_VARIANT(node); if (!node) return; if (TREE_CODE(node) != RECORD_TYPE) return; auto marking = target_markings.find(node); if (marking == target_markings.end()) return; Target::UID existing_target; if (find_target_log(node, existing_target)) return; Target::UID tuid = next_target_uid++; TargetTree new_tt; new_tt.t = node; new_tt.uid = tuid; target_trees.push_back(new_tt); targets.emplace(tuid, Target{}); Target& target = targets.at(tuid); target.uid = tuid; // Log all members for (tree field = TYPE_FIELDS(node); field; field = DECL_CHAIN(field)) { if (TREE_CODE(field) != FIELD_DECL) continue; HOST_WIDE_INT field_byte_offset = 0; if (TREE_CODE(DECL_FIELD_OFFSET(field)) == INTEGER_CST) field_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field)); HOST_WIDE_INT field_bit_offset = 0; if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field)) == INTEGER_CST) field_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field)); HOST_WIDE_INT field_bit_offset_bytes = field_bit_offset / 8; field_byte_offset += field_bit_offset_bytes; field_bit_offset -= field_bit_offset_bytes * 8; HOST_WIDE_INT field_bit_size = 0; if (TREE_CODE(DECL_SIZE(field)) == INTEGER_CST) field_bit_size = tree_to_uhwi(DECL_SIZE(field)); bool is_bitfield = (DECL_BIT_FIELD_TYPE(field) != NULL_TREE); bool is_multibyte = (field_bit_size % 8 == 0 && field_bit_offset == 0); bool is_dangerous = (is_bitfield || !is_multibyte); HOST_WIDE_INT field_offset_bit_size = field_bit_offset + field_bit_size; HOST_WIDE_INT effective_field_size = field_offset_bit_size / 8; if (field_offset_bit_size % 8 != 0) effective_field_size += 1; Member member; member.offset = (Member::OFF)field_byte_offset; member.size = (Member::SIZE)effective_field_size; member.flags = (is_dangerous ? Member::FLAG_DANGERZONE : 0); target.log_member(member); } } static tree log_target_attribute(tree* node, tree name, tree args, int flags, bool* no_add_attrs) { if (!node) return NULL_TREE; if (TREE_CODE(*node) != RECORD_TYPE) return NULL_TREE; tree type_main_variant = TYPE_MAIN_VARIANT(*node); if (!type_main_variant) return NULL_TREE; target_markings.insert(type_main_variant); return NULL_TREE; } static struct attribute_spec spslr_attribute = { "spslr", 0, 0, false, false, false, false, log_target_attribute, NULL }; void on_register_attributes(void* event_data, void* data) { register_attribute(&spslr_attribute); } static void on_type_complete(void* event_data, void* user_data) { tree type = (tree)event_data; log_target(type); } // Early hook to make COMPONENT_REF nodes survice front end struct SPSLROffsetofCallData { using UID = unsigned long; UID uid; Target::UID target; Member::OFF member; }; static SPSLROffsetofCallData::UID spslr_offsetof_next_uid = 0; static std::unordered_map spslr_offsetof_calls; static tree make_spslr_offsetof_decl(SPSLROffsetofCallData::UID uid) { tree fntype = build_function_type(sizetype, NULL_TREE); char fnname[128]; snprintf(fnname, sizeof(fnname), "%s%lu", SPSLR_OFFSETOF, uid); tree fnname_tree = get_identifier(fnname); if (!fnname_tree) return NULL_TREE; tree fndecl = build_fn_decl(IDENTIFIER_POINTER(fnname_tree), fntype); if (!fndecl) return NULL_TREE; DECL_EXTERNAL(fndecl) = 1; TREE_PUBLIC(fndecl) = 1; DECL_ARTIFICIAL(fndecl) = 1; // Prevent VOP problems later when removing calls // Explanation -> VOPs mark memory side-effects, which these calls have none of anyways DECL_PURE_P(fndecl) = 1; DECL_IS_NOVOPS(fndecl) = 1; return fndecl; } static bool is_relevant_offsetof(tree ref, Target::UID& tuid, Member::OFF& moff) { if (!ref || TREE_CODE(ref) != COMPONENT_REF) return false; tree base = TREE_OPERAND(ref, 0); if (!base) return false; tree base_type = TREE_TYPE(base); if (!base_type) return false; if (!find_target_log(base_type, tuid)) return false; auto target_it = targets.find(tuid); if (target_it == targets.end()) return false; const Target& t = target_it->second; tree field = TREE_OPERAND(ref, 1); HOST_WIDE_INT field_byte_offset = 0; if (TREE_CODE(DECL_FIELD_OFFSET(field)) == INTEGER_CST) field_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field)); HOST_WIDE_INT field_bit_offset = 0; if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field)) == INTEGER_CST) field_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field)); Member::OFF effective_field_offset = (field_byte_offset + (field_bit_offset / 8)); moff = effective_field_offset; const Member* m = t.get_member(effective_field_offset); if (!m) return false; return !(m->flags & Member::FLAG_DANGERZONE); } static tree instrument_offsetof_maybe(tree ref) { SPSLROffsetofCallData call_data; if (!is_relevant_offsetof(ref, call_data.target, call_data.member)) return NULL_TREE; location_t loc = EXPR_LOCATION(ref); tree result_type = TREE_TYPE(ref); call_data.uid = spslr_offsetof_next_uid++; tree spslr_offsetof_decl = NULL_TREE; if (!(spslr_offsetof_decl = make_spslr_offsetof_decl(call_data.uid))) { std::cerr << "spslr_pinpoint -> failed to instrument COMPONENT_REF (" << SPSLR_OFFSETOF << " unavailable)" << std::endl; return NULL_TREE; } spslr_offsetof_calls.emplace(call_data.uid, call_data); tree call_tree = build_call_expr_loc(loc, spslr_offsetof_decl, 0); // Get base as char* tree base = TREE_OPERAND(ref, 0); tree base_addr; if (TREE_CODE(base) == ADDR_EXPR) { // base_addr = base; std::cerr << "spslr_pinpoint -> unexpected ADDR_EXPR as COMPONENT_REF base!" << std::endl; return NULL_TREE; } else { base_addr = build_fold_addr_expr(base); } tree char_ptr_type = build_pointer_type(char_type_node); tree base_char_ptr = fold_convert(char_ptr_type, base_addr); // Add __spslr_offsetof, cast back to field pointer, then dereference tree plus = build2_loc(loc, POINTER_PLUS_EXPR, char_ptr_type, base_char_ptr, call_tree); tree field_ptr_type = build_pointer_type(result_type); tree cast_back = build1_loc(loc, NOP_EXPR, field_ptr_type, plus); tree new_ref = build1_loc(loc, INDIRECT_REF, result_type, cast_back); return new_ref; } static void on_build_component_ref(void* event_data, void* user_data) { tree* component_ref_node = (tree*)event_data; if (!component_ref_node) return; tree repl = instrument_offsetof_maybe(*component_ref_node); if (repl) *component_ref_node = repl; } // Identify any missing COMPONENT_REFs (e.g. from CONSTRUCTOR trees) static const pass_data log_component_refs_pass_data = { GIMPLE_PASS, "log_component_refs", OPTGROUP_NONE, TV_NONE, 0,0,0,0, TODO_update_ssa }; struct log_component_refs_pass : gimple_opt_pass { log_component_refs_pass(gcc::context *ctxt); unsigned int execute(function* fun) override; }; log_component_refs_pass::log_component_refs_pass(gcc::context *ctxt) : gimple_opt_pass(log_component_refs_pass_data, ctxt) {} struct GCRChain { struct Link { tree t = NULL_TREE; bool relevant = false; SPSLROffsetofCallData call_data; }; bool relevant = false; std::list links; tree base = NULL_TREE; }; static tree walk_tree_contains_component_ref(tree* tp, int* walk_subtrees, void* data) { int* found_flag = (int*)data; if (!tp || !*tp) return NULL_TREE; if (TREE_CODE(*tp) == COMPONENT_REF) *found_flag = 1; return NULL_TREE; } static bool contains_component_ref(tree ref) { int found_flag = 0; walk_tree(&ref, walk_tree_contains_component_ref, &found_flag, NULL); return found_flag != 0; } static bool gimple_component_ref_chain(tree ref, GCRChain& chain) { if (!ref) return false; if (TREE_CODE(ref) != COMPONENT_REF) { if (chain.links.empty()) return false; if (contains_component_ref(ref)) return false; chain.base = ref; return true; } GCRChain::Link link; link.t = ref; link.relevant = is_relevant_offsetof(ref, link.call_data.target, link.call_data.member); if (link.relevant) { link.call_data.uid = spslr_offsetof_next_uid++; chain.relevant = true; } chain.links.push_front(link); return gimple_component_ref_chain(TREE_OPERAND(ref, 0), chain); } static HOST_WIDE_INT get_field_offset_bits(tree field) { gcc_assert(TREE_CODE(field) == FIELD_DECL); // Get the offset expression (may not be a constant early in compilation) tree offset_tree = DECL_FIELD_OFFSET(field); HOST_WIDE_INT bitpos_within_unit = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field)); // Convert the byte offset to bits HOST_WIDE_INT bit_offset = bitpos_within_unit; if (offset_tree && TREE_CODE(offset_tree) == INTEGER_CST) bit_offset += tree_to_shwi(offset_tree) * BITS_PER_UNIT; return bit_offset; } static bool get_field_offset(tree field, Member::OFF& off) { if (!field || TREE_CODE(field) != FIELD_DECL) return false; if (DECL_BIT_FIELD(field)) return false; tree byte_offset_tree = DECL_FIELD_OFFSET(field); tree bit_offset_tree = DECL_FIELD_BIT_OFFSET(field); if (!byte_offset_tree || !bit_offset_tree) return false; if (TREE_CODE(byte_offset_tree) != INTEGER_CST || TREE_CODE(bit_offset_tree) != INTEGER_CST) return false; HOST_WIDE_INT byte_offset = tree_to_uhwi(byte_offset_tree); HOST_WIDE_INT bit_offset = tree_to_uhwi(bit_offset_tree); if (bit_offset % 8 != 0) return false; off = (Member::OFF)byte_offset + (Member::OFF)bit_offset / 8; return true; } static tree gimple_instrument_offsetof_maybe(tree ref, gimple_stmt_iterator* gsi) { GCRChain gcrc; if (!gimple_component_ref_chain(ref, gcrc)) { std::cerr << "spslr_pinpoint -> failed to parse COMPONENT_REF chain" << std::endl; return NULL_TREE; } if (!gcrc.relevant) return NULL_TREE; // Store base pointer in a temporary variable (as char*) tree char_ptr_type = build_pointer_type(char_type_node); tree base_tmp = NULL_TREE; { tree base_ptr; if (TREE_CODE(gcrc.base) == ADDR_EXPR) { std::cerr << "spslr_pinpoint -> unexpected ADDR_EXPR as COMPONENT_REF base!" << std::endl; return NULL_TREE; } else { base_ptr = build_fold_addr_expr(gcrc.base); } tree base_char_ptr = fold_convert(char_ptr_type, base_ptr); base_tmp = create_tmp_var(char_ptr_type, NULL); gimple* base_tmp_assignment = gimple_build_assign(base_tmp, base_char_ptr); gsi_insert_before(gsi, base_tmp_assignment, GSI_SAME_STMT); } // For each component ref in chain, add the member offset to the pointer for (const GCRChain::Link& cr : gcrc.links) { // NOTE -> Could fold into single call here (needs to track what offsets contribute, +irrelevant combined) if (cr.relevant) { // Add offsetof call tree spslr_offsetof_decl = NULL_TREE; if (!(spslr_offsetof_decl = make_spslr_offsetof_decl(cr.call_data.uid))) { std::cerr << "spslr_pinpoint -> failed to instrument COMPONENT_REF (" << SPSLR_OFFSETOF << " unavailable)" << std::endl; return NULL_TREE; } spslr_offsetof_calls.emplace(cr.call_data.uid, cr.call_data); // Call to __spslr_offsetof is a separate statement gimple* call_stmt = gimple_build_call(spslr_offsetof_decl, 0); tree offset_tmp = create_tmp_var(size_type_node, NULL); gimple_call_set_lhs(call_stmt, offset_tmp); gsi_insert_before(gsi, call_stmt, GSI_SAME_STMT); // Add call return value to current base pointer tree addition = build2(POINTER_PLUS_EXPR, char_ptr_type, base_tmp, offset_tmp); base_tmp = create_tmp_var(char_ptr_type, NULL); gimple* addition_assignment = gimple_build_assign(base_tmp, addition); gsi_insert_before(gsi, addition_assignment, GSI_SAME_STMT); } else { // Add offsetof contant tree field = TREE_OPERAND(cr.t, 1); Member::OFF offset = 0; if (!get_field_offset(field, offset)) { std::cerr << "spslr_pinpoint -> failed to get offset of an irrelevant member " << "in a relevant COMPONENT_REF chain" << std::endl; return NULL_TREE; } // Add constant offset to current base pointer tree addition = build2(POINTER_PLUS_EXPR, char_ptr_type, base_tmp, build_int_cst(sizetype, offset)); base_tmp = create_tmp_var(char_ptr_type, NULL); gimple* addition_assignment = gimple_build_assign(base_tmp, addition); gsi_insert_before(gsi, addition_assignment, GSI_SAME_STMT); } } // Cast char pointer back to field pointer and dereference tree field_ptr_type = build_pointer_type(TREE_TYPE(ref)); tree field_ptr = fold_convert(field_ptr_type, base_tmp); tree field_ptr_tmp = create_tmp_var(field_ptr_type, NULL); gimple* field_ptr_tmp_assignment = gimple_build_assign(field_ptr_tmp, field_ptr); gsi_insert_before(gsi, field_ptr_tmp_assignment, GSI_SAME_STMT); tree offset0 = fold_convert(field_ptr_type, build_int_cst(sizetype, 0)); tree result_ref = build2(MEM_REF, TREE_TYPE(ref), field_ptr_tmp, offset0); return result_ref; } static void instrument_tree(const std::list& path, gimple_stmt_iterator* gsi, unsigned& cancel_levels) { if (path.empty() || !gsi) return; tree ref = *path.back(); if (!ref || TREE_CODE(ref) != COMPONENT_REF) return; cancel_levels = 1; tree instrumented_ref = gimple_instrument_offsetof_maybe(ref, gsi); if (!instrumented_ref) return; gimple_set_modified(gsi_stmt(*gsi), true); *path.back() = instrumented_ref; // At this point, instrumented_ref is a MEM_REF node (off=0). A wrapping ADDR_EXPR cancels it out. if (path.size() < 2) return; tree* parent = *(++path.rbegin()); if (TREE_CODE(*parent) == ADDR_EXPR) { // Note -> the base of the MEM_REF is expected to have the same type as the ADDR_EXPR *parent = TREE_OPERAND(instrumented_ref, 0); cancel_levels++; } } struct TreeWalkData { std::list path; gimple_stmt_iterator* gsi; unsigned cancel_levels; }; static tree walk_tree_level(tree* tp, int* walk_subtrees, void* data) { TreeWalkData* twd = (TreeWalkData*)data; if (!twd) return NULL_TREE; if (!twd->path.empty() && twd->path.back() == tp) return NULL_TREE; // root of this level if (walk_subtrees) *walk_subtrees = 0; twd->cancel_levels = 0; twd->path.push_back(tp); instrument_tree(twd->path, twd->gsi, twd->cancel_levels); if (twd->cancel_levels == 0) walk_tree(tp, walk_tree_level, data, NULL); twd->path.pop_back(); if (twd->cancel_levels > 0) twd->cancel_levels--; // Cancel current level if there are still cancel_levels due return twd->cancel_levels == 0 ? NULL_TREE : *tp; } static bool walk_gimple_stmt(gimple_stmt_iterator* gsi) { if (!gsi || gsi_end_p(*gsi)) return false; gimple* stmt = gsi_stmt(*gsi); for (int i = 0; i < gimple_num_ops(stmt); i++) { tree* op = gimple_op_ptr(stmt, i); if (!op || !*op) continue; TreeWalkData twd; twd.gsi = gsi; walk_tree_level(op, NULL, &twd); } return true; } unsigned int log_component_refs_pass::execute(function* fun) { const char* funcname = fun->decl && DECL_NAME(fun->decl) ? IDENTIFIER_POINTER(DECL_NAME(fun->decl)) : ""; tree fndecl = fun->decl; if (!fndecl) return 0; gimple_seq body = gimple_body(fndecl); for (gimple_stmt_iterator gsi = gsi_start(body); walk_gimple_stmt(&gsi); gsi_next(&gsi)); // print_gimple_seq(stderr, gimple_body(fun->decl), 0, TDF_NONE); return 0; } // Gimple print pass for debugging static const pass_data print_pass_data = { GIMPLE_PASS, "gimple_print", OPTGROUP_NONE, TV_NONE, 0,0,0,0, 0 }; struct print_pass : gimple_opt_pass { print_pass(gcc::context *ctxt); unsigned int execute(function* fun) override; }; print_pass::print_pass(gcc::context *ctxt) : gimple_opt_pass(print_pass_data, ctxt) {} unsigned int print_pass::execute(function* fun) { const char* funcname = fun->decl && DECL_NAME(fun->decl) ? IDENTIFIER_POINTER(DECL_NAME(fun->decl)) : ""; fprintf(stderr, "[spslr_pinpoint] Function: %s\n", funcname); basic_block bb; FOR_EACH_BB_FN(bb, fun) { for (gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { gimple* stmt = gsi_stmt(gsi); print_gimple_stmt(stderr, stmt, 0, TDF_SLIM); } } fprintf(stderr, "\n"); return 0; } // At early RTL, replace __spslr_offsetof_ with UNSPECs to avoid ABI and clobbering "problems" static bool extract_callee_symbol (rtx call_rtx, const char** out_name) { if (!call_rtx || GET_CODE(call_rtx) != CALL) return false; rtx op0 = XEXP(call_rtx, 0); if (!op0) return false; rtx addr = XEXP(op0, 0); if (addr && GET_CODE(addr) == SYMBOL_REF) { *out_name = XSTR(addr, 0); return true; } return false; } static bool parse_uid_from_name(const char* name, unsigned long* out_uid) { if (!name || !out_uid) return false; if (strncmp(name, SPSLR_OFFSETOF, strlen(SPSLR_OFFSETOF)) != 0) return false; const char *p = name + strlen(SPSLR_OFFSETOF); if (*p == 0) return false; char *endp = nullptr; unsigned long v = strtoul(p, &endp, 10); if (endp == p) return false; *out_uid = v; return true; } static bool extract_dest_and_call (rtx pat, rtx* out_dest, rtx* out_call) { if (!pat) return false; if (GET_CODE(pat) == SET) { rtx src = SET_SRC(pat); if (GET_CODE(src) == CALL) { *out_dest = SET_DEST(pat); *out_call = src; return true; } return false; } if (GET_CODE(pat) == PARALLEL) { // Look for a SET whose src is CALL int n = XVECLEN(pat, 0); for (int i = 0; i < n; ++i) { rtx elt = XVECEXP(pat, 0, i); if (GET_CODE(elt) == SET && GET_CODE(SET_SRC(elt)) == CALL) { *out_dest = SET_DEST(elt); *out_call = SET_SRC(elt); return true; } } } return false; } static void call_to_unspec(function* fn) { if (!fn) return; unsigned replaced = 0; basic_block bb; FOR_EACH_BB_FN(bb, fn) { for (rtx_insn* insn = BB_HEAD(bb); insn != NEXT_INSN(BB_END(bb)); insn = NEXT_INSN(insn)) { if (!INSN_P(insn) || !CALL_P(insn)) continue; rtx pat = PATTERN(insn); rtx dest = NULL_RTX; rtx call = NULL_RTX; if (!extract_dest_and_call(pat, &dest, &call)) continue; const char *name = nullptr; if (!extract_callee_symbol(call, &name)) continue; unsigned long uid = 0; if (!parse_uid_from_name(name, &uid)) continue; // We expect a returning call (assigned to dest) if (!dest || !REG_P(dest) || GET_MODE(dest) == VOIDmode) continue; machine_mode mode = GET_MODE(dest); // Build: (set dest (unspec:mode [(const_int uid)] UNSPEC_SPSLR_OFFSETOF)) rtvec vec = gen_rtvec(1, GEN_INT((HOST_WIDE_INT) uid)); rtx uns = gen_rtx_UNSPEC(mode, vec, UNSPEC_SPSLR_OFFSETOF); // Note -> maybe volatile rtx set = gen_rtx_SET(dest, uns); emit_insn_before(set, insn); delete_insn(insn); replaced++; } } if (replaced) { df_set_bb_dirty(ENTRY_BLOCK_PTR_FOR_FN(fn)); df_set_bb_dirty(EXIT_BLOCK_PTR_FOR_FN(fn)); } } const pass_data call_to_unspec_pass_data = { RTL_PASS, "call_to_unspec", OPTGROUP_NONE, TV_NONE, PROP_rtl, 0, 0, 0, 0 }; struct call_to_unspec_pass : rtl_opt_pass { call_to_unspec_pass(gcc::context* ctxt) : rtl_opt_pass(call_to_unspec_pass_data, ctxt) {} unsigned int execute(function* fn) override { call_to_unspec(fn); return 0; } }; // Late RTL pass replaces UNSPECs with labeled constants (must happen before vregs, no optimizations afterwards) static bool lookup_initial_member_offset(SPSLROffsetofCallData::UID uid, Member::OFF& ioff) { auto call_data_it = spslr_offsetof_calls.find(uid); if (call_data_it == spslr_offsetof_calls.end()) return false; const SPSLROffsetofCallData& call_data = call_data_it->second; // call_data.target, call_data.member ioff = call_data.member; return true; } static void emit_named_asm_label_before(SPSLROffsetofCallData::UID uid, rtx_insn* before) { char name[128]; snprintf(name, sizeof(name), "%s%lu:\n", SPSLR_OFFSETOF, uid); /* Build empty operand vectors. */ rtvec no_out = rtvec_alloc(0); rtvec no_in = rtvec_alloc(0); rtvec no_cl = rtvec_alloc(0); const char* empty_constraints = ""; /* Location: use current insn’s location if available. */ location_t loc = INSN_LOCATION(before); /* Create a zero-operand, volatile asm insn. */ rtx asmops = gen_rtx_ASM_OPERANDS (VOIDmode, ggc_strdup(name), empty_constraints, 1, no_out, no_in, no_cl, loc); /* Emit it right before the target insn. This does NOT affect the CFG. */ emit_insn_before (asmops, before); } static void unspec_to_labeled_const(function* fn) { basic_block bb; FOR_EACH_BB_FN(bb, fn) { for (rtx_insn *insn = BB_HEAD(bb); insn != NEXT_INSN(BB_END(bb)); insn = NEXT_INSN(insn)) { if (!INSN_P(insn)) continue; rtx pat = PATTERN(insn); if (GET_CODE(pat) != SET) continue; rtx src = SET_SRC(pat); if (GET_CODE(src) != UNSPEC) continue; if (XINT(src, 1) != UNSPEC_SPSLR_OFFSETOF) continue; /* Extract UID from UNSPEC operands. */ if (XVEC(src, 0) == NULL || XVECLEN(src, 0) < 1) continue; rtx arg = XVECEXP(src, 0, 0); if (!CONST_INT_P(arg)) continue; SPSLROffsetofCallData::UID uid = (SPSLROffsetofCallData::UID)INTVAL(arg); rtx dest = SET_DEST(pat); Member::OFF initial_offset = uid; // TODO -> Use value that forces 32 bit (currently uses 32 bit anyways)? if (!lookup_initial_member_offset(uid, initial_offset)) { std::cerr << "Failed to query initial member offset for access uid " << uid << "!" << std::endl; return; } // Generate asm label emit_named_asm_label_before(uid, insn); /* 2. Replace UNSPEC with constant. */ rtx new_set = gen_rtx_SET(dest, GEN_INT(initial_offset)); PATTERN(insn) = new_set; INSN_CODE(insn) = -1; /* force re-recognition */ std::cout << "Inserted labeled initial member offset " << initial_offset << " for access uid " << uid << "!" << std::endl; } } } const pass_data unspec_to_lconst_pass_data = { RTL_PASS, "unspec_to_lconst", OPTGROUP_NONE, TV_NONE, PROP_rtl, 0, 0, 0, 0 }; struct unspec_to_lconst_pass : rtl_opt_pass { unspec_to_lconst_pass (gcc::context *ctxt) : rtl_opt_pass(unspec_to_lconst_pass_data, ctxt) {} unsigned int execute(function* fn) override { unspec_to_labeled_const(fn); return 0; } }; // Hook everything up in plugin_init int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) { if (!plugin_default_version_check(version, &gcc_version)) { fprintf(stderr, "GCC version mismatch!\n"); std::cerr << "spslr_pinpoint -> GCC version mismatch" << std::endl; return 1; } register_callback(plugin_info->base_name, PLUGIN_ATTRIBUTES, on_register_attributes, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, on_type_complete, NULL); register_callback(plugin_info->base_name, PLUGIN_BUILD_COMPONENT_REF, on_build_component_ref, NULL); struct register_pass_info log_component_refs_pass_info; log_component_refs_pass_info.pass = new log_component_refs_pass(nullptr); log_component_refs_pass_info.ref_pass_instance_number = 1; log_component_refs_pass_info.reference_pass_name = "cfg"; log_component_refs_pass_info.pos_op = PASS_POS_INSERT_BEFORE; register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &log_component_refs_pass_info); struct register_pass_info print_pass_info; print_pass_info.pass = new print_pass(nullptr); print_pass_info.ref_pass_instance_number = 1; print_pass_info.reference_pass_name = "cfg"; print_pass_info.pos_op = PASS_POS_INSERT_AFTER; register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &print_pass_info); struct register_pass_info call_to_unspec_pass_info; call_to_unspec_pass_info.pass = new call_to_unspec_pass(nullptr); call_to_unspec_pass_info.reference_pass_name = "expand"; call_to_unspec_pass_info.ref_pass_instance_number = 1; call_to_unspec_pass_info.pos_op = PASS_POS_INSERT_AFTER; register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &call_to_unspec_pass_info); struct register_pass_info unspec_to_lconst_pass_info; unspec_to_lconst_pass_info.pass = new unspec_to_lconst_pass(nullptr); unspec_to_lconst_pass_info.reference_pass_name = "vregs"; unspec_to_lconst_pass_info.ref_pass_instance_number = 1; unspec_to_lconst_pass_info.pos_op = PASS_POS_INSERT_BEFORE; register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &unspec_to_lconst_pass_info); return 0; }