diff --git a/pinpoint/pinpoint.cpp b/pinpoint/pinpoint.cpp index 565bf3c..863ade2 100644 --- a/pinpoint/pinpoint.cpp +++ b/pinpoint/pinpoint.cpp @@ -13,10 +13,20 @@ int plugin_init(struct plugin_name_args* plugin_info, struct plugin_gcc_version* return 1; } - // Stage 0 -> logic that happens before all usual passes + // Stage 0 -> separates relevant field offsets into function calls + register_callback(plugin_info->base_name, PLUGIN_ATTRIBUTES, on_register_attributes, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, on_finish_type, NULL); register_callback(plugin_info->base_name, PLUGIN_BUILD_COMPONENT_REF, on_preserve_component_ref, NULL); + struct register_pass_info separate_offset_pass_info; + separate_offset_pass_info.pass = new separate_offset_pass(nullptr); + separate_offset_pass_info.ref_pass_instance_number = 1; + separate_offset_pass_info.reference_pass_name = "cfg"; + separate_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER; + register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &separate_offset_pass_info); + + // Stage 1 -> TODO + return 0; } diff --git a/pinpoint/safegcc/safe-tree.h b/pinpoint/safegcc/safe-tree.h index 0017178..b2c0625 100644 --- a/pinpoint/safegcc/safe-tree.h +++ b/pinpoint/safegcc/safe-tree.h @@ -4,5 +4,6 @@ #define SAFEGCC_TREE_H #include +#include #endif diff --git a/pinpoint/stage0/CMakeLists.txt b/pinpoint/stage0/CMakeLists.txt index b4d77fd..7aa01a8 100644 --- a/pinpoint/stage0/CMakeLists.txt +++ b/pinpoint/stage0/CMakeLists.txt @@ -1,3 +1,3 @@ target_sources(spslr_pinpoint PRIVATE on_register_attributes.cpp on_finish_type.cpp on_preserve_component_ref.cpp - target.cpp separator.cpp) + target.cpp separator.cpp separate_offset_pass.cpp) target_include_directories(spslr_pinpoint PRIVATE .) diff --git a/pinpoint/stage0/separate_offset_pass.cpp b/pinpoint/stage0/separate_offset_pass.cpp new file mode 100644 index 0000000..0c3c0ce --- /dev/null +++ b/pinpoint/stage0/separate_offset_pass.cpp @@ -0,0 +1,247 @@ +#include // TODO +#include +#include +#include + +#include + +struct ComponentRefChain { + struct Link { + tree t = NULL_TREE; + bool relevant = false; + UID target; + std::size_t offset; + }; + + 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 tree_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 component_ref_chain(tree ref, ComponentRefChain& chain) { + if (!ref) + return false; + + if (TREE_CODE(ref) != COMPONENT_REF) { + if (chain.links.empty()) + return false; + + if (tree_contains_component_ref(ref)) + return false; + + chain.base = ref; + return true; + } + + ComponentRefChain::Link link; + link.t = ref; + link.relevant = TargetType::reference(ref, link.target, link.offset); + + if (link.relevant) + chain.relevant = true; + + chain.links.push_front(link); + return component_ref_chain(TREE_OPERAND(ref, 0), chain); +} + +static tree separate_offset_chain_maybe(tree ref, gimple_stmt_iterator* gsi) { + ComponentRefChain crc; + if (!component_ref_chain(ref, crc)) + pinpoint_fatal("separate_offset_chain_maybe encountered invalid COMPONENT_REF chain"); + + if (!crc.relevant) + return NULL_TREE; + + // Get base pointer + + tree base_tmp = NULL_TREE; + + if (TREE_CODE(crc.base) == ADDR_EXPR) + pinpoint_fatal("separate_offset_chain_maybe encountered ADDR_EXPR as base of COMPONENT_REF"); + + base_tmp = build_fold_addr_expr(crc.base); + + // For each component ref in chain, add the member offset to the pointer + + for (const ComponentRefChain::Link& cr : crc.links) { + // NOTE -> Could fold into single call here (needs to track what offsets contribute, +irrelevant combined) + if (cr.relevant) { + // Call is a separate statement, the result is added to the base pointer + tree return_tmp = create_tmp_var(size_type_node, NULL); + + gimple* call_stmt = make_stage0_gimple_separator(return_tmp, cr.target, cr.offset); + if (!call_stmt) + pinpoint_fatal("separate_offset_chain_maybe failed to make gimple separator for target %u at offset %u", + (unsigned)cr.target, (unsigned)cr.offset); + + gsi_insert_before(gsi, call_stmt, GSI_SAME_STMT); + + // Add call return value to current base pointer (result is field pointer) + tree field_ptr_type = build_pointer_type(TREE_TYPE(cr.t)); + tree field_ptr = build2(POINTER_PLUS_EXPR, field_ptr_type, base_tmp, return_tmp); + + base_tmp = create_tmp_var(field_ptr_type, NULL); + gimple* field_ptr_assignment = gimple_build_assign(base_tmp, field_ptr); + gsi_insert_before(gsi, field_ptr_assignment, GSI_SAME_STMT); + } else { + // Add offsetof contant + tree field_decl = TREE_OPERAND(cr.t, 1); + + std::size_t field_offset; + bool field_bitfield; + + if (!field_info(field_decl, &field_offset, nullptr, &field_bitfield)) + pinpoint_fatal("separate_offset_chain_maybe failed to get field info of non-target access"); + + if (field_bitfield) + pinpoint_fatal("separate_offset_chain_maybe encountered bitfield access in relevant COMPONENT_REF chain"); + + // Add constant offset to current base pointer (result is field pointer) + tree field_ptr_type = build_pointer_type(TREE_TYPE(cr.t)); + tree field_ptr = build2(POINTER_PLUS_EXPR, field_ptr_type, base_tmp, build_int_cst(sizetype, field_offset)); + + base_tmp = create_tmp_var(field_ptr_type, NULL); + gimple* field_ptr_assignment = gimple_build_assign(base_tmp, field_ptr); + gsi_insert_before(gsi, field_ptr_assignment, GSI_SAME_STMT); + } + } + + // Current pointer is a field pointer -> dereference + + tree offset0 = fold_convert(TREE_TYPE(base_tmp), build_int_cst(sizetype, 0)); + tree result_ref = build2(MEM_REF, TREE_TYPE(ref), base_tmp, offset0); + + return result_ref; +} + +static void dispatch_separation_maybe(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 = separate_offset_chain_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++; + } +} + +static const pass_data separate_offset_pass_data = { + GIMPLE_PASS, + "separate_offset", + OPTGROUP_NONE, + TV_NONE, + 0,0,0,0, + TODO_update_ssa +}; + +separate_offset_pass::separate_offset_pass(gcc::context* ctxt) : gimple_opt_pass(separate_offset_pass_data, ctxt) {} + +struct TreeWalkData { + std::list path; + gimple_stmt_iterator* gsi; + unsigned cancel_levels; + std::function&, gimple_stmt_iterator*, unsigned&)> callback; +}; + +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); + + twd->callback(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, + std::function&, gimple_stmt_iterator*, unsigned&)> callback) { + if (!gsi || gsi_end_p(*gsi) || !callback) + 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; + twd.callback = callback; + + walk_tree_level(op, NULL, &twd); + } + + return true; +} + +unsigned int separate_offset_pass::execute(function* fn) { + if (!fn) + return 0; + + basic_block bb; + FOR_EACH_BB_FN(bb, fn) { + for (gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { + if (!walk_gimple_stmt(&gsi, dispatch_separation_maybe)) + pinpoint_fatal("separate_offset pass failed to walk gimple statement"); + } + } + + return 0; +} diff --git a/pinpoint/stage0/separator.cpp b/pinpoint/stage0/separator.cpp index 13fd881..39b22bb 100644 --- a/pinpoint/stage0/separator.cpp +++ b/pinpoint/stage0/separator.cpp @@ -40,23 +40,25 @@ tree make_stage0_ast_separator(UID target, std::size_t offset) { return build_call_expr(decl, 2, arg0, arg1); } -gimple* make_stage0_gimple_separator(UID target, std::size_t offset) { +gimple* make_stage0_gimple_separator(tree lhs, UID target, std::size_t offset) { + if (!lhs) + return nullptr; + 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) + if (!arg0 || !arg1) return nullptr; gimple* call = gimple_build_call(decl, 2, arg0, arg1); if (!call) return nullptr; - gimple_call_set_lhs(call, res); + gimple_call_set_lhs(call, lhs); return call; } diff --git a/pinpoint/stage0/stage0.h b/pinpoint/stage0/stage0.h index 9e7b99f..d294022 100644 --- a/pinpoint/stage0/stage0.h +++ b/pinpoint/stage0/stage0.h @@ -55,13 +55,21 @@ private: static TargetType* find_mutable(tree t); }; +bool field_info(tree field_decl, std::size_t* offset, std::size_t* size, bool* bitfield); + /* 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 +gimple* make_stage0_gimple_separator(tree lhs, UID target, std::size_t offset); bool is_stage0_separator(gimple* stmt, UID& target, std::size_t& offset); void on_register_attributes(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); + +struct separate_offset_pass : gimple_opt_pass { + separate_offset_pass(gcc::context* ctxt); + unsigned int execute(function* fn) override; +}; + diff --git a/pinpoint/stage0/target.cpp b/pinpoint/stage0/target.cpp index e03d783..2940c69 100644 --- a/pinpoint/stage0/target.cpp +++ b/pinpoint/stage0/target.cpp @@ -125,7 +125,7 @@ const TargetType* TargetType::find(UID uid) { return &it->second; } -static bool field_info(tree field_decl, std::size_t* offset, std::size_t* size, bool* bitfield) { +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; diff --git a/playground/spslr_pinpoint.cpp b/playground/spslr_pinpoint.cpp index 3746359..12cd9d0 100644 --- a/playground/spslr_pinpoint.cpp +++ b/playground/spslr_pinpoint.cpp @@ -34,308 +34,6 @@ int plugin_is_GPL_compatible; #define UNSPEC_SPSLR_OFFSETOF 1042 #endif -// 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; - - // Get base pointer - - 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); - } - - base_tmp = base_ptr; - } - - // 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 (result is field pointer) - tree field_ptr_type = build_pointer_type(TREE_TYPE(cr.t)); - tree field_ptr = build2(POINTER_PLUS_EXPR, field_ptr_type, base_tmp, offset_tmp); - - base_tmp = create_tmp_var(field_ptr_type, NULL); - gimple* addition_assignment = gimple_build_assign(base_tmp, field_ptr); - 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 (result is field pointer) - tree field_ptr_type = build_pointer_type(TREE_TYPE(cr.t)); - tree field_ptr = build2(POINTER_PLUS_EXPR, field_ptr_type, base_tmp, build_int_cst(sizetype, offset)); - - base_tmp = create_tmp_var(field_ptr_type, NULL); - gimple* addition_assignment = gimple_build_assign(base_tmp, field_ptr); - gsi_insert_before(gsi, addition_assignment, GSI_SAME_STMT); - } - } - - // Current pointer is a field pointer -> dereference - - tree offset0 = fold_convert(TREE_TYPE(base_tmp), build_int_cst(sizetype, 0)); - tree result_ref = build2(MEM_REF, TREE_TYPE(ref), base_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; -} - // 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) { @@ -622,15 +320,6 @@ int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version return 1; } - 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); - /* TODO vregs is almost immediately after expand (maybe the first one) diff --git a/subject/CMakeLists.txt b/subject/CMakeLists.txt index 108c03f..90598c1 100644 --- a/subject/CMakeLists.txt +++ b/subject/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(subject main.c) add_dependencies(subject spslr_pinpoint spslr_finalize spslr_selfpatch) target_link_libraries(subject PRIVATE spslr_selfpatch) -target_compile_options(subject PRIVATE -fplugin=$) +target_compile_options(subject PRIVATE -fplugin=$ -fdump-tree-separate_offset)