On Tue Sep 22, 2026 at 3:07 AM PDT, Uros Bizjak wrote:
> On Tue, Sep 22, 2026 at 11:34 AM <[email protected]> wrote:
>>
>> From: Kyrylo Tkachov <[email protected]>
>>
>> Different switch tables can reach the same case block. The CET pass
>> inserts an ENDBR for each table, although the destination needs only one.
>> This also affects dispatches that share a table.
>>
>> Reuse an existing ENDBR after the destination label. The x86 table-sharing
>> hook remains disabled.
>>
>> The regression test has two ordinary tables with eight common destinations.
>> It emits 17 ENDBR instructions before the fix and 9 after it, in both
>> 32-bit and 64-bit code. Both outputs assemble. A 64-bit runtime check
>> passes on x86_64.
>>
>> This patch was written by an AI agent as I'm not familiar enough with
>> the x86 internals and can't vouch for it. But if the x86 maintainers
>> want it I'm happy to push it.
>>
>> Assisted-by: OpenAI Codex 5.6 Sol
>>
>> gcc/ChangeLog:
>>
>> * config/i386/i386-features.cc
>> (rest_of_insert_endbr_and_patchable_area): Reuse an existing
>> ENDBR at a switch destination.
>>
>> gcc/testsuite/ChangeLog:
>>
>> * gcc.target/i386/cet-switch-4.c: New test.
>>
>> Cc: Uros Bizjak <[email protected]>
>> Signed-off-by: Kyrylo Tkachov <[email protected]>
>> ---
>> gcc/config/i386/i386-features.cc | 4 ++
>> gcc/testsuite/gcc.target/i386/cet-switch-4.c | 56 ++++++++++++++++++++
>> 2 files changed, 60 insertions(+)
>> create mode 100644 gcc/testsuite/gcc.target/i386/cet-switch-4.c
>>
>> diff --git a/gcc/config/i386/i386-features.cc
>> b/gcc/config/i386/i386-features.cc
>> index 1d26aa0c336..7c992eadf3c 100644
>> --- a/gcc/config/i386/i386-features.cc
>> +++ b/gcc/config/i386/i386-features.cc
>> @@ -3274,6 +3274,10 @@ rest_of_insert_endbr_and_patchable_area (bool
>> need_endbr,
>> dest_blk = e->dest;
>> insn = BB_HEAD (dest_blk);
>> gcc_assert (LABEL_P (insn));
>> + rtx_insn *next = next_nonnote_nondebug_insn (insn);
>> + if (next && INSN_P (next)
>> + && recog_memoized (next) == CODE_FOR_nop_endbr)
>
> The condition should read:
>
> if (next && NONJUMP_INSN_P (next)
NONDEBUG_INSN_P (next)?
> && GET_CODE (PATTERN (next)) == UNSPEC_VOLATILE
> && XINT (PATTERN (next), 1) == UNSPECV_NOP_ENDBR)
>
> IMO, this should prevent duplicate ENDBR insns during any reorg. CC
> Hongtao and HJ.
Yes, it's a nice optimization.
>
> Uros.
>
>> + continue;
>> endbr = gen_nop_endbr ();
>> emit_insn_after (endbr, insn);
>> }
>> diff --git a/gcc/testsuite/gcc.target/i386/cet-switch-4.c
>> b/gcc/testsuite/gcc.target/i386/cet-switch-4.c
>> new file mode 100644
>> index 00000000000..53cb960f0d5
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/i386/cet-switch-4.c
>> @@ -0,0 +1,56 @@
>> +/* PR middle-end/126236 */
>> +/* { dg-do compile } */
>> +/* { dg-options "-O2 -fcf-protection=branch -mcet-switch -fno-pie -fno-pic"
>> } */
>> +/* { dg-final { scan-assembler-times "endbr32" 9 { target ia32 } } } */
>> +/* { dg-final { scan-assembler-times "endbr64" 9 { target { ! ia32 } } } }
>> */
>> +/* { dg-final { scan-assembler-times "\[ \t]+jmp\[ \t]+\[*]" 2 } } */
>> +
>> +/* Both tables reach the same eight destinations. */
>> +
>> +void f0 (void);
>> +void f1 (void);
>> +void f2 (void);
>> +void f3 (void);
>> +void f4 (void);
>> +void f5 (void);
>> +void f6 (void);
>> +void f7 (void);
>> +
>> +void
>> +foo (unsigned x, int which)
>> +{
>> + if (which)
>> + switch (x)
>> + {
>> + case 0: goto a;
>> + case 1: goto b;
>> + case 2: goto c;
>> + case 3: goto d;
>> + case 4: goto e;
>> + case 5: goto f;
>> + case 6: goto g;
>> + case 7: goto h;
>> + default: return;
>> + }
>> + else
>> + switch (x)
>> + {
>> + case 0: goto h;
>> + case 1: goto g;
>> + case 2: goto f;
>> + case 3: goto e;
>> + case 4: goto d;
>> + case 5: goto c;
>> + case 6: goto b;
>> + case 7: goto a;
>> + default: return;
>> + }
>> +a: f0 (); return;
>> +b: f1 (); return;
>> +c: f2 (); return;
>> +d: f3 (); return;
>> +e: f4 (); return;
>> +f: f5 (); return;
>> +g: f6 (); return;
>> +h: f7 (); return;
>> +}
>> --
>> 2.50.1 (Apple Git-155)
>>