[v2,3/4] stdlib: Remove if inline asm context casts if compiler does not support it

Message ID 20221102145559.1962008-4-adhemerval.zanella@linaro.org
State Dropped
Headers
Series Initial fixes for clang build support |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Adhemerval Zanella Nov. 2, 2022, 2:55 p.m. UTC
  The inline asm constraints lvalue casts on longlong.h are not fully
supported on all compilers.  The clang rejects the code by default:

  error: invalid use of a cast in a inline asm context requiring an
  lvalue: remove the cast or build with -fheinous-gnu-extensions

And even using -fheinous-gnu-extensions it does not seem to be a long-term
solution, since clang accepts it but still throws an warning:

  warning: invalid use of a cast in an inline asm context requiring an
  lvalue: accepted due to -fheinous-gnu-extensions, but clang may remove
  support for  this in the future

And clang also does not provide a a -Wno-heinous-gnu-extensions to suppress
it.

For GCC the cast acts as a limited form of typechecking where if the cast
can be removed, then the output operand had a type of the proper width,
otherwise compiler issue an error.  That's the main reason why the casts
are still kept in GCC [1], although there are a long-standing bug report
to actually remove them [2].

To enable longlong.h support on clang this patch uses a simple mechanism
to remove casts them if compiler does not fully support it.  The script
uses simple string substitution to remove the cast if the asm constraint
is found.  For GCC it should be a no-op, the longlong.h is copies as-is.

Checked on a build for all affected ABIs.

[1] https://gcc.gnu.org/pipermail/gcc-patches/2021-October/581722.html
[2] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=2803
---
 Makerules                            |  13 ++++
 configure                            |  29 ++++++++
 configure.ac                         |  22 ++++++
 scripts/filter-asm-inline-cast.py    | 102 +++++++++++++++++++++++++++
 stdlib/{longlong.h => longlong.h.in} |   0
 stdlib/strtod_l.c                    |   2 +-
 6 files changed, 167 insertions(+), 1 deletion(-)
 create mode 100755 scripts/filter-asm-inline-cast.py
 rename stdlib/{longlong.h => longlong.h.in} (100%)
  

Patch

diff --git a/Makerules b/Makerules
index 09c0cf8357..1ca16bf080 100644
--- a/Makerules
+++ b/Makerules
@@ -351,6 +351,19 @@  $(common-objpfx)$(lib-names-stmp-abi): $(..)scripts/lib-names.awk \
 endif
 common-generated += $(lib-names-h-abi) $(lib-names-stmp-abi)
 endif
+
+# The inline asm constraints lvalue casts on longlong.h are not fully
+# supported on all compilers.  However, the cast acts as a limited form of
+# typechecking where if the cast can be removed, then the output operand
+# had a type of the proper width, otherwise compiler issue an error.
+# For the case compiler does not fully support the script tries to remove
+# it.
+before-compile += $(common-objpfx)stdlib/longlong.h
+$(common-objpfx)stdlib/longlong.h: $(..)stdlib/longlong.h.in $(..)scripts/filter-asm-inline-cast.py
+	$(make-target-directory)
+	$(PYTHON) $(..)scripts/filter-asm-inline-cast.py --filter $(config-remove-cast-inline-asm) $< $@.tmp
+	mv -f $@.tmp $@
+common-generated += $(common-objpfx)stdlib/longlong.h
 
 ###############################################################################
 # NOTE!  Everything adding to before-compile needs to come before this point! #
diff --git a/configure b/configure
index e23ea95a49..045a51cfba 100755
--- a/configure
+++ b/configure
@@ -6376,6 +6376,35 @@  $as_echo "$libc_cv_wno_ignored_attributes" >&6; }
 config_vars="$config_vars
 config-cflags-wno-ignored-attributes = $libc_cv_wno_ignored_attributes"
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if cast in inline asm context is supported" >&5
+$as_echo_n "checking if cast in inline asm context is supported... " >&6; }
+if ${libc_cv_cast_inline_asm_context+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat > conftest.c <<EOF
+void foo (void)
+{
+  int i;
+  __asm ("" : "=r" ((unsigned int) i));
+}
+EOF
+libc_cv_remove_cast_inline_asm=no
+if ! { ac_try='${CC-cc} $CFLAGS $CPPFLAGS -Werror -c conftest.c'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then
+  libc_cv_remove_cast_inline_asm=yes
+fi
+rm -f conftest.*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libc_cv_cast_inline_asm_context" >&5
+$as_echo "$libc_cv_cast_inline_asm_context" >&6; }
+config_vars="$config_vars
+config-remove-cast-inline-asm = $libc_cv_remove_cast_inline_asm"
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether cc puts quotes around section names" >&5
 $as_echo_n "checking whether cc puts quotes around section names... " >&6; }
 if ${libc_cv_have_section_quotes+:} false; then :
diff --git a/configure.ac b/configure.ac
index 7275b53345..238a7267ef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1426,6 +1426,28 @@  rm -f conftest*])
 LIBC_CONFIG_VAR([config-cflags-wno-ignored-attributes],
 		[$libc_cv_wno_ignored_attributes])
 
+dnl clang emits and error for cast usage in inline asm context and although
+dnl -fheinous-gnu-extensions option allows to just instruct it to emit an
+dnl warning, clang also explicit warns that the option might be removed in
+dnl the future.
+AC_CACHE_CHECK([if cast in inline asm context is supported],
+	       libc_cv_cast_inline_asm_context, [dnl
+cat > conftest.c <<EOF
+void foo (void)
+{
+  int i;
+  __asm ("" : "=r" ((unsigned int) i));
+}
+EOF
+libc_cv_remove_cast_inline_asm=no
+if ! AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS -Werror -c conftest.c])
+then
+  libc_cv_remove_cast_inline_asm=yes
+fi
+rm -f conftest.*])
+LIBC_CONFIG_VAR([config-remove-cast-inline-asm],
+		[$libc_cv_remove_cast_inline_asm])
+
 AC_CACHE_CHECK(whether cc puts quotes around section names,
 	       libc_cv_have_section_quotes,
 	       [cat > conftest.c <<EOF
diff --git a/scripts/filter-asm-inline-cast.py b/scripts/filter-asm-inline-cast.py
new file mode 100755
index 0000000000..319751e7f2
--- /dev/null
+++ b/scripts/filter-asm-inline-cast.py
@@ -0,0 +1,102 @@ 
+#!/usr/bin/python3
+# Filter out inline asm casts.
+# Copyright (C) 2018-2022 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+import argparse
+import sys
+import re
+
+# Each asm constraint where the cast occurs.
+CONSTRAINT_LIST=(
+    '\"%0\"',
+    '\"%1\"',
+    '\"%dI\"'
+    '\"%rJ\"',
+    '\"%r\"',
+    '\"0\"',
+    '\"1\"',
+    '\"=&g\"',
+    '\"=&r\"',
+    '\"=a\"',
+    '\"=d\"',
+    '\"=g\"',
+    '\"=r\"',
+    '\"dI\"',
+    '\"g\"',
+    '\"rICal\"',
+    '\"rI\"',
+    '\"rJ\"',
+    '\"%rJ\"',
+    '\"rM\"',
+    '\"rQR\"',
+    '\"r\"',
+    '\"rm\"',
+    '\"rme\"',
+    '\"x\"',
+    '\"d\"',
+    '\"=&d\"',
+    '\"dmi\"',
+    '\"%rM\"',
+)
+
+# The cast type used in lvalue asm contraint input.
+REPLACE_CASTS = (
+    "(UDItype)",
+    "(USItype)",
+    "(unsigned int)",
+)
+
+def multireplace(string, repl):
+    if not repl:
+        return string
+
+    # Place longer string first, to avoid shorter substring to match first.
+    rep_sorted = sorted(repl, key=len, reverse=True)
+    rep_escaped = map(re.escape, rep_sorted)
+    pattern = re.compile("|".join(rep_escaped), 0)
+
+    return pattern.sub(lambda match: "", string)
+
+
+def get_parser():
+    def strbool(string):
+        return True if string.lower() == 'yes' else False
+
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('--filter', dest='filter',
+                        help='Filter out inoine asm casts',
+                        type=strbool)
+    parser.add_argument('input', help='Input file')
+    parser.add_argument('output', help='Ouput file')
+    return parser
+
+
+def main(argv):
+    """The main entry point."""
+    parser = get_parser()
+    opts = parser.parse_args(argv)
+    with open(opts.input, 'r') as i_file, open(opts.output, 'w') as o_file:
+        for line in i_file:
+            replace = REPLACE_CASTS \
+                if opts.filter and any(substring in line for substring in CONSTRAINT_LIST) \
+                else None
+            o_file.write(multireplace(line, replace))
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/stdlib/longlong.h b/stdlib/longlong.h.in
similarity index 100%
rename from stdlib/longlong.h
rename to stdlib/longlong.h.in
diff --git a/stdlib/strtod_l.c b/stdlib/strtod_l.c
index 3ebb491e22..fe59907cb2 100644
--- a/stdlib/strtod_l.c
+++ b/stdlib/strtod_l.c
@@ -81,7 +81,7 @@  extern double ____strtod_l_internal (const char *, char **, int, locale_t);
 #include <gmp-mparam.h>
 #include <gmp.h>
 #include "gmp-impl.h"
-#include "longlong.h"
+#include <stdlib/longlong.h>
 #include "fpioconst.h"
 
 #include <assert.h>