match.pd: Defer some CTZ/CLZ foldings until after ubsan pass for -fsanitize=builtin [PR115127]

As the following testcase shows, -fsanitize=builtin instruments the
builtins in the ubsan pass which is done shortly after going into
SSA, but if optimizations optimize the builtins away before that,
nothing is instrumented.  Now, I think it is just fine if the
result of the builtins isn't used in any way and we just DCE them,
but in the following optimizations the result is used.
So, the following patch for -fsanitize=builtin only defers the
optimizations that might turn single argument CLZ/CTZ (aka undefined
at zero) until the ubsan pass is done.
Now, we don't have PROP_ubsan and am not sure it is worth adding it,
there is PROP_ssa set by the ssa pass which is 3 passes before
ubsan, but there are only 2 warning passes in between, so PROP_ssa
looked good enough to me.

2024-12-12  Jakub Jelinek  <jakub@redhat.com>

	PR sanitizer/115127
	* match.pd (clz (X) == C, ctz (X) == C, ctz (X) >= C): Don't
	optimize if -fsanitize=builtin and not yet in SSA form.

	* c-c++-common/ubsan/builtin-2.c: New test.
This commit is contained in:
Jakub Jelinek 2024-12-12 19:47:46 +01:00 committed by Jakub Jelinek
parent 2cbb2408a8
commit f7d1b9cdc0
2 changed files with 122 additions and 10 deletions

View file

@ -9636,13 +9636,17 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
cmp (lt ge)
(simplify
(op (clz:s@2 @0) INTEGER_CST@1)
(if (integer_zerop (@1) && single_use (@2))
/* clz(X) == 0 is (int)X < 0 and clz(X) != 0 is (int)X >= 0. */
(with { tree stype = signed_type_for (TREE_TYPE (@0)); }
(cmp (convert:stype @0) { build_zero_cst (stype); }))
/* clz(X) == (prec-1) is X == 1 and clz(X) != (prec-1) is X != 1. */
(if (wi::to_wide (@1) == TYPE_PRECISION (TREE_TYPE (@0)) - 1)
(op @0 { build_one_cst (TREE_TYPE (@0)); }))))))
(if (!sanitize_flags_p (SANITIZE_BUILTIN)
/* For -fsanitize=builtin give ubsan pass a chance
to instrument it first. */
|| (cfun && (cfun->curr_properties & PROP_ssa) != 0))
(if (integer_zerop (@1) && single_use (@2))
/* clz(X) == 0 is (int)X < 0 and clz(X) != 0 is (int)X >= 0. */
(with { tree stype = signed_type_for (TREE_TYPE (@0)); }
(cmp (convert:stype @0) { build_zero_cst (stype); }))
/* clz(X) == (prec-1) is X == 1 and clz(X) != (prec-1) is X != 1. */
(if (wi::to_wide (@1) == TYPE_PRECISION (TREE_TYPE (@0)) - 1)
(op @0 { build_one_cst (TREE_TYPE (@0)); })))))))
(for op (eq ne)
cmp (lt ge)
(simplify
@ -9682,7 +9686,13 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(op (ctz:s @0) INTEGER_CST@1)
(with { bool ok = true;
HOST_WIDE_INT val = 0;
if (!tree_fits_shwi_p (@1))
if (sanitize_flags_p (SANITIZE_BUILTIN)
/* For -fsanitize=builtin give ubsan pass a chance
to instrument it first. */
&& (!cfun
|| (cfun->curr_properties & PROP_ssa) == 0))
ok = false;
else if (!tree_fits_shwi_p (@1))
ok = false;
else
{
@ -9713,8 +9723,15 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(op (ctz:s @0) INTEGER_CST@1)
(with { tree type0 = TREE_TYPE (@0);
int prec = TYPE_PRECISION (type0);
bool ok = true;
if (sanitize_flags_p (SANITIZE_BUILTIN)
/* For -fsanitize=builtin give ubsan pass a chance
to instrument it first. */
&& (!cfun
|| (cfun->curr_properties & PROP_ssa) == 0))
ok = false;
}
(if (prec <= MAX_FIXED_MODE_SIZE)
(if (ok && prec <= MAX_FIXED_MODE_SIZE)
(if (tree_int_cst_sgn (@1) < 0 || wi::to_widest (@1) >= prec)
{ constant_boolean_node (op == EQ_EXPR ? false : true, type); }
(op (bit_and @0 { wide_int_to_tree (type0,
@ -9815,7 +9832,13 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
else if (TYPE_PRECISION (type0)
== TYPE_PRECISION (long_long_unsigned_type_node))
cfn = CFN_BUILT_IN_CTZLL;
} }
}
if (sanitize_flags_p (SANITIZE_BUILTIN)
/* For -fsanitize=builtin give ubsan pass a chance
to instrument it first. */
&& (!cfun
|| (cfun->curr_properties & PROP_ssa) == 0))
cfn = CFN_LAST; }
(if (cfn == CFN_CTZ)
(IFN_CTZ (convert:type0 @0))
(if (cfn == CFN_BUILT_IN_CTZ)

View file

@ -0,0 +1,89 @@
/* PR sanitizer/115127 */
/* { dg-do run } */
/* { dg-options "-fsanitize=undefined" } */
#include <stdio.h>
__attribute__((noipa)) int
f1 (unsigned a)
{
return __builtin_clz (a) == 0;
}
__attribute__((noipa)) int
f2 (unsigned long a)
{
return __builtin_clzl (a) != 0;
}
__attribute__((noipa)) int
f3 (unsigned long long a)
{
return __builtin_clzll (a) == __CHAR_BIT__ * __SIZEOF_LONG_LONG__ - 1;
}
__attribute__((noipa)) int
f4 (unsigned a)
{
return __builtin_clz (a) != __CHAR_BIT__ * __SIZEOF_INT__ - 1;
}
__attribute__((noipa)) int
f5 (unsigned long a)
{
return __builtin_ctzl (a) == 0;
}
__attribute__((noipa)) int
f6 (unsigned long long a)
{
return __builtin_ctzll (a) != 0;
}
__attribute__((noipa)) int
f7 (unsigned a)
{
return __builtin_ctz (a) == 4;
}
__attribute__((noipa)) int
f8 (unsigned long a)
{
return __builtin_ctzl (a) != 4;
}
__attribute__((noipa)) int
f9 (unsigned long long a)
{
return __builtin_ctzll (a) >= 4;
}
__attribute__((noipa)) int
f10 (unsigned a)
{
return __builtin_ctz (a) < 4;
}
int
main ()
{
fprintf (stderr, "FOO MARKER1\n");
f1 (0);
f2 (0);
f3 (0);
f4 (0);
fprintf (stderr, "FOO MARKER2\n");
f5 (0);
f6 (0);
f7 (0);
f8 (0);
f9 (0);
f10 (0);
fprintf (stderr, "FOO MARKER3\n");
}
/* { dg-output "FOO MARKER1(\n|\r\n|\r)" } */
/* { dg-output "(\[^\n\r]*runtime error: passing zero to __builtin_clz\\\(\\\), which is not a valid argument\[^\n\r]*(\n|\r\n|\r)){4}" } */
/* { dg-output "FOO MARKER2(\n|\r\n|\r)" } */
/* { dg-output "(\[^\n\r]*runtime error: passing zero to __builtin_ctz\\\(\\\), which is not a valid argument\[^\n\r]*(\n|\r\n|\r)){6}" } */
/* { dg-output "FOO MARKER3" } */