982 lines
27 KiB
C++
982 lines
27 KiB
C++
#include <iostream>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <map>
|
||
#include <list>
|
||
|
||
#include <gcc-plugin.h>
|
||
#include <plugin-version.h>
|
||
#include <tree.h>
|
||
#include <langhooks.h>
|
||
#include <gimple.h>
|
||
#include <gimplify.h>
|
||
#include <stringpool.h>
|
||
#include <tree-pass.h>
|
||
#include <gimple-iterator.h>
|
||
#include <gimple-pretty-print.h>
|
||
#include <tree-pretty-print.h>
|
||
#include <tree-dump.h>
|
||
#include <print-tree.h>
|
||
#include <tree-iterator.h>
|
||
#include <rtl.h>
|
||
#include <memmodel.h>
|
||
#include <emit-rtl.h>
|
||
#include <df.h>
|
||
|
||
int plugin_is_GPL_compatible;
|
||
|
||
#ifndef SPSLR_OFFSETOF
|
||
#define SPSLR_OFFSETOF "__spslr_offsetof_" /* with suffix <uid> */
|
||
#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<Member::OFF, Member> 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<Target::UID, Target> targets;
|
||
static std::list<TargetTree> target_trees;
|
||
|
||
static std::unordered_set<tree> 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<Member::OFF>(tmp_member.offset + tmp_member.size,
|
||
existing_member.offset + existing_member.size);
|
||
|
||
Member::OFF combined_offset = std::min<Member::OFF>(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<SPSLROffsetofCallData::UID, SPSLROffsetofCallData> 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 << "<uid> 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<Link> 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 << "<uid> 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<tree*>& 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<tree*> 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))
|
||
: "<anonymous>";
|
||
|
||
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))
|
||
: "<anonymous>";
|
||
|
||
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_<uid> 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;
|
||
}
|