Re: [PATCH 1/1] forwprop: Extend memcmp forwprop inlining to the general case
Andrea Pinski <[email protected]> Sun, 2 Aug 2026 02:01:06 -0700
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CALvbMcBeye19qNtaPf9jNkiuWdzDenVGZOTvqdH9Zh+XdcD8Sg@mail.gmail.com> |
On Fri, Jul 31, 2026 at 5:08 AM Tudor-Stefan Magirescu <[email protected]> wrote: > > Extend simplify_builtin_memcmp to inline memcmp calls regardless of how > the result is used, not just when the result is compared with zero for > equality. The call is inlined when the length argument is a small constant > power-of-two such that the loaded arguments fit in a machine word, emitting > a branchless signed comparison sequence instead. > > On little-endian targets, the loaded value must be byte-swapped before > comparison. The hook TARGET_MEMCMP_INLINE_USING_BSWAP_P gates this > optimization. It defaults to false and should be overridden by targets with > an efficient byte-swap instruction. x86 and AArch64 override the hook to > enable the optimization. Instead of adding a new target hook. I think it is just better to look to see if there is byteswap optab for that mode. That is just `can_open_code_p (bswap_optab, mode)`. This should simplify the code here too. Thanks, Andrea > > On big-endian targets, no byte-swap is required and the optimization is > enabled unconditionally when the size is appropriate. > > Bootstrapped and tested on x86_64-linux-gnu. > > gcc/ChangeLog: > > * config/aarch64/aarch64.cc (aarch64_memcmp_inline_using_bswap_p): New function. > (TARGET_MEMCMP_INLINE_USING_BSWAP_P): New macro. > * config/i386/i386.cc (ix86_memcmp_inline_using_bswap_p): New function. > (TARGET_MEMCMP_INLINE_USING_BSWAP_P): New macro. > * doc/tm.texi: Regenerate. > * doc/tm.texi.in: Add hook TARGET_MEMCMP_INLINE_USING_BSWAP_P. > * target.def (): New hook. > * tree-ssa-forwprop.cc (create_builtin_bswap): New function. > (simplify_builtin_memcmp): Generalize memcmp inlining beyond equality with 0. > > gcc/testsuite/ChangeLog: > > * gcc.dg/builtin-stringop-chk-8.c: Adjust for new memcmp inlining. > * gcc.target/aarch64/memcmp-rev-1.c: New test. > * gcc.target/i386/memcmp-bswap-1.c: New test. > * gcc.target/i386/memcmp-bswap-2.c: New test. > * gcc.target/powerpc/memcmp-inline-1.c: New test. > > Signed-off-by: Tudor-Stefan Magirescu <[email protected]> > --- > gcc/config/aarch64/aarch64.cc | 19 +++++ > gcc/config/i386/i386.cc | 22 +++++ > gcc/doc/tm.texi | 7 ++ > gcc/doc/tm.texi.in | 2 + > gcc/target.def | 9 ++ > gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c | 2 +- > .../gcc.target/aarch64/memcmp-rev-1.c | 26 ++++++ > .../gcc.target/i386/memcmp-bswap-1.c | 14 ++++ > .../gcc.target/i386/memcmp-bswap-2.c | 13 +++ > .../gcc.target/powerpc/memcmp-inline-1.c | 24 ++++++ > gcc/tree-ssa-forwprop.cc | 84 +++++++++++++++++-- > 11 files changed, 213 insertions(+), 9 deletions(-) > create mode 100644 gcc/testsuite/gcc.target/aarch64/memcmp-rev-1.c > create mode 100644 gcc/testsuite/gcc.target/i386/memcmp-bswap-1.c > create mode 100644 gcc/testsuite/gcc.target/i386/memcmp-bswap-2.c > create mode 100644 gcc/testsuite/gcc.target/powerpc/memcmp-inline-1.c > > diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc > index a1f91dd425e..4785cae9234 100644 > --- a/gcc/config/aarch64/aarch64.cc > +++ b/gcc/config/aarch64/aarch64.cc > @@ -30818,6 +30818,22 @@ aarch64_promoted_type (const_tree t) > return NULL_TREE; > } > > +/* Implement the TARGET_MEMCMP_INLINE_USING_BSWAP_P hook. */ > + > +static bool > +aarch64_memcmp_inline_using_bswap_p (machine_mode mode) > +{ > + switch (mode) > + { > + case HImode: > + case SImode: > + case DImode: > + return true; > + default: > + return false; > + } > +} > + > /* Implement the TARGET_OPTAB_SUPPORTED_P hook. */ > > static bool > @@ -34373,6 +34389,9 @@ aarch64_libgcc_floating_mode_supported_p > #undef TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA > #define TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA aarch64_output_addr_const_extra > > +#undef TARGET_MEMCMP_INLINE_USING_BSWAP_P > +#define TARGET_MEMCMP_INLINE_USING_BSWAP_P aarch64_memcmp_inline_using_bswap_p > + > #undef TARGET_OPTAB_SUPPORTED_P > #define TARGET_OPTAB_SUPPORTED_P aarch64_optab_supported_p > > diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc > index 23f65ab401d..94c297131f3 100644 > --- a/gcc/config/i386/i386.cc > +++ b/gcc/config/i386/i386.cc > @@ -11419,6 +11419,24 @@ ix86_use_by_pieces_infrastructure_p (unsigned HOST_WIDE_INT size, > return default_use_by_pieces_infrastructure_p (size, align, op, > speed_p); > } > + > +/* Implement the TARGET_MEMCMP_INLINE_USING_BSWAP_P hook. */ > + > +static bool > +ix86_memcmp_inline_using_bswap_p (machine_mode mode) > +{ > + switch (mode) > + { > + case HImode: > + return true; > + case SImode: > + return TARGET_BSWAP; > + case DImode: > + return TARGET_BSWAP && TARGET_64BIT; > + default: > + return false; > + } > +} > > /* Allow {LABEL | SYMBOL}_REF - SYMBOL_REF-FOR-PICBASE for Mach-O as > this is used for to form addresses to local data when -fPIC is in > @@ -28563,6 +28581,10 @@ static const scoped_attribute_specs *const ix86_attribute_table[] = > #define TARGET_USE_BY_PIECES_INFRASTRUCTURE_P \ > ix86_use_by_pieces_infrastructure_p > > +#undef TARGET_MEMCMP_INLINE_USING_BSWAP_P > +#define TARGET_MEMCMP_INLINE_USING_BSWAP_P \ > + ix86_memcmp_inline_using_bswap_p > + > #undef TARGET_OVERLAP_OP_BY_PIECES_P > #define TARGET_OVERLAP_OP_BY_PIECES_P hook_bool_void_true > > diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi > index 85e17525683..c28a195cac7 100644 > --- a/gcc/doc/tm.texi > +++ b/gcc/doc/tm.texi > @@ -7177,6 +7177,13 @@ The hook must return true whenever @code{STRICT_ALIGNMENT} is true. > The default implementation returns @code{STRICT_ALIGNMENT}. > @end deftypefn > > +@deftypefn {Target Hook} bool TARGET_MEMCMP_INLINE_USING_BSWAP_P (machine_mode @var{mode}) > +Return true if @code{memcmp} calls for blocks of @var{mode} size > +should be inlined using a load, byte-swap and integer comparison > +sequence. This is only profitable on targets with an efficient > +byte-swap instruction. The default is false. > +@end deftypefn > + > @defmac MOVE_RATIO (@var{speed}) > The threshold of number of scalar memory-to-memory move insns, @emph{below} > which a sequence of insns should be generated instead of a > diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in > index 1a9edd0635d..aa94c616d46 100644 > --- a/gcc/doc/tm.texi.in > +++ b/gcc/doc/tm.texi.in > @@ -4653,6 +4653,8 @@ other fields in the same word of the structure, but to different bytes. > > @hook TARGET_SLOW_UNALIGNED_ACCESS > > +@hook TARGET_MEMCMP_INLINE_USING_BSWAP_P > + > @defmac MOVE_RATIO (@var{speed}) > The threshold of number of scalar memory-to-memory move insns, @emph{below} > which a sequence of insns should be generated instead of a > diff --git a/gcc/target.def b/gcc/target.def > index 884fe1bd57e..065b8b0663e 100644 > --- a/gcc/target.def > +++ b/gcc/target.def > @@ -4005,6 +4005,15 @@ The default implementation returns @code{STRICT_ALIGNMENT}.", > bool, (machine_mode mode, unsigned int align), > default_slow_unaligned_access) > > +DEFHOOK > +(memcmp_inline_using_bswap_p, > + "Return true if @code{memcmp} calls for blocks of @var{mode} size\n\ > +should be inlined using a load, byte-swap and integer comparison\n\ > +sequence. This is only profitable on targets with an efficient\n\ > +byte-swap instruction. The default is false.", > + bool, (machine_mode mode), > + hook_bool_mode_false) > + > DEFHOOK > (optab_supported_p, > "Return true if the optimizers should use optab @var{op} with\n\ > diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c > index f2e9c481172..bd3cbb64bbd 100644 > --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c > +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c > @@ -78,7 +78,7 @@ void test_memop_warn_local (void *p, const void *q) > memchr ("123", i, 5); /* { dg-warning "specified bound 5 exceeds source size 4" "memchr" } */ > memchr (a, i, sizeof a + 1); /* { dg-warning "specified bound 5 exceeds source size 4" "memchr" } */ > > - memcmp (p, "", 2); /* { dg-warning "specified bound 2 exceeds source size 1" "memcmp" } */ > + memcmp (p, "", 3); /* { dg-warning "specified bound 3 exceeds source size 1" "memcmp" } */ > memcmp (p, "123", 5); /* { dg-warning "specified bound 5 exceeds source size 4" "memcmp" } */ > memcmp (p, a, sizeof a + 1); /* { dg-warning "specified bound 5 exceeds source size 4" "memcmp" } */ > > diff --git a/gcc/testsuite/gcc.target/aarch64/memcmp-rev-1.c b/gcc/testsuite/gcc.target/aarch64/memcmp-rev-1.c > new file mode 100644 > index 00000000000..bc3212d79e5 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/aarch64/memcmp-rev-1.c > @@ -0,0 +1,26 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +#include <string.h> > + > +_Bool > +cmp16 (const void* a, const void* b) > +{ > + return memcmp (a, b, 2) < 0; > +} > + > +_Bool > +cmp32 (const void* a, const void* b) > +{ > + return memcmp (a, b, 4) > 0; > +} > + > +int > +cmp64 (const void* a, const void* b) > +{ > + return memcmp (a, b, 8); > +} > + > +/* { dg-final { scan-assembler-times "rev16" 2 } } */ > +/* { dg-final { scan-assembler-times "rev\t" 4 } } */ > +/* { dg-final { scan-assembler-not "bl.*memcmp" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/memcmp-bswap-1.c b/gcc/testsuite/gcc.target/i386/memcmp-bswap-1.c > new file mode 100644 > index 00000000000..7646c01ce1c > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/memcmp-bswap-1.c > @@ -0,0 +1,14 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > +/* { dg-skip-if "" { *-*-* } { "-march=i386" } } */ > + > +#include <string.h> > + > +_Bool > +cmp32 (const void* a, const void* b) > +{ > + return memcmp (a, b, 4) < 0; > +} > + > +/* { dg-final { scan-assembler-times "\tbswap\t%e" 2 } } */ > +/* { dg-final { scan-assembler-not "call.*memcmp" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/memcmp-bswap-2.c b/gcc/testsuite/gcc.target/i386/memcmp-bswap-2.c > new file mode 100644 > index 00000000000..dadb1153896 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/memcmp-bswap-2.c > @@ -0,0 +1,13 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -march=x86-64" } */ > + > +#include <string.h> > + > +_Bool > +cmp64 (const void* a, const void* b) > +{ > + return memcmp (a, b, 8) < 0; > +} > + > +/* { dg-final { scan-assembler-times "\tbswap\t%r" 2 } } */ > +/* { dg-final { scan-assembler-not "call.*memcmp" } } */ > diff --git a/gcc/testsuite/gcc.target/powerpc/memcmp-inline-1.c b/gcc/testsuite/gcc.target/powerpc/memcmp-inline-1.c > new file mode 100644 > index 00000000000..53da98ae23e > --- /dev/null > +++ b/gcc/testsuite/gcc.target/powerpc/memcmp-inline-1.c > @@ -0,0 +1,24 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +#include <string.h> > + > +_Bool > +cmp16 (const void* a, const void* b) > +{ > + return memcmp (a, b, 2) < 0; > +} > + > +_Bool > +cmp32 (const void* a, const void* b) > +{ > + return memcmp (a, b, 4) > 0; > +} > + > +int > +cmp64 (const void* a, const void* b) > +{ > + return memcmp (a, b, 8); > +} > + > +/* { dg-final { scan-assembler-not "bl.*memcmp" } } */ > diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc > index 12c07c99c9b..f16adac76c2 100644 > --- a/gcc/tree-ssa-forwprop.cc > +++ b/gcc/tree-ssa-forwprop.cc > @@ -1981,6 +1981,31 @@ do_simple_agr_dse (gassign *stmt, bool full_walk) > } > } > > +/* Return the bswap builtin for LEN bits, or NULL_TREE if the target > + cannot expand it. */ > + > +static tree > +create_builtin_bswap (unsigned HOST_WIDE_INT len) > +{ > + switch (len) > + { > + case 16: > + return (builtin_decl_explicit_p (BUILT_IN_BSWAP16) > + && can_open_code_p (bswap_optab, HImode)) > + ? builtin_decl_explicit (BUILT_IN_BSWAP16) : NULL_TREE; > + case 32: > + return (builtin_decl_explicit_p (BUILT_IN_BSWAP32) > + && can_open_code_p (bswap_optab, SImode)) > + ? builtin_decl_explicit (BUILT_IN_BSWAP32) : NULL_TREE; > + case 64: > + return (builtin_decl_explicit_p (BUILT_IN_BSWAP64) > + && can_open_code_p (bswap_optab, DImode)) > + ? builtin_decl_explicit (BUILT_IN_BSWAP64) : NULL_TREE; > + default: > + return NULL_TREE; > + } > +} > + > /* Optimizes builtin memcmps for small constant sizes. > GSI_P is the GSI for the call. STMT is the call itself. > */ > @@ -2006,8 +2031,9 @@ simplify_builtin_memcmp (gimple_stmt_iterator *gsi_p, gcall *stmt) > equality comparison to zero. */ > tree res = gimple_call_lhs (stmt); > > - if (!res || !use_in_zero_equality (res)) > + if (!res) > return false; > + bool use_in_zero_eq = use_in_zero_equality (res); > > unsigned HOST_WIDE_INT leni; > > @@ -2046,16 +2072,58 @@ simplify_builtin_memcmp (gimple_stmt_iterator *gsi_p, gcall *stmt) > tree tem2 = fold_const_aggregate_ref (arg2); > if (tem2) > arg2 = tem2; > - res = fold_convert_loc (loc, TREE_TYPE (res), > - fold_build2_loc (loc, NE_EXPR, > - boolean_type_node, > - arg1, arg2)); > - gimplify_and_update_call_from_tree (gsi_p, res); > - return true; > + > + if (use_in_zero_eq) > + { > + res = fold_convert_loc (loc, TREE_TYPE (res), > + fold_build2_loc (loc, NE_EXPR, > + boolean_type_node, > + arg1, arg2)); > + gimplify_and_update_call_from_tree (gsi_p, res); > + return true; > + } > + > + tree bswap_decl = NULL_TREE; > + bool bytes_reversal_required > + = !BYTES_BIG_ENDIAN && (leni > CHAR_TYPE_SIZE); > + if (!bytes_reversal_required > + || ((bswap_decl = create_builtin_bswap (leni)) > + && targetm.memcmp_inline_using_bswap_p (mode))) > + { > + if (bytes_reversal_required) > + { > + /* save_expr ensures bswap calls are not duplicated > + during gimplification. */ > + arg1 = save_expr (build_call_expr_loc (loc, bswap_decl, > + 1, arg1)); > + arg2 = save_expr (build_call_expr_loc (loc, bswap_decl, > + 1, arg2)); > + } > + tree cmp_lt > + = fold_convert_loc (loc, TREE_TYPE (res), > + build2_loc (loc, LT_EXPR, > + boolean_type_node, > + arg1, arg2)); > + tree cmp_gt > + = fold_convert_loc (loc, TREE_TYPE (res), > + build2_loc (loc, GT_EXPR, > + boolean_type_node, > + arg1, arg2)); > + res = fold_build2_loc (loc, MINUS_EXPR, TREE_TYPE (res), > + cmp_gt, cmp_lt); > + gimplify_and_update_call_from_tree (gsi_p, res); > + return true; > + } > + > + return false; > } > } > > - /* Replace memcmp with memcmp_eq if the above fails. */ > + if (!use_in_zero_eq) > + return false; > + > + /* Fall back to replacing memcmp with memcmp_eq if the result is only used in > + a comparison to zero and the above inlining attempt did not apply. */ > if (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) == BUILT_IN_MEMCMP_EQ) > return false; > if (!fold_before_rtl_expansion_p ()) > -- > 2.43.0 >