Signed-off-by: York Jasper Niebuhr <[email protected]>
---
scripts/Makefile.gcc-plugins | 9 +
scripts/gcc-plugins/Makefile | 18 +
scripts/gcc-plugins/asm_offset_pass.c | 79 +++
scripts/gcc-plugins/dpin_registry.c | 199 +++++++
scripts/gcc-plugins/dpin_registry.h | 22 +
scripts/gcc-plugins/ipin_registry.c | 367 +++++++++++++
scripts/gcc-plugins/ipin_registry.h | 57 ++
scripts/gcc-plugins/layout_hash.c | 61 +++
scripts/gcc-plugins/layout_hash.h | 8 +
scripts/gcc-plugins/on_finish_decl.c | 8 +
scripts/gcc-plugins/on_finish_type.c | 12 +
scripts/gcc-plugins/on_finish_unit.c | 336 ++++++++++++
.../gcc-plugins/on_preserve_component_ref.c | 99 ++++
scripts/gcc-plugins/on_register_attributes.c | 52 ++
scripts/gcc-plugins/on_start_unit.c | 11 +
scripts/gcc-plugins/passes.h | 31 ++
scripts/gcc-plugins/pinpoint.c | 103 ++++
scripts/gcc-plugins/pinpoint.h | 75 +++
.../gcc-plugins/rtl_ipin_survival_scan_pass.c | 37 ++
scripts/gcc-plugins/safe-attribs.h | 9 +
scripts/gcc-plugins/safe-diagnostic.h | 10 +
scripts/gcc-plugins/safe-gcc-plugin.h | 6 +
scripts/gcc-plugins/safe-ggc.h | 8 +
scripts/gcc-plugins/safe-gimple.h | 14 +
scripts/gcc-plugins/safe-input.h | 9 +
scripts/gcc-plugins/safe-langhooks.h | 8 +
scripts/gcc-plugins/safe-md5.h | 8 +
scripts/gcc-plugins/safe-output.h | 8 +
scripts/gcc-plugins/safe-plugin-version.h | 8 +
scripts/gcc-plugins/safe-rtl.h | 17 +
scripts/gcc-plugins/safe-tree.h | 10 +
scripts/gcc-plugins/separate_offset_pass.c | 327 ++++++++++++
scripts/gcc-plugins/serialize.c | 303 +++++++++++
scripts/gcc-plugins/serialize.h | 115 ++++
.../gcc-plugins/target_hash_builtin_pass.c | 167 ++++++
scripts/gcc-plugins/target_registry.c | 492 ++++++++++++++++++
scripts/gcc-plugins/target_registry.h | 59 +++
37 files changed, 3162 insertions(+)
create mode 100644 scripts/gcc-plugins/asm_offset_pass.c
create mode 100644 scripts/gcc-plugins/dpin_registry.c
create mode 100644 scripts/gcc-plugins/dpin_registry.h
create mode 100644 scripts/gcc-plugins/ipin_registry.c
create mode 100644 scripts/gcc-plugins/ipin_registry.h
create mode 100644 scripts/gcc-plugins/layout_hash.c
create mode 100644 scripts/gcc-plugins/layout_hash.h
create mode 100644 scripts/gcc-plugins/on_finish_decl.c
create mode 100644 scripts/gcc-plugins/on_finish_type.c
create mode 100644 scripts/gcc-plugins/on_finish_unit.c
create mode 100644 scripts/gcc-plugins/on_preserve_component_ref.c
create mode 100644 scripts/gcc-plugins/on_register_attributes.c
create mode 100644 scripts/gcc-plugins/on_start_unit.c
create mode 100644 scripts/gcc-plugins/passes.h
create mode 100644 scripts/gcc-plugins/pinpoint.c
create mode 100644 scripts/gcc-plugins/pinpoint.h
create mode 100644 scripts/gcc-plugins/rtl_ipin_survival_scan_pass.c
create mode 100644 scripts/gcc-plugins/safe-attribs.h
create mode 100644 scripts/gcc-plugins/safe-diagnostic.h
create mode 100644 scripts/gcc-plugins/safe-gcc-plugin.h
create mode 100644 scripts/gcc-plugins/safe-ggc.h
create mode 100644 scripts/gcc-plugins/safe-gimple.h
create mode 100644 scripts/gcc-plugins/safe-input.h
create mode 100644 scripts/gcc-plugins/safe-langhooks.h
create mode 100644 scripts/gcc-plugins/safe-md5.h
create mode 100644 scripts/gcc-plugins/safe-output.h
create mode 100644 scripts/gcc-plugins/safe-plugin-version.h
create mode 100644 scripts/gcc-plugins/safe-rtl.h
create mode 100644 scripts/gcc-plugins/safe-tree.h
create mode 100644 scripts/gcc-plugins/separate_offset_pass.c
create mode 100644 scripts/gcc-plugins/serialize.c
create mode 100644 scripts/gcc-plugins/serialize.h
create mode 100644 scripts/gcc-plugins/target_hash_builtin_pass.c
create mode 100644 scripts/gcc-plugins/target_registry.c
create mode 100644 scripts/gcc-plugins/target_registry.h
diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins
index b0e1423b09c2..2ae9d571d812 100644
--- a/scripts/Makefile.gcc-plugins
+++ b/scripts/Makefile.gcc-plugins
@@ -8,6 +8,15 @@ ifdef CONFIG_GCC_PLUGIN_LATENT_ENTROPY
endif
export DISABLE_LATENT_ENTROPY_PLUGIN
+PINPOINT_PLUGIN_CFLAGS := \
+ -fplugin=$(objtree)/scripts/gcc-plugins/pinpoint_plugin.so \
+ -D__SPSLR__
+
+gcc-plugin-$(CONFIG_SPSLR) += pinpoint_plugin.so
+gcc-plugin-cflags-$(CONFIG_SPSLR) += -D__SPSLR__
+
+export PINPOINT_PLUGIN_CFLAGS
+
# All the plugin CFLAGS are collected here in case a build target needs to
# filter them out of the KBUILD_CFLAGS.
GCC_PLUGINS_CFLAGS := $(strip $(addprefix
-fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y))
$(gcc-plugin-cflags-y)) -DGCC_PLUGINS
diff --git a/scripts/gcc-plugins/Makefile b/scripts/gcc-plugins/Makefile
index 05b14aba41ef..1ecc9e923b85 100644
--- a/scripts/gcc-plugins/Makefile
+++ b/scripts/gcc-plugins/Makefile
@@ -22,6 +22,24 @@ targets += randomize_layout_seed.h
#
# foo-objs := foo.o foo2.o
+pinpoint_plugin-objs := \
+ pinpoint.o \
+ asm_offset_pass.o \
+ dpin_registry.o \
+ ipin_registry.o \
+ layout_hash.o \
+ on_finish_decl.o \
+ on_finish_type.o \
+ on_finish_unit.o \
+ on_preserve_component_ref.o \
+ on_register_attributes.o \
+ on_start_unit.o \
+ rtl_ipin_survival_scan_pass.o \
+ separate_offset_pass.o \
+ serialize.o \
+ target_registry.o \
+ target_hash_builtin_pass.o
+
always-y += $(GCC_PLUGIN)
GCC_PLUGINS_DIR = $(shell $(CC) -print-file-name=plugin)
diff --git a/scripts/gcc-plugins/asm_offset_pass.c
b/scripts/gcc-plugins/asm_offset_pass.c
new file mode 100644
index 000000000000..5a1be25f1f2c
--- /dev/null
+++ b/scripts/gcc-plugins/asm_offset_pass.c
@@ -0,0 +1,79 @@
+#include <unordered_map>
+
+#include <pinpoint.h>
+#include <passes.h>
+#include <ipin_registry.h>
+
+/*
+ * gsi_replace() changes the statement, but SSA names that were defined by the
+ * separator call still point at the old def statement. Retarget those SSA defs
+ * so later GCC passes see the asm marker as the producer of the offset value.
+ */
+
+static void pin_update_ssa_def(function *fn, gimple *old_def, gimple *new_def)
+{
+ if (!fn || !old_def)
+ return;
+
+ // Stage 0 separator call was definition statement of temporary variable
+
+ unsigned i;
+ tree name;
+ FOR_EACH_SSA_NAME(i, name, fn)
+ {
+ if (!name)
+ continue;
+
+ if (SSA_NAME_DEF_STMT(name) != old_def)
+ continue;
+
+ SSA_NAME_DEF_STMT(name) = new_def;
+ }
+}
+
+static void pin_assemble_maybe(function *fn, gimple_stmt_iterator *gsi)
+{
+ if (!gsi)
+ return;
+
+ gimple *stmt = gsi_stmt(*gsi);
+ if (!stmt)
+ return;
+
+ ipin::handle pin = ipin::identify_gimple_separator(stmt);
+ if (pin == ipin::invalid)
+ return;
+
+ gimple *replacement = ipin::make_gimple_pin(gimple_call_lhs(stmt), pin);
+ if (!replacement)
+ pinpoint_fatal("failed to construct ipin");
+
+ gsi_replace(gsi, replacement, true);
+ pin_update_ssa_def(fn, stmt, replacement);
+}
+
+static const pass_data asm_offset_pass_data = {
+ GIMPLE_PASS, "asm_offset", OPTGROUP_NONE, TV_NONE, 0, 0, 0,
+ 0, TODO_update_ssa
+};
+
+asm_offset_pass::asm_offset_pass(gcc::context *ctxt)
+ : gimple_opt_pass(asm_offset_pass_data, ctxt)
+{
+}
+
+unsigned int asm_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))
+ pin_assemble_maybe(fn, &gsi);
+ }
+
+ return 0;
+}
diff --git a/scripts/gcc-plugins/dpin_registry.c
b/scripts/gcc-plugins/dpin_registry.c
new file mode 100644
index 000000000000..415f7befe296
--- /dev/null
+++ b/scripts/gcc-plugins/dpin_registry.c
@@ -0,0 +1,199 @@
+#include <string>
+#include <unordered_set>
+
+#include <pinpoint.h>
+#include <dpin_registry.h>
+#include <target_registry.h>
+
+static std::list<dpin> pins;
+static std::unordered_set<std::string> seen_dpin_symbols;
+
+PINPOINT_GC_PRESERVE_CALLBACK()
+{
+ for (const dpin &pin : pins)
+ for (const dpin::component &c : pin.components)
+ PINPOINT_GC_MARK_TREE(c.target);
+}
+
+void dpin::reset()
+{
+ pins.clear();
+ seen_dpin_symbols.clear();
+}
+
+const std::list<dpin> &dpin::inspect()
+{
+ return pins;
+}
+
+static std::list<dpin::component> compile_datapin_components(tree type);
+static std::list<dpin::component> compile_datapin_record_components(tree type);
+static std::list<dpin::component> compile_datapin_array_components(tree type);
+
+/*
+ * Build the list of randomized objects contained in a static object.
+ *
+ * A dpin may describe the object itself, nested target structs, arrays of
+ * targets, or targets embedded inside non-target containers. The level field
+ * records nesting depth so the runtime can patch inner objects before
+ * their containing objects.
+ */
+
+static std::list<dpin::component> compile_datapin_components(tree type)
+{
+ std::list<dpin::component> components;
+
+ tree relevant = target::main_variant(type);
+ if (target::is_target(relevant)) {
+ components.push_back(dpin::component{
+ .offset = 0,
+ .level = 0,
+ .target = relevant,
+ });
+ }
+
+ std::list<dpin::component> sub_components;
+ if (TREE_CODE(type) == RECORD_TYPE)
+ sub_components = compile_datapin_record_components(type);
+ else if (TREE_CODE(type) == ARRAY_TYPE)
+ sub_components = compile_datapin_array_components(type);
+
+ for (const dpin::component &sc : sub_components) {
+ components.push_back(dpin::component{
+ .offset = sc.offset,
+ .level = sc.level + 1,
+ .target = sc.target,
+ });
+ }
+
+ // Note -> should probably make sure that randomized structs are never
used in unions!
+ return components;
+}
+
+static std::list<dpin::component> compile_datapin_record_components(tree type)
+{
+ std::list<dpin::component> components;
+
+ if (TREE_CODE(type) != RECORD_TYPE || !COMPLETE_TYPE_P(type))
+ return components;
+
+ for (tree field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) {
+ if (TREE_CODE(field) != FIELD_DECL)
+ continue;
+
+ if (!target::field_has_size(field))
+ continue; // flexible array member / dynamic-size
trailing array
+
+ if (target::field_is_bitfield(field))
+ continue;
+
+ std::size_t field_offset = target::field_offset(field);
+ tree field_type = TREE_TYPE(field);
+
+ std::list<dpin::component> field_components =
+ compile_datapin_components(field_type);
+
+ for (const dpin::component &fc : field_components) {
+ components.push_back(dpin::component{
+ .offset = field_offset + fc.offset,
+ .level = fc.level,
+ .target = fc.target,
+ });
+ }
+ }
+
+ return components;
+}
+
+static std::list<dpin::component> compile_datapin_array_components(tree type)
+{
+ std::list<dpin::component> components;
+
+ if (TREE_CODE(type) != ARRAY_TYPE)
+ return components;
+
+ tree elem_type = TREE_TYPE(type);
+ if (!elem_type)
+ return components;
+
+ std::list<dpin::component> elem_components =
+ compile_datapin_components(elem_type);
+ if (elem_components.empty())
+ return components;
+
+ tree domain = TYPE_DOMAIN(type);
+ if (!domain)
+ pinpoint_fatal("dpin: failed to get array domain");
+
+ tree min_t = TYPE_MIN_VALUE(domain);
+ tree max_t = TYPE_MAX_VALUE(domain);
+ if (!min_t || !max_t || TREE_CODE(min_t) != INTEGER_CST ||
+ TREE_CODE(max_t) != INTEGER_CST)
+ pinpoint_fatal("dpin: failed to get constant array bounds");
+
+ HOST_WIDE_INT min_i = tree_to_shwi(min_t);
+ HOST_WIDE_INT max_i = tree_to_shwi(max_t);
+
+ tree elem_size_t = TYPE_SIZE_UNIT(elem_type);
+ if (!elem_size_t || TREE_CODE(elem_size_t) != INTEGER_CST)
+ pinpoint_fatal("dpin: failed to get constant element size");
+
+ std::size_t elem_size = tree_to_uhwi(elem_size_t);
+
+ for (HOST_WIDE_INT i = min_i; i <= max_i; ++i) {
+ std::size_t element_offset =
+ static_cast<std::size_t>(i - min_i) * elem_size;
+
+ for (const dpin::component &ec : elem_components) {
+ components.push_back(dpin::component{
+ .offset = element_offset + ec.offset,
+ .level = ec.level,
+ .target = ec.target,
+ });
+ }
+ }
+
+ return components;
+}
+
+static bool compile_datapin(tree type, dpin &pin)
+{
+ pin.components = compile_datapin_components(type);
+ return !pin.components.empty();
+}
+
+void dpin::consider_static_var(tree var)
+{
+ if (!var || TREE_CODE(var) != VAR_DECL)
+ return;
+
+ if (!TREE_STATIC(var) || DECL_EXTERNAL(var))
+ return;
+
+ tree type = TREE_TYPE(var);
+ if (!type)
+ return;
+
+ tree symbol_tree = DECL_ASSEMBLER_NAME(var);
+ const char *symbol = symbol_tree ? IDENTIFIER_POINTER(symbol_tree) :
+ nullptr;
+ if (!symbol)
+ pinpoint_fatal("dpin: failed to get static variable symbol");
+
+ std::string sym{ symbol };
+
+ /*
+ * Multiple VAR_DECLs can name the same emitted object, for example
through
+ * export or alias machinery. Emit at most one dpin per assembler
symbol.
+ */
+ if (!seen_dpin_symbols.insert(sym).second)
+ return;
+
+ dpin pin;
+ if (!compile_datapin(type, pin))
+ return;
+
+ DECL_PRESERVE_P(var) = 1;
+ pin.symbol = sym;
+ pins.emplace_back(std::move(pin));
+}
diff --git a/scripts/gcc-plugins/dpin_registry.h
b/scripts/gcc-plugins/dpin_registry.h
new file mode 100644
index 000000000000..500535ce0bf2
--- /dev/null
+++ b/scripts/gcc-plugins/dpin_registry.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <cstddef>
+#include <list>
+#include <string>
+
+#include <safe-tree.h>
+
+struct dpin {
+ struct component {
+ std::size_t offset = 0;
+ std::size_t level = 0;
+ tree target = NULL_TREE;
+ };
+
+ std::string symbol;
+ std::list<component> components;
+
+ static void consider_static_var(tree var);
+ static void reset();
+ static const std::list<dpin> &inspect();
+};
diff --git a/scripts/gcc-plugins/ipin_registry.c
b/scripts/gcc-plugins/ipin_registry.c
new file mode 100644
index 000000000000..41e2d525a08f
--- /dev/null
+++ b/scripts/gcc-plugins/ipin_registry.c
@@ -0,0 +1,367 @@
+#include <cstring>
+#include <algorithm>
+
+#include <pinpoint.h>
+#include <ipin_registry.h>
+#include <target_registry.h>
+
+#define PINPOINT_SEPARATOR "__spslr_offsetof"
+#define PINPOINT_IPIN_MARKER "spslr_ipin_marker"
+#define PINPOINT_IPIN_SYMBOL_PREFIX "spslr_ipin_" /* suffixed with "<uid>" */
+#define PINPOINT_IPIN_WIDTH_SYMBOL_PREFIX \
+ "spslr_ipin_width_" /* suffixed with "<uid>" */
+
+static ipin::handle next_ipin_handle = 0;
+static std::map<ipin::handle, ipin> ipins;
+
+static tree separator_decl = NULL_TREE;
+
+PINPOINT_GC_PRESERVE_CALLBACK()
+{
+ PINPOINT_GC_MARK_TREE(separator_decl);
+
+ for (const auto &[h, pin] : ipins)
+ PINPOINT_GC_MARK_TREE(pin.field);
+}
+
+/*
+ * Separators are compiler-internal marker calls, not runtime calls.
+ *
+ * They carry a unique ID with which metadata is associated. The call is
+ * declared pure/no-vops so GCC treats it as having no memory side effects; a
+ * later pinpoint pass must remove every separator before code generation.
+ */
+
+static tree make_separator_decl()
+{
+ if (separator_decl)
+ return separator_decl;
+
+ tree args = tree_cons(NULL_TREE, sizetype, NULL_TREE);
+ tree type = build_function_type(sizetype, args);
+
+ tree tmp_decl = build_fn_decl(PINPOINT_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);
+}
+
+static ipin *get_pin(ipin::handle pin)
+{
+ auto it = ipins.find(pin);
+ return it == ipins.end() ? nullptr : &it->second;
+}
+
+ipin::handle ipin::make(tree field)
+{
+ handle h = next_ipin_handle++;
+
+ ipin pin;
+ pin.status = state::pending;
+ pin.field = field;
+
+ ipins.emplace(h, std::move(pin));
+ return h;
+}
+
+tree ipin::make_ast_separator(ipin::handle pin)
+{
+ ipin *p = get_pin(pin);
+ if (!p)
+ pinpoint_fatal("ipin: inknown pin in make_ast_separator");
+
+ tree decl = make_separator_decl();
+ if (!decl)
+ return NULL_TREE;
+
+ tree arg0 = size_int(pin);
+ if (!arg0)
+ return NULL_TREE;
+
+ p->status = state::separator;
+ return build_call_expr(decl, 1, arg0);
+}
+
+gimple *ipin::make_gimple_separator(tree lhs, ipin::handle pin)
+{
+ if (!lhs)
+ return nullptr;
+
+ ipin *p = get_pin(pin);
+ if (!p)
+ pinpoint_fatal("ipin: unknown pin in make_gimple_separator");
+
+ tree decl = make_separator_decl();
+ if (!decl)
+ return nullptr;
+
+ tree arg0 = size_int(pin);
+ if (!arg0)
+ return nullptr;
+
+ gimple *call = gimple_build_call(decl, 1, arg0);
+ if (!call)
+ return nullptr;
+
+ gimple_call_set_lhs(call, lhs);
+ p->status = state::separator;
+ return call;
+}
+
+static bool decl_is_separator(tree fndecl)
+{
+ if (!fndecl)
+ return false;
+
+ tree name_tree = DECL_NAME(fndecl);
+ if (!name_tree)
+ return false;
+
+ const char *name = IDENTIFIER_POINTER(name_tree);
+ if (!name)
+ return false;
+
+ return strcmp(name, PINPOINT_SEPARATOR) == 0;
+}
+
+ipin::handle ipin::identify_gimple_separator(gimple *stmt)
+{
+ if (!stmt || !is_gimple_call(stmt))
+ return invalid;
+
+ tree fndecl = gimple_call_fndecl(stmt);
+ if (!decl_is_separator(fndecl))
+ return invalid;
+
+ tree arg0 = gimple_call_arg(stmt, 0);
+ if (!arg0 || TREE_CODE(arg0) != INTEGER_CST)
+ pinpoint_fatal("ipin: separator has invalid ipin handle");
+
+ return static_cast<handle>(tree_to_uhwi(arg0));
+}
+
+static bool lhs_type_is_64bit(tree lhs)
+{
+ if (!lhs)
+ return false;
+
+ tree type = TREE_TYPE(lhs);
+ if (!type)
+ return false;
+
+ tree size = TYPE_SIZE(type);
+ if (!size || TREE_CODE(size) != INTEGER_CST)
+ return false;
+
+ return tree_to_uhwi(size) == 64;
+}
+
+static tree make_asm_operand(const char *constraint_text, tree operand_tree)
+{
+ tree constraint_str =
+ build_string(strlen(constraint_text) + 1, constraint_text);
+ tree inner_list = build_tree_list(NULL_TREE, constraint_str);
+ tree outer_list = build_tree_list(inner_list, operand_tree);
+ return outer_list;
+}
+
+/*
+ * The symbol does not denote executable code as a callable function; it
denotes
+ * the four-byte immediate field inside the instruction stream.
+ */
+static std::string make_final_x86_64_asm(const std::string &field_symbol,
+ const std::string &width_symbol,
+ std::size_t imm)
+{
+ char buf[512];
+
+ std::snprintf(buf, sizeof(buf),
+ "# %s\n"
+ " movq $fieldlabel(%zu, %s, %s), %%0",
+ PINPOINT_IPIN_MARKER, imm, field_symbol.c_str(),
+ width_symbol.c_str());
+
+ return std::string(buf);
+}
+
+gimple *ipin::make_gimple_pin(tree lhs, ipin::handle pin)
+{
+ if (!lhs)
+ return nullptr;
+
+ ipin *p = get_pin(pin);
+ if (!p)
+ pinpoint_fatal("ipin: unknown pin in make_gimple_pin");
+
+ if (!lhs_type_is_64bit(lhs))
+ pinpoint_fatal("ipin: expected 64-bit destination type");
+
+ /*
+ * Do not emit the final label here. GCC may duplicate this asm later.
+ * The original pin id is carried only as an input operand.
+ */
+ std::string asm_str = "# " PINPOINT_IPIN_MARKER;
+
+ tree arg0 = build_int_cst(size_type_node, pin);
+
+ vec<tree, va_gc> *outputs = NULL;
+ vec<tree, va_gc> *inputs = NULL;
+
+ vec_safe_push(outputs, make_asm_operand("=r", lhs));
+ vec_safe_push(inputs, make_asm_operand("i", arg0));
+
+ gasm *new_gasm = gimple_build_asm_vec(ggc_strdup(asm_str.c_str()),
+ inputs, outputs, NULL, NULL);
+ if (!new_gasm)
+ return nullptr;
+
+ /*
+ * Non-volatile is intentional: unused field-offset computations should
die
+ * normally. Only offsets that survive optimization become instruction
pins.
+ */
+ gimple_asm_set_volatile(new_gasm, false);
+
+ p->status = state::pin;
+ return new_gasm;
+}
+
+static bool extract_asm_operands(rtx x, rtx &asm_out)
+{
+ if (!x)
+ return false;
+
+ if (GET_CODE(x) == ASM_OPERANDS) {
+ asm_out = x;
+ return true;
+ }
+
+ if (GET_CODE(x) == SET)
+ return extract_asm_operands(SET_SRC(x), asm_out);
+
+ if (GET_CODE(x) == PARALLEL) {
+ for (int i = 0; i < XVECLEN(x, 0); ++i) {
+ if (extract_asm_operands(XVECEXP(x, 0, i), asm_out))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ipin::handle ipin::identify_rtl_pin(rtx x)
+{
+ rtx asm_rtx = nullptr;
+ if (!extract_asm_operands(x, asm_rtx))
+ return invalid;
+
+ if (!asm_rtx || GET_CODE(asm_rtx) != ASM_OPERANDS)
+ return invalid;
+
+ const char *templ = ASM_OPERANDS_TEMPLATE(asm_rtx);
+ if (!templ || !std::strstr(templ, PINPOINT_IPIN_MARKER))
+ return invalid;
+
+ if (ASM_OPERANDS_INPUT_LENGTH(asm_rtx) != 1)
+ pinpoint_fatal(
+ "ipin: RTL pin asm has invalid number of inputs");
+
+ rtx in0 = ASM_OPERANDS_INPUT(asm_rtx, 0);
+ if (!CONST_INT_P(in0))
+ pinpoint_fatal("ipin: RTL ipin id is not CONST_INT");
+
+ return static_cast<handle>(INTVAL(in0));
+}
+
+void ipin::mark_live(ipin::handle pin, rtx at)
+{
+ ipin::handle found = identify_rtl_pin(at);
+
+ if (found != pin) {
+ pinpoint_fatal(
+ "ipin: mark_live called with mismatching RTL pin");
+ }
+
+ rtx asm_rtx = nullptr;
+
+ if (!extract_asm_operands(at, asm_rtx) || !asm_rtx ||
+ GET_CODE(asm_rtx) != ASM_OPERANDS) {
+ pinpoint_fatal(
+ "ipin: mark_live could not recover ASM_OPERANDS");
+ }
+
+ ipin *p = get_pin(pin);
+ if (!p) {
+ pinpoint_fatal("ipin: tried to mark unknown pin live");
+ }
+
+ ipin::handle live_handle = pin;
+ ipin *live_pin = p;
+
+ if (p->status == state::live) {
+ live_handle = next_ipin_handle++;
+
+ ipin clone = *p;
+ clone.status = state::pin;
+ clone.symbol.clear();
+ clone.width_symbol.clear();
+
+ auto inserted = ipins.emplace(live_handle, std::move(clone));
+ live_pin = &inserted.first->second;
+ }
+
+ live_pin->status = state::live;
+
+ const std::string handle_suffix = std::to_string(live_handle);
+
+ live_pin->symbol =
+ ".L" + std::string(PINPOINT_IPIN_SYMBOL_PREFIX) + handle_suffix;
+
+ live_pin->width_symbol =
+ ".L" + std::string(PINPOINT_IPIN_WIDTH_SYMBOL_PREFIX) +
+ handle_suffix;
+
+ std::size_t offset = target::field_offset(live_pin->field);
+
+ std::string final_asm = make_final_x86_64_asm(
+ live_pin->symbol, live_pin->width_symbol, offset);
+
+ XSTR(asm_rtx, 0) = ggc_strdup(final_asm.c_str());
+}
+
+std::size_t ipin::live_count()
+{
+ std::size_t n = 0;
+
+ for (const auto &[h, pin] : ipins) {
+ if (pin.status == state::live)
+ ++n;
+ }
+
+ return n;
+}
+
+const std::map<ipin::handle, ipin> &ipin::inspect()
+{
+ return ipins;
+}
+
+const ipin *ipin::inspect(ipin::handle pin)
+{
+ return get_pin(pin);
+}
+
+void ipin::reset()
+{
+ ipins.clear();
+ next_ipin_handle = 0;
+}
diff --git a/scripts/gcc-plugins/ipin_registry.h
b/scripts/gcc-plugins/ipin_registry.h
new file mode 100644
index 000000000000..0fff090ed6e7
--- /dev/null
+++ b/scripts/gcc-plugins/ipin_registry.h
@@ -0,0 +1,57 @@
+#pragma once
+#include <cstddef>
+#include <string>
+#include <limits>
+#include <map>
+
+#include <safe-tree.h>
+#include <safe-gimple.h>
+#include <safe-rtl.h>
+
+struct ipin {
+ enum class state { pending, separator, pin, live };
+
+ state status = state::pending;
+
+ /* Field that the ipin refers to */
+ tree field = NULL_TREE;
+
+ /*
+ * The field-address and field-width symbols are available once the pin
is
+ * marked live and its final inline assembly has been constructed.
+ */
+ std::string symbol;
+ std::string width_symbol;
+
+ using handle = std::size_t;
+ static constexpr handle invalid = std::numeric_limits<handle>::max();
+
+ /* Register new ipin to track through compilation */
+ static handle make(tree field);
+
+ /* Construct an AST separator tree refering to an ipin */
+ static tree make_ast_separator(handle pin);
+
+ /* Construct a GIMPLE separator statement refering to an ipin */
+ static gimple *make_gimple_separator(tree lhs, handle pin);
+
+ /* Check if a GIMPLE statement is a separator refering to an ipin */
+ static handle identify_gimple_separator(gimple *stmt);
+
+ /* Construct a GIMPLE ipin - currently as ASM statement */
+ static gimple *make_gimple_pin(tree lhs, handle pin);
+
+ /* Check if an RTL instruction is an ipin */
+ static handle identify_rtl_pin(rtx x);
+
+ /* Mark an ipin as being present in the final asm */
+ static void mark_live(handle pin, rtx at);
+ static std::size_t live_count();
+
+ /* Inspect the state of one or more ipins */
+ static const std::map<handle, ipin> &inspect();
+ static const ipin *inspect(handle pin);
+
+ /* Reset ipin registry */
+ static void reset();
+};
diff --git a/scripts/gcc-plugins/layout_hash.c
b/scripts/gcc-plugins/layout_hash.c
new file mode 100644
index 000000000000..6200bd037f54
--- /dev/null
+++ b/scripts/gcc-plugins/layout_hash.c
@@ -0,0 +1,61 @@
+#include <cstdint>
+#include <cstring>
+#include <string>
+
+#include <layout_hash.h>
+#include <target_registry.h>
+
+#include <safe-md5.h>
+
+namespace
+{
+
+void append_u64(std::string &buf, std::uint64_t v)
+{
+ for (unsigned i = 0; i < 8; i++)
+ buf.push_back(static_cast<char>((v >> (i * 8)) & 0xff));
+}
+
+void append_size(std::string &buf, std::size_t v)
+{
+ append_u64(buf, static_cast<std::uint64_t>(v));
+}
+
+void append_string(std::string &buf, const std::string &s)
+{
+ append_size(buf, s.size());
+ buf.append(s);
+}
+
+}
+
+std::array<std::byte, 16> compute_layout_hash(tree target_type)
+{
+ std::string buf;
+
+ append_string(buf, "spslr-layout-hash-v2");
+
+ for (const std::string &ctx : target::context_chain(target_type))
+ append_string(buf, ctx);
+
+ append_string(buf, target::name(target_type));
+ append_size(buf, target::size(target_type));
+
+ for (const target::compressed_field &field :
+ target::compressed_fields(target_type)) {
+ append_string(buf, field.name);
+ append_size(buf, field.size);
+ append_size(buf, field.offset);
+ append_size(buf, field.alignment);
+ append_size(buf, field.fixed ? 1 : 0);
+ }
+
+ unsigned char digest[16];
+ md5_buffer(buf.data(), buf.size(), digest);
+
+ std::array<std::byte, 16> out;
+ for (std::size_t i = 0; i < out.size(); i++)
+ out[i] = static_cast<std::byte>(digest[i]);
+
+ return out;
+}
diff --git a/scripts/gcc-plugins/layout_hash.h
b/scripts/gcc-plugins/layout_hash.h
new file mode 100644
index 000000000000..b4b660bd0b96
--- /dev/null
+++ b/scripts/gcc-plugins/layout_hash.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+
+#include <safe-tree.h>
+
+std::array<std::byte, 16> compute_layout_hash(tree target_type);
diff --git a/scripts/gcc-plugins/on_finish_decl.c
b/scripts/gcc-plugins/on_finish_decl.c
new file mode 100644
index 000000000000..6781d2516e75
--- /dev/null
+++ b/scripts/gcc-plugins/on_finish_decl.c
@@ -0,0 +1,8 @@
+#include <passes.h>
+#include <dpin_registry.h>
+
+void on_finish_decl(void *plugin_data, void *user_data)
+{
+ tree decl = (tree)plugin_data;
+ dpin::consider_static_var(decl);
+}
diff --git a/scripts/gcc-plugins/on_finish_type.c
b/scripts/gcc-plugins/on_finish_type.c
new file mode 100644
index 000000000000..023470f8cc8c
--- /dev/null
+++ b/scripts/gcc-plugins/on_finish_type.c
@@ -0,0 +1,12 @@
+#include <passes.h>
+#include <target_registry.h>
+
+void on_finish_type(void *plugin_data, void *user_data)
+{
+ tree t = target::main_variant((tree)plugin_data);
+
+ if (!target::is_target(t))
+ return;
+
+ target::validate(t);
+}
diff --git a/scripts/gcc-plugins/on_finish_unit.c
b/scripts/gcc-plugins/on_finish_unit.c
new file mode 100644
index 000000000000..5229dbb53c52
--- /dev/null
+++ b/scripts/gcc-plugins/on_finish_unit.c
@@ -0,0 +1,336 @@
+#include <string>
+#include <filesystem>
+#include <vector>
+#include <algorithm>
+#include <unordered_map>
+
+#include <pinpoint.h>
+#include <passes.h>
+#include <ipin_registry.h>
+#include <dpin_registry.h>
+#include <serialize.h>
+#include <target_registry.h>
+#include <safe-input.h>
+#include <safe-output.h>
+
+/*
+ * Finish-unit emits the per-compilation-unit metadata into the object asm.
+ *
+ * Only information that survived all earlier filtering should be dumped here:
+ * target layouts, data pins for static objects, and live instruction pins
+ * whose immediates exist in the final object.
+ */
+
+namespace
+{
+
+static std::string src_filename()
+{
+ namespace fs = std::filesystem;
+
+ if (!main_input_filename)
+ return {};
+
+ return fs::weakly_canonical(main_input_filename).generic_string();
+}
+
+using selfpatch::hash16_t;
+
+struct emitted_target {
+ tree target;
+ hash16_t hash;
+ std::size_t unit_target_idx;
+};
+
+struct emitted_dpin {
+ std::string addr_expr;
+ std::size_t unit_target_idx;
+};
+
+static hash16_t to_hash16(const std::array<std::byte, 16> &in)
+{
+ hash16_t out{};
+
+ for (std::size_t i = 0; i < out.size(); ++i)
+ out[i] = static_cast<std::uint8_t>(in[i]);
+
+ return out;
+}
+
+static std::string add_offset_expr(const std::string &symbol, std::size_t off)
+{
+ if (off == 0)
+ return symbol;
+
+ return symbol + " + " + std::to_string(off);
+}
+
+static std::vector<emitted_target> collect_all_targets()
+{
+ std::vector<emitted_target> out;
+ out.reserve(target::target_count());
+
+ target::iterate_targets([&](tree t) {
+ out.push_back({
+ .target = t,
+ .hash = to_hash16(target::layout_hash(t)),
+ .unit_target_idx = out.size(),
+ });
+ });
+
+ return out;
+}
+
+static std::unordered_map<tree, std::size_t>
+make_unit_target_idx_map(const std::vector<emitted_target> &targets)
+{
+ std::unordered_map<tree, std::size_t> out;
+
+ for (const emitted_target &target : targets)
+ out[target.target] = target.unit_target_idx;
+
+ return out;
+}
+
+static std::vector<emitted_dpin>
+collect_dpins(const std::unordered_map<tree, std::size_t> &target_idx)
+{
+ std::vector<emitted_dpin> out;
+
+ for (const dpin &pin : dpin::inspect()) {
+ if (pin.symbol.empty())
+ pinpoint_fatal(
+ "finish-unit: data pin has empty symbol");
+
+ std::vector<dpin::component> components(pin.components.begin(),
+ pin.components.end());
+
+ /*
+ * Data pins with deeper nesting level must be patched first.
+ */
+ std::sort(components.begin(), components.end(),
+ [](const dpin::component &a,
+ const dpin::component &b) {
+ return a.level > b.level;
+ });
+
+ for (const dpin::component &c : components) {
+ const auto target_it = target_idx.find(c.target);
+
+ if (target_it == target_idx.end())
+ pinpoint_fatal(
+ "finish-unit: dpin target not in unit
target map");
+
+ out.push_back({
+ .addr_expr =
+ add_offset_expr(pin.symbol, c.offset),
+ .unit_target_idx = target_it->second,
+ });
+ }
+ }
+
+ return out;
+}
+
+static void emit_target_metadata(FILE *out,
+ const std::vector<emitted_target> &targets)
+{
+ for (const emitted_target &ut : targets) {
+ const std::string target_sym =
+ selfpatch::target_symbol(ut.hash);
+ const std::string hash_sym =
+ selfpatch::target_hash_symbol(ut.hash);
+ const std::string layout_sym =
+ selfpatch::target_layout_symbol(ut.hash);
+ const std::string fields_sym =
+ selfpatch::make_local_label("target_fields");
+
+ const std::string target_name = selfpatch::emit_strtab_entry(
+ out, target::qualified_name(ut.target));
+
+ selfpatch::emit_targets_section(out, ut.hash);
+ selfpatch::emit_hidden_global_label(out, target_sym);
+
+ /* This can only be done here, because the hash is the first
+ * component of the target metadata. */
+ selfpatch::emit_hidden_global_label(out, hash_sym);
+
+ selfpatch::target_desc target_desc{
+ .hash = ut.hash,
+ .name_label = target_name,
+ .layout_symbol = layout_sym,
+ };
+
+ selfpatch::emit_target(out, target_desc);
+ selfpatch::emit_pop_section(out);
+
+ selfpatch::emit_target_layouts_section(out, ut.hash);
+ selfpatch::emit_hidden_global_label(out, layout_sym);
+
+ const std::vector<target::compressed_field> &fields =
+ target::compressed_fields(ut.target);
+
+ selfpatch::target_layout_desc layout_desc{
+ .size = target::size(ut.target),
+ .field_cnt = fields.size(),
+ .fields_symbol = fields_sym,
+ };
+
+ selfpatch::emit_target_layout(out, layout_desc);
+
+ selfpatch::emit_label(out, fields_sym);
+
+ for (const target::compressed_field &field : fields) {
+ const std::string field_name =
+ selfpatch::emit_strtab_entry(out, field.name);
+
+ std::size_t field_flags = field.fixed ? 1 : 0;
+
+ selfpatch::target_field_desc field_desc{
+ .name_label = field_name,
+ .size = field.size,
+ .offset = field.offset,
+ .alignment = field.alignment,
+ .flags = field_flags,
+ };
+
+ selfpatch::emit_target_field(out, field_desc);
+ }
+
+ selfpatch::emit_pop_section(out);
+ }
+}
+
+static void emit_unit_target_refs(FILE *out,
+ const std::vector<emitted_target> &targets,
+ const std::string &target_refs_sym)
+{
+ selfpatch::emit_cu_target_refs_section(out);
+ selfpatch::emit_label(out, target_refs_sym);
+
+ for (const emitted_target &target : targets) {
+ selfpatch::target_ref_desc ref{
+ .target_symbol = selfpatch::target_symbol(target.hash),
+ };
+
+ selfpatch::emit_target_ref(out, ref);
+ }
+
+ selfpatch::emit_pop_section(out);
+}
+
+static void emit_ipins(FILE *out,
+ const std::unordered_map<tree, std::size_t> &target_idx,
+ const std::string &ipins_sym)
+{
+ std::map<ipin::handle, std::string> expr_syms;
+
+ selfpatch::emit_ipins_section(out);
+ selfpatch::emit_label(out, ipins_sym);
+
+ for (const auto &[h, pin] : ipin::inspect()) {
+ if (pin.status != ipin::state::live)
+ continue;
+
+ if (pin.symbol.empty() || pin.width_symbol.empty())
+ pinpoint_fatal(
+ "finish-unit: live ipin is missing field
symbols");
+
+ std::string expr_sym = selfpatch::make_local_label("ipin_expr");
+ expr_syms.emplace(h, expr_sym);
+
+ selfpatch::ipin_desc desc{
+ .addr_expr = pin.symbol,
+ .size_expr = pin.width_symbol,
+ .expr_symbol = expr_sym,
+ };
+
+ selfpatch::emit_ipin(out, desc);
+ }
+
+ for (const auto &[h, pin] : ipin::inspect()) {
+ if (pin.status != ipin::state::live)
+ continue;
+
+ tree pin_target = target::from_field(pin.field);
+ const auto target_it = target_idx.find(pin_target);
+
+ if (target_it == target_idx.end())
+ pinpoint_fatal(
+ "finish-unit: ipin target not in unit target
map");
+
+ auto expr_sym_it = expr_syms.find(h);
+ if (expr_sym_it == expr_syms.end())
+ pinpoint_fatal("finish-unit: lost ipin expr symbol");
+
+ selfpatch::emit_label(out, expr_sym_it->second);
+
+ selfpatch::ipin_expr_desc expr{
+ .unit_target_idx = target_it->second,
+ .field = target::field_index(pin.field),
+ };
+
+ selfpatch::emit_ipin_expr(out, expr);
+ }
+
+ selfpatch::emit_pop_section(out);
+}
+
+static void emit_dpins(FILE *out, const std::vector<emitted_dpin> &dpins,
+ const std::string &dpins_sym)
+{
+ selfpatch::emit_dpins_section(out);
+ selfpatch::emit_label(out, dpins_sym);
+
+ for (const emitted_dpin &pin : dpins) {
+ selfpatch::dpin_desc desc{
+ .addr_expr = pin.addr_expr,
+ .unit_target_idx = pin.unit_target_idx,
+ };
+
+ selfpatch::emit_dpin(out, desc);
+ }
+
+ selfpatch::emit_pop_section(out);
+}
+
+} // namespace
+
+void on_finish_unit(void *plugin_data, void *user_data)
+{
+ FILE *out = asm_out_file;
+
+ const std::vector<emitted_target> targets = collect_all_targets();
+ const auto target_idx = make_unit_target_idx_map(targets);
+ const std::vector<emitted_dpin> dpins = collect_dpins(target_idx);
+
+ const std::string source_label =
+ selfpatch::emit_strtab_entry(out, src_filename());
+
+ const std::string target_refs_sym =
+ selfpatch::make_local_label("target_refs");
+ const std::string ipins_sym = selfpatch::make_local_label("ipins");
+ const std::string dpins_sym = selfpatch::make_local_label("dpins");
+
+ selfpatch::emit_comment(out, "SPSLR runtime metadata");
+
+ emit_target_metadata(out, targets);
+ emit_unit_target_refs(out, targets, target_refs_sym);
+ emit_ipins(out, target_idx, ipins_sym);
+ emit_dpins(out, dpins, dpins_sym);
+
+ selfpatch::emit_units_section(out);
+
+ selfpatch::unit_desc unit{
+ .source_label = source_label,
+ .target_ref_cnt = targets.size(),
+ .target_refs_symbol = target_refs_sym,
+ .ipin_cnt = ipin::live_count(),
+ .ipins_symbol = ipins_sym,
+ .dpin_cnt = dpins.size(),
+ .dpins_symbol = dpins_sym,
+ };
+
+ selfpatch::emit_unit(out, unit);
+ selfpatch::emit_pop_section(out);
+}
diff --git a/scripts/gcc-plugins/on_preserve_component_ref.c
b/scripts/gcc-plugins/on_preserve_component_ref.c
new file mode 100644
index 000000000000..133269438914
--- /dev/null
+++ b/scripts/gcc-plugins/on_preserve_component_ref.c
@@ -0,0 +1,99 @@
+#include <pinpoint.h>
+#include <passes.h>
+#include <ipin_registry.h>
+#include <target_registry.h>
+#include <safe-tree.h>
+
+static tree materialize_c_rvalue(location_t loc, tree expr)
+{
+ gcc_assert(!lvalue_p(expr));
+
+ tree type = TREE_TYPE(expr);
+
+ tree tmp = build_decl(loc, VAR_DECL, create_tmp_var_name("spslr_rval"),
+ type);
+
+ DECL_CONTEXT(tmp) = current_function_decl;
+ DECL_ARTIFICIAL(tmp) = 1;
+ DECL_IGNORED_P(tmp) = 1;
+ TREE_USED(tmp) = 1;
+ TREE_ADDRESSABLE(tmp) = 1;
+ DECL_CHAIN(tmp) = NULL_TREE;
+
+ // layout_decl(tmp, 0); - not available to plugins
+
+ tree init = build2_loc(loc, INIT_EXPR, type, tmp, expr);
+
+ tree body = build2(COMPOUND_EXPR, type, init, tmp);
+
+ SET_EXPR_LOCATION(body, loc);
+
+ tree bind = build3(BIND_EXPR, type, tmp, body, NULL_TREE);
+
+ SET_EXPR_LOCATION(bind, loc);
+ TREE_SIDE_EFFECTS(bind) = 1;
+
+ return bind;
+}
+
+/*
+ * Preserve an early COMPONENT_REF before GCC folds it into a plain constant
+ * offset.
+ *
+ * The custom GCC hook calls this while the frontend still knows that an
+ * expression is "base.field". We rewrite it into pointer arithmetic whose
+ * offset comes from a synthetic separator call:
+ *
+ * base.field -> *(typeof(field) *)((char *)&base + separator(uid))
+ *
+ * Later passes replace the separator with an instruction pin.
+ */
+
+static tree ast_separate_offset(tree ref, ipin::handle pin)
+{
+ tree separator = ipin::make_ast_separator(pin);
+ if (!separator)
+ pinpoint_fatal(
+ "ast_separate_offset failed to generate AST separator");
+
+ 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");
+
+ // Turn rvalue objects into adressable lvalues
+ if (!lvalue_p(base))
+ base = materialize_c_rvalue(EXPR_LOCATION(base), 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;
+
+ tree field;
+ if (!target::component_ref(*ref, &field))
+ return;
+
+ if (target::field_is_fixed(field))
+ return;
+
+ ipin::handle pin = ipin::make(field);
+ tree separated = ast_separate_offset(*ref, pin);
+ if (separated)
+ *ref = separated;
+}
diff --git a/scripts/gcc-plugins/on_register_attributes.c
b/scripts/gcc-plugins/on_register_attributes.c
new file mode 100644
index 000000000000..221693a1c465
--- /dev/null
+++ b/scripts/gcc-plugins/on_register_attributes.c
@@ -0,0 +1,52 @@
+#include <pinpoint.h>
+#include <passes.h>
+
+static tree check_spslr_attribute(tree *node, tree name, tree args, int flags,
+ bool *no_add_attrs)
+{
+ if (!node || !*node || TREE_CODE(*node) != RECORD_TYPE) {
+ *no_add_attrs = true;
+ pinpoint_debug(SPSLR_ATTRIBUTE
+ " attribute only applies to record types");
+ }
+
+ return NULL_TREE;
+}
+
+static tree check_spslr_field_fixed_attribute(tree *node, tree name, tree args,
+ int flags, bool *no_add_attrs)
+{
+ if (!node || !*node || TREE_CODE(*node) != FIELD_DECL) {
+ *no_add_attrs = true;
+ pinpoint_debug(SPSLR_FIELD_FIXED_ATTRIBUTE
+ " attribute only applies to struct fields");
+ }
+ return NULL_TREE;
+}
+
+/*
+ * __attribute__((spslr)) marks a record type as a randomization target.
+ */
+
+static struct attribute_spec spslr_attribute = {
+ SPSLR_ATTRIBUTE, 0, 0, false, false, false, false,
+ check_spslr_attribute, NULL
+};
+
+/*
+ * __attribute__((spslr_field_fixed)) marks a field as layout-sensitive.
+ * Fixed fields remain part of the target, but are treated as dangerous so
+ * instruction/data pins are not generated for offsets that would become
+ * ambiguous after randomization.
+ */
+
+static struct attribute_spec spslr_fixed_field_attribute = {
+ SPSLR_FIELD_FIXED_ATTRIBUTE, 0, 0, false, false, false, false,
+ check_spslr_field_fixed_attribute, NULL
+};
+
+void on_register_attributes(void *plugin_data, void *user_data)
+{
+ register_attribute(&spslr_attribute);
+ register_attribute(&spslr_fixed_field_attribute);
+}
diff --git a/scripts/gcc-plugins/on_start_unit.c
b/scripts/gcc-plugins/on_start_unit.c
new file mode 100644
index 000000000000..dc8917209de7
--- /dev/null
+++ b/scripts/gcc-plugins/on_start_unit.c
@@ -0,0 +1,11 @@
+#include <passes.h>
+#include <ipin_registry.h>
+#include <dpin_registry.h>
+#include <target_registry.h>
+
+void on_start_unit(void *plugin_data, void *user_data)
+{
+ target::reset();
+ dpin::reset();
+ ipin::reset();
+}
diff --git a/scripts/gcc-plugins/passes.h b/scripts/gcc-plugins/passes.h
new file mode 100644
index 000000000000..f22949330e80
--- /dev/null
+++ b/scripts/gcc-plugins/passes.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <safe-gimple.h>
+#include <safe-rtl.h>
+
+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);
+void on_finish_decl(void *plugin_data, void *user_data);
+void on_start_unit(void *plugin_data, void *user_data);
+void on_finish_unit(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;
+};
+
+struct asm_offset_pass : gimple_opt_pass {
+ asm_offset_pass(gcc::context *ctxt);
+ unsigned int execute(function *fn) override;
+};
+
+struct rtl_ipin_survival_scan_pass : rtl_opt_pass {
+ rtl_ipin_survival_scan_pass(gcc::context *ctxt);
+ unsigned int execute(function *fn) override;
+};
+
+struct target_hash_builtin_pass : gimple_opt_pass {
+ target_hash_builtin_pass(gcc::context *ctxt);
+ unsigned int execute(function *fn) override;
+};
diff --git a/scripts/gcc-plugins/pinpoint.c b/scripts/gcc-plugins/pinpoint.c
new file mode 100644
index 000000000000..619487fdb53f
--- /dev/null
+++ b/scripts/gcc-plugins/pinpoint.c
@@ -0,0 +1,103 @@
+#include <filesystem>
+#include <string>
+
+#include <pinpoint.h>
+#include <passes.h>
+#include <safe-gcc-plugin.h>
+#include <safe-plugin-version.h>
+
+int plugin_is_GPL_compatible;
+
+bool pinpoint_verbose_enabled;
+
+void pinpoint_gc_preserve(void *, void *)
+{
+ for (pinpoint_gc_preserve_fn *p = PINPOINT_GC_SECTION_START;
+ p != PINPOINT_GC_SECTION_END; ++p) {
+ if (*p)
+ (*p)();
+ }
+}
+
+int plugin_init(struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ if (!plugin_default_version_check(version, &gcc_version)) {
+ plugin_print_early_error(
+ "incompatible GCC/plugin versions: plugin built for GCC
%s, loaded by GCC %s",
+ gcc_version.basever, version->basever);
+ return 1;
+ }
+
+ pinpoint_verbose_enabled = false;
+
+ for (int i = 0; i < plugin_info->argc; ++i) {
+ if (!strcmp(plugin_info->argv[i].key, "verbose"))
+ pinpoint_verbose_enabled = true;
+ }
+
+ /*
+ * Pinpoint runs as a staged GCC plugin because no single GCC IR level
has
+ * all information SPSLR needs.
+ *
+ * Stage 0 runs while COMPONENT_REF trees are built or still available
and records
+ * which structure field offsets are randomization-sensitive.
+ *
+ * Stage 1 replaces synthetic separator calls with asm instructions
whose immediate
+ * operands are labeled for runtime patching.
+ *
+ * The final callback emits the collected metadata for patchcompile.
+ */
+
+ register_callback(plugin_info->base_name, PLUGIN_START_UNIT,
+ on_start_unit, NULL);
+ 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);
+ register_callback(plugin_info->base_name, PLUGIN_FINISH_DECL,
+ on_finish_decl, NULL);
+ register_callback(plugin_info->base_name, PLUGIN_GGC_MARKING,
+ pinpoint_gc_preserve, NULL);
+
+ struct register_pass_info target_hash_builtin_pass_info;
+ target_hash_builtin_pass_info.pass =
+ new target_hash_builtin_pass(nullptr);
+ target_hash_builtin_pass_info.ref_pass_instance_number = 1;
+ target_hash_builtin_pass_info.reference_pass_name = "cfg";
+ target_hash_builtin_pass_info.pos_op = PASS_POS_INSERT_AFTER;
+ register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP,
+ nullptr, &target_hash_builtin_pass_info);
+
+ 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 = "spslr_target_hash";
+ 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);
+
+ struct register_pass_info asm_offset_pass_info;
+ asm_offset_pass_info.pass = new asm_offset_pass(nullptr);
+ asm_offset_pass_info.ref_pass_instance_number = 1;
+ asm_offset_pass_info.reference_pass_name = "separate_offset";
+ asm_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER;
+ register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP,
+ nullptr, &asm_offset_pass_info);
+
+ struct register_pass_info rtl_ipin_survival_scan_pass_info;
+ rtl_ipin_survival_scan_pass_info.pass =
+ new rtl_ipin_survival_scan_pass(nullptr);
+ rtl_ipin_survival_scan_pass_info.ref_pass_instance_number = 1;
+ rtl_ipin_survival_scan_pass_info.reference_pass_name = "final";
+ rtl_ipin_survival_scan_pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+ register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP,
+ nullptr, &rtl_ipin_survival_scan_pass_info);
+
+ register_callback(plugin_info->base_name, PLUGIN_FINISH_UNIT,
+ on_finish_unit, NULL);
+
+ return 0;
+}
diff --git a/scripts/gcc-plugins/pinpoint.h b/scripts/gcc-plugins/pinpoint.h
new file mode 100644
index 000000000000..7f88cda32f48
--- /dev/null
+++ b/scripts/gcc-plugins/pinpoint.h
@@ -0,0 +1,75 @@
+#pragma once
+#include <cstdio>
+#include <safe-diagnostic.h>
+#include <safe-ggc.h>
+
+#define SPSLR_ATTRIBUTE "spslr"
+#define SPSLR_FIELD_FIXED_ATTRIBUTE "spslr_field_fixed"
+#define SPSLR_TARGET_HASH_BUILTIN "__spslr_target_hash"
+
+extern bool pinpoint_verbose_enabled;
+
+#define plugin_print_early_error(fmt, ...) \
+ do { \
+ std::fprintf(stderr, "[spslr::pinpoint] error: " fmt "\n", \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define pinpoint_debug_loc(loc, fmt, ...) \
+ do { \
+ if (pinpoint_verbose_enabled) \
+ inform((loc), "[spslr::pinpoint] " fmt, \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define pinpoint_debug(fmt, ...) \
+ pinpoint_debug_loc(UNKNOWN_LOCATION, fmt, ##__VA_ARGS__)
+
+#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__)
+
+using pinpoint_gc_preserve_fn = void (*)();
+
+#define PINPOINT_GC_CONCAT2(a, b) a##b
+#define PINPOINT_GC_CONCAT(a, b) PINPOINT_GC_CONCAT2(a, b)
+
+#define PINPOINT_GC_USED __attribute__((used))
+
+#define PINPOINT_GC_SECTION "pinpoint_gc_mark"
+#define PINPOINT_GC_SECTION_START __start_pinpoint_gc_mark
+#define PINPOINT_GC_SECTION_END __stop_pinpoint_gc_mark
+
+#define PINPOINT_GC_SECTION_ATTRIB
__attribute__((section(PINPOINT_GC_SECTION)))
+
+#define PINPOINT_GC_PRESERVE_CALLBACK() \
+ PINPOINT_GC_PRESERVE_CALLBACK_IMPL(__COUNTER__)
+
+#define PINPOINT_GC_PRESERVE_CALLBACK_IMPL(id) \
+ static void PINPOINT_GC_CONCAT(pinpoint_gc_preserve_cb_, id)(); \
+ static pinpoint_gc_preserve_fn PINPOINT_GC_CONCAT( \
+ pinpoint_gc_preserve_reg_, id) \
+ PINPOINT_GC_USED PINPOINT_GC_SECTION_ATTRIB = \
+ PINPOINT_GC_CONCAT(pinpoint_gc_preserve_cb_, id); \
+ static void PINPOINT_GC_CONCAT(pinpoint_gc_preserve_cb_, id)()
+
+#define PINPOINT_GC_MARK_TREE(t) \
+ do { \
+ if ((t) != NULL_TREE) \
+ ggc_mark(t); \
+ } while (0)
+
+#define PINPOINT_GC_MARK(p) \
+ do { \
+ if (p) \
+ ggc_mark(p); \
+ } while (0)
+
+extern "C" {
+extern pinpoint_gc_preserve_fn PINPOINT_GC_SECTION_START[];
+extern pinpoint_gc_preserve_fn PINPOINT_GC_SECTION_END[];
+}
+
+void pinpoint_gc_preserve(void *, void *);
diff --git a/scripts/gcc-plugins/rtl_ipin_survival_scan_pass.c
b/scripts/gcc-plugins/rtl_ipin_survival_scan_pass.c
new file mode 100644
index 000000000000..a30f709f6fb6
--- /dev/null
+++ b/scripts/gcc-plugins/rtl_ipin_survival_scan_pass.c
@@ -0,0 +1,37 @@
+#include <pinpoint.h>
+#include <passes.h>
+#include <ipin_registry.h>
+#include <safe-rtl.h>
+
+static const pass_data rtl_ipin_survival_scan_pass_data = {
+ RTL_PASS,
+ "spslr_rtl_ipin_survival_scan",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_rtl,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+rtl_ipin_survival_scan_pass::rtl_ipin_survival_scan_pass(gcc::context *ctxt)
+ : rtl_opt_pass(rtl_ipin_survival_scan_pass_data, ctxt)
+{
+}
+
+unsigned int rtl_ipin_survival_scan_pass::execute(function *fn)
+{
+ (void)fn;
+
+ for (rtx_insn *insn = get_insns(); insn; insn = NEXT_INSN(insn)) {
+ if (!NONDEBUG_INSN_P(insn))
+ continue;
+
+ ipin::handle pin = ipin::identify_rtl_pin(PATTERN(insn));
+ if (pin != ipin::invalid)
+ ipin::mark_live(pin, PATTERN(insn));
+ }
+
+ return 0;
+}
diff --git a/scripts/gcc-plugins/safe-attribs.h
b/scripts/gcc-plugins/safe-attribs.h
new file mode 100644
index 000000000000..2d62fe75c72b
--- /dev/null
+++ b/scripts/gcc-plugins/safe-attribs.h
@@ -0,0 +1,9 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_ATTRIBS_H
+#define SAFEGCC_ATTRIBS_H
+
+#include <stringpool.h>
+#include <attribs.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-diagnostic.h
b/scripts/gcc-plugins/safe-diagnostic.h
new file mode 100644
index 000000000000..c58b739d88ed
--- /dev/null
+++ b/scripts/gcc-plugins/safe-diagnostic.h
@@ -0,0 +1,10 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_DIAGNOSTIC_H
+#define SAFEGCC_DIAGNOSTIC_H
+
+#include <c-family/c-common.h>
+#include <diagnostic.h>
+#include <diagnostic-core.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-gcc-plugin.h
b/scripts/gcc-plugins/safe-gcc-plugin.h
new file mode 100644
index 000000000000..fcd111636a1c
--- /dev/null
+++ b/scripts/gcc-plugins/safe-gcc-plugin.h
@@ -0,0 +1,6 @@
+#ifndef SAFEGCC_GCC_PLUGIN_H
+#define SAFEGCC_GCC_PLUGIN_H
+
+#include <gcc-plugin.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-ggc.h b/scripts/gcc-plugins/safe-ggc.h
new file mode 100644
index 000000000000..5df62ad492e8
--- /dev/null
+++ b/scripts/gcc-plugins/safe-ggc.h
@@ -0,0 +1,8 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_GGC_H
+#define SAFEGCC_GGC_H
+
+#include <ggc.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-gimple.h
b/scripts/gcc-plugins/safe-gimple.h
new file mode 100644
index 000000000000..2eb0bae2f0a4
--- /dev/null
+++ b/scripts/gcc-plugins/safe-gimple.h
@@ -0,0 +1,14 @@
+#include <safe-gcc-plugin.h>
+#include <safe-tree.h>
+
+#ifndef SAFEGCC_GIMPLE_H
+#define SAFEGCC_GIMPLE_H
+
+#include <gimple.h>
+#include <gimplify.h>
+#include <gimple-iterator.h>
+#include <gimple-pretty-print.h>
+#include <gimplify-me.h>
+#include <ssa.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-input.h b/scripts/gcc-plugins/safe-input.h
new file mode 100644
index 000000000000..fe24435830fe
--- /dev/null
+++ b/scripts/gcc-plugins/safe-input.h
@@ -0,0 +1,9 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_INPUT_H
+#define SAFEGCC_INPUT_H
+
+#include <input.h>
+#include <libiberty.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-langhooks.h
b/scripts/gcc-plugins/safe-langhooks.h
new file mode 100644
index 000000000000..3fbea6ccb579
--- /dev/null
+++ b/scripts/gcc-plugins/safe-langhooks.h
@@ -0,0 +1,8 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_LANGHOOKS_H
+#define SAFEGCC_LANGHOOKS_H
+
+#include <langhooks.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-md5.h b/scripts/gcc-plugins/safe-md5.h
new file mode 100644
index 000000000000..8341cb193467
--- /dev/null
+++ b/scripts/gcc-plugins/safe-md5.h
@@ -0,0 +1,8 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_MD5_H
+#define SAFEGCC_MD5_H
+
+#include <md5.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-output.h
b/scripts/gcc-plugins/safe-output.h
new file mode 100644
index 000000000000..5da7fec494be
--- /dev/null
+++ b/scripts/gcc-plugins/safe-output.h
@@ -0,0 +1,8 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_OUTPUT_H
+#define SAFEGCC_OUTPUT_H
+
+#include <output.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-plugin-version.h
b/scripts/gcc-plugins/safe-plugin-version.h
new file mode 100644
index 000000000000..e43a1689da8c
--- /dev/null
+++ b/scripts/gcc-plugins/safe-plugin-version.h
@@ -0,0 +1,8 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_PLUGIN_VERSION_H
+#define SAFEGCC_PLUGIN_VERSION_H
+
+#include <plugin-version.h>
+
+#endif
diff --git a/scripts/gcc-plugins/safe-rtl.h b/scripts/gcc-plugins/safe-rtl.h
new file mode 100644
index 000000000000..f89decdb1d18
--- /dev/null
+++ b/scripts/gcc-plugins/safe-rtl.h
@@ -0,0 +1,17 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_RTL_H
+#define SAFEGCC_RTL_H
+
+#include <rtl.h>
+#include <rtl-iter.h>
+#include <memmodel.h>
+#include <emit-rtl.h>
+#include <function.h>
+#include <expr.h>
+#include <hard-reg-set.h>
+
+#undef toupper
+#undef tolower
+
+#endif
diff --git a/scripts/gcc-plugins/safe-tree.h b/scripts/gcc-plugins/safe-tree.h
new file mode 100644
index 000000000000..d968f8b9675a
--- /dev/null
+++ b/scripts/gcc-plugins/safe-tree.h
@@ -0,0 +1,10 @@
+#include <safe-gcc-plugin.h>
+
+#ifndef SAFEGCC_TREE_H
+#define SAFEGCC_TREE_H
+
+#include <tree.h>
+#include <tree-pass.h>
+#include <c-tree.h>
+
+#endif
diff --git a/scripts/gcc-plugins/separate_offset_pass.c
b/scripts/gcc-plugins/separate_offset_pass.c
new file mode 100644
index 000000000000..9598f1148896
--- /dev/null
+++ b/scripts/gcc-plugins/separate_offset_pass.c
@@ -0,0 +1,327 @@
+#include <functional>
+#include <list>
+
+#include <pinpoint.h>
+#include <passes.h>
+#include <ipin_registry.h>
+#include <target_registry.h>
+
+/*
+ * AccessChain flattens nested COMPONENT_REF / ARRAY_REF expressions from
+ * outer base to inner access.
+ *
+ * This lets the pass rebuild the expression one step at a time while replacing
+ * only SPSLR-relevant field offsets with separator calls. Non-target offsets
+ * stay as ordinary constant pointer arithmetic.
+ */
+
+struct AccessChain {
+ struct Step {
+ enum Kind { STEP_COMPONENT, STEP_ARRAY } kind = STEP_COMPONENT;
+
+ tree t = NULL_TREE;
+
+ /* COMPONENT_REF data */
+ bool relevant = false;
+ tree field = NULL_TREE;
+ ipin::handle pin = ipin::invalid;
+ };
+
+ bool relevant = false;
+ std::list<Step> steps;
+ 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 access_chain(tree ref, AccessChain &chain)
+{
+ if (!ref)
+ return false;
+
+ switch (TREE_CODE(ref)) {
+ case COMPONENT_REF: {
+ AccessChain::Step step;
+ step.kind = AccessChain::Step::STEP_COMPONENT;
+ step.t = ref;
+ tree field;
+ step.relevant = target::component_ref(ref, &field) &&
+ !target::field_is_fixed(field);
+ if (step.relevant) {
+ step.field = field;
+ step.pin = ipin::make(field);
+ chain.relevant = true;
+ }
+ chain.steps.push_front(step);
+ return access_chain(TREE_OPERAND(ref, 0), chain);
+ }
+
+ case ARRAY_REF:
+ case ARRAY_RANGE_REF: {
+ AccessChain::Step step;
+ step.kind = AccessChain::Step::STEP_ARRAY;
+ step.t = ref;
+ chain.steps.push_front(step);
+ return access_chain(TREE_OPERAND(ref, 0), chain);
+ }
+
+ default:
+ /*
+ * Access chain construction needs to reach all COMPONENT_REFs.
Further
+ * implementation may be required to cover all possible AST
scenarios.
+ */
+ if (tree_contains_component_ref(ref))
+ return false;
+
+ chain.base = ref;
+ return true;
+ }
+}
+
+/*
+ * Rewrite a relevant field-access chain into explicit pointer arithmetic.
+ *
+ * The resulting MEM_REF has offset zero; all interesting byte offsets have
+ * either become separator calls or fixed constants. This makes the later asm
+ * marker pass independent of GCC's original COMPONENT_REF tree shape.
+ */
+
+static tree separate_offset_chain_maybe(tree ref, gimple_stmt_iterator *gsi)
+{
+ AccessChain chain;
+ if (!access_chain(ref, chain)) {
+ pinpoint_fatal(
+ "separate_offset_chain_maybe encountered invalid access
chain: top=%s base=%s",
+ get_tree_code_name(TREE_CODE(ref)),
+ TREE_OPERAND(ref, 0) ? get_tree_code_name(TREE_CODE(
+ TREE_OPERAND(ref, 0))) :
+ "<null>");
+ }
+
+ if (!chain.relevant)
+ return NULL_TREE;
+
+ tree cur_expr = chain.base;
+
+ // NOTE -> Could fold into single call here (needs to track what
offsets contribute, +irrelevant combined)
+
+ for (const AccessChain::Step &step : chain.steps) {
+ if (step.kind == AccessChain::Step::STEP_COMPONENT) {
+ if (TREE_CODE(cur_expr) == ADDR_EXPR)
+ pinpoint_fatal(
+ "separate_offset_chain_maybe
encountered ADDR_EXPR as base of COMPONENT_REF");
+
+ tree cur_ptr = build_fold_addr_expr(cur_expr);
+
+ tree field_ptr_type =
+ build_pointer_type(TREE_TYPE(step.t));
+ tree field_ptr;
+
+ if (step.relevant) {
+ tree return_tmp =
+ create_tmp_var(size_type_node, NULL);
+ gimple *call_stmt = ipin::make_gimple_separator(
+ return_tmp, step.pin);
+ if (!call_stmt)
+ pinpoint_fatal(
+ "separate_offset_chain_maybe
failed to make gimple separator");
+
+ gsi_insert_before(gsi, call_stmt,
+ GSI_SAME_STMT);
+ field_ptr = build2(POINTER_PLUS_EXPR,
+ field_ptr_type, cur_ptr,
+ return_tmp);
+ } else {
+ tree field_decl = TREE_OPERAND(step.t, 1);
+
+ std::size_t field_offset =
+ target::field_offset(field_decl);
+ bool field_bitfield =
+ target::field_is_bitfield(field_decl);
+ if (field_bitfield)
+ pinpoint_fatal(
+ "separate_offset_chain_maybe
encountered bitfield access in relevant COMPONENT_REF chain");
+
+ field_ptr = build2(POINTER_PLUS_EXPR,
+ field_ptr_type, cur_ptr,
+ build_int_cst(sizetype,
+ field_offset));
+ }
+
+ tree ptr_tmp = create_tmp_var(field_ptr_type, NULL);
+
+ tree ptr_val = force_gimple_operand_gsi(
+ gsi, field_ptr,
+ true, // require simple result
+ ptr_tmp, // target temp
+ true, // insert before current stmt
+ GSI_SAME_STMT);
+
+ tree offset0 = fold_convert(TREE_TYPE(ptr_val),
+ build_int_cst(sizetype, 0));
+
+ cur_expr = build2(MEM_REF, TREE_TYPE(step.t), ptr_val,
+ offset0);
+
+ continue;
+ }
+
+ if (step.kind == AccessChain::Step::STEP_ARRAY) {
+ tree idx = TREE_OPERAND(step.t, 1);
+ tree low = TREE_OPERAND(step.t, 2);
+ tree elts = TREE_OPERAND(step.t, 3);
+
+ cur_expr = build4(TREE_CODE(step.t), TREE_TYPE(step.t),
+ cur_expr, idx, low, elts);
+ continue;
+ }
+ }
+
+ return cur_expr;
+}
+
+static void dispatch_separation_maybe(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 = 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<tree *> path;
+ gimple_stmt_iterator *gsi;
+ unsigned cancel_levels;
+ std::function<void(const std::list<tree *> &, 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<void(const std::list<tree *> &,
+ gimple_stmt_iterator *, unsigned &)>
+ callback)
+{
+ if (!gsi || gsi_end_p(*gsi) || !callback)
+ return false;
+
+ gimple *stmt = gsi_stmt(*gsi);
+
+ for (std::size_t 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/scripts/gcc-plugins/serialize.c b/scripts/gcc-plugins/serialize.c
new file mode 100644
index 000000000000..d30e49464aea
--- /dev/null
+++ b/scripts/gcc-plugins/serialize.c
@@ -0,0 +1,303 @@
+#include "serialize.h"
+
+#include <cctype>
+
+namespace selfpatch
+{
+
+namespace
+{
+
+constexpr std::string_view section_entry = "spslr_entry";
+constexpr std::string_view section_units = "spslr_units";
+constexpr std::string_view section_targets = "spslr_targets";
+constexpr std::string_view section_target_layouts = "spslr_target_layouts";
+constexpr std::string_view section_ipins = "spslr_ipins";
+constexpr std::string_view section_dpins = "spslr_dpins";
+constexpr std::string_view section_strtab = "spslr_strtab";
+constexpr std::string_view section_cu_target_refs = "spslr_cu_target_refs";
+
+std::size_t next_local_label_id = 0;
+
+std::string quote_asm_string(std::string_view s)
+{
+ std::string out;
+ out.reserve(s.size() + 8);
+ out.push_back('"');
+
+ for (unsigned char c : s) {
+ switch (c) {
+ case '\\':
+ out += "\\\\";
+ break;
+ case '"':
+ out += "\\\"";
+ break;
+ case '\n':
+ out += "\\n";
+ break;
+ case '\r':
+ out += "\\r";
+ break;
+ case '\t':
+ out += "\\t";
+ break;
+ case '\0':
+ out += "\\000";
+ break;
+ default:
+ if (std::isprint(c)) {
+ out.push_back(static_cast<char>(c));
+ } else {
+ char buf[5];
+ std::snprintf(buf, sizeof(buf), "\\%03o", c);
+ out += buf;
+ }
+ break;
+ }
+ }
+
+ out.push_back('"');
+ return out;
+}
+
+} // namespace
+
+void emit_comment(FILE *out, std::string_view text)
+{
+ std::fprintf(out, "\n/* %.*s */\n", static_cast<int>(text.size()),
+ text.data());
+}
+
+void emit_push_section(FILE *out, std::string_view name)
+{
+ /* Future implementation should differentiate between module and host
+ * section permissions. Module relocations may cause DT_TEXTREL issues
+ * if the metadata sections are read-only. */
+ std::fprintf(out, ".pushsection %.*s,\"aw\",@progbits\n",
+ static_cast<int>(name.size()), name.data());
+}
+
+void emit_push_comdat_section(FILE *out, std::string_view name,
+ std::string_view group_symbol)
+{
+ /* Future implementation should differentiate between module and host
+ * section permissions. Module relocations may cause DT_TEXTREL issues
+ * if the metadata sections are read-only. */
+ std::fprintf(out, ".pushsection %.*s,\"awG\",@progbits,%.*s,comdat\n",
+ static_cast<int>(name.size()), name.data(),
+ static_cast<int>(group_symbol.size()),
+ group_symbol.data());
+}
+
+void emit_pop_section(FILE *out)
+{
+ std::fprintf(out, ".popsection\n");
+}
+
+void emit_units_section(FILE *out)
+{
+ emit_push_section(out, section_units);
+}
+
+void emit_targets_section(FILE *out, const hash16_t &hash)
+{
+ emit_push_comdat_section(out, section_targets,
+ comdat_target_symbol(hash));
+}
+
+void emit_target_layouts_section(FILE *out, const hash16_t &hash)
+{
+ emit_push_comdat_section(out, section_target_layouts,
+ comdat_target_symbol(hash));
+}
+
+void emit_ipins_section(FILE *out)
+{
+ emit_push_section(out, section_ipins);
+}
+
+void emit_dpins_section(FILE *out)
+{
+ emit_push_section(out, section_dpins);
+}
+
+void emit_strtab_section(FILE *out)
+{
+ emit_push_section(out, section_strtab);
+}
+
+void emit_cu_target_refs_section(FILE *out)
+{
+ emit_push_section(out, section_cu_target_refs);
+}
+
+void emit_label(FILE *out, std::string_view label)
+{
+ std::fprintf(out, "%.*s:\n", static_cast<int>(label.size()),
+ label.data());
+}
+
+void emit_hidden_global_label(FILE *out, std::string_view label)
+{
+ std::fprintf(out, ".globl %.*s\n", static_cast<int>(label.size()),
+ label.data());
+ std::fprintf(out, ".hidden %.*s\n", static_cast<int>(label.size()),
+ label.data());
+ emit_label(out, label);
+}
+
+std::string make_local_label(std::string_view stem)
+{
+ std::string out = ".Lspslr_";
+ out.append(stem);
+ out.push_back('_');
+ out.append(std::to_string(next_local_label_id++));
+ return out;
+}
+
+std::string emit_strtab_entry(FILE *out, std::string_view value)
+{
+ const std::string label = make_local_label("str");
+
+ emit_strtab_section(out);
+ emit_label(out, label);
+ emit_c_string(out, value);
+ emit_pop_section(out);
+
+ return label;
+}
+
+void emit_unit(FILE *out, const unit_desc &unit)
+{
+ emit_quad_symbol(out, unit.source_label);
+ emit_quad(out, unit.target_ref_cnt);
+ emit_quad_symbol(out, unit.target_refs_symbol);
+ emit_quad(out, unit.ipin_cnt);
+ emit_quad_symbol(out, unit.ipins_symbol);
+ emit_quad(out, unit.dpin_cnt);
+ emit_quad_symbol(out, unit.dpins_symbol);
+}
+
+void emit_target(FILE *out, const target_desc &target)
+{
+ emit_bytes(out, target.hash.data(), target.hash.size());
+ emit_quad_symbol(out, target.name_label);
+ emit_quad_symbol(out, target.layout_symbol);
+}
+
+void emit_target_layout(FILE *out, const target_layout_desc &layout)
+{
+ emit_quad(out, layout.size);
+ emit_quad(out, layout.field_cnt);
+ emit_quad_symbol(out, layout.fields_symbol);
+}
+
+void emit_target_field(FILE *out, const target_field_desc &field)
+{
+ emit_quad_symbol(out, field.name_label);
+ emit_quad(out, field.size);
+ emit_quad(out, field.offset);
+ emit_quad(out, field.alignment);
+ emit_quad(out, field.flags);
+}
+
+void emit_target_ref(FILE *out, const target_ref_desc &target)
+{
+ emit_quad_symbol(out, target.target_symbol);
+}
+
+void emit_ipin(FILE *out, const ipin_desc &ipin)
+{
+ emit_quad_expr(out, ipin.addr_expr);
+ emit_quad_expr(out, ipin.size_expr);
+ emit_quad_symbol(out, ipin.expr_symbol);
+}
+
+void emit_dpin(FILE *out, const dpin_desc &dpin)
+{
+ emit_quad_expr(out, dpin.addr_expr);
+ emit_quad(out, dpin.unit_target_idx);
+}
+
+void emit_ipin_expr(FILE *out, const ipin_expr_desc &expr)
+{
+ emit_quad(out, expr.unit_target_idx);
+ emit_quad(out, expr.field);
+}
+
+void emit_quad(FILE *out, std::size_t value)
+{
+ std::fprintf(out, ".quad %zu\n", value);
+}
+
+void emit_quad_symbol(FILE *out, std::string_view symbol)
+{
+ emit_quad_expr(out, symbol);
+}
+
+void emit_quad_expr(FILE *out, std::string_view expr)
+{
+ std::fprintf(out, ".quad %.*s\n", static_cast<int>(expr.size()),
+ expr.data());
+}
+
+void emit_bytes(FILE *out, const void *data, std::size_t size)
+{
+ const auto *bytes = static_cast<const std::uint8_t *>(data);
+
+ for (std::size_t i = 0; i < size; ++i) {
+ if (i % 16 == 0)
+ std::fprintf(out, ".byte ");
+ else
+ std::fprintf(out, ",");
+
+ std::fprintf(out, "0x%02x", bytes[i]);
+
+ if (i % 16 == 15 || i + 1 == size)
+ std::fprintf(out, "\n");
+ }
+}
+
+void emit_c_string(FILE *out, std::string_view value)
+{
+ const std::string quoted = quote_asm_string(value);
+ std::fprintf(out, ".asciz %s\n", quoted.c_str());
+}
+
+std::string hash_hex(const hash16_t &hash)
+{
+ static constexpr char digits[] = "0123456789abcdef";
+
+ std::string out;
+ out.resize(hash.size() * 2);
+
+ for (std::size_t i = 0; i < hash.size(); ++i) {
+ out[i * 2] = digits[(hash[i] >> 4) & 0x0f];
+ out[i * 2 + 1] = digits[hash[i] & 0x0f];
+ }
+
+ return out;
+}
+
+std::string comdat_target_symbol(const hash16_t &hash)
+{
+ return "__comdat_spslr_target_" + hash_hex(hash);
+}
+
+std::string target_symbol(const hash16_t &hash)
+{
+ return "__spslr_target_" + hash_hex(hash);
+}
+
+std::string target_hash_symbol(const hash16_t &hash)
+{
+ return "__spslr_target_hash_" + hash_hex(hash);
+}
+
+std::string target_layout_symbol(const hash16_t &hash)
+{
+ return "__spslr_target_layout_" + hash_hex(hash);
+}
+
+} // namespace selfpatch
diff --git a/scripts/gcc-plugins/serialize.h b/scripts/gcc-plugins/serialize.h
new file mode 100644
index 000000000000..7c358dd17645
--- /dev/null
+++ b/scripts/gcc-plugins/serialize.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <string>
+#include <string_view>
+
+namespace selfpatch
+{
+
+using hash16_t = std::array<std::uint8_t, 16>;
+
+/*
+ * Entry point is:
+ * __start_spslr_units
+ * __stop_spslr_units
+ * __start_spslr_targets
+ * __stop_spslr_targets
+ */
+
+struct unit_desc {
+ std::string source_label;
+ std::size_t target_ref_cnt = 0;
+ std::string target_refs_symbol;
+ std::size_t ipin_cnt = 0;
+ std::string ipins_symbol;
+ std::size_t dpin_cnt = 0;
+ std::string dpins_symbol;
+};
+
+struct target_desc {
+ hash16_t hash{};
+ std::string name_label;
+ std::string layout_symbol;
+};
+
+struct target_layout_desc {
+ std::size_t size = 0;
+ std::size_t field_cnt = 0;
+ std::string fields_symbol;
+};
+
+struct target_field_desc {
+ std::string name_label;
+ std::size_t size = 0;
+ std::size_t offset = 0;
+ std::size_t alignment = 0;
+ std::uint64_t flags = 0;
+};
+
+struct target_ref_desc {
+ std::string target_symbol;
+};
+
+struct ipin_desc {
+ std::string addr_expr;
+ std::string size_expr;
+ std::string expr_symbol;
+};
+
+struct dpin_desc {
+ std::string addr_expr;
+ std::size_t unit_target_idx = 0;
+};
+
+struct ipin_expr_desc {
+ std::size_t unit_target_idx = 0;
+ std::size_t field = 0;
+};
+
+void emit_comment(FILE *out, std::string_view text);
+
+void emit_push_section(FILE *out, std::string_view name);
+void emit_push_comdat_section(FILE *out, std::string_view name,
+ std::string_view group_symbol);
+void emit_pop_section(FILE *out);
+
+void emit_units_section(FILE *out);
+void emit_targets_section(FILE *out, const hash16_t &hash);
+void emit_target_layouts_section(FILE *out, const hash16_t &hash);
+void emit_ipins_section(FILE *out);
+void emit_dpins_section(FILE *out);
+void emit_strtab_section(FILE *out);
+void emit_cu_target_refs_section(FILE *out);
+
+void emit_label(FILE *out, std::string_view label);
+void emit_hidden_global_label(FILE *out, std::string_view label);
+
+std::string make_local_label(std::string_view stem);
+std::string emit_strtab_entry(FILE *out, std::string_view value);
+
+void emit_unit(FILE *out, const unit_desc &unit);
+void emit_target(FILE *out, const target_desc &target);
+void emit_target_layout(FILE *out, const target_layout_desc &layout);
+void emit_target_field(FILE *out, const target_field_desc &field);
+void emit_target_ref(FILE *out, const target_ref_desc &target);
+void emit_ipin(FILE *out, const ipin_desc &ipin);
+void emit_dpin(FILE *out, const dpin_desc &dpin);
+void emit_ipin_expr(FILE *out, const ipin_expr_desc &expr);
+
+void emit_quad(FILE *out, std::size_t value);
+void emit_quad_symbol(FILE *out, std::string_view symbol);
+void emit_quad_expr(FILE *out, std::string_view expr);
+void emit_bytes(FILE *out, const void *data, std::size_t size);
+void emit_c_string(FILE *out, std::string_view value);
+
+std::string hash_hex(const hash16_t &hash);
+std::string comdat_target_symbol(const hash16_t &hash);
+std::string target_symbol(const hash16_t &hash);
+std::string target_hash_symbol(const hash16_t &hash);
+std::string target_layout_symbol(const hash16_t &hash);
+
+} // namespace selfpatch
diff --git a/scripts/gcc-plugins/target_hash_builtin_pass.c
b/scripts/gcc-plugins/target_hash_builtin_pass.c
new file mode 100644
index 000000000000..d35b8df9abbc
--- /dev/null
+++ b/scripts/gcc-plugins/target_hash_builtin_pass.c
@@ -0,0 +1,167 @@
+#include <cstring>
+#include <map>
+#include <string>
+
+#include <pinpoint.h>
+#include <passes.h>
+#include <serialize.h>
+#include <target_registry.h>
+
+#include <safe-gimple.h>
+#include <safe-tree.h>
+
+namespace
+{
+
+static std::map<std::string, tree> hash_symbol_decls;
+
+static selfpatch::hash16_t to_hash16(const target::layout_hash_t &in)
+{
+ selfpatch::hash16_t out{};
+
+ for (std::size_t i = 0; i < out.size(); ++i)
+ out[i] = static_cast<std::uint8_t>(in[i]);
+
+ return out;
+}
+
+static bool called_decl_name_is(tree fndecl, const char *wanted)
+{
+ if (!fndecl || !DECL_NAME(fndecl))
+ return false;
+
+ const char *name = IDENTIFIER_POINTER(DECL_NAME(fndecl));
+ return name && std::strcmp(name, wanted) == 0;
+}
+
+static tree call_argument_pointee_type(gimple *stmt)
+{
+ if (!stmt || !is_gimple_call(stmt) || gimple_call_num_args(stmt) != 1)
+ return NULL_TREE;
+
+ tree arg = gimple_call_arg(stmt, 0);
+ if (!arg)
+ return NULL_TREE;
+
+ /* Case: &__spslr_target_hash_type_anchor_N */
+ if (TREE_CODE(arg) == ADDR_EXPR) {
+ tree obj = TREE_OPERAND(arg, 0);
+ if (obj) {
+ tree obj_type = TREE_TYPE(obj);
+ if (obj_type)
+ return target::main_variant(obj_type);
+ }
+ }
+
+ /* Fallback: argument still has pointer type T *. */
+ tree arg_type = TREE_TYPE(arg);
+ if (arg_type && POINTER_TYPE_P(arg_type))
+ return target::main_variant(TREE_TYPE(arg_type));
+
+ return NULL_TREE;
+}
+
+static tree make_hash_symbol_decl(const std::string &symbol)
+{
+ auto it = hash_symbol_decls.find(symbol);
+ if (it != hash_symbol_decls.end())
+ return it->second;
+
+ tree byte_type =
+ build_qualified_type(unsigned_char_type_node, TYPE_QUAL_CONST);
+ tree array_type = build_array_type_nelts(byte_type, 16);
+
+ tree decl = build_decl(UNKNOWN_LOCATION, VAR_DECL,
+ get_identifier(symbol.c_str()), array_type);
+
+ DECL_EXTERNAL(decl) = 1;
+ TREE_PUBLIC(decl) = 1;
+ TREE_READONLY(decl) = 1;
+ DECL_ARTIFICIAL(decl) = 1;
+ DECL_IGNORED_P(decl) = 1;
+
+ hash_symbol_decls.emplace(symbol, decl);
+ return decl;
+}
+
+static tree make_hash_pointer_expr(tree target_type, tree result_type)
+{
+ const selfpatch::hash16_t hash =
+ to_hash16(target::layout_hash(target_type));
+
+ const std::string symbol = selfpatch::target_hash_symbol(hash);
+
+ tree decl = make_hash_symbol_decl(symbol);
+ tree addr = build_fold_addr_expr(decl);
+
+ return fold_convert(result_type, addr);
+}
+
+static tree make_null_pointer_expr(tree result_type)
+{
+ return fold_convert(result_type, null_pointer_node);
+}
+
+} // namespace
+
+PINPOINT_GC_PRESERVE_CALLBACK()
+{
+ for (const auto &[symbol, decl] : hash_symbol_decls)
+ PINPOINT_GC_MARK_TREE(decl);
+}
+
+static const pass_data target_hash_builtin_pass_data = {
+ GIMPLE_PASS, "spslr_target_hash", OPTGROUP_NONE, TV_NONE, 0, 0, 0,
+ 0, TODO_update_ssa
+};
+
+target_hash_builtin_pass::target_hash_builtin_pass(gcc::context *ctxt)
+ : gimple_opt_pass(target_hash_builtin_pass_data, ctxt)
+{
+}
+
+unsigned int target_hash_builtin_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);) {
+ gimple *stmt = gsi_stmt(gsi);
+
+ if (!is_gimple_call(stmt) ||
+ !called_decl_name_is(gimple_call_fndecl(stmt),
+ SPSLR_TARGET_HASH_BUILTIN)) {
+ gsi_next(&gsi);
+ continue;
+ }
+
+ tree lhs = gimple_call_lhs(stmt);
+ if (!lhs) {
+ gsi_remove(&gsi, true);
+ continue;
+ }
+
+ tree result_type = TREE_TYPE(lhs);
+ tree target_type = call_argument_pointee_type(stmt);
+
+ tree rhs = NULL_TREE;
+
+ if (target_type &&
+ target::is_validated_target(target_type))
+ rhs = make_hash_pointer_expr(target_type,
+ result_type);
+ else
+ rhs = make_null_pointer_expr(result_type);
+
+ gimple *replacement = gimple_build_assign(lhs, rhs);
+ gsi_replace(&gsi, replacement, true);
+ gsi_next(&gsi);
+ }
+ }
+
+ return 0;
+}
diff --git a/scripts/gcc-plugins/target_registry.c
b/scripts/gcc-plugins/target_registry.c
new file mode 100644
index 000000000000..a692520467bd
--- /dev/null
+++ b/scripts/gcc-plugins/target_registry.c
@@ -0,0 +1,492 @@
+#include <algorithm>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <pinpoint.h>
+#include <target_registry.h>
+#include <layout_hash.h>
+
+#include <safe-attribs.h>
+#include <safe-langhooks.h>
+
+struct validated_target {
+ std::vector<target::compressed_field> fields{};
+ std::map<tree, std::size_t> field_indices{};
+ target::layout_hash_t hash{};
+};
+
+static std::map<tree, validated_target> validated_targets;
+
+PINPOINT_GC_PRESERVE_CALLBACK()
+{
+ for (const auto &[t, info] : validated_targets)
+ PINPOINT_GC_MARK_TREE(t);
+}
+
+using field_callback = std::function<void(tree field_decl)>;
+
+static void iterate_fields(tree type, const field_callback &cb)
+{
+ type = target::main_variant(type);
+
+ if (!type || !COMPLETE_TYPE_P(type))
+ pinpoint_fatal("target::iterate_fields: incomplete target");
+
+ for (tree f = TYPE_FIELDS(type); f; f = DECL_CHAIN(f)) {
+ if (TREE_CODE(f) == FIELD_DECL)
+ cb(f);
+ }
+}
+
+static void build_compressed_fields(tree type, validated_target &vt)
+{
+ vt.fields.clear();
+
+ std::size_t compressed_idx = 0;
+
+ iterate_fields(type, [&](tree field) {
+ std::string name = target::field_name(field);
+ std::size_t off = target::field_offset(field);
+ std::size_t sz = target::field_size(field);
+ std::size_t end = off + sz;
+ bool fixed = target::field_is_fixed(field);
+
+ if (vt.fields.empty()) {
+ vt.fields.push_back({
+ .name = name,
+ .offset = off,
+ .size = sz,
+ .alignment = target::field_alignment(field),
+ .fixed = fixed,
+ });
+ vt.field_indices[field] = compressed_idx;
+ return;
+ }
+
+ target::compressed_field &prev = vt.fields.back();
+ std::size_t prev_end = prev.offset + prev.size;
+
+ if (off < prev.offset)
+ pinpoint_fatal(
+ "target::build_compressed_fields: invalid field
order in target \"%s\"",
+ target::qualified_name(type).c_str());
+
+ if (off >= prev_end) {
+ vt.fields.push_back({
+ .name = name,
+ .offset = off,
+ .size = sz,
+ .alignment = target::field_alignment(field),
+ .fixed = fixed,
+ });
+ vt.field_indices[field] = ++compressed_idx;
+ return;
+ }
+
+ if (!prev.fixed || !fixed) {
+ pinpoint_fatal(
+ "target::build_compressed_fields: overlapping
non-fixed field in target \"%s\": \"%s\"",
+ target::qualified_name(type).c_str(),
+ target::field_name(field).c_str());
+ }
+
+ /*
+ * Fixed overlapping fields are represented as one immovable
byte
+ * range in the runtime metadata. Alignment is irrelevant
because
+ * the randomizer will never move this synthetic field.
+ */
+ if (end > prev_end)
+ prev.size = end - prev.offset;
+
+ prev.alignment = 1;
+ prev.fixed = true;
+ prev.name = prev.name + "+" + name;
+
+ vt.field_indices[field] = compressed_idx;
+ });
+}
+
+static void remember_target(tree type)
+{
+ type = target::main_variant(type);
+ if (!type)
+ return;
+
+ if (validated_targets.find(type) != validated_targets.end())
+ return;
+
+ auto new_vt = validated_targets.emplace(type, validated_target{});
+ if (!new_vt.second)
+ pinpoint_fatal(
+ "remember_target failed to log new validated target");
+
+ validated_target &vt = new_vt.first->second;
+
+ /* Must build compressed fields before hash, because hash queries them
*/
+ build_compressed_fields(type, vt);
+ vt.hash = compute_layout_hash(type);
+}
+
+bool target::is_validated_target(tree type)
+{
+ type = main_variant(type);
+ return type && validated_targets.find(type) != validated_targets.end();
+}
+
+const target::layout_hash_t &target::layout_hash(tree type)
+{
+ type = main_variant(type);
+
+ auto it = validated_targets.find(type);
+ if (it == validated_targets.end())
+ pinpoint_fatal(
+ "target::layout_hash: type is not a validated SPSLR
target");
+
+ return it->second.hash;
+}
+
+tree target::main_variant(tree type)
+{
+ if (!type || TREE_CODE(type) != RECORD_TYPE)
+ return NULL_TREE;
+
+ return TYPE_MAIN_VARIANT(type);
+}
+
+tree target::from_field(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ return NULL_TREE;
+
+ return main_variant(DECL_CONTEXT(field_decl));
+}
+
+bool target::is_target(tree type)
+{
+ type = main_variant(type);
+
+ if (!type || TREE_CODE(type) != RECORD_TYPE)
+ return false;
+
+ return lookup_attribute(SPSLR_ATTRIBUTE, TYPE_ATTRIBUTES(type));
+}
+
+std::string target::field_name(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ return "<error>";
+
+ tree name = DECL_NAME(field_decl);
+ if (!name)
+ return "<anonymous>";
+
+ return IDENTIFIER_POINTER(name);
+}
+
+std::string target::name(tree type)
+{
+ type = main_variant(type);
+ if (!type)
+ return "<error>";
+
+ tree name_tree = TYPE_NAME(type);
+ if (!name_tree)
+ return "<anonymous>";
+
+ if (TREE_CODE(name_tree) == TYPE_DECL && DECL_NAME(name_tree))
+ return IDENTIFIER_POINTER(DECL_NAME(name_tree));
+
+ if (TREE_CODE(name_tree) == IDENTIFIER_NODE)
+ return IDENTIFIER_POINTER(name_tree);
+
+ return "<anonymous>";
+}
+
+static std::string decl_context_name(tree decl)
+{
+ if (!decl)
+ return "<anonymous>";
+
+ tree name = DECL_NAME(decl);
+ if (!name)
+ return "<anonymous>";
+
+ return IDENTIFIER_POINTER(name);
+}
+
+std::vector<std::string> target::context_chain(tree type)
+{
+ std::vector<std::string> out;
+
+ type = main_variant(type);
+ if (!type)
+ return out;
+
+ tree type_name = TYPE_NAME(type);
+ tree ctx = NULL_TREE;
+
+ if (type_name && TREE_CODE(type_name) == TYPE_DECL)
+ ctx = DECL_CONTEXT(type_name);
+
+ if (!ctx)
+ ctx = TYPE_CONTEXT(type);
+
+ for (; ctx;) {
+ if (TREE_CODE(ctx) == TRANSLATION_UNIT_DECL)
+ break;
+
+ if (TREE_CODE(ctx) == RECORD_TYPE) {
+ out.push_back(name(ctx));
+ ctx = TYPE_CONTEXT(ctx);
+ continue;
+ }
+
+ if (DECL_P(ctx)) {
+ out.push_back(decl_context_name(ctx));
+ ctx = DECL_CONTEXT(ctx);
+ continue;
+ }
+
+ if (TYPE_P(ctx)) {
+ out.push_back(name(ctx));
+ ctx = TYPE_CONTEXT(ctx);
+ continue;
+ }
+
+ break;
+ }
+
+ std::reverse(out.begin(), out.end());
+ return out;
+}
+
+std::string target::qualified_name(tree type)
+{
+ std::string out;
+
+ for (const std::string &ctx : context_chain(type)) {
+ if (!out.empty())
+ out += "::";
+ out += ctx;
+ }
+
+ if (!out.empty())
+ out += "::";
+
+ out += name(type);
+ return out;
+}
+
+std::size_t target::size(tree type)
+{
+ type = main_variant(type);
+
+ tree size_tree = type ? TYPE_SIZE(type) : NULL_TREE;
+ if (!size_tree || TREE_CODE(size_tree) != INTEGER_CST)
+ pinpoint_fatal("target::size: non-constant target size");
+
+ HOST_WIDE_INT bits = tree_to_uhwi(size_tree);
+ if (bits < 0 || bits % BITS_PER_UNIT)
+ pinpoint_fatal("target::size: target size is not byte-aligned");
+
+ return static_cast<std::size_t>(bits / BITS_PER_UNIT);
+}
+
+std::size_t target::field_offset(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ pinpoint_fatal(
+ "target::field_offset can only be applied to FIELD_DECL
trees");
+
+ tree field_byte_offset_tree = DECL_FIELD_OFFSET(field_decl);
+ tree field_bit_offset_tree = DECL_FIELD_BIT_OFFSET(field_decl);
+
+ if (!field_byte_offset_tree ||
+ TREE_CODE(field_byte_offset_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_offset was unable to fetch byte offset");
+
+ if (!field_bit_offset_tree ||
+ TREE_CODE(field_bit_offset_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_offset was unable to fetch bit offset");
+
+ HOST_WIDE_INT byte_offset = tree_to_uhwi(field_byte_offset_tree);
+ HOST_WIDE_INT bit_offset = tree_to_uhwi(field_bit_offset_tree);
+ HOST_WIDE_INT bit_offset_bytes = bit_offset / BITS_PER_UNIT;
+
+ return byte_offset + bit_offset_bytes;
+}
+
+bool target::field_has_size(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ return false;
+
+ tree bit_offset = DECL_FIELD_BIT_OFFSET(field_decl);
+ tree bit_size = DECL_SIZE(field_decl);
+
+ return bit_offset && TREE_CODE(bit_offset) == INTEGER_CST && bit_size &&
+ TREE_CODE(bit_size) == INTEGER_CST;
+}
+
+std::size_t target::field_size(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ pinpoint_fatal(
+ "target::field_size can only be applied to FIELD_DECL
trees");
+
+ tree field_bit_offset_tree = DECL_FIELD_BIT_OFFSET(field_decl);
+ tree field_bit_size_tree = DECL_SIZE(field_decl);
+
+ if (!field_bit_offset_tree ||
+ TREE_CODE(field_bit_offset_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_size was unable to fetch bit offset");
+
+ if (!field_bit_size_tree ||
+ TREE_CODE(field_bit_size_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_size was unable to fetch bit size");
+
+ HOST_WIDE_INT bit_offset =
+ tree_to_uhwi(field_bit_offset_tree) % BITS_PER_UNIT;
+ HOST_WIDE_INT bit_size = tree_to_uhwi(field_bit_size_tree) + bit_offset;
+
+ HOST_WIDE_INT bit_overhang = bit_size % BITS_PER_UNIT;
+ if (bit_overhang != 0)
+ bit_size += (8 - bit_overhang);
+
+ return static_cast<std::size_t>(bit_size / BITS_PER_UNIT);
+}
+
+std::size_t target::field_alignment(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ pinpoint_fatal(
+ "target::field_alignment can only be applied to
FIELD_DECL trees");
+
+ HOST_WIDE_INT alignment_bits = DECL_ALIGN(field_decl);
+ if (alignment_bits <= 0 && TREE_TYPE(field_decl))
+ alignment_bits = TYPE_ALIGN(TREE_TYPE(field_decl));
+ if (alignment_bits <= 0)
+ alignment_bits = BITS_PER_UNIT;
+
+ std::size_t alignment = static_cast<std::size_t>(
+ (alignment_bits + BITS_PER_UNIT - 1) / BITS_PER_UNIT);
+ if (alignment == 0)
+ alignment = 1;
+
+ return alignment;
+}
+
+bool target::field_is_bitfield(tree field_decl)
+{
+ if (!field_decl || TREE_CODE(field_decl) != FIELD_DECL)
+ pinpoint_fatal(
+ "target::field_is_bitfield can only be applied to
FIELD_DECL trees");
+
+ tree field_bit_offset_tree = DECL_FIELD_BIT_OFFSET(field_decl);
+ tree field_bit_size_tree = DECL_SIZE(field_decl);
+
+ if (!field_bit_offset_tree ||
+ TREE_CODE(field_bit_offset_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_is_bitfield was unable to fetch bit
offset");
+
+ if (!field_bit_size_tree ||
+ TREE_CODE(field_bit_size_tree) != INTEGER_CST)
+ pinpoint_fatal(
+ "target::field_is_bitfield was unable to fetch bit
size");
+
+ HOST_WIDE_INT bit_offset =
+ tree_to_uhwi(field_bit_offset_tree) % BITS_PER_UNIT;
+ HOST_WIDE_INT bit_size = tree_to_uhwi(field_bit_size_tree);
+
+ bool decl_bitfield = DECL_BIT_FIELD_TYPE(field_decl) != NULL_TREE;
+ bool extra_bitfield = bit_size % 8 != 0 || bit_offset != 0;
+
+ return decl_bitfield || extra_bitfield;
+}
+
+bool target::field_is_fixed(tree field_decl)
+{
+ return field_is_bitfield(field_decl) ||
+ lookup_attribute(SPSLR_FIELD_FIXED_ATTRIBUTE,
+ DECL_ATTRIBUTES(field_decl));
+}
+
+const std::vector<target::compressed_field> &
+target::compressed_fields(tree type)
+{
+ type = main_variant(type);
+
+ auto it = validated_targets.find(type);
+ if (it == validated_targets.end())
+ pinpoint_fatal(
+ "target::compressed_fields: type is not a validated
SPSLR target");
+
+ return it->second.fields;
+}
+
+void target::iterate_targets(const target_callback &cb)
+{
+ for (const auto &[t, info] : validated_targets)
+ cb(t);
+}
+
+std::size_t target::target_count()
+{
+ return validated_targets.size();
+}
+
+void target::validate(tree type)
+{
+ type = main_variant(type);
+ remember_target(type);
+}
+
+bool target::component_ref(tree ref, tree *field_decl)
+{
+ if (!ref || TREE_CODE(ref) != COMPONENT_REF)
+ return false;
+
+ tree field = TREE_OPERAND(ref, 1);
+ if (!field || TREE_CODE(field) != FIELD_DECL)
+ return false;
+
+ tree type = from_field(field);
+ if (!is_target(type))
+ return false;
+
+ if (field_decl)
+ *field_decl = field;
+
+ return true;
+}
+
+std::size_t target::field_index(tree field_decl)
+{
+ tree type = from_field(field_decl);
+ if (!type)
+ pinpoint_fatal(
+ "target::field_index: field does not belong to a
target");
+
+ auto vt = validated_targets.find(type);
+ if (vt == validated_targets.end())
+ pinpoint_fatal(
+ "target::field_index: field does not belong to a
validated target");
+
+ auto it = vt->second.field_indices.find(field_decl);
+ if (it == vt->second.field_indices.end())
+ pinpoint_fatal(
+ "target::field_index: field does not belong to a
validated target");
+
+ return it->second;
+}
+
+void target::reset()
+{
+ validated_targets.clear();
+}
diff --git a/scripts/gcc-plugins/target_registry.h
b/scripts/gcc-plugins/target_registry.h
new file mode 100644
index 000000000000..5531cefffb38
--- /dev/null
+++ b/scripts/gcc-plugins/target_registry.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <cstddef>
+#include <functional>
+#include <string>
+#include <vector>
+#include <array>
+
+#include <safe-tree.h>
+
+struct target {
+ struct compressed_field {
+ std::string name{};
+ std::size_t offset{};
+ std::size_t size{};
+ std::size_t alignment{};
+ bool fixed{};
+ };
+
+ static tree main_variant(tree type);
+ static tree from_field(tree field_decl);
+
+ static bool is_target(tree type);
+
+ static std::string name(tree type);
+ static std::vector<std::string> context_chain(tree type);
+ static std::string qualified_name(tree type);
+
+ static std::size_t size(tree type);
+
+ static std::string field_name(tree field_decl);
+ static std::size_t field_offset(tree field_decl);
+ static bool field_has_size(tree field_decl);
+ static std::size_t field_size(tree field_decl);
+ static std::size_t field_alignment(tree field_decl);
+ static bool field_is_bitfield(tree field_decl);
+ static bool field_is_fixed(tree field_decl);
+
+ static bool component_ref(tree ref, tree *field_decl);
+
+ static const std::vector<compressed_field> &
+ compressed_fields(tree type);
+
+ using target_callback = std::function<void(tree target_type)>;
+ static void iterate_targets(const target_callback &cb);
+ static std::size_t target_count();
+
+ static void validate(tree type);
+
+ /* THe field index is into compressed_fields */
+ static std::size_t field_index(tree field_decl);
+
+ using layout_hash_t = std::array<std::byte, 16>;
+
+ static bool is_validated_target(tree type);
+ static const layout_hash_t &layout_hash(tree type);
+
+ static void reset();
+};
--
2.43.0