Adds a test suite for KCFI (Kernel Control Flow Integrity) ABI.

Core Functionality Tests:
- kcfi-basics.c: Basic KCFI preamble generation, IPA analysis, and
  __kcfi_typeid_ symbol emission for external address-taken functions.
- kcfi-type-mangling.c: Type ID hashing validation with Itanium C++ ABI
  mangling for function signatures covering basic types, pointers, const
  qualifiers, multiple parameters, return types, arrays, function pointers,
  structs/unions/enums, typedef canonicalization, and recursive type
  definitions.
- kcfi-no-sanitize.c: no_sanitize("kcfi") attribute support for
  instrumentation disabling.
- kcfi-no-sanitize-inline.c: Regression test for preserving no_sanitize("kcfi")
  attributes during function inlining. Tests that functions marked with both
  no_sanitize("kcfi") and always_inline correctly suppress KCFI checks when
  inlined.
- kcfi-trap-section.c: Trap section (.kcfi_traps) generation and
  proper trap instruction placement.

Optimization and Code Generation Tests:
- kcfi-adjacency.c: Regression test ensuring KCFI checks remain
  immediately adjacent to indirect calls with no intervening instructions.
- kcfi-call-sharing.c: Prevents optimizer from incorrectly sharing
  KCFI checks between different function pointer types.
- kcfi-tail-calls.c: KCFI protection preservation when indirect calls
  are converted to tail calls under optimization.
- kcfi-ipa-robustness.c: IPA pass robustness with compiler-generated
  constructs like static_assert.

Addressing and Environment Tests:
- kcfi-complex-addressing.c: Structure member and array element function
  pointers with complex addressing modes.
- kcfi-pic-addressing.c: Position-independent code with PIC addressing
  modes and symbol references.
- kcfi-cold-partition.c: Cold function attributes and block partitioning
  with -freorder-blocks-and-partition.

Architecture-Specific Verification:
- kcfi-aarch64-esr.c: AArch64 ESR encoding in BRK instructions with
  proper exception register values.
- kcfi-offset-validation.c: Call site offset validation across
  architectures.
- kcfi-insn-sequence.c: Exact instruction sequence verification for
  KCFI runtime checks.

Patchable Function Entry Integration:
- kcfi-patchable-none.c: Standard case without patchable entries.
- kcfi-patchable-basic.c: Basic patchable entries (5,2 configuration).
- kcfi-patchable-prefix-only.c: Equal prefix/entry split (3,3
  configuration).
- kcfi-patchable-entry-only.c: Entry-only NOPs (4,0 configuration).
- kcfi-patchable-medium.c: Medium prefix NOPs (8,4 configuration).
- kcfi-patchable-large.c: Large prefix NOPs (11,11 configuration).

Tests can be run via:
  make check-gcc RUNTESTFLAGS='kcfi.exp'

Signed-off-by: Kees Cook <k...@kernel.org>
---
 gcc/testsuite/gcc.dg/kcfi/kcfi-aarch64-esr.c  |  36 +
 gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c    |  83 ++
 gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c       |  83 ++
 gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c |  75 ++
 .../gcc.dg/kcfi/kcfi-cold-partition.c         | 133 +++
 .../gcc.dg/kcfi/kcfi-complex-addressing.c     | 116 +++
 .../gcc.dg/kcfi/kcfi-insn-sequence.c          |  42 +
 .../gcc.dg/kcfi/kcfi-ipa-robustness.c         |  54 ++
 .../gcc.dg/kcfi/kcfi-no-sanitize-inline.c     |  96 ++
 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c  |  39 +
 .../gcc.dg/kcfi/kcfi-offset-validation.c      |  41 +
 .../gcc.dg/kcfi/kcfi-patchable-basic.c        |  54 ++
 .../gcc.dg/kcfi/kcfi-patchable-entry-only.c   |  36 +
 .../gcc.dg/kcfi/kcfi-patchable-large.c        |  47 +
 .../gcc.dg/kcfi/kcfi-patchable-medium.c       |  52 ++
 .../gcc.dg/kcfi/kcfi-patchable-none.c         |  18 +
 .../gcc.dg/kcfi/kcfi-patchable-prefix-only.c  |  48 +
 .../gcc.dg/kcfi/kcfi-pic-addressing.c         |  98 ++
 gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c   | 111 +++
 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c |  56 ++
 .../gcc.dg/kcfi/kcfi-type-mangling.c          | 864 ++++++++++++++++++
 gcc/testsuite/gcc.dg/kcfi/kcfi.exp            |  36 +
 22 files changed, 2218 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-aarch64-esr.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-insn-sequence.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-none.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-type-mangling.c
 create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi.exp

diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-aarch64-esr.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-aarch64-esr.c
new file mode 100644
index 000000000000..6bbbba0431b0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-aarch64-esr.c
@@ -0,0 +1,36 @@
+/* Test AArch64 KCFI ESR encoding in BRK instructions */
+/* { dg-do compile { target aarch64*-*-* } } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void target_function(int x, char y) {
+    /* Different signature to get different type ID */
+}
+
+int main() {
+    void (*func_ptr)(int, char) = target_function;
+
+    /* This should generate BRK with ESR encoding */
+    func_ptr(42, 'a');
+
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_target_function:" } } */
+
+/* AArch64 specific: Should have BRK instruction with proper ESR encoding
+   ESR format: 0x8000 | ((type_reg & 31) << 5) | (addr_reg & 31)
+
+   Test the ESR encoding by checking for the expected value.
+   Since we know this test uses x2, we expect ESR = 0x8000 | (17<<5) | 2 = 
33314
+
+   A truly dynamic test would need to extract the register from blr and compute
+   the corresponding ESR, but DejaGnu's regex limitations make this complex.
+   This test validates the specific case and documents the encoding.
+   */
+/* { dg-final { scan-assembler "blr\\s+x2" { target aarch64*-*-* } } } */
+/* { dg-final { scan-assembler "brk\\s+#33314" { target aarch64*-*-* } } } */
+
+/* Should have KCFI check with type comparison */
+/* { dg-final { scan-assembler {ldur\t*w16, \[x[0-9]+, #-4\]} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler {cmp\t*w16, w17} { target aarch64*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
new file mode 100644
index 000000000000..35678abe0e12
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
@@ -0,0 +1,83 @@
+/* Test KCFI check/transfer adjacency - regression test for instruction 
insertion */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+/* This test ensures that KCFI security checks remain immediately adjacent
+   to their corresponding indirect calls/jumps, with no executable instructions
+   between the type ID check and the control flow transfer. */
+
+/* External function pointers to prevent optimization */
+extern void (*complex_func_ptr)(int, int, int, int);
+extern int (*return_func_ptr)(int, int);
+
+/* Function with complex argument preparation that could tempt
+   the optimizer to insert instructions between KCFI check and call */
+__attribute__((noinline)) void test_complex_args(int a, int b, int c, int d) {
+    /* Complex argument expressions that might cause instruction scheduling */
+    complex_func_ptr(a * 2, b + c, d - a, (a << 1) | b);
+}
+
+/* Function with return value handling */
+__attribute__((noinline)) int test_return_value(int x, int y) {
+    /* Return value handling that shouldn't interfere with adjacency */
+    int result = return_func_ptr(x + 1, y * 2);
+    return result + 1;
+}
+
+/* Test struct field access that caused issues in try-catch.c */
+struct call_info {
+    void (*handler)(void);
+    int status;
+    int data;
+};
+
+extern struct call_info *global_call_info;
+
+__attribute__((noinline)) void test_struct_field_call(void) {
+    /* This pattern caused adjacency issues before the fix */
+    global_call_info->handler();
+}
+
+/* Test conditional indirect call */
+__attribute__((noinline)) void test_conditional_call(int flag) {
+    if (flag) {
+        global_call_info->handler();
+    }
+}
+
+/* Should have KCFI instrumentation for all indirect calls */
+
+/* x86_64: Complete KCFI check sequence should be present */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r1[01]d\n\taddl\t[^,]+, 
%r1[01]d\n\tje\t\.L[0-9]+\n\t\.L[0-9]+:\n\tud2} { target x86_64-*-* } } } */
+
+/* AArch64: Complete KCFI check sequence should be present */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-[0-9]+\]\n\tmov\tw17, 
#[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, 
w17\n\tb\.eq\t\.L[0-9]+\n\t\.L[0-9]+:\n\tbrk\t#[0-9]+} { target aarch64*-*-* } 
} } */
+
+/* RISC-V: Complete KCFI check sequence should be present */
+/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, 
\.L[a-zA-Z0-9_]+\n\.L[a-zA-Z0-9_]+:\n\tebreak} { target riscv*-*-* } } } */
+
+/* Trap instructions are now validated as part of the complete compound 
patterns above */
+
+/* Should have trap section with entries */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
+
+/* AArch64 should NOT have trap section (uses brk immediate instead) */
+/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } 
*/
+
+/* Critical adjacency requirement: After each KCFI type comparison,
+   there should be only a conditional jump and label before the indirect 
transfer.
+   No executable instructions should be inserted between the security check
+   and the control flow transfer.
+
+   Pattern on x86_64 should be:
+   movl $(-TYPEID), %r10d # Load inverse type ID
+   addl OFFSET(%reg), %r10d # Add actual type ID
+   je   .LabelN           # Jump if zero (match)
+   .LN:                  # Trap label (not executable)
+   ud2                   # Trap instruction
+   .LabelN:              # Call label (not executable)
+   call/jmp *TARGET      # Indirect transfer
+
+   This test serves as a regression test to ensure this adjacency
+   property is maintained across compiler changes. */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
new file mode 100644
index 000000000000..db1fcfd8f5e2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
@@ -0,0 +1,83 @@
+/* Test basic KCFI functionality - preamble generation */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+/* Extern function declarations - should NOT get KCFI preambles */
+extern void external_func(void);
+extern int external_func_int(int x);
+
+void regular_function(int x) {
+    /* This should get KCFI preamble */
+}
+
+void static_target_function(int x) {
+    /* Target function that can be called indirectly */
+}
+
+static void static_caller(void) {
+    /* Static function that makes an indirect call */
+    /* Should NOT get KCFI preamble (not address-taken) */
+    /* But must generate KCFI check for the indirect call */
+    void (*local_ptr)(int) = static_target_function;
+    local_ptr(42);  /* This should generate KCFI check */
+}
+
+void (*func_ptr)(int) = regular_function;
+void (*ext_ptr)(void) = external_func;  /* Make external_func address-taken */
+
+int main() {
+    func_ptr(42);
+    ext_ptr();        /* Indirect call to external_func */
+    external_func_int(10);  /* Direct call to external_func_int */
+    static_caller();  /* Direct call to static function */
+    return 0;
+}
+
+/* Verify KCFI preamble exists for regular_function */
+/* { dg-final { scan-assembler {__cfi_regular_function:} } } */
+
+/* Target function should have preamble (address-taken) */
+/* { dg-final { scan-assembler {__cfi_static_target_function:} } } */
+
+/* Static caller should NOT have preamble (it's only called directly, not 
address-taken) */
+/* { dg-final { scan-assembler-not {__cfi_static_caller:} } } */
+
+/* x86_64: Verify type ID in preamble (after NOPs, before function label) */
+/* { dg-final { scan-assembler 
{__cfi_regular_function:\n\t+nop\n.*\n\t+movl\t+\$0x[0-9a-f]+, %eax} { target 
x86_64-*-* } } } */
+
+/* x86_64: Static function should generate complete KCFI check sequence */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d\n\tje\t\.L[0-9]+\n\t\.L[0-9]+:\n\tud2} 
{ target x86_64-*-* } } } */
+
+/* AArch64: Verify type ID word in preamble */
+/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word 0x[0-9a-f]+} 
{ target aarch64*-*-* } } } */
+
+/* AArch64: Static function should generate complete KCFI check sequence */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]\n\tmov\tw17, 
#[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, 
w17\n\tb\.eq\t\.L[0-9]+\n\t\.L[0-9]+:\n\tbrk\t#[0-9]+} { target aarch64*-*-* } 
} } */
+
+/* RISC-V: Verify type ID word in preamble */
+/* { dg-final { scan-assembler 
{__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */
+
+/* RISC-V: Static function should generate KCFI check for indirect call */
+/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, 
\.L[a-zA-Z0-9_]+\n\.L[a-zA-Z0-9_]+:\n\tebreak} { target riscv*-*-* } } } */
+
+/* Extern functions should NOT get KCFI preambles */
+/* { dg-final { scan-assembler-not {__cfi_external_func:} } } */
+/* { dg-final { scan-assembler-not {__cfi_external_func_int:} } } */
+
+/* Local functions should NOT get __kcfi_typeid_ symbols */
+/* Only external declarations that are address-taken should get __kcfi_typeid_ 
*/
+/* { dg-final { scan-assembler-not {__kcfi_typeid_regular_function} } } */
+/* { dg-final { scan-assembler-not {__kcfi_typeid_main} } } */
+
+/* External address-taken functions should get __kcfi_typeid_ symbols */
+/* { dg-final { scan-assembler {__kcfi_typeid_external_func} } } */
+
+/* External functions that are only called directly should NOT get 
__kcfi_typeid_ symbols */
+/* { dg-final { scan-assembler-not {__kcfi_typeid_external_func_int} } } */
+
+/* Should have trap section for KCFI checks */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
+
+/* AArch64 should NOT have trap section (uses brk immediate instead) */
+/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } 
*/
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
new file mode 100644
index 000000000000..e8c480e81de2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
@@ -0,0 +1,75 @@
+/* Test KCFI check sharing bug - optimizer incorrectly shares KCFI checks 
between different function types */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+/* Reproduce the pattern from Linux kernel internal_create_group where:
+   - Two different function pointer types (is_visible vs is_bin_visible)
+   - Both get loaded into the same register (%rcx)
+   - Optimizer creates shared KCFI check with wrong type ID
+   - This causes CFI failures in production kernel */
+
+struct kobject { int dummy; };
+struct attribute { int dummy; };
+struct bin_attribute { int dummy; };
+
+struct attribute_group {
+    const char *name;
+    int (*is_visible)(struct kobject *, struct attribute *, int);       // 
Type ID A
+    int (*is_bin_visible)(struct kobject *, const struct bin_attribute *, 
int);  // Type ID B
+    struct attribute **attrs;
+    const struct bin_attribute **bin_attrs;
+};
+
+/* Function that mimics __first_visible from kernel - gets inlined into caller 
*/
+static int __first_visible(const struct attribute_group *grp, struct kobject 
*kobj)
+{
+    /* Path 1: Call is_visible function pointer */
+    if (grp->attrs && grp->attrs[0] && grp->is_visible)
+        return grp->is_visible(kobj, grp->attrs[0], 0);
+
+    /* Path 2: Call is_bin_visible function pointer */
+    if (grp->bin_attrs && grp->bin_attrs[0] && grp->is_bin_visible)
+        return grp->is_bin_visible(kobj, grp->bin_attrs[0], 0);
+
+    return 0;
+}
+
+/* Main function that triggers the optimization bug */
+int test_kcfi_check_sharing(struct kobject *kobj, const struct attribute_group 
*grp)
+{
+    /* This should inline __first_visible and create the problematic pattern 
where:
+       1. Both function pointers get loaded into same register
+       2. Optimizer shares KCFI check between them
+       3. Uses wrong type ID for one of the calls */
+    return __first_visible(grp, kobj);
+}
+
+/* Each indirect call should have its own KCFI check with correct type ID
+
+   Should see:
+   1. KCFI check for is_visible call with is_visible type ID
+   2. KCFI check for is_bin_visible call with is_bin_visible type ID */
+
+/* Verify we have TWO different KCFI check sequences */
+/* Each check should have different type ID constants */
+/* x86: { dg-final { scan-assembler-times {movl\s+\$-?[0-9]+,\s+%r10d} 2 { 
target i?86-*-* x86_64-*-* } } } */
+/* AArch64: { dg-final { scan-assembler-times {mov\s+w17, #[0-9]+} 2 { target 
aarch64*-*-* } } } */
+/* RISC-V: { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 2 { target 
riscv*-*-* } } } */
+
+/* Verify the checks use DIFFERENT type IDs (not shared) */
+/* We should NOT see the same type ID used twice - that would indicate sharing 
bug */
+/* x86: { dg-final { scan-assembler-not 
{movl\s+\$(-?[0-9]+),\s+%r10d.*movl\s+\$\1,\s+%r10d} { target i?86-*-* 
x86_64-*-* } } } */
+/* AArch64: { dg-final { scan-assembler-not {mov\s+w17, #([0-9]+).*mov\s+w17, 
#\1} { target aarch64*-*-* } } } */
+/* RISC-V: { dg-final { scan-assembler-not {lui\s+t2, ([0-9]+)\s.*lui\s+t2, 
\1\s} { target riscv*-*-* } } } */
+
+/* Verify each call follows its own check (not shared) */
+/* Should have 2 separate trap instructions */
+/* x86: { dg-final { scan-assembler-times {ud2} 2 { target i?86-*-* x86_64-*-* 
} } } */
+/* AArch64: { dg-final { scan-assembler-times {brk\s+#[0-9]+} 2 { target 
aarch64*-*-* } } } */
+/* RISC-V: { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } 
} } */
+
+/* Verify 2 separate call sites */
+/* x86: { dg-final { scan-assembler-times {jmp\s+\*%[a-z0-9]+} 2 { target 
i?86-*-* x86_64-*-* } } } */
+/* AArch64: Allow both blr (regular call) and br (tail call) */
+/* AArch64: { dg-final { scan-assembler-times {br      x[0-9]+} 2 { target 
aarch64*-*-* } } } */
+/* RISC-V: { dg-final { scan-assembler-times {jalr\t[a-z0-9]+} 2 { target 
riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
new file mode 100644
index 000000000000..f4fa9f6646a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
@@ -0,0 +1,133 @@
+/* Test KCFI cold function and cold partition behavior */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2 -freorder-blocks-and-partition" } */
+
+void regular_function(void) {
+    /* Regular function should get preamble */
+}
+
+/* Cold-attributed function should STILL get preamble (it's a regular 
function, just marked cold) */
+__attribute__((cold))
+void cold_attributed_function(void) {
+    /* This function has cold attribute but should still get KCFI preamble */
+}
+
+/* Hot-attributed function should get preamble */
+__attribute__((hot))
+void hot_attributed_function(void) {
+    /* This function is explicitly hot and should get KCFI preamble */
+}
+
+/* Global to prevent optimization from eliminating cold paths */
+extern void abort(void);
+
+/* Additional function to test that normal functions still get preambles */
+__attribute__((noinline))
+int another_regular_function(int x) {
+    return x + 42;
+}
+
+/* Function designed to generate cold partitions under optimization */
+__attribute__((noinline))
+void function_with_cold_partition(int condition) {
+    /* Hot path - very likely to execute */
+    if (__builtin_expect(condition == 42, 1)) {
+        /* Simple hot path that optimizer will keep inline */
+        return;
+    }
+
+    /* Cold paths that actually do something to prevent elimination */
+    if (__builtin_expect(condition < 0, 0)) {
+        /* Error path 1 - call abort to prevent elimination */
+        abort();
+    }
+
+    if (__builtin_expect(condition > 1000000, 0)) {
+        /* Error path 2 - call abort to prevent elimination */
+        abort();
+    }
+
+    if (__builtin_expect(condition == 999999, 0)) {
+        /* Error path 3 - more substantial cold code */
+        volatile int sum = 0;
+        for (volatile int i = 0; i < 100; i++) {
+            sum += i * condition;
+        }
+        if (sum > 0)
+            abort();
+    }
+
+    /* More cold paths - switch with many unlikely cases */
+    switch (condition) {
+        case 1000001: case 1000002: case 1000003: case 1000004: case 1000005:
+        case 1000006: case 1000007: case 1000008: case 1000009: case 1000010:
+            /* Each case does some work before abort */
+            volatile int work = condition * 2;
+            if (work > 0) abort();
+            break;
+        default:
+            if (condition != 42) {
+                /* Fallback cold path - substantial work */
+                volatile int result = 0;
+                for (volatile int j = 0; j < condition % 50; j++) {
+                    result += j;
+                }
+                if (result >= 0) abort();
+            }
+    }
+}
+
+/* Test function pointers to ensure address-taken detection works */
+void test_function_pointers(void) {
+    void (*regular_ptr)(void) = regular_function;
+    void (*cold_ptr)(void) = cold_attributed_function;
+    void (*hot_ptr)(void) = hot_attributed_function;
+
+    regular_ptr();
+    cold_ptr();
+    hot_ptr();
+}
+
+int main() {
+    regular_function();
+    cold_attributed_function();
+    hot_attributed_function();
+    function_with_cold_partition(42); /* Normal case - stay in hot path */
+    another_regular_function(5);
+    test_function_pointers();
+    return 0;
+}
+
+/* Regular function should have preamble */
+/* { dg-final { scan-assembler "__cfi_regular_function:" } } */
+
+/* Cold-attributed function should STILL have preamble (it's a legitimate 
function) */
+/* { dg-final { scan-assembler "__cfi_cold_attributed_function:" } } */
+
+/* Hot-attributed function should have preamble */
+/* { dg-final { scan-assembler "__cfi_hot_attributed_function:" } } */
+
+/* Function that generates cold partitions should have preamble for main entry 
*/
+/* { dg-final { scan-assembler "__cfi_function_with_cold_partition:" } } */
+
+/* Address-taken functions should have preambles */
+/* { dg-final { scan-assembler "__cfi_test_function_pointers:" } } */
+
+/* The function should generate a .cold partition */
+/* { dg-final { scan-assembler "function_with_cold_partition\\.cold:" } } */
+
+/* The .cold partition should NOT get a __cfi_ preamble since it's never 
reached via indirect calls */
+/* { dg-final { scan-assembler-not 
"__cfi_function_with_cold_partition\\.cold:" } } */
+
+/* Additional regular function should get preamble */
+/* { dg-final { scan-assembler "__cfi_another_regular_function:" } } */
+
+/* Test coverage summary:
+   1. Cold-attributed function (__attribute__((cold))): SHOULD get preamble
+   2. Cold partition (-freorder-blocks-and-partition): should NOT get preamble
+   3. IPA split .part function (split_part=true): Logic in place, would skip 
if triggered
+
+   Note: IPA function splitting (creating .part functions with 
split_part=true) requires
+   specific optimization conditions that are difficult to trigger reliably in 
tests.
+   The KCFI logic correctly handles this case using the split_part flag check.
+*/
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
new file mode 100644
index 000000000000..ac7e2db2d654
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
@@ -0,0 +1,116 @@
+/* Test KCFI with complex addressing modes (structure members, array elements) 
*/
+/* This is a regression test for the change_address_1 RTL error that occurred 
*/
+/* when target_addr was PLUS(reg, offset) instead of a simple register */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+struct function_table {
+    int (*callback1)(int);
+    int (*callback2)(int, int);
+    void (*callback3)(void);
+    int data;
+};
+
+static int handler1(int x) {
+    return x * 2;
+}
+
+static int handler2(int x, int y) {
+    return x + y;
+}
+
+static void handler3(void) {
+    /* Empty handler */
+}
+
+/* Test indirect calls through structure members - this creates PLUS(reg, 
offset) addressing */
+int test_struct_members(struct function_table *table) {
+    int result = 0;
+
+    /* These indirect calls will generate complex addressing modes:
+     * call *(%rdi)          - callback1 at offset 0
+     * call *8(%rdi)         - callback2 at offset 8
+     * call *16(%rdi)        - callback3 at offset 16
+     * Our KCFI instrumentation must handle PLUS(reg, struct_offset) + 
kcfi_offset */
+
+    result += table->callback1(10);        /* MEM[PLUS(reg, 0)] -> PLUS(reg, 
-4) for KCFI */
+    result += table->callback2(5, 7);      /* MEM[PLUS(reg, 8)] -> PLUS(reg, 
4) for KCFI */
+    table->callback3();                    /* MEM[PLUS(reg, 16)] -> PLUS(reg, 
12) for KCFI */
+
+    return result;
+}
+
+/* Test indirect calls through array elements - another source of complex 
addressing */
+typedef int (*func_array_t)(int);
+
+int test_array_elements(func_array_t functions[], int index) {
+    /* This creates addressing like MEM[PLUS(PLUS(reg, index*8), 0)]
+     * which should be simplified to MEM[PLUS(reg, index*8)] */
+    return functions[index](42);  /* Complex array indexing */
+}
+
+/* Test with global structure */
+static struct function_table global_table = {
+    .callback1 = handler1,
+    .callback2 = handler2,
+    .callback3 = handler3,
+    .data = 100
+};
+
+int test_global_struct(void) {
+    /* Access through global structure - may generate different addressing 
patterns */
+    return global_table.callback1(20) + global_table.callback2(3, 4);
+}
+
+/* Test nested structure access */
+struct nested_table {
+    struct function_table inner;
+    int extra_data;
+};
+
+int test_nested_struct(struct nested_table *nested) {
+    /* Even more complex addressing: nested structure member access */
+    return nested->inner.callback1(15);  /* MEM[PLUS(reg, inner_offset + 
callback1_offset)] */
+}
+
+int main() {
+    struct function_table local_table = {
+        .callback1 = handler1,
+        .callback2 = handler2,
+        .callback3 = handler3,
+        .data = 50
+    };
+
+    func_array_t func_array[] = { handler1, handler1, handler1 };
+
+    int result = 0;
+    result += test_struct_members(&local_table);
+    result += test_array_elements(func_array, 1);
+    result += test_global_struct();
+
+    struct nested_table nested = { .inner = local_table, .extra_data = 200 };
+    result += test_nested_struct(&nested);
+
+    return result;
+}
+
+/* Verify that all address-taken functions get KCFI preambles */
+/* { dg-final { scan-assembler {__cfi_handler1:} } } */
+/* { dg-final { scan-assembler {__cfi_handler2:} } } */
+/* { dg-final { scan-assembler {__cfi_handler3:} } } */
+
+/* x86_64: Verify KCFI checks are generated for indirect calls through complex 
addressing */
+/* The key test: these should compile without change_address_1 errors 
(kernel-style encoding) */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */
+
+/* AArch64: Verify KCFI checks for complex addressing */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */
+
+/* RISC-V: Verify KCFI check sequence for complex addressing */
+/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, 
\.L[a-zA-Z0-9_]+\n\.L[a-zA-Z0-9_]+:\n\tebreak} { target riscv*-*-* } } } */
+
+/* Should have trap section (x86 and RISC-V - AArch64 uses brk immediate) */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-insn-sequence.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-insn-sequence.c
new file mode 100644
index 000000000000..eac730c54956
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-insn-sequence.c
@@ -0,0 +1,42 @@
+/* Test for exact expected KCFI instrumentation instruction sequence */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void untaken_function(int x) {
+    /* Target function should get preamble */
+}
+
+void target_function(int x) {
+    /* Target function should get preamble */
+}
+
+int main() {
+    void (*func_ptr)(int) = target_function;
+
+    /* This indirect call should get KCFI check */
+    func_ptr(42);
+
+    untaken_function(15);
+
+    return 0;
+}
+
+/* Should have KCFI preamble for target */
+/* { dg-final { scan-assembler "__cfi_target_function:" } } */
+
+/* Should not have KCFI preamble for local non-address-take function */
+/* { dg-final { scan-assembler-not "__cfi_untaken-function:" } } */
+
+/* x86_64: Complete KCFI check sequence (kernel-style encoding) - updated for 
unified trap emission */
+/* Note: Single regex to enforce exact sequence - based on actual hexdump 
analysis */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%rax\), 
%r10d\n\tje\t\.L[0-9]+\n\t\.L[0-9]+:\n\tud2} { target x86_64-*-* } } } */
+
+/* AArch64: Complete KCFI check sequence */
+/* Load type ID, load expected type, compare, conditional branch, trap, branch 
target, call */
+/* Note: Single regex to enforce exact sequence - based on actual hexdump 
analysis */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]\n\tmov\tw17, 
#[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, 
w17\n\tb\.eq\t\.L[0-9]+\n\t\.L([^:]+):\n\tbrk\t#[0-9]+\n\t\.L[0-9]+:\n\t+blr\tx[0-9]+}
 { target aarch64*-*-* } } } */
+
+/* RISC-V: Complete KCFI check sequence */
+/* Load type ID, load expected type, compare, conditional branch, trap, branch 
target, call */
+/* Note: Single regex to enforce exact sequence - based on actual hexdump 
analysis */
+/* { dg-final { scan-assembler {lw\tt1, -[0-9]+\([^)]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, [0-9-]+\n\tbeq\tt1, t2, 
\.Lkcfi_pass_[0-9]+\n\.Lkcfi_trap_[0-9]+:\n\tebreak\n\t\.pushsection\t\.kcfi_traps[^\n]*\n\.Lkcfi_trap_entry_[0-9]+:\n\t\.word\t\.Lkcfi_trap_[0-9]+
 - 
\.Lkcfi_trap_entry_[0-9]+\n\t\.popsection\n\.Lkcfi_pass_[0-9]+:\n\tjalr\t[^,\s]+}
 { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
new file mode 100644
index 000000000000..2a5f1d311b6b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
@@ -0,0 +1,54 @@
+/* Test KCFI IPA pass robustness with compiler-generated constructs */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+#include <stddef.h>
+
+/* Test various compiler-generated constructs that could confuse IPA pass */
+
+/* 1. static_assert - this was causing the original crash */
+typedef struct {
+    int field1;
+    char field2;
+} test_struct_t;
+
+static_assert(offsetof(test_struct_t, field1) == 0, "layout check 1");
+static_assert(offsetof(test_struct_t, field2) == 4, "layout check 2");
+static_assert(sizeof(test_struct_t) >= 5, "size check");
+
+/* 2. Regular functions that should get KCFI analysis */
+void regular_function(void) {
+    /* Should get KCFI preamble */
+}
+
+static void static_function(void) {
+    /* With -O2: correctly identified as not address-taken, no preamble */
+}
+
+void address_taken_function(void) {
+    /* Should get KCFI preamble (address taken below) */
+}
+
+/* 3. Function pointer to create address-taken scenario */
+void (*func_ptr)(void) = address_taken_function;
+
+/* 4. More static_asserts mixed with function definitions */
+static_assert(sizeof(void*) == 8, "pointer size check");
+
+int main(void) {
+    regular_function();    /* Direct call */
+    static_function();     /* Direct call to static */
+    func_ptr();           /* Indirect call */
+
+    static_assert(sizeof(int) == 4, "int size check");
+
+    return 0;
+}
+
+/* Verify KCFI preambles are generated appropriately */
+/* { dg-final { scan-assembler "__cfi_regular_function:" } } */
+/* { dg-final { scan-assembler "__cfi_address_taken_function:" } } */
+/* { dg-final { scan-assembler "__cfi_main:" } } */
+
+/* With -O2: static_function correctly identified as not address-taken */
+/* { dg-final { scan-assembler-not "__cfi_static_function:" } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
new file mode 100644
index 000000000000..2f0efc16486f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
@@ -0,0 +1,96 @@
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+/* Test that no_sanitize("kcfi") attribute is preserved during inlining */
+
+extern void external_side_effect(int value);
+
+/* Regular function (should get KCFI checks) */
+__attribute__((noinline))
+void normal_function(void (*callback)(int))
+{
+    /* This indirect call must generate KCFI checks */
+    callback(300);
+    external_side_effect(300);
+}
+
+/* Regular function marked with no_sanitize("kcfi") (positive control) */
+__attribute__((noinline, no_sanitize("kcfi")))
+void sensitive_non_inline_function(void (*callback)(int))
+{
+    /* This indirect call should NOT generate KCFI checks */
+    callback(100);
+    external_side_effect(100);
+}
+
+/* Function marked with both no_sanitize("kcfi") and always_inline */
+__attribute__((always_inline, no_sanitize("kcfi")))
+static inline void sensitive_inline_function(void (*callback)(int))
+{
+    /* This indirect call should NOT generate KCFI checks when inlined */
+    callback(42);
+    external_side_effect(42);
+}
+
+/* Explicit wrapper for testing sensitive_inline_function behavior */
+__attribute__((noinline))
+void wrap_sensitive_inline(void (*callback)(int))
+{
+    sensitive_inline_function(callback);
+}
+
+/* Function marked with only always_inline (should get KCFI checks) */
+__attribute__((always_inline))
+static inline void normal_inline_function(void (*callback)(int))
+{
+    /* This indirect call must generate KCFI checks when inlined */
+    callback(200);
+    external_side_effect(200);
+}
+
+/* Explicit wrapper for testing normal_inline_function behavior */
+__attribute__((noinline))
+void wrap_normal_inline(void (*callback)(int))
+{
+    normal_inline_function(callback);
+}
+
+void test_callback(int value)
+{
+    external_side_effect(value);
+}
+
+static void (*volatile function_pointer)(int) = test_callback;
+
+int main(void)
+{
+    void (*fn_ptr)(int) = function_pointer;
+
+    normal_function(fn_ptr);
+    wrap_normal_inline(fn_ptr);
+    sensitive_non_inline_function(fn_ptr);
+    wrap_sensitive_inline(fn_ptr);
+
+    return 0;
+}
+
+/* Verify correct number of KCFI checks: exactly 2 */
+/* { dg-final { scan-assembler-times {ud2} 2 { target i?86-*-* x86_64-*-* } } 
} */
+/* { dg-final { scan-assembler-times {brk\s+#33313} 2 { target aarch64*-*-* } 
} } */
+/* { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } } } */
+
+/* Positive controls: these should have KCFI checks */
+/* { dg-final { scan-assembler 
{normal_function:.*ud2.*\.size\s+normal_function} { target i?86-*-* x86_64-*-* 
} } } */
+/* { dg-final { scan-assembler 
{wrap_normal_inline:.*ud2.*\.size\s+wrap_normal_inline} { target i?86-*-* 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler 
{normal_function:.*brk\s+#33313.*\.size\s+normal_function} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler 
{wrap_normal_inline:.*brk\s+#33313.*\.size\s+wrap_normal_inline} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler 
{normal_function:.*ebreak.*\.size\s+normal_function} { target riscv*-*-* } } } 
*/
+/* { dg-final { scan-assembler 
{wrap_normal_inline:.*ebreak.*\.size\s+wrap_normal_inline} { target riscv*-*-* 
} } } */
+
+/* Negative controls: these should NOT have KCFI checks */
+/* { dg-final { scan-assembler-not 
{sensitive_non_inline_function:.*ud2.*\.size\s+sensitive_non_inline_function} { 
target i?86-*-* x86_64-*-* } } } */
+/* { dg-final { scan-assembler-not 
{wrap_sensitive_inline:.*ud2.*\.size\s+wrap_sensitive_inline} { target i?86-*-* 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler-not 
{sensitive_non_inline_function:.*brk\s+#33313.*\.size\s+sensitive_non_inline_function}
 { target aarch64*-*-* } } } */
+/* { dg-final { scan-assembler-not 
{wrap_sensitive_inline:.*brk\s+#33313.*\.size\s+wrap_sensitive_inline} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler-not 
{sensitive_non_inline_function:.*ebreak.*\.size\s+sensitive_non_inline_function}
 { target riscv*-*-* } } } */
+/* { dg-final { scan-assembler-not 
{wrap_sensitive_inline:.*ebreak.*\.size\s+wrap_sensitive_inline} { target 
riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
new file mode 100644
index 000000000000..1a48fd1407f3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
@@ -0,0 +1,39 @@
+/* Test KCFI with no_sanitize attribute */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void target_function(void) {
+    /* This should get KCFI preamble */
+}
+
+void caller_with_checks(void) {
+    /* This function should generate KCFI checks */
+    void (*func_ptr)(void) = target_function;
+    func_ptr();
+}
+
+__attribute__((no_sanitize("kcfi")))
+void caller_no_checks(void) {
+    /* This function should NOT generate KCFI checks due to no_sanitize */
+    void (*func_ptr)(void) = target_function;
+    func_ptr();
+}
+
+int main() {
+    caller_with_checks();    /* This should generate checks inside */
+    caller_no_checks();      /* This should NOT generate checks inside */
+    return 0;
+}
+
+/* All functions should get preambles regardless of no_sanitize */
+/* { dg-final { scan-assembler "__cfi_target_function:" } } */
+/* { dg-final { scan-assembler "__cfi_caller_with_checks:" } } */
+/* { dg-final { scan-assembler "__cfi_caller_no_checks:" } } */
+/* { dg-final { scan-assembler "__cfi_main:" } } */
+
+/* caller_with_checks() should generate KCFI check (1 check) */
+/* caller_no_checks() should NOT generate KCFI check due to no_sanitize 
attribute */
+/* Total: exactly 1 KCFI check in the entire program */
+/* { dg-final { scan-assembler-times {addl\t-4\(%r[ad]x\), %r1[01]d} 1 { 
target x86_64-*-* } } } */
+/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 1 { target 
aarch64-*-* } } } */
+/* { dg-final { scan-assembler-times {lw\tt1, -[0-9]+\(} 1 { target riscv*-*-* 
} } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
new file mode 100644
index 000000000000..735892c65997
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
@@ -0,0 +1,41 @@
+/* Test KCFI call-site offset validation across architectures */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void target_func_a(void) { }
+void target_func_b(int x) { }
+void target_func_c(int x, int y) { }
+
+int main() {
+    void (*ptr_a)(void) = target_func_a;
+    void (*ptr_b)(int) = target_func_b;
+    void (*ptr_c)(int, int) = target_func_c;
+
+    /* Multiple indirect calls - each should use -4 offset in standard case */
+    ptr_a();
+    ptr_b(1);
+    ptr_c(1, 2);
+
+    return 0;
+}
+
+/* Should have KCFI preambles for all functions */
+/* { dg-final { scan-assembler "__cfi_target_func_a:" } } */
+/* { dg-final { scan-assembler "__cfi_target_func_b:" } } */
+/* { dg-final { scan-assembler "__cfi_target_func_c:" } } */
+
+/* x86_64: All call sites should use -4 offset for KCFI type ID loads 
(kernel-style encoding) */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
+
+/* AArch64: All call sites should use -4 offset */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target 
aarch64*-*-* } } } */
+
+/* RISC-V: All call sites should use -4 offset */
+/* { dg-final { scan-assembler {lw\tt1, -4\(} { target riscv*-*-* } } } */
+
+/* Should have trap section with multiple entries */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
+
+/* AArch64 should NOT have trap section (uses brk immediate instead) */
+/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } 
*/
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
new file mode 100644
index 000000000000..f21de4021124
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
@@ -0,0 +1,54 @@
+/* Test KCFI with patchable function entries - basic case */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fpatchable-function-entry=5,2" } */
+
+void test_function(int x) {
+    /* Function should get both KCFI preamble and patchable entries */
+}
+
+int main() {
+    test_function(42);
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_test_function:" } } */
+
+/* Should have patchable function entry section */
+/* { dg-final { scan-assembler "__patchable_function_entries" } } */
+
+/* x86_64: Should have exactly 2 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { 
target x86_64-*-* } } } */
+
+/* x86_64: Should have exactly 3 entry NOPs between .cfi_startproc and pushq */
+/* { dg-final { scan-assembler 
{\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */
+
+/* x86_64: KCFI should have exactly 9 NOPs between __cfi_ and movl */
+/* { dg-final { scan-assembler 
{__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl}
 { target x86_64-*-* } } } */
+
+/* x86_64: Validate KCFI type ID is present */
+/* { dg-final { scan-assembler {movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* 
} } } */
+
+/* AArch64: Should have exactly 2 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { 
target aarch64*-*-* } } } */
+
+/* AArch64: Should have exactly 3 entry NOPs between .cfi_startproc and sub sp 
*/
+/* { dg-final { scan-assembler 
{\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*sub\t*sp} { target aarch64*-*-* } 
} } */
+
+/* AArch64: KCFI should have only .word immediate (no NOPs) */
+/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word 0x[0-9a-f]+} { 
target aarch64*-*-* } } } */
+
+/* AArch64: Validate clean KCFI boundary - .word then immediate end/size */
+/* { dg-final { scan-assembler {\.word 
0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, 
\.-__cfi_test_function} { target aarch64*-*-* } } } */
+
+/* RISC-V: Should have exactly 2 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { 
target riscv*-*-* } } } */
+
+/* RISC-V: Should have exactly 3 entry NOPs before .cfi_startproc followed by 
addi sp */
+/* { dg-final { scan-assembler 
{nop\n\t*nop\n\t*nop\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*addi\t*sp} { target 
riscv*-*-* } } } */
+
+/* RISC-V: KCFI should have only .word immediate (no NOPs) */
+/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { 
target riscv*-*-* } } } */
+
+/* RISC-V: Validate clean KCFI boundary - .word then immediate end/size */
+/* { dg-final { scan-assembler 
{\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function,
 \.-__cfi_test_function} { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
new file mode 100644
index 000000000000..68a42dd32995
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
@@ -0,0 +1,36 @@
+/* Test KCFI with patchable function entries - entry NOPs only */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fpatchable-function-entry=4,0" } */
+
+void test_function(void) {
+    /* All NOPs are entry NOPs, no prefix NOPs for KCFI */
+}
+
+int main() {
+    test_function();
+    return 0;
+}
+
+/* Should NOT have KCFI preamble (no prefix NOPs) */
+/* { dg-final { scan-assembler-not "__cfi_test_function:" } } */
+
+/* x86_64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } 
} } */
+
+/* x86_64: No prefix NOPs - function type should come immediately before 
function */
+/* { dg-final { scan-assembler {\.type\t*test_function, 
@function\n*test_function:} { target x86_64-*-* } } } */
+
+/* AArch64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*stp} { target aarch64*-*-* } 
} } */
+
+/* AArch64: No prefix NOPs - function type should come immediately before 
function */
+/* { dg-final { scan-assembler {\.type\t*test_function, 
%function\n*test_function:} { target aarch64*-*-* } } } */
+
+/* RISC-V: All 4 NOPs are entry NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } 
} */
+
+/* RISC-V: No prefix NOPs - function type should come immediately before 
function */
+/* { dg-final { scan-assembler {\.type\t*test_function, 
@function\n*test_function:} { target riscv*-*-* } } } */
+
+/* Should have patchable function entry section */
+/* { dg-final { scan-assembler "__patchable_function_entries" } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
new file mode 100644
index 000000000000..015b021b9a0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
@@ -0,0 +1,47 @@
+/* Test KCFI with large patchable function entries */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fpatchable-function-entry=11,11" } */
+
+void test_function(void) {
+    /* 11 prefix NOPs, 0 entry NOPs - maximum prefix case */
+}
+
+int main() {
+    void (*func_ptr)(void) = test_function;
+    func_ptr(); /* Call site should use -(11+4) = -15 offset */
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_test_function:" } } */
+
+/* Should have patchable function entry section */
+/* { dg-final { scan-assembler "__patchable_function_entries" } } */
+
+/* x86_64: Should have exactly 11 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type}
 { target x86_64-*-* } } } */
+
+/* x86_64: Should have 0 entry NOPs - function starts immediately with pushq 
(no __kcfi_typeid for definitions) */
+/* { dg-final { scan-assembler 
{test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} 
{ target x86_64-*-* } } } */
+
+/* x86_64: KCFI should have 0 NOPs - goes directly to movl 
(offsetToAlignment(11+5,16)=0) */
+/* { dg-final { scan-assembler {__cfi_test_function:\n\t*movl} { target 
x86_64-*-* } } } */
+
+/* x86_64: Validate KCFI type ID is present */
+/* { dg-final { scan-assembler {movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* 
} } } */
+
+/* x86_64: Call site should use -15 offset (kernel-style encoding) */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-15\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
+
+/* AArch64: Should have exactly 11 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type}
 { target aarch64*-*-* } } } */
+
+/* AArch64: Call site should use -15 offset */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-15\]} { target 
aarch64*-*-* } } } */
+
+/* RISC-V: Should have 11 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type}
 { target riscv*-*-* } } } */
+
+/* RISC-V: Call site should use -15 offset (same as x86/AArch64) */
+/* { dg-final { scan-assembler {lw\tt1, -15\(} { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
new file mode 100644
index 000000000000..e6922cf81355
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
@@ -0,0 +1,52 @@
+/* Test KCFI with medium patchable function entries */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fpatchable-function-entry=8,4" } */
+
+void test_function(void) {
+    /* 4 prefix NOPs, 4 entry NOPs - should affect KCFI offset */
+}
+
+int main() {
+    void (*func_ptr)(void) = test_function;
+    func_ptr(); /* Call site should use -(4+4) = -8 offset */
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_test_function:" } } */
+
+/* Should have patchable function entry section */
+/* { dg-final { scan-assembler "__patchable_function_entries" } } */
+
+/* x86_64: Should have exactly 4 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* 
} } } */
+
+/* x86_64: Should have exactly 4 entry NOPs between .cfi_startproc and pushq */
+/* { dg-final { scan-assembler 
{\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* 
} } } */
+
+/* x86_64: KCFI should have exactly 7 NOPs between __cfi_ and movl 
(offsetToAlignment(4+5,16)=7) */
+/* { dg-final { scan-assembler 
{__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl}
 { target x86_64-*-* } } } */
+
+/* x86_64: Validate KCFI type ID is present */
+/* { dg-final { scan-assembler {movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* 
} } } */
+
+/* x86_64: Call site should use -8 offset (kernel-style encoding) */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-8\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
+
+/* AArch64: Should have exactly 4 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target 
aarch64*-*-* } } } */
+
+/* AArch64: Should have exactly 4 entry NOPs after .cfi_startproc */
+/* { dg-final { scan-assembler 
{\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target aarch64*-*-* } } } */
+
+/* AArch64: Call site should use -8 offset */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-8\]} { target 
aarch64*-*-* } } } */
+
+/* RISC-V: Should have exactly 4 prefix NOPs between .LPFE and .type */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* 
} } } */
+
+/* RISC-V: Should have 4 entry NOPs */
+/* { dg-final { scan-assembler 
{test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } 
} */
+
+/* RISC-V: Call site should use -8 offset (same as x86/AArch64) */
+/* { dg-final { scan-assembler {lw\tt1, -8\(} { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-none.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-none.c
new file mode 100644
index 000000000000..01a611ca754d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-none.c
@@ -0,0 +1,18 @@
+/* Test KCFI without patchable function entries - standard case */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void test_function(void) {
+    /* Standard KCFI without patchable entries */
+}
+
+int main() {
+    test_function();
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_test_function:" } } */
+
+/* Should NOT have patchable function entry section */
+/* { dg-final { scan-assembler-not "__patchable_function_entries" } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
new file mode 100644
index 000000000000..5a500f3a8ea9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
@@ -0,0 +1,48 @@
+/* Test KCFI with patchable function entries - prefix NOPs only */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fpatchable-function-entry=3,3" } */
+
+void test_function(void) {
+    /* All NOPs are prefix NOPs, should integrate with KCFI */
+}
+
+int main() {
+    test_function();
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_test_function:" } } */
+
+/* x86_64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target 
x86_64-*-* } } } */
+
+/* x86_64: No entry NOPs - function should start immediately with prologue (no 
__kcfi_typeid for definitions) */
+/* { dg-final { scan-assembler 
{test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} 
{ target x86_64-*-* } } } */
+
+/* x86_64: KCFI padding should have exactly 8 NOPs 
(offsetToAlignment(3+5,16)=8) */
+/* { dg-final { scan-assembler 
{__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl}
 { target x86_64-*-* } } } */
+
+/* AArch64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target 
aarch64*-*-* } } } */
+
+/* AArch64: No entry NOPs - function should start immediately with prologue 
(no __kcfi_typeid for definitions) */
+/* { dg-final { scan-assembler 
{test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*nop\n\t*ret} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} 
{ target aarch64*-*-* } } } */
+
+/* AArch64: KCFI type ID should be immediate word (no alignment NOPs needed) */
+/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word 0x[0-9a-f]+} { 
target aarch64*-*-* } } } */
+
+/* RISC-V: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs */
+/* { dg-final { scan-assembler 
{\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target 
riscv*-*-* } } } */
+
+/* RISC-V: No entry NOPs - function should start immediately with 
.cfi_startproc */
+/* { dg-final { scan-assembler 
{test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc} { target riscv*-*-* } } } */
+/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} 
{ target riscv*-*-* } } } */
+
+/* RISC-V: KCFI type ID should be immediate word */
+/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { 
target riscv*-*-* } } } */
+
+/* Should have patchable function entry section */
+/* { dg-final { scan-assembler "__patchable_function_entries" } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
new file mode 100644
index 000000000000..d6c580e1e502
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
@@ -0,0 +1,98 @@
+/* Test KCFI with position-independent code addressing modes */
+/* This is a regression test for complex addressing like PLUS(PLUS(...), 
symbol_ref) */
+/* which can occur with PIC and caused change_address_1 RTL errors */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2 -fpic" } */
+
+/* Global function pointer table that creates PIC addressing */
+struct callbacks {
+    int (*handler1)(int);
+    void (*handler2)(void);
+    int (*handler3)(int, int);
+};
+
+static int simple_handler(int x) {
+    return x * 2;
+}
+
+static void void_handler(void) {
+    /* Empty handler */
+}
+
+static int complex_handler(int a, int b) {
+    return a + b;
+}
+
+/* Global structure that will require PIC addressing */
+struct callbacks global_callbacks = {
+    .handler1 = simple_handler,
+    .handler2 = void_handler,
+    .handler3 = complex_handler
+};
+
+/* Function that uses PIC addressing to access global callbacks */
+int test_pic_addressing(int value) {
+    /* These indirect calls through global structure create complex addressing
+     * like PLUS(PLUS(GOT_base, symbol_offset), struct_offset) which previously
+     * caused RTL errors in KCFI instrumentation */
+
+    int result = 0;
+    result += global_callbacks.handler1(value);
+
+    global_callbacks.handler2();
+
+    result += global_callbacks.handler3(value, result);
+
+    return result;
+}
+
+/* Test with function pointer arrays */
+static int (*func_array[])(int) = {
+    simple_handler,
+    simple_handler,
+    simple_handler
+};
+
+int test_pic_array(int index, int value) {
+    /* Array access with PIC can also create complex addressing */
+    return func_array[index % 3](value);
+}
+
+/* Test with dynamic PIC addressing */
+struct callbacks *get_callbacks(void) {
+    return &global_callbacks;
+}
+
+int test_dynamic_pic(int value) {
+    /* Dynamic access through function call creates very complex addressing */
+    struct callbacks *cb = get_callbacks();
+    return cb->handler1(value) + cb->handler3(value, value);
+}
+
+int main() {
+    int result = 0;
+    result += test_pic_addressing(10);
+    result += test_pic_array(1, 20);
+    result += test_dynamic_pic(5);
+    return result;
+}
+
+/* Verify that all address-taken functions get KCFI preambles */
+/* { dg-final { scan-assembler {__cfi_simple_handler:} } } */
+/* { dg-final { scan-assembler {__cfi_void_handler:} } } */
+/* { dg-final { scan-assembler {__cfi_complex_handler:} } } */
+
+/* x86_64: Verify KCFI checks are generated (should compile without RTL 
errors, kernel-style encoding) */
+/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, 
%r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */
+
+/* AArch64: Verify KCFI checks */
+/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */
+
+/* RISC-V: Verify complete KCFI check sequence */
+/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, 
\.L[a-zA-Z0-9_]+\n\.L[a-zA-Z0-9_]+:\n\tebreak} { target riscv*-*-* } } } */
+
+/* Should have trap section */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
new file mode 100644
index 000000000000..7b8877de4254
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
@@ -0,0 +1,111 @@
+/* Test KCFI protection when indirect calls get converted to tail calls */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -O2" } */
+
+typedef int (*func_ptr_t)(int);
+typedef void (*void_func_ptr_t)(void);
+
+struct function_table {
+    func_ptr_t process;
+    void_func_ptr_t cleanup;
+};
+
+/* Target functions */
+int process_data(int x) { return x * 2; }
+void cleanup_data(void) {}
+
+/* Initialize function table */
+volatile struct function_table vtable = {
+    .process = &process_data,
+    .cleanup = &cleanup_data
+};
+
+/* Test 1: Indirect call through struct member that should become tail call */
+int test_struct_indirect_call(int x) {
+    /* This is an indirect call that should be converted to tail call:
+     * Without -fno-optimize-sibling-calls: should become "jmp 
*vtable+0(%rip)" with KCFI
+     * With -fno-optimize-sibling-calls: should be "call *vtable+0(%rip)" with 
KCFI */
+    return vtable.process(x);
+}
+
+/* Test 2: Indirect call through function pointer parameter */
+int test_param_indirect_call(func_ptr_t handler, int x) {
+    /* This is an indirect call that should be converted to tail call:
+     * Without -fno-optimize-sibling-calls: should become "jmp *%rdi" with KCFI
+     * With -fno-optimize-sibling-calls: should be "call *%rdi" with KCFI */
+    return handler(x);
+}
+
+/* Test 3: Void indirect call through struct member */
+void test_void_indirect_call(void) {
+    /* This is an indirect call that should be converted to tail call:
+     * Without -fno-optimize-sibling-calls: should become "jmp 
*vtable+8(%rip)" with KCFI
+     * With -fno-optimize-sibling-calls: should be "call *vtable+8(%rip)" with 
KCFI */
+    vtable.cleanup();
+}
+
+/* Test 4: Non-tail call for comparison (should always be call with KCFI) */
+int test_non_tail_indirect_call(func_ptr_t handler, int x) {
+    /* This should never become a tail call - always "call *%rdi" with KCFI */
+    int result = handler(x);
+    return result + 1;  /* Prevents tail call optimization */
+}
+
+/* Should have KCFI preambles for all functions (same for both architectures) 
*/
+/* { dg-final { scan-assembler-times "__cfi_process_data:" 1 } } */
+/* { dg-final { scan-assembler-times "__cfi_cleanup_data:" 1 } } */
+/* { dg-final { scan-assembler-times "__cfi_test_struct_indirect_call:" 1 } } 
*/
+/* { dg-final { scan-assembler-times "__cfi_test_param_indirect_call:" 1 } } */
+/* { dg-final { scan-assembler-times "__cfi_test_void_indirect_call:" 1 } } */
+/* { dg-final { scan-assembler-times "__cfi_test_non_tail_indirect_call:" 1 } 
} */
+
+/* Should have exactly 4 KCFI checks for indirect calls (load type ID + 
compare) */
+/* { dg-final { scan-assembler-times {movl\t\$-?[0-9]+, %r10d} 4 { target 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler-times {addl\t-4\(%r[a-z0-9]+\), %r10d} 4 { 
target x86_64-*-* } } } */
+
+/* Should have exactly 4 trap sections and 4 trap instructions */
+/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target x86_64-*-* } } 
} */
+/* { dg-final { scan-assembler-times "ud2" 4 { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target riscv*-*-* } } 
} */
+/* { dg-final { scan-assembler-times "ebreak" 4 { target riscv*-*-* } } } */
+
+/* Should NOT have unprotected direct jumps to vtable */
+/* { dg-final { scan-assembler-not {jmp\t\*vtable\(%rip\)} { target x86_64-*-* 
} } } */
+/* { dg-final { scan-assembler-not {jmp\t\*vtable\+8\(%rip\)} { target 
x86_64-*-* } } } */
+
+/* Should have exactly 3 protected tail calls (jmp through register after KCFI 
check) */
+/* { dg-final { scan-assembler-times {jmp\t\*%[a-z0-9]+} 3 { target x86_64-*-* 
} } } */
+
+/* Should have exactly 1 regular call (non-tail call case) */
+/* { dg-final { scan-assembler-times {call\t\*%[a-z0-9]+} 1 { target 
x86_64-*-* } } } */
+
+/* RISC-V: Should have exactly 4 complete KCFI check sequences */
+/* { dg-final { scan-assembler-times {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, 
[0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, 
\.L[a-zA-Z0-9_]+\n\.L[a-zA-Z0-9_]+:\n\tebreak} 4 { target riscv*-*-* } } } */
+
+/* RISC-V: Should have exactly 4 KCFI checks for indirect calls (load type ID 
+ compare) */
+/* { dg-final { scan-assembler-times {lw\tt1, -4\([a-z0-9]+\)} 4 { target 
riscv*-*-* } } } */
+/* { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 4 { target riscv*-*-* } 
} } */
+
+/* RISC-V: Should have exactly 3 protected tail calls (jr after KCFI check - 
no return address save) */
+/* { dg-final { scan-assembler-times {jalr\tx0, [a-z0-9]+, 0} 3 { target 
riscv*-*-* } } } */
+
+/* RISC-V: Should have exactly 1 regular call (non-tail call case - saves 
return address) */
+/* { dg-final { scan-assembler-times {jalr\tx1, [a-z0-9]+, 0} 1 { target 
riscv*-*-* } } } */
+
+/* AArch64-specific patterns */
+/* Should have exactly 4 KCFI checks for indirect calls (load type ID from -4 
offset + compare) */
+/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 4 { target 
aarch64-*-* } } } */
+/* { dg-final { scan-assembler-times {cmp\tw16, w17} 4 { target aarch64-*-* } 
} } */
+
+/* Should have exactly 4 trap instructions */
+/* { dg-final { scan-assembler-times {brk\t#[0-9]+} 4 { target aarch64-*-* } } 
} */
+
+/* Should have exactly 3 protected tail calls (br through register after KCFI 
check) */
+/* { dg-final { scan-assembler-times {br\tx[0-9]+} 3 { target aarch64-*-* } } 
} */
+
+/* Should have exactly 1 regular call (non-tail call case) */
+/* { dg-final { scan-assembler-times {blr\tx[0-9]+} 1 { target aarch64-*-* } } 
} */
+
+/* Type ID loading should use mov + movk pattern for 32-bit constants */
+/* { dg-final { scan-assembler {mov\tw17, #[0-9]+} { target aarch64-*-* } } } 
*/
+/* { dg-final { scan-assembler {movk\tw17, #[0-9]+, lsl #16} { target 
aarch64-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
new file mode 100644
index 000000000000..8b0c4819e11b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
@@ -0,0 +1,56 @@
+/* Test KCFI trap section generation */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi" } */
+
+void target_function(void) {}
+
+int main() {
+    void (*func_ptr)(void) = target_function;
+
+    /* Multiple indirect calls to generate trap entries */
+    func_ptr();
+    func_ptr();
+
+    return 0;
+}
+
+/* Should have KCFI preamble */
+/* { dg-final { scan-assembler "__cfi_target_function:" } } */
+
+/* Should have exactly 2 trap labels in code */
+/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ud2} 2 { target x86_64-*-* 
} } } */
+/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*brk} 2 { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ebreak} 2 { target 
riscv*-*-* } } } */
+
+/* Should have .kcfi_traps section with exactly 2 entries (x86 and RISC-V - 
AArch64 uses brk immediate) */
+/* { dg-final { scan-assembler 
{\.pushsection\t*\.kcfi_traps,"ao",@progbits,\.text} { target x86_64-*-* } } } 
*/
+/* { dg-final { scan-assembler-times {\.L[^:]+:\n\t*\.long} 2 { target 
x86_64-*-* } } } */
+
+/* AArch64 should NOT have .kcfi_traps section (uses brk immediate instead) */
+/* { dg-final { scan-assembler-not {\.pushsection\t*\.kcfi_traps} { target 
aarch64*-*-* } } } */
+/* { dg-final { scan-assembler-not {\.L[^:]+:\n\t*\.long} { target 
aarch64*-*-* } } } */
+
+/* Each section entry must reference its corresponding trap label directly 
(x86 and RISC-V) */
+/* Entry references trap label directly (no offset needed) */
+/* { dg-final { scan-assembler 
{\.L([^:]+):\n\t*\.long\t*\.L([^\s]+)\s+-\s+\.L\1} { target x86_64-*-* } } } */
+
+/* RISC-V: Should have .kcfi_traps section with exactly 2 entries */
+/* { dg-final { scan-assembler 
{\.pushsection\t*\.kcfi_traps,"ao",@progbits,\.text} { target riscv*-*-* } } } 
*/
+/* { dg-final { scan-assembler-times {\.Lkcfi_trap_entry_[0-9]+:\n\t*\.word} 2 
{ target riscv*-*-* } } } */
+
+/* AArch64 should NOT have RISC-V-style .kcfi_traps section entries */
+/* { dg-final { scan-assembler-not {\.Lkcfi_trap_entry_[0-9]+:\n\t*\.word} { 
target aarch64*-*-* } } } */
+
+/* RISC-V section entries must reference their corresponding trap labels */
+/* Entry references trap label directly (no offset needed) */
+/* { dg-final { scan-assembler 
{\.Lkcfi_trap_entry_([0-9]+):\n\t*\.word\t*\.Lkcfi_trap_\1\s+-\s+\.Lkcfi_trap_entry_\1}
 { target riscv*-*-* } } } */
+
+/* AArch64 should NOT have RISC-V-style trap label references */
+/* { dg-final { scan-assembler-not 
{\.Lkcfi_trap_entry_([0-9]+):\n\t*\.word\t*\.Lkcfi_trap_\1} { target 
aarch64*-*-* } } } */
+
+/* Section must be properly closed (x86 and RISC-V) */
+/* { dg-final { scan-assembler {\.popsection} { target x86_64-*-* } } } */
+/* { dg-final { scan-assembler {\.popsection} { target riscv*-*-* } } } */
+
+/* AArch64 should NOT have .popsection (no .kcfi_traps section to close) */
+/* { dg-final { scan-assembler-not {\.popsection} { target aarch64*-*-* } } } 
*/
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-type-mangling.c 
b/gcc/testsuite/gcc.dg/kcfi/kcfi-type-mangling.c
new file mode 100644
index 000000000000..36cdfd3ed890
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-type-mangling.c
@@ -0,0 +1,864 @@
+/* Test KCFI type ID hashing - verify different signatures generate different 
__kcfi_typeid_ symbols */
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=kcfi -fdump-tree-kcfi0-details" } */
+
+/* Test __kcfi_typeid_ symbol generation for address-taken functions.
+   Verify precise type discrimination using Itanium C++ ABI mangling. */
+
+/* External function declarations - these will get __kcfi_typeid_ symbols when 
address-taken */
+extern void func_void(void);                    /* FvvE -> 0x68d6d5b6 */
+extern void func_char(char x);                  /* FvcE -> 0x14f227f7 */
+extern void func_int(int x);                    /* FviE -> 0x28e33ce9 */
+extern void func_long(long x);                  /* FvlE -> 0x64f06750 */
+
+/* Basic types - verify exact type IDs match with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_void\n\t\.set\t__kcfi_typeid_func_void, 
0x68d6d5b6} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_char\n\t\.set\t__kcfi_typeid_func_char, 
0x14f227f7} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_int\n\t\.set\t__kcfi_typeid_func_int, 0x28e33ce9} 
} } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_long\n\t\.set\t__kcfi_typeid_func_long, 
0x64f06750} } } */
+
+/* Verify basic types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvvE' typeid=0x68d6d5b6} kcfi0 } 
} */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvcE' typeid=0x14f227f7} kcfi0 } 
} */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFviE' typeid=0x28e33ce9} kcfi0 } 
} */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvlE' typeid=0x64f06750} kcfi0 } 
} */
+
+/* Count verification - basic types (void type used by multiple functions) */
+/* { dg-final { scan-assembler-times {0x68d6d5b6} 2 } } +1 from 
local_func_void preamble below */
+/* { dg-final { scan-assembler-times {0x14f227f7} 1 } } */
+/* { dg-final { scan-assembler-times {0x28e33ce9} 1 } } */
+/* { dg-final { scan-assembler-times {0x64f06750} 1 } } */
+
+/* Pointer parameter types - must all differ */
+extern void func_int_ptr(int *x);               /* FvPiE -> 0x6d478137 */
+extern void func_char_ptr(char *x);             /* FvPcE -> 0x81389629 */
+extern void func_void_ptr(void *x);             /* FvPvE -> 0x5d1c8700 */
+
+/* Pointer types - verify they all differ with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_int_ptr\n\t\.set\t__kcfi_typeid_func_int_ptr, 
0x6d478137} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_char_ptr\n\t\.set\t__kcfi_typeid_func_char_ptr, 
0x81389629} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_void_ptr\n\t\.set\t__kcfi_typeid_func_void_ptr, 
0x5d1c8700} } } */
+
+/* Verify pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPiE' typeid=0x6d478137} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPcE' typeid=0x81389629} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPvE' typeid=0x5d1c8700} kcfi0 
} } */
+
+/* Count verification - pointer types (will appear twice due to array decay 
earlier) */
+/* { dg-final { scan-assembler-times {0x6d478137} 2 } } */
+/* { dg-final { scan-assembler-times {0x81389629} 2 } } */
+/* { dg-final { scan-assembler-times {0x5d1c8700} 1 } } */
+
+/* Const qualifier discrimination - const vs non-const must have different 
type IDs */
+extern void func_const_int_ptr(const int *x);   /* FvPKiE -> const int* (must 
differ from int*) */
+extern void func_const_char_ptr(const char *x); /* FvPKcE -> const char* (must 
differ from char*) */
+extern void func_const_void_ptr(const void *x); /* FvPKvE -> const void* (must 
differ from void*) */
+
+/* Const qualifier types - verify const vs non-const have different type IDs */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_int_ptr\n\t\.set\t__kcfi_typeid_func_const_int_ptr,
 0xcc6626a0} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_char_ptr\n\t\.set\t__kcfi_typeid_func_const_char_ptr,
 0xb0750516} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_void_ptr\n\t\.set\t__kcfi_typeid_func_const_void_ptr,
 0xdc8f8dd7} } } */
+
+/* Verify const qualifier types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPKiE' typeid=0xcc6626a0} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPKcE' typeid=0xb0750516} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPKvE' typeid=0xdc8f8dd7} kcfi0 
} } */
+
+/* Count verification - const qualifier types should appear exactly once */
+/* { dg-final { scan-assembler-times {0xcc6626a0} 1 } } */
+/* { dg-final { scan-assembler-times {0xb0750516} 1 } } */
+/* { dg-final { scan-assembler-times {0xdc8f8dd7} 1 } } */
+
+/* Nested pointer types */
+extern void func_int_ptr_ptr(int **x);          /* FvPPiE -> 0x3d62bef1 */
+extern void func_char_ptr_ptr(char **x);        /* FvPPcE -> 0x494939ef */
+
+/* Nested pointers with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_int_ptr_ptr\n\t\.set\t__kcfi_typeid_func_int_ptr_ptr,
 0x3d62bef1} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_char_ptr_ptr\n\t\.set\t__kcfi_typeid_func_char_ptr_ptr,
 0x494939ef} } } */
+
+/* Verify nested pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPPiE' typeid=0x3d62bef1} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPPcE' typeid=0x494939ef} kcfi0 
} } */
+
+/* Count verification - nested pointer types should appear exactly once */
+/* { dg-final { scan-assembler-times {0x3d62bef1} 1 } } */
+/* { dg-final { scan-assembler-times {0x494939ef} 1 } } */
+
+/* Multiple parameter types - order matters */
+extern void func_int_char(int x, char y);       /* FvicE -> 0x0e0cb81e */
+extern void func_char_int(char x, int y);       /* FvciE -> 0xab661a02 */
+extern void func_two_int(int x, int y);         /* FviiE -> 0x0a2649b8 */
+
+/* Multiple parameter tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_int_char\n\t\.set\t__kcfi_typeid_func_int_char, 
0x0e0cb81e} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_char_int\n\t\.set\t__kcfi_typeid_func_char_int, 
0xab661a02} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_two_int\n\t\.set\t__kcfi_typeid_func_two_int, 
0x0a2649b8} } } */
+
+/* Verify multiple parameter types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvicE' typeid=0x0e0cb81e} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvciE' typeid=0xab661a02} kcfi0 
} } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFviiE' typeid=0x0a2649b8} kcfi0 
} } */
+
+/* Count verification - multiple parameter types should appear exactly once */
+/* { dg-final { scan-assembler-times {0x0e0cb81e} 1 } } */
+/* { dg-final { scan-assembler-times {0xab661a02} 1 } } */
+/* { dg-final { scan-assembler-times {0x0a2649b8} 1 } } */
+
+/* Return types */
+extern int func_return_int(void);               /* FivE -> 0x426f60ef */
+extern char func_return_char(void);             /* FcvE -> 0x3688e5f1 */
+extern void* func_return_ptr(void);             /* FPvvE -> 0x924cfbe6 */
+
+/* Return type tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_return_int\n\t\.set\t__kcfi_typeid_func_return_int,
 0x4193590b} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_return_char\n\t\.set\t__kcfi_typeid_func_return_char,
 0xe340f049} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_return_ptr\n\t\.set\t__kcfi_typeid_func_return_ptr,
 0xa8267e10} } } */
+
+/* Verify return types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFivE' typeid=0x4193590b} kcfi0 } 
} */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFcvE' typeid=0xe340f049} kcfi0 } 
} */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFPvvE' typeid=0xa8267e10} kcfi0 
} } */
+
+/* Count verification - return types should appear exactly once */
+/* { dg-final { scan-assembler-times {0x4193590b} 1 } } */
+/* { dg-final { scan-assembler-times {0xe340f049} 1 } } */
+/* { dg-final { scan-assembler-times {0xa8267e10} 1 } } */
+
+/* Array parameters - decay to pointers */
+extern void func_int_array(int arr[]);          /* FvPiE -> 0x6d478137 (same 
as int*) */
+extern void func_char_array(char arr[]);        /* FvPcE -> 0x81389629 (same 
as char*) */
+
+/* Array decay validation - arrays should have SAME type ID as corresponding 
pointers */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_int_array\n\t\.set\t__kcfi_typeid_func_int_array, 
0x6d478137} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_char_array\n\t\.set\t__kcfi_typeid_func_char_array,
 0x81389629} } } */
+/* Counted below. */
+
+/* Function pointer parameters */
+extern void func_fptr_void(void (*fp)(void));   /* FvPFvvEE -> 0xbe908da1 */
+extern void func_fptr_int(void (*fp)(int));     /* FvPFviEE -> 0x8f3bc4fe */
+extern void func_fptr_ret_int(int (*fp)(void)); /* FvPFivEE -> 0xdcab0e2c */
+
+/* Function pointer parameter tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_fptr_void\n\t\.set\t__kcfi_typeid_func_fptr_void, 
0x5f78c457} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_fptr_int\n\t\.set\t__kcfi_typeid_func_fptr_int, 
0x8f3bc4fe} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_fptr_ret_int\n\t\.set\t__kcfi_typeid_func_fptr_ret_int,
 0x5993cc14} } } */
+
+/* Verify function pointer parameter types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPFvvEE' typeid=0x5f78c457} 
kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPFviEE' typeid=0x8f3bc4fe} 
kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPFivEE' typeid=0x5993cc14} 
kcfi0 } } */
+
+/* Count verification - function pointer parameter types should appear exactly 
once */
+/* { dg-final { scan-assembler-times {0x5f78c457} 1 } } */
+/* { dg-final { scan-assembler-times {0x8f3bc4fe} 1 } } */
+/* { dg-final { scan-assembler-times {0x5993cc14} 1 } } */
+
+/* Struct/union/enum parameter types: each struct name must produce different 
type IDs */
+struct test_struct_a { int x; };
+struct test_struct_b { int y; };
+struct test_struct_c { int z; };
+union test_union_a { int i; float f; };
+union test_union_b { int j; float g; };
+enum test_enum_a { ENUM_A_VAL1, ENUM_A_VAL2 };
+enum test_enum_b { ENUM_B_VAL1, ENUM_B_VAL2 };
+
+/* Functions taking struct pointers - must have different type IDs */
+extern void func_struct_a_ptr(struct test_struct_a *x);  /* 
Fv14test_struct_aPiE -> unique */
+extern void func_struct_b_ptr(struct test_struct_b *x);  /* 
Fv14test_struct_bPiE -> unique */
+extern void func_struct_c_ptr(struct test_struct_c *x);  /* 
Fv14test_struct_cPiE -> unique */
+
+/* Struct pointer tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_a_ptr\n\t\.set\t__kcfi_typeid_func_struct_a_ptr,
 0x778e8c02} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_b_ptr\n\t\.set\t__kcfi_typeid_func_struct_b_ptr,
 0x1791c679} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_c_ptr\n\t\.set\t__kcfi_typeid_func_struct_c_ptr,
 0x43944a54} } } */
+
+/* Verify struct pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP13test_struct_aE' 
typeid=0x778e8c02} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP13test_struct_bE' 
typeid=0x1791c679} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP13test_struct_cE' 
typeid=0x43944a54} kcfi0 } } */
+
+/* Count verification - struct pointer types should appear exactly once */
+/* { dg-final { scan-assembler-times {0x778e8c02} 1 } } */
+/* { dg-final { scan-assembler-times {0x1791c679} 1 } } */
+/* { dg-final { scan-assembler-times {0x43944a54} 1 } } */
+
+/* Functions taking const struct pointers - must differ from non-const 
versions */
+extern void func_const_struct_a_ptr(const struct test_struct_a *x);  /* 
FvPK14test_struct_aE -> unique, different from non-const */
+extern void func_const_struct_b_ptr(const struct test_struct_b *x);  /* 
FvPK14test_struct_bE -> unique, different from non-const */
+extern void func_const_struct_c_ptr(const struct test_struct_c *x);  /* 
FvPK14test_struct_cE -> unique, different from non-const */
+
+/* Const struct pointer tests with precise patterns - must differ from 
non-const */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_struct_a_ptr\n\t\.set\t__kcfi_typeid_func_const_struct_a_ptr,
 0x763752c9} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_struct_b_ptr\n\t\.set\t__kcfi_typeid_func_const_struct_b_ptr,
 0x5634e1d2} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_const_struct_c_ptr\n\t\.set\t__kcfi_typeid_func_const_struct_c_ptr,
 0x32326a8f} } } */
+
+/* Verify const struct pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPK13test_struct_aE' 
typeid=0x763752c9} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPK13test_struct_bE' 
typeid=0x5634e1d2} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvPK13test_struct_cE' 
typeid=0x32326a8f} kcfi0 } } */
+
+/* Count verification - const struct pointer types should appear exactly once 
*/
+/* { dg-final { scan-assembler-times {0x763752c9} 1 } } */
+/* { dg-final { scan-assembler-times {0x5634e1d2} 1 } } */
+/* { dg-final { scan-assembler-times {0x32326a8f} 1 } } */
+
+extern void func_union_a_ptr(union test_union_a *x);     /* 
Fv13test_union_aPiE -> unique */
+extern void func_union_b_ptr(union test_union_b *x);     /* 
Fv13test_union_bPiE -> unique */
+extern void func_enum_a_ptr(enum test_enum_a *x);        /* Fv11test_enum_aPiE 
-> unique */
+extern void func_enum_b_ptr(enum test_enum_b *x);        /* Fv11test_enum_bPiE 
-> unique */
+
+/* Union member access discrimination test - prevents regression of union 
member bug */
+struct tasklet_like_struct {
+    int state;
+    union {
+       /* First union member - should NOT be used for callback calls */
+        void (*func)(unsigned long data);
+       /* Second union member - should be used for callback calls */
+        void (*callback)(struct tasklet_like_struct *t);
+    };
+    unsigned long data;
+};
+
+/* Function with callback signature - this should match when accessed via 
union->callback */
+extern void tasklet_callback_function(struct tasklet_like_struct *t);  /* 
FvP19tasklet_like_structE -> unique */
+
+/* Function with func signature - this should NOT match callback calls */
+extern void tasklet_func_function(unsigned long data);                  /* 
FvmE -> different from callback */
+
+/* Union member access discrimination tests - MUST use correct union member 
type */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_tasklet_callback_function\n\t\.set\t__kcfi_typeid_tasklet_callback_function,
 0x5634f4ec} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_tasklet_func_function\n\t\.set\t__kcfi_typeid_tasklet_func_function,
 0x48edfca5} } } */
+
+/* Verify union member discrimination tests */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP19tasklet_like_structE' 
typeid=0x5634f4ec} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvmE' typeid=0x48edfca5} kcfi0 } 
} */
+
+/* Union pointer tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_union_a_ptr\n\t\.set\t__kcfi_typeid_func_union_a_ptr,
 0xb03d9275} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_union_b_ptr\n\t\.set\t__kcfi_typeid_func_union_b_ptr,
 0x003a3ece} } } */
+
+/* Verify union pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP12test_union_aE' 
typeid=0xb03d9275} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP12test_union_bE' 
typeid=0x003a3ece} kcfi0 } } */
+
+/* Count verification - union pointer types should appear exactly once */
+/* { dg-final { scan-assembler-times {0xb03d9275} 1 } } */
+/* { dg-final { scan-assembler-times {0x003a3ece} 1 } } */
+
+/* Enum pointer tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_enum_a_ptr\n\t\.set\t__kcfi_typeid_func_enum_a_ptr,
 0x9a32df78} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_enum_b_ptr\n\t\.set\t__kcfi_typeid_func_enum_b_ptr,
 0xaa2c3ce3} } } */
+
+/* Verify enum pointer types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP11test_enum_aE' 
typeid=0x9a32df78} kcfi0 } } */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP11test_enum_bE' 
typeid=0xaa2c3ce3} kcfi0 } } */
+
+/* Count verification - enum pointer types should appear exactly once */
+/* { dg-final { scan-assembler-times {0x9a32df78} 1 } } */
+/* { dg-final { scan-assembler-times {0xaa2c3ce3} 1 } } */
+
+/* Count verification - union member discrimination types should appear 
exactly once */
+/* The key test is that callback and func functions have DIFFERENT type IDs, 
proving union member discrimination works */
+/* { dg-final { scan-assembler-times {0x5634f4ec} 1 } } */
+/* { dg-final { scan-assembler-times {0x48edfca5} 1 } } */
+
+/* Indirect call through t->callback union must use correct callback type ID 
(0x5634f4ec).
+   The decimal value -1446311148 is the two's complement of 0x5634f4ec used in 
KCFI checks.  */
+/* { dg-final { scan-assembler-times {\tmovl\t\$-1446311148, %r10d} 1 { target 
x86_64-*-* } } } */
+/* { dg-final { scan-assembler-times {\tmov\tw17, #62700\n\tmovk\tw17, #22068, 
lsl #16} 1 { target aarch64-*-* } } } */
+/* { dg-final { scan-assembler-times {\tlui\tt2, 353103\n\taddiw\tt2, t2, 
1260} 1 { target riscv*-*-* } } } */
+
+/* Functions returning struct pointers - must have different type IDs */
+extern struct test_struct_a* func_ret_struct_a_ptr(void); /* 
F14test_struct_aPvE -> unique */
+extern struct test_struct_b* func_ret_struct_b_ptr(void); /* 
F14test_struct_bPvE -> unique */
+extern struct test_struct_c* func_ret_struct_c_ptr(void); /* 
F14test_struct_cPvE -> unique */
+
+/* Struct return pointer tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_a_ptr\n\t\.set\t__kcfi_typeid_func_ret_struct_a_ptr,
 0x39c14222} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_b_ptr\n\t\.set\t__kcfi_typeid_func_ret_struct_b_ptr,
 0xe26c7223} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_c_ptr\n\t\.set\t__kcfi_typeid_func_ret_struct_c_ptr,
 0x4efac19c} } } */
+
+/* Verify struct return pointer types */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFP13test_struct_avE' typeid=0x39c14222} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFP13test_struct_bvE' typeid=0xe26c7223} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFP13test_struct_cvE' typeid=0x4efac19c} kcfi0 } } */
+
+/* { dg-final { scan-assembler-times {0x39c14222} 1 } } */
+/* { dg-final { scan-assembler-times {0xe26c7223} 1 } } */
+/* { dg-final { scan-assembler-times {0x4efac19c} 1 } } */
+
+/* Functions taking structs by value - must have different type IDs */
+extern void func_struct_a_val(struct test_struct_a x);   /* Fv14test_struct_aE 
-> unique */
+extern void func_struct_b_val(struct test_struct_b x);   /* Fv14test_struct_bE 
-> unique */
+extern void func_struct_c_val(struct test_struct_c x);   /* Fv14test_struct_cE 
-> unique */
+
+/* Struct by-value parameter tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_a_val\n\t\.set\t__kcfi_typeid_func_struct_a_val,
 0x3c685468} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_b_val\n\t\.set\t__kcfi_typeid_func_struct_b_val,
 0xcc60e853} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_c_val\n\t\.set\t__kcfi_typeid_func_struct_c_val,
 0xf0635f96} } } */
+
+/* Verify struct by-value parameter types */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFv13test_struct_aE' typeid=0x3c685468} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFv13test_struct_bE' typeid=0xcc60e853} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFv13test_struct_cE' typeid=0xf0635f96} kcfi0 } } */
+
+/* { dg-final { scan-assembler-times {0x3c685468} 1 } } */
+/* { dg-final { scan-assembler-times {0xcc60e853} 1 } } */
+/* { dg-final { scan-assembler-times {0xf0635f96} 1 } } */
+
+/* Functions returning structs by value - must have different type IDs */
+extern struct test_struct_a func_ret_struct_a_val(void); /* F14test_struct_avE 
-> unique */
+extern struct test_struct_b func_ret_struct_b_val(void); /* F14test_struct_bvE 
-> unique */
+extern struct test_struct_c func_ret_struct_c_val(void); /* F14test_struct_cvE 
-> unique */
+
+/* Struct return by-value tests with precise patterns */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_a_val\n\t\.set\t__kcfi_typeid_func_ret_struct_a_val,
 0x872d60f8} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_b_val\n\t\.set\t__kcfi_typeid_func_ret_struct_b_val,
 0x12ecd535} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_struct_c_val\n\t\.set\t__kcfi_typeid_func_ret_struct_c_val,
 0x6f7b0b7e} } } */
+
+/* Verify struct return by-value types - using correct P prefix for function 
pointer */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPF13test_struct_avE' typeid=0x872d60f8} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPF13test_struct_bvE' typeid=0x12ecd535} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPF13test_struct_cvE' typeid=0x6f7b0b7e} kcfi0 } } */
+
+/* { dg-final { scan-assembler-times {0x872d60f8} 1 } } */
+/* { dg-final { scan-assembler-times {0x12ecd535} 1 } } */
+/* { dg-final { scan-assembler-times {0x6f7b0b7e} 1 } } */
+
+/* Mixed struct parameters - order and type must matter */
+extern void func_struct_a_b(struct test_struct_a *a, struct test_struct_b *b); 
/* unique */
+extern void func_struct_b_a(struct test_struct_b *b, struct test_struct_a *a); 
/* different! */
+
+/* Mixed struct parameter tests - MUST be different (parameter order matters) 
*/
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_a_b\n\t\.set\t__kcfi_typeid_func_struct_a_b,
 0x3858f101} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_b_a\n\t\.set\t__kcfi_typeid_func_struct_b_a,
 0x9c8f2985} } } */
+
+/* Verify mixed struct parameter types */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP13test_struct_aP13test_struct_bE' typeid=0x3858f101} kcfi0 } } 
*/
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP13test_struct_bP13test_struct_aE' typeid=0x9c8f2985} kcfi0 } } 
*/
+
+/* { dg-final { scan-assembler-times {0x3858f101} 1 } } */
+/* { dg-final { scan-assembler-times {0x9c8f2985} 1 } } */
+
+/* Typedef structs - must be different from named structs */
+typedef struct { int value; } typedef_struct_x;
+typedef struct { int value; } typedef_struct_y;  /* Same layout but different 
typedef name */
+extern void func_typedef_x_ptr(typedef_struct_x *x);  /* Must be unique */
+extern void func_typedef_y_ptr(typedef_struct_y *x);  /* Must be different 
from typedef_struct_x */
+
+/* Typedef struct tests - MUST be different from each other */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_typedef_x_ptr\n\t\.set\t__kcfi_typeid_func_typedef_x_ptr,
 0xd04404df} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_typedef_y_ptr\n\t\.set\t__kcfi_typeid_func_typedef_y_ptr,
 0xf4467c22} } } */
+
+/* Verify typedef struct pointer types */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP16typedef_struct_xE' typeid=0xd04404df} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP16typedef_struct_yE' typeid=0xf4467c22} kcfi0 } } */
+
+/* { dg-final { scan-assembler-times {0xd04404df} 1 } } */
+/* { dg-final { scan-assembler-times {0xf4467c22} 1 } } */
+
+/* Typedef vs open-coded function types - MUST have identical type IDs */
+typedef void (*func_ptr_typedef)(int x, char y);
+extern void func_with_typedef_param(func_ptr_typedef fp);               /* 
Should match open-coded */
+extern void func_with_opencoded_param(void (*fp)(int x, char y));      /* 
Should match typedef */
+
+/* Function parameter types - typedef and open-coded should generate SAME type 
ID */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_with_typedef_param\n\t\.set\t__kcfi_typeid_func_with_typedef_param,
 0xc3f653f3} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_with_opencoded_param\n\t\.set\t__kcfi_typeid_func_with_opencoded_param,
 0xc3f653f3} } } */
+
+/* Verify function pointer parameter types */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvPFvicEE' 
typeid=0xc3f653f3} kcfi0 } } */
+
+/* Verify exact count - each typedef/opencoded pair should generate exactly 2 
symbols with identical values */
+/* { dg-final { scan-assembler-times {0xc3f653f3} 2 } } */
+
+/* Typedef vs open-coded function types - MUST have identical type IDs */
+typedef int (*ret_func_ptr_typedef)(void);
+extern ret_func_ptr_typedef func_ret_typedef_param(void);              /* 
Should match open-coded */
+extern int (*func_ret_opencoded_param(void))(void);                    /* 
Should match typedef */
+
+/* Return function pointer types - typedef and open-coded should generate SAME 
type ID */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_typedef_param\n\t\.set\t__kcfi_typeid_func_ret_typedef_param,
 0xff38a80c} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_ret_opencoded_param\n\t\.set\t__kcfi_typeid_func_ret_opencoded_param,
 0xff38a80c} } } */
+
+/* Verify return function pointer types */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFPFivEvE' 
typeid=0xff38a80c} kcfi0 } } */
+
+/* Verify exact count - each typedef/opencoded pair should generate exactly 2 
symbols with identical values */
+/* { dg-final { scan-assembler-times {0xff38a80c} 2 } } */
+
+/* Anonymous struct via typedef - should get typedef name as struct name */
+typedef struct { int anon_member_1; } anon_typedef_1;
+typedef struct { int anon_member_2; } anon_typedef_2;
+extern void func_anon_typedef_1(anon_typedef_1 *param);                /* 
Should use typedef name */
+extern void func_anon_typedef_2(anon_typedef_2 *param);                /* 
Should be different from anon_typedef_1 */
+
+/* Anonymous typedef struct tests - MUST be different from each other */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_anon_typedef_1\n\t\.set\t__kcfi_typeid_func_anon_typedef_1,
 0xeb2cfcf5} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_anon_typedef_2\n\t\.set\t__kcfi_typeid_func_anon_typedef_2,
 0x3b29a94e} } } */
+
+/* Verify anonymous typedef struct types */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP14anon_typedef_1E' typeid=0xeb2cfcf5} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: 
mangled='_ZTSPFvP14anon_typedef_2E' typeid=0x3b29a94e} kcfi0 } } */
+
+/* { dg-final { scan-assembler-times {0xeb2cfcf5} 1 } } */
+/* { dg-final { scan-assembler-times {0x3b29a94e} 1 } } */
+
+/* Local function definitions - these will NOT get __kcfi_typeid_ symbols 
(only external declarations do) */
+void local_func_void(void) { }                  /* FvvE -> 0x126cd6c8 */
+void local_func_short(short x) { }              /* FvsE -> 0x34cb4ae7 */
+void local_func_uint(unsigned int x) { }        /* FvjE -> 0x08e0cbf2 */
+void local_func_float(float x) { }              /* FvfE -> 0x48ff45c6 */
+
+/* Local function validation - verify local function definitions do NOT get 
__kcfi_typeid_ symbols */
+/* { dg-final { scan-assembler-not {\t\.weak\t__kcfi_typeid_local_func_void\n} 
} } */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_short\n} } } */
+/* { dg-final { scan-assembler-not {\t\.weak\t__kcfi_typeid_local_func_uint\n} 
} } */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_float\n} } } */
+
+/* Local pointer parameter types */
+void local_func_double_ptr(double *x) { }       /* FvPdE -> 0x713f38be */
+void local_func_float_ptr(float *x) { }         /* FvPfE -> 0xbd442d90 */
+
+/* Local pointer parameter types - should NOT emit symbols */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_double_ptr\n} } } */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_float_ptr\n} } } */
+
+/* Local nested pointers */
+void local_func_void_ptr_ptr(void **x) { }      /* FvPPvE -> 0x1d2eb12e */
+
+/* Local nested pointers - should NOT emit symbols */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_void_ptr_ptr\n} } } */
+
+/* Local mixed parameters */
+void local_func_ptr_val(int *x, int y) { }      /* FvPiiE -> 0x79c26342 */
+void local_func_val_ptr(int x, int *y) { }      /* FviPiE -> 0x467eba8c */
+
+/* Local mixed parameter validation - should NOT emit symbols */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_ptr_val\n} } } */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_val_ptr\n} } } */
+
+/* Local return types */
+float local_func_return_float(void) { return 0.0f; }    /* FfvE -> 0xf29546d8 
*/
+double local_func_return_double(void) { return 0.0; }   /* FdvE -> 0x268f8886 
*/
+
+/* Local return type discrimination - should NOT emit symbols */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_return_float\n} } } */
+/* { dg-final { scan-assembler-not 
{\t\.weak\t__kcfi_typeid_local_func_return_double\n} } } */
+
+/* Verify local function mangle strings appear in KCFI dump (even though no 
symbols are emitted) */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvsE' 
typeid=0x34cb4ae7} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvfE' 
typeid=0x48ff45c6} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvPdE' 
typeid=0x713f38be} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvPfE' 
typeid=0xbd442d90} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvPPvE' 
typeid=0x1d2eb12e} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFvPiiE' 
typeid=0x79c26342} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFviPiE' 
typeid=0x467eba8c} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFfvE' 
typeid=0x3b5a51e6} kcfi0 } } */
+/* { dg-final { scan-tree-dump {KCFI type ID: mangled='_ZTSPFdvE' 
typeid=0x1043bac0} kcfi0 } } */
+
+struct not_void {
+       int nothing;
+};
+
+/* Function that takes addresses to make functions visible to KCFI */
+void test_address_taken(struct not_void *arg)
+{
+    /* External functions - taking addresses generates __kcfi_typeid_ symbols 
*/
+    void (*p1)(void) = func_void;
+    void (*p2)(char) = func_char;
+    void (*p3)(int) = func_int;
+    void (*p4)(long) = func_long;
+
+    void (*p5)(int*) = func_int_ptr;
+    void (*p6)(char*) = func_char_ptr;
+    void (*p7)(void*) = func_void_ptr;
+
+    void (*p_const_int_ptr)(const int*) = func_const_int_ptr;
+    void (*p_const_char_ptr)(const char*) = func_const_char_ptr;
+    void (*p_const_void_ptr)(const void*) = func_const_void_ptr;
+
+    void (*p8)(int**) = func_int_ptr_ptr;
+    void (*p9)(char**) = func_char_ptr_ptr;
+
+    void (*p10)(int, char) = func_int_char;
+    void (*p11)(char, int) = func_char_int;
+    void (*p12)(int, int) = func_two_int;
+
+    int (*p13)(void) = func_return_int;
+    char (*p14)(void) = func_return_char;
+    void* (*p15)(void) = func_return_ptr;
+
+    /* Array parameters - should decay to pointers */
+    void (*p16)(int*) = func_int_array;
+    void (*p17)(char*) = func_char_array;
+
+    /* Function pointer parameters */
+    void (*p18)(void(*)(void)) = func_fptr_void;
+    void (*p19)(void(*)(int)) = func_fptr_int;
+    void (*p20)(int(*)(void)) = func_fptr_ret_int;
+
+    /* Struct/union/enum function pointers - taking addresses generates 
__kcfi_typeid_ symbols */
+    void (*p_struct_a_ptr)(struct test_struct_a*) = func_struct_a_ptr;
+    void (*p_struct_b_ptr)(struct test_struct_b*) = func_struct_b_ptr;
+    void (*p_struct_c_ptr)(struct test_struct_c*) = func_struct_c_ptr;
+
+    /* Const struct function pointers - taking addresses generates 
__kcfi_typeid_ symbols */
+    void (*p_const_struct_a_ptr)(const struct test_struct_a*) = 
func_const_struct_a_ptr;
+    void (*p_const_struct_b_ptr)(const struct test_struct_b*) = 
func_const_struct_b_ptr;
+    void (*p_const_struct_c_ptr)(const struct test_struct_c*) = 
func_const_struct_c_ptr;
+    void (*p_union_a_ptr)(union test_union_a*) = func_union_a_ptr;
+    void (*p_union_b_ptr)(union test_union_b*) = func_union_b_ptr;
+    void (*p_enum_a_ptr)(enum test_enum_a*) = func_enum_a_ptr;
+    void (*p_enum_b_ptr)(enum test_enum_b*) = func_enum_b_ptr;
+
+    struct test_struct_a* (*p_ret_struct_a_ptr)(void) = func_ret_struct_a_ptr;
+    struct test_struct_b* (*p_ret_struct_b_ptr)(void) = func_ret_struct_b_ptr;
+    struct test_struct_c* (*p_ret_struct_c_ptr)(void) = func_ret_struct_c_ptr;
+
+    void (*p_struct_a_val)(struct test_struct_a) = func_struct_a_val;
+    void (*p_struct_b_val)(struct test_struct_b) = func_struct_b_val;
+    void (*p_struct_c_val)(struct test_struct_c) = func_struct_c_val;
+
+    struct test_struct_a (*p_ret_struct_a_val)(void) = func_ret_struct_a_val;
+    struct test_struct_b (*p_ret_struct_b_val)(void) = func_ret_struct_b_val;
+    struct test_struct_c (*p_ret_struct_c_val)(void) = func_ret_struct_c_val;
+
+    void (*p_struct_a_b)(struct test_struct_a*, struct test_struct_b*) = 
func_struct_a_b;
+    void (*p_struct_b_a)(struct test_struct_b*, struct test_struct_a*) = 
func_struct_b_a;
+
+    void (*p_typedef_x_ptr)(typedef_struct_x*) = func_typedef_x_ptr;
+    void (*p_typedef_y_ptr)(typedef_struct_y*) = func_typedef_y_ptr;
+
+    /* Typedef vs open-coded function type assignments - should generate 
identical type IDs */
+    void (*p_with_typedef_param)(func_ptr_typedef) = func_with_typedef_param;
+    void (*p_with_opencoded_param)(void (*)(int, char)) = 
func_with_opencoded_param;
+    ret_func_ptr_typedef (*p_ret_typedef_param)(void) = func_ret_typedef_param;
+    int (*(*p_ret_opencoded_param)(void))(void) = func_ret_opencoded_param;
+
+    /* Anonymous struct typedef assignments - should generate unique type IDs 
*/
+    void (*p_anon_typedef_1)(anon_typedef_1 *) = func_anon_typedef_1;
+    void (*p_anon_typedef_2)(anon_typedef_2 *) = func_anon_typedef_2;
+
+    /* Union member access discrimination test - take addresses to generate 
__kcfi_typeid_ symbols */
+    void (*p_tasklet_callback)(struct tasklet_like_struct *) = 
tasklet_callback_function;
+    void (*p_tasklet_func)(unsigned long) = tasklet_func_function;
+
+    /* Local functions - taking addresses does NOT generate __kcfi_typeid_ 
symbols (only external declarations do) */
+    void (*p21)(void) = local_func_void;
+    void (*p22)(short) = local_func_short;
+    void (*p23)(unsigned int) = local_func_uint;
+    void (*p24)(float) = local_func_float;
+
+    void (*p25)(double*) = local_func_double_ptr;
+    void (*p26)(float*) = local_func_float_ptr;
+
+    void (*p27)(void**) = local_func_void_ptr_ptr;
+
+    void (*p28)(int*, int) = local_func_ptr_val;
+    void (*p29)(int, int*) = local_func_val_ptr;
+
+    float (*p30)(void) = local_func_return_float;
+    double (*p31)(void) = local_func_return_double;
+
+    /* Use pointers to prevent optimization - external functions */
+    if (p1) p1();
+    if (p2) p2('x');
+    if (p3) p3(42);
+    if (p4) p4(42L);
+    if (p5) p5((int*)0);
+    if (p6) p6((char*)0);
+    if (p7) p7((void*)0);
+
+    /* Use const qualifier pointers to prevent optimization */
+    if (p_const_int_ptr) p_const_int_ptr((const int*)0);
+    if (p_const_char_ptr) p_const_char_ptr((const char*)0);
+    if (p_const_void_ptr) p_const_void_ptr((const void*)0);
+    if (p8) p8((int**)0);
+    if (p9) p9((char**)0);
+    if (p10) p10(1, 'x');
+    if (p11) p11('x', 1);
+    if (p12) p12(1, 2);
+    if (p13) p13();
+    if (p14) p14();
+    if (p15) p15();
+    if (p16) p16((int*)0);
+    if (p17) p17((char*)0);
+    if (p18) p18((void(*)(void))0);
+    if (p19) p19((void(*)(int))0);
+    if (p20) p20((int(*)(void))0);
+
+    /* Use pointers to prevent optimization - local functions */
+    if (p21) p21();
+    if (p22) p22(1);
+    if (p23) p23(1U);
+    if (p24) p24(1.0f);
+    if (p25) p25((double*)0);
+    if (p26) p26((float*)0);
+    if (p27) p27((void**)0);
+    if (p28) p28((int*)0, 1);
+    if (p29) p29(1, (int*)0);
+    if (p30) p30();
+    if (p31) p31();
+
+    /* Use struct/union/enum function pointers to generate KCFI type IDs */
+    if (p_struct_a_ptr) p_struct_a_ptr((struct test_struct_a*)0);
+    if (p_struct_b_ptr) p_struct_b_ptr((struct test_struct_b*)0);
+    if (p_struct_c_ptr) p_struct_c_ptr((struct test_struct_c*)0);
+    if (p_const_struct_a_ptr) p_const_struct_a_ptr((const struct 
test_struct_a*)0);
+    if (p_const_struct_b_ptr) p_const_struct_b_ptr((const struct 
test_struct_b*)0);
+    if (p_const_struct_c_ptr) p_const_struct_c_ptr((const struct 
test_struct_c*)0);
+    if (p_union_a_ptr) p_union_a_ptr((union test_union_a*)0);
+    if (p_union_b_ptr) p_union_b_ptr((union test_union_b*)0);
+    if (p_enum_a_ptr) p_enum_a_ptr((enum test_enum_a*)0);
+    if (p_enum_b_ptr) p_enum_b_ptr((enum test_enum_b*)0);
+
+    /* Use struct return type function pointers to generate type IDs */
+    if (p_ret_struct_a_ptr) p_ret_struct_a_ptr();
+    if (p_ret_struct_b_ptr) p_ret_struct_b_ptr();
+    if (p_ret_struct_c_ptr) p_ret_struct_c_ptr();
+
+    /* Use struct by-value parameter function pointers to generate type IDs */
+    struct test_struct_a dummy_a = {};
+    struct test_struct_b dummy_b = {};
+    struct test_struct_c dummy_c = {};
+    if (p_struct_a_val) p_struct_a_val(dummy_a);
+    if (p_struct_b_val) p_struct_b_val(dummy_b);
+    if (p_struct_c_val) p_struct_c_val(dummy_c);
+
+    /* Use struct return by-value function pointers to generate type IDs */
+    if (p_ret_struct_a_val) p_ret_struct_a_val();
+    if (p_ret_struct_b_val) p_ret_struct_b_val();
+    if (p_ret_struct_c_val) p_ret_struct_c_val();
+
+    /* Use multi-parameter struct function pointers to generate type IDs */
+    if (p_struct_a_b) p_struct_a_b((struct test_struct_a*)0, (struct 
test_struct_b*)0);
+    if (p_struct_b_a) p_struct_b_a((struct test_struct_b*)0, (struct 
test_struct_a*)0);
+
+    /* Use typedef struct function pointers to generate type IDs */
+    if (p_typedef_x_ptr) p_typedef_x_ptr((typedef_struct_x*)0);
+    if (p_typedef_y_ptr) p_typedef_y_ptr((typedef_struct_y*)0);
+
+    /* Use typedef vs open-coded function pointers to generate type IDs */
+    if (p_with_typedef_param) p_with_typedef_param((func_ptr_typedef)0);
+    if (p_with_opencoded_param) p_with_opencoded_param((void (*)(int, char))0);
+    if (p_ret_typedef_param) p_ret_typedef_param();
+    if (p_ret_opencoded_param) p_ret_opencoded_param();
+
+    /* Use anonymous typedef function pointers to generate type IDs */
+    if (p_anon_typedef_1) p_anon_typedef_1((anon_typedef_1*)0);
+    if (p_anon_typedef_2) p_anon_typedef_2((anon_typedef_2*)0);
+
+    /* Use tasklet func function pointer to generate the missing FvmE type ID 
*/
+    if (p_tasklet_func) p_tasklet_func(0);
+
+    struct tasklet_like_struct test_tasklet = { };
+    test_tasklet.callback = tasklet_callback_function;  /* Set callback 
function */
+
+    /* This indirect call through union->callback MUST generate type ID 
0x5634f4ec (callback signature) */
+    /* NOT type ID 0x48edfca5 (func signature from first union member) */
+    /* Force indirect call through union member access */
+    struct tasklet_like_struct *volatile tasklet_ptr = &test_tasklet;
+    if (tasklet_ptr->callback) {
+        /* This call should match tasklet_callback_function type ID */
+        tasklet_ptr->callback(tasklet_ptr);
+    }
+}
+
+/* Named struct and its typedef should have IDENTICAL type IDs after 
canonicalization */
+struct named_for_typedef_test { int member; };
+typedef struct named_for_typedef_test named_for_typedef_test_t;
+
+extern void func_named_struct_param(struct named_for_typedef_test *param);
+extern void func_typedef_struct_param(named_for_typedef_test_t *param);
+
+/* Named struct typedef canonicalization - MUST have identical type IDs */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_named_struct_param\n\t\.set\t__kcfi_typeid_func_named_struct_param,
 0x010f62ae} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_typedef_struct_param\n\t\.set\t__kcfi_typeid_func_typedef_struct_param,
 0x010f62ae} } } */
+
+/* Verify named struct typedef canonicalization types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP22named_for_typedef_testE' 
typeid=0x010f62ae} kcfi0 } } */
+
+/* Verify exact count - both should generate exactly 2 symbols with identical 
values */
+/* { dg-final { scan-assembler-times {0x010f62ae} 2 } } */
+
+void test_named_struct_typedef_canonicalization(struct not_void *arg) {
+    /* These should be compatible after canonicalization */
+    void (*fp_struct)(struct named_for_typedef_test *) = 
func_named_struct_param;
+    void (*fp_typedef)(struct named_for_typedef_test *) = 
func_typedef_struct_param;  /* Should work with canonicalization */
+
+    /* Take addresses to generate type IDs */
+    if (fp_struct) fp_struct((struct named_for_typedef_test *)0);
+    if (fp_typedef) fp_typedef((struct named_for_typedef_test *)0);
+}
+
+/* Basic type typedef canonicalization - typedef should canonicalize to 
underlying basic type */
+
+/* Basic type typedefs commonly used in kernel code */
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+
+/* Functions with basic type typedef vs original type parameters */
+extern void func_u8_param(u8 param);
+extern void func_unsigned_char_param(unsigned char param);
+extern void func_u16_param(u16 param);
+extern void func_unsigned_short_param(unsigned short param);
+extern void func_u32_param(u32 param);
+extern void func_unsigned_int_param(unsigned int param);
+
+void test_basic_typedef_canonicalization(struct not_void *arg) {
+    /* These should be compatible after canonicalization */
+    void (*fp_u8)(unsigned char) = func_u8_param;                    /* Should 
work with canonicalization */
+    void (*fp_uchar)(unsigned char) = func_unsigned_char_param;      /* Should 
work normally */
+    void (*fp_u16)(unsigned short) = func_u16_param;                 /* Should 
work with canonicalization */
+    void (*fp_ushort)(unsigned short) = func_unsigned_short_param;   /* Should 
work normally */
+    void (*fp_u32)(unsigned int) = func_u32_param;                   /* Should 
work with canonicalization */
+    void (*fp_uint)(unsigned int) = func_unsigned_int_param;         /* Should 
work normally */
+
+    /* Take addresses to generate type IDs */
+    if (fp_u8) fp_u8(0);
+    if (fp_uchar) fp_uchar(0);
+    if (fp_u16) fp_u16(0);
+    if (fp_ushort) fp_ushort(0);
+    if (fp_u32) fp_u32(0);
+    if (fp_uint) fp_uint(0);
+}
+
+/* Basic type typedef canonicalization - MUST have identical type IDs after 
canonicalization */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u8_param\n\t\.set\t__kcfi_typeid_func_u8_param, 
0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_unsigned_char_param\n\t\.set\t__kcfi_typeid_func_unsigned_char_param,
 0x54e5c0c4} } } */
+
+/* Verify basic type canonicalization (u8/unsigned char) */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvhE' typeid=0x54e5c0c4} kcfi0 } 
} */
+
+/* Count test is below, which includes other tests that use this hash. */
+
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u16_param\n\t\.set\t__kcfi_typeid_func_u16_param, 
0x34dc9408} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_unsigned_short_param\n\t\.set\t__kcfi_typeid_func_unsigned_short_param,
 0x34dc9408} } } */
+
+/* Verify basic type canonicalization (u16/unsigned short) */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvtE' typeid=0x34dc9408} kcfi0 } 
} */
+
+/* { dg-final { scan-assembler-times {0x34dc9408} 2 } } */
+
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u32_param\n\t\.set\t__kcfi_typeid_func_u32_param, 
0x08e0cbf2} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_unsigned_int_param\n\t\.set\t__kcfi_typeid_func_unsigned_int_param,
 0x08e0cbf2} } } */
+
+/* Verify basic type canonicalization (u32/unsigned int) */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvjE' typeid=0x08e0cbf2} kcfi0 } 
} */
+
+/* Count test is below, which includes other tests that use this hash. */
+
+/* Verify exact count - each typedef/basic type pair should generate exactly 2 
symbols with identical values */
+/* Note: Counts updated below to include recursive typedef tests */
+
+/* Recursive typedef canonicalization - test multi-level typedef chains */
+
+/* Kernel-style recursive typedef chains that need full canonicalization */
+typedef unsigned char __u8_recursive;
+typedef __u8_recursive u8_recursive;
+
+typedef unsigned int __u32_recursive;
+typedef __u32_recursive u32_recursive;
+
+/* Three-level typedef chains */
+typedef unsigned char base_u8_recursive_t;
+typedef base_u8_recursive_t mid_u8_recursive_t;
+typedef mid_u8_recursive_t top_u8_recursive_t;
+
+/* Struct recursive typedef chains */
+struct recursive_struct_test { int value; };
+typedef struct recursive_struct_test base_recursive_struct_t;
+typedef base_recursive_struct_t top_recursive_struct_t;
+
+/* Functions with recursive typedefs - MUST have same type IDs as canonical 
forms */
+extern void func_u8_recursive_chain(u8_recursive param);          /* 
u8_recursive -> __u8_recursive -> unsigned char */
+extern void func_u8_recursive_mid(__u8_recursive param);          /* 
__u8_recursive -> unsigned char */
+extern void func_u8_recursive_base(unsigned char param);          /* unsigned 
char (baseline) */
+
+extern void func_u32_recursive_chain(u32_recursive param);        /* 
u32_recursive -> __u32_recursive -> unsigned int */
+extern void func_u32_recursive_mid(__u32_recursive param);        /* 
__u32_recursive -> unsigned int */
+extern void func_u32_recursive_base(unsigned int param);          /* unsigned 
int (baseline) */
+
+extern void func_three_level_recursive(top_u8_recursive_t param); /* top -> 
mid -> base -> unsigned char */
+extern void func_three_level_mid(mid_u8_recursive_t param);       /* mid -> 
base -> unsigned char */
+extern void func_three_level_base(base_u8_recursive_t param);     /* base -> 
unsigned char */
+extern void func_three_level_final(unsigned char param);          /* unsigned 
char (baseline) */
+
+extern void func_struct_recursive_chain(top_recursive_struct_t *param);     /* 
Should resolve to struct name */
+extern void func_struct_recursive_mid(base_recursive_struct_t *param);      /* 
Should resolve to struct name */
+extern void func_struct_recursive_original(struct recursive_struct_test 
*param); /* struct name (baseline) */
+
+void test_recursive_canonicalization(struct not_void *arg) {
+    /* Recursive typedef function pointers - should be compatible after full 
canonicalization */
+    void (*fp_u8_chain)(unsigned char) = func_u8_recursive_chain;        /* 
Should work after 2-level canonicalization */
+    void (*fp_u8_mid)(unsigned char) = func_u8_recursive_mid;            /* 
Should work after 1-level canonicalization */
+    void (*fp_u8_base)(unsigned char) = func_u8_recursive_base;          /* 
Should work normally */
+
+    void (*fp_u32_chain)(unsigned int) = func_u32_recursive_chain;       /* 
Should work after 2-level canonicalization */
+    void (*fp_u32_mid)(unsigned int) = func_u32_recursive_mid;           /* 
Should work after 1-level canonicalization */
+    void (*fp_u32_base)(unsigned int) = func_u32_recursive_base;         /* 
Should work normally */
+
+    void (*fp_three_chain)(unsigned char) = func_three_level_recursive;  /* 
Should work after 3-level canonicalization */
+    void (*fp_three_mid)(unsigned char) = func_three_level_mid;          /* 
Should work after 2-level canonicalization */
+    void (*fp_three_base)(unsigned char) = func_three_level_base;        /* 
Should work after 1-level canonicalization */
+    void (*fp_three_final)(unsigned char) = func_three_level_final;      /* 
Should work normally */
+
+    void (*fp_struct_chain)(struct recursive_struct_test *) = 
func_struct_recursive_chain;  /* Should work after canonicalization */
+    void (*fp_struct_mid)(struct recursive_struct_test *) = 
func_struct_recursive_mid;      /* Should work after canonicalization */
+    void (*fp_struct_orig)(struct recursive_struct_test *) = 
func_struct_recursive_original; /* Should work normally */
+
+    /* Use function pointers to prevent optimization */
+    if (fp_u8_chain) fp_u8_chain(0);
+    if (fp_u8_mid) fp_u8_mid(0);
+    if (fp_u8_base) fp_u8_base(0);
+    if (fp_u32_chain) fp_u32_chain(0);
+    if (fp_u32_mid) fp_u32_mid(0);
+    if (fp_u32_base) fp_u32_base(0);
+    if (fp_three_chain) fp_three_chain(0);
+    if (fp_three_mid) fp_three_mid(0);
+    if (fp_three_base) fp_three_base(0);
+    if (fp_three_final) fp_three_final(0);
+    if (fp_struct_chain) fp_struct_chain((struct recursive_struct_test *)0);
+    if (fp_struct_mid) fp_struct_mid((struct recursive_struct_test *)0);
+    if (fp_struct_orig) fp_struct_orig((struct recursive_struct_test *)0);
+}
+
+/* Recursive typedef canonicalization validation - MUST have identical type 
IDs after full canonicalization */
+
+/* u8 recursive chain - all should resolve to unsigned char */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u8_recursive_chain\n\t\.set\t__kcfi_typeid_func_u8_recursive_chain,
 0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u8_recursive_mid\n\t\.set\t__kcfi_typeid_func_u8_recursive_mid,
 0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u8_recursive_base\n\t\.set\t__kcfi_typeid_func_u8_recursive_base,
 0x54e5c0c4} } } */
+
+/* u32 recursive chain - all should resolve to unsigned int */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u32_recursive_chain\n\t\.set\t__kcfi_typeid_func_u32_recursive_chain,
 0x08e0cbf2} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u32_recursive_mid\n\t\.set\t__kcfi_typeid_func_u32_recursive_mid,
 0x08e0cbf2} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_u32_recursive_base\n\t\.set\t__kcfi_typeid_func_u32_recursive_base,
 0x08e0cbf2} } } */
+
+/* Three-level u8 recursive chain - all should resolve to unsigned char */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_three_level_recursive\n\t\.set\t__kcfi_typeid_func_three_level_recursive,
 0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_three_level_mid\n\t\.set\t__kcfi_typeid_func_three_level_mid,
 0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_three_level_base\n\t\.set\t__kcfi_typeid_func_three_level_base,
 0x54e5c0c4} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_three_level_final\n\t\.set\t__kcfi_typeid_func_three_level_final,
 0x54e5c0c4} } } */
+
+/* Struct recursive chain - all should resolve to same struct name */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_recursive_chain\n\t\.set\t__kcfi_typeid_func_struct_recursive_chain,
 0x2e72122c} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_recursive_mid\n\t\.set\t__kcfi_typeid_func_struct_recursive_mid,
 0x2e72122c} } } */
+/* { dg-final { scan-assembler 
{\t\.weak\t__kcfi_typeid_func_struct_recursive_original\n\t\.set\t__kcfi_typeid_func_struct_recursive_original,
 0x2e72122c} } } */
+
+/* Update counts to include recursive typedef tests */
+/* Note: u8/unsigned char recursive tests add 7 more occurrences (actual 
count: 9) */
+/* { dg-final { scan-assembler-times {0x54e5c0c4} 9 } } */
+
+/* Note: u32/unsigned int recursive tests add 3 more occurrences (actual 
count: 6) */
+/* { dg-final { scan-assembler-times {0x08e0cbf2} 6 } } */
+
+/* Verify struct recursive typedef canonicalization types */
+/* { dg-final { scan-tree-dump {mangled='_ZTSPFvP21recursive_struct_testE' 
typeid=0x2e72122c} kcfi0 } } */
+
+/* Struct recursive: 3 identical type IDs */
+/* { dg-final { scan-assembler-times {0x2e72122c} 3 } } */
+
diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi.exp 
b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp
new file mode 100644
index 000000000000..2aebcbe1c01b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp
@@ -0,0 +1,36 @@
+#   Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# GCC testsuite for KCFI (Kernel Control Flow Integrity) tests.
+
+# Load support procs.
+load_lib gcc-dg.exp
+
+# If a testcase doesn't have special options, use these.
+global DEFAULT_CFLAGS
+if ![info exists DEFAULT_CFLAGS] then {
+    set DEFAULT_CFLAGS ""
+}
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] \
+       "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
-- 
2.34.1


Reply via email to