Pinpoint plugin on_preserve_component_ref

This commit is contained in:
York Jasper Niebuhr 2025-10-24 23:07:51 +02:00
parent b725c35be5
commit 3f97c2654d
11 changed files with 227 additions and 218 deletions

View File

@ -1,3 +1,4 @@
#pragma once #pragma once
#define SPSLR_ATTRIBUTE "spslr" #define SPSLR_ATTRIBUTE "spslr"
#define SPSLR_PINPOINT_STAGE0_SEPARATOR "__spslr_offsetof"

View File

@ -0,0 +1,8 @@
#pragma once
#include <safe-diagnostic.h>
#define pinpoint_fatal_loc(loc, fmt, ...) \
fatal_error((loc), "[spslr_pinpoint] " fmt, ##__VA_ARGS__)
#define pinpoint_fatal(fmt, ...) \
pinpoint_fatal_loc(UNKNOWN_LOCATION, fmt, ##__VA_ARGS__)

View File

@ -0,0 +1,11 @@
#include <safe-gcc-plugin.h>
#ifndef SAFEGCC_GIMPLE_H
#define SAFEGCC_GIMPLE_H
#include <gimple.h>
#include <gimplify.h>
#include <gimple-iterator.h>
#include <gimple-pretty-print.h>
#endif

View File

@ -1,8 +0,0 @@
#pragma once
#include <safe-diagnostic.h>
#define spslr_fatal_loc(loc, fmt, ...) \
fatal_error((loc), "[spslr_pinpoint] " fmt, ##__VA_ARGS__)
#define spslr_fatal(fmt, ...) \
spslr_fatal_loc(UNKNOWN_LOCATION, fmt, ##__VA_ARGS__)

View File

@ -1,2 +1,3 @@
target_sources(spslr_pinpoint PRIVATE on_register_attributes.cpp on_finish_type.cpp on_preserve_component_ref.cpp target.cpp) target_sources(spslr_pinpoint PRIVATE on_register_attributes.cpp on_finish_type.cpp on_preserve_component_ref.cpp
target.cpp separator.cpp)
target_include_directories(spslr_pinpoint PRIVATE .) target_include_directories(spslr_pinpoint PRIVATE .)

View File

@ -1,5 +1,5 @@
#include <stage0.h> #include <stage0.h>
#include <spslr_error.h> #include <pinpoint_error.h>
void on_finish_type(void* plugin_data, void* user_data) { void on_finish_type(void* plugin_data, void* user_data) {
tree t = (tree)plugin_data; tree t = (tree)plugin_data;
@ -9,6 +9,6 @@ void on_finish_type(void* plugin_data, void* user_data) {
return; return;
if (!type->fetch_fields()) if (!type->fetch_fields())
spslr_fatal("on_finish_type failed to fetch fields of target \"%s\"", type->name().c_str()); pinpoint_fatal("on_finish_type failed to fetch fields of target \"%s\"", type->name().c_str());
} }

View File

@ -1,6 +1,40 @@
#include <stage0.h> #include <stage0.h>
#include <pinpoint_error.h>
void on_preserve_component_ref(void* plugin_data, void* user_data) { static tree ast_separate_offset(tree ref, UID target, std::size_t offset) {
tree separator = make_stage0_ast_separator(target, offset);
if (!separator)
pinpoint_fatal("ast_separate_offset failed to generate AST separator for target %u at offset %u",
(unsigned)target, (unsigned)offset);
tree base = TREE_OPERAND(ref, 0);
// ADDR_EXPR can never be a valid base, but such trees can happen during parsing before checks
if (TREE_CODE(base) == ADDR_EXPR)
pinpoint_fatal("ast_separate_offset encountered ADDR_EXPR as COMPONENT_REF base");
tree base_ptr = build_fold_addr_expr(base);
tree field_type = TREE_TYPE(ref); // Type of COMPONENT_REF is type of the accessed field
tree field_ptr_type = build_pointer_type(field_type);
tree field_ptr = build2(POINTER_PLUS_EXPR, field_ptr_type, base_ptr, separator);
tree field_ref = build1(INDIRECT_REF, field_type, field_ptr);
return field_ref;
}
void on_preserve_component_ref(void* plugin_data, void* user_data) {
tree* ref = (tree*)plugin_data;
if (!ref)
return;
UID target;
std::size_t offset;
if (!TargetType::reference(*ref, target, offset))
return;
tree separated = ast_separate_offset(*ref, target, offset);
if (separated)
*ref = separated;
} }

View File

@ -0,0 +1,67 @@
#include <stage0.h>
#include <pinpoint_config.h>
static tree separator_decl = NULL_TREE;
static tree make_separator_decl() {
if (separator_decl)
return separator_decl;
tree args = tree_cons(NULL_TREE, sizetype, tree_cons(NULL_TREE, sizetype, NULL_TREE));
tree type = build_function_type(sizetype, args);
tree tmp_decl = build_fn_decl(SPSLR_PINPOINT_STAGE0_SEPARATOR, type);
if (!tmp_decl)
return NULL_TREE;
DECL_EXTERNAL(tmp_decl) = 1;
TREE_PUBLIC(tmp_decl) = 1;
DECL_ARTIFICIAL(tmp_decl) = 1;
/* Prevent VOP problems later when removing calls (VOPs mark memory
side-effects, which these calls have none of anyways */
DECL_PURE_P(tmp_decl) = 1;
DECL_IS_NOVOPS(tmp_decl) = 1;
return (separator_decl = tmp_decl);
}
tree make_stage0_ast_separator(UID target, std::size_t offset) {
tree decl = make_separator_decl();
if (!decl)
return NULL_TREE;
tree arg0 = size_int(target);
tree arg1 = size_int(offset);
if (!arg0 || !arg1)
return NULL_TREE;
return build_call_expr(decl, 2, arg0, arg1);
}
gimple* make_stage0_gimple_separator(UID target, std::size_t offset) {
tree decl = make_separator_decl();
if (!decl)
return nullptr;
tree arg0 = size_int(target);
tree arg1 = size_int(offset);
tree res = create_tmp_var(size_type_node, NULL);
if (!arg0 || !arg1 || !res)
return nullptr;
gimple* call = gimple_build_call(decl, 2, arg0, arg1);
if (!call)
return nullptr;
gimple_call_set_lhs(call, res);
return call;
}
bool is_stage0_separator(gimple* stmt, UID& target, std::size_t& offset) {
// TODO
return false;
}

View File

@ -5,6 +5,7 @@
#include <limits> #include <limits>
#include <safe-tree.h> #include <safe-tree.h>
#include <safe-gimple.h>
using UID = std::size_t; using UID = std::size_t;
constexpr UID UID_INVALID = std::numeric_limits<UID>::max(); constexpr UID UID_INVALID = std::numeric_limits<UID>::max();
@ -41,17 +42,26 @@ public:
bool fields() const; bool fields() const;
std::string name() const; std::string name() const;
const Field* field(std::size_t off, bool exact = true) const; const Field* field(std::size_t off, bool exact = true) const;
UID uid() const;
static void add(tree t); static void add(tree t);
static std::size_t count(); static std::size_t count();
static const TargetType* find(tree t); // O(n) static const TargetType* find(tree t); // O(n)
static const TargetType* find(UID uid); // O(1) static const TargetType* find(UID uid); // O(1)
static bool reference(tree ref, UID& target, std::size_t& offset);
private: private:
friend void on_finish_type(void*, void*); friend void on_finish_type(void*, void*);
bool fetch_fields(bool redo = false); bool fetch_fields(bool redo = false);
static TargetType* find_mutable(tree t); static TargetType* find_mutable(tree t);
}; };
/* Stage 0 offsetof separators are function calls, such as:
SPSLR_PINPOINT_STAGE0_SEPARATOR(target, member offset) */
tree make_stage0_ast_separator(UID target, std::size_t offset);
gimple* make_stage0_gimple_separator(UID target, std::size_t offset); // lhs is a new temporary variable
bool is_stage0_separator(gimple* stmt, UID& target, std::size_t& offset);
void on_register_attributes(void* plugin_data, void* user_data); void on_register_attributes(void* plugin_data, void* user_data);
void on_finish_type(void* plugin_data, void* user_data); void on_finish_type(void* plugin_data, void* user_data);
void on_preserve_component_ref(void* plugin_data, void* user_data); void on_preserve_component_ref(void* plugin_data, void* user_data);

View File

@ -71,6 +71,10 @@ const TargetType::Field* TargetType::field(std::size_t off, bool exact) const {
return &maybe; return &maybe;
} }
UID TargetType::uid() const {
return m_uid;
}
void TargetType::add(tree t) { void TargetType::add(tree t) {
if (find(t) != nullptr) if (find(t) != nullptr)
return; return;
@ -121,6 +125,87 @@ const TargetType* TargetType::find(UID uid) {
return &it->second; return &it->second;
} }
static bool field_info(tree field_decl, std::size_t* offset, std::size_t* size, bool* bitfield) {
if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
return false;
HOST_WIDE_INT tmp_byte_offset = 0;
if (TREE_CODE(DECL_FIELD_OFFSET(field_decl)) == INTEGER_CST)
tmp_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field_decl));
else
return false;
HOST_WIDE_INT tmp_bit_offset = 0;
if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field_decl)) == INTEGER_CST)
tmp_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field_decl));
else
return false;
HOST_WIDE_INT bit_offset_bytes = tmp_bit_offset / 8;
tmp_byte_offset += bit_offset_bytes;
tmp_bit_offset -= bit_offset_bytes * 8;
HOST_WIDE_INT tmp_bit_size = 0;
if (TREE_CODE(DECL_SIZE(field_decl)) == INTEGER_CST)
tmp_bit_size = tree_to_uhwi(DECL_SIZE(field_decl));
else
return false;
bool tmp_bitfield = (DECL_BIT_FIELD_TYPE(field_decl) != NULL_TREE);
tmp_bitfield |= !(tmp_bit_size % 8 == 0 && tmp_bit_offset == 0);
// Intra-byte offset counts towards size
tmp_bit_size += tmp_bit_offset;
// Round size up to entire byte
HOST_WIDE_INT tmp_bit_overhang = tmp_bit_size % 8;
if (tmp_bit_overhang != 0)
tmp_bit_size += (8 - tmp_bit_overhang);
// Set all outputs
if (offset)
*offset = static_cast<std::size_t>(tmp_byte_offset);
if (size)
*size = static_cast<std::size_t>(tmp_bit_size / 8);
if (bitfield)
*bitfield = tmp_bitfield;
return true;
}
bool TargetType::reference(tree ref, UID& target, std::size_t& offset) {
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;
const TargetType* base_target = TargetType::find(base_type);
if (!base_target)
return false;
target = base_target->uid();
tree field_decl = TREE_OPERAND(ref, 1);
if (!field_info(field_decl, &offset, nullptr, nullptr))
return false;
const Field* f = base_target->field(offset, false);
if (!f || (f->flags & Field::FLAG_DANGEROUS))
return false;
return true;
}
static bool foreach_record_field(tree t, std::function<bool(const TargetType::Field&)> callback) { static bool foreach_record_field(tree t, std::function<bool(const TargetType::Field&)> callback) {
if (!t || TREE_CODE(t) != RECORD_TYPE) if (!t || TREE_CODE(t) != RECORD_TYPE)
return false; return false;
@ -132,35 +217,13 @@ static bool foreach_record_field(tree t, std::function<bool(const TargetType::Fi
if (TREE_CODE(field_decl) != FIELD_DECL) if (TREE_CODE(field_decl) != FIELD_DECL)
continue; continue;
HOST_WIDE_INT field_byte_offset = 0;
if (TREE_CODE(DECL_FIELD_OFFSET(field_decl)) == INTEGER_CST)
field_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field_decl));
HOST_WIDE_INT field_bit_offset = 0;
if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field_decl)) == INTEGER_CST)
field_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field_decl));
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_decl)) == INTEGER_CST)
field_bit_size = tree_to_uhwi(DECL_SIZE(field_decl));
bool is_bitfield = (DECL_BIT_FIELD_TYPE(field_decl) != 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;
TargetType::Field field; TargetType::Field field;
field.offset = static_cast<decltype(field.offset)>(field_byte_offset); bool is_bitfield;
field.size = static_cast<decltype(field.size)>(effective_field_size);
field.flags = (is_dangerous ? TargetType::Field::FLAG_DANGEROUS : 0); if (!field_info(field_decl, &field.offset, &field.size, &is_bitfield))
return false;
field.flags = (is_bitfield ? TargetType::Field::FLAG_DANGEROUS : 0);
if (!callback(field)) if (!callback(field))
return false; return false;

View File

@ -34,140 +34,6 @@ int plugin_is_GPL_compatible;
#define UNSPEC_SPSLR_OFFSETOF 1042 #define UNSPEC_SPSLR_OFFSETOF 1042
#endif #endif
// 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) // Identify any missing COMPONENT_REFs (e.g. from CONSTRUCTOR trees)
static const pass_data log_component_refs_pass_data = { static const pass_data log_component_refs_pass_data = {
@ -470,41 +336,6 @@ unsigned int log_component_refs_pass::execute(function* fun) {
return 0; 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" // 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) { static bool extract_callee_symbol (rtx call_rtx, const char** out_name) {
@ -791,8 +622,6 @@ int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version
return 1; 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); register_callback(plugin_info->base_name, PLUGIN_BUILD_COMPONENT_REF, on_build_component_ref, NULL);
struct register_pass_info log_component_refs_pass_info; struct register_pass_info log_component_refs_pass_info;
@ -802,13 +631,6 @@ int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version
log_component_refs_pass_info.pos_op = PASS_POS_INSERT_BEFORE; 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); 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);
/* /*
TODO TODO
vregs is almost immediately after expand (maybe the first one) vregs is almost immediately after expand (maybe the first one)