jit: fix __builtin_unreachable [PR 95426]
authorDavid Malcolm <dmalcolm@redhat.com>
Sun, 31 May 2020 17:28:41 +0000 (13:28 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Tue, 2 Jun 2020 22:24:47 +0000 (18:24 -0400)
PR jit/95426 reports a crash deep inside "expand" when using
__builtin_unreachable via gcc_jit_context_get_builtin_function,
due to BLOCK_FOR_INSN being erroneously used on a barrier within
rtl_verify_bb_pointers.

The root cause turns out to be that I didn't implement
LANG_HOOKS_COMMON_ATTRIBUTE_TABLE and LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
for the jit "frontend".  When building a decl for the builtin, the
libgccjit frontend generates a chain of attributes names, but when
this is passed to decl_attributes and the attributes are looked up by
namespace and name within lookup_scoped_attribute_spec, attributes_table
is empty.  Hence no attributes were being associated with the fndecl,
and so ECF_NORETURN was not set on the gimple_call (along with various
other flags missing on the decl, etc), and so the call is treated as
not terminating its BB, and so the CFG rapidly diverges from the
equivalent created by the C frontend.

This patch fixes things by implementing these langhooks, copying the
minimal attribute-handling code from LTO.  I stepped through the
creation of the fndecl and verified that with this fix it has the same
attributes as the equivalent created by the C frontend.

gcc/jit/ChangeLog:
PR jit/95426
* dummy-frontend.c: Include "options.h", "stringpool.h", and
"attribs.h".
(ATTR_EXCL): New, copied from lto/lto-lang.c.
(attr_noreturn_exclusions): Likewise.
(attr_returns_twice_exclusions): Likewise.
(attr_const_pure_exclusions): Likewise.
(jit_attribute_table): Likewise, copied from lto_attribute_table.
(jit_format_attribute_table): Likewise, copied from
lto_format_attribute_table.
(handle_noreturn_attribute): New, copied from lto/lto-lang.c.
(handle_leaf_attribute): Likewise.
(handle_const_attribute): Likewise.
(handle_malloc_attribute): Likewise.
(handle_pure_attribute): Likewise.
(handle_novops_attribute): Likewise.
(get_nonnull_operand): Likewise.
(handle_nonnull_attribute): Likewise.
(handle_nothrow_attribute): Likewise.
(handle_sentinel_attribute): Likewise.
(handle_type_generic_attribute): Likewise.
(handle_transaction_pure_attribute): Likewise.
(handle_returns_twice_attribute): Likewise.
(handle_patchable_function_entry_attribute): Likewise.
(ignore_attribute): Likewise.
(handle_format_attribute): Likewise.
(handle_format_arg_attribute): Likewise.
(handle_fnspec_attribute): Likewise.
(LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Define.
(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Define.

gcc/testsuite/ChangeLog:
PR jit/95426
* jit.dg/all-non-failing-tests.h: Add note about...
* jit.dg/test-builtin-unreachable.c: New test.

gcc/jit/dummy-frontend.c
gcc/testsuite/jit.dg/all-non-failing-tests.h
gcc/testsuite/jit.dg/test-builtin-unreachable.c [new file with mode: 0644]

index 6c7b7992a4d40b26ce3394fd83eab612cdfc6ae6..aa64a6eb7a035029b778786264a9e4567cf0ea1d 100644 (file)
@@ -26,10 +26,462 @@ along with GCC; see the file COPYING3.  If not see
 #include "langhooks.h"
 #include "langhooks-def.h"
 #include "diagnostic.h"
-
+#include "options.h"
+#include "stringpool.h"
+#include "attribs.h"
 
 #include <mpfr.h>
 
+/* Attribute handling.  */
+
+static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+static tree handle_leaf_attribute (tree *, tree, tree, int, bool *);
+static tree handle_const_attribute (tree *, tree, tree, int, bool *);
+static tree handle_malloc_attribute (tree *, tree, tree, int, bool *);
+static tree handle_pure_attribute (tree *, tree, tree, int, bool *);
+static tree handle_novops_attribute (tree *, tree, tree, int, bool *);
+static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
+static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
+static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
+static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *);
+static tree handle_transaction_pure_attribute (tree *, tree, tree, int, bool *);
+static tree handle_returns_twice_attribute (tree *, tree, tree, int, bool *);
+static tree handle_patchable_function_entry_attribute (tree *, tree, tree,
+                                                      int, bool *);
+static tree ignore_attribute (tree *, tree, tree, int, bool *);
+
+static tree handle_format_attribute (tree *, tree, tree, int, bool *);
+static tree handle_fnspec_attribute (tree *, tree, tree, int, bool *);
+static tree handle_format_arg_attribute (tree *, tree, tree, int, bool *);
+
+/* Helper to define attribute exclusions.  */
+#define ATTR_EXCL(name, function, type, variable)      \
+  { name, function, type, variable }
+
+/* Define attributes that are mutually exclusive with one another.  */
+static const struct attribute_spec::exclusions attr_noreturn_exclusions[] =
+{
+  ATTR_EXCL ("noreturn", true, true, true),
+  ATTR_EXCL ("alloc_align", true, true, true),
+  ATTR_EXCL ("alloc_size", true, true, true),
+  ATTR_EXCL ("const", true, true, true),
+  ATTR_EXCL ("malloc", true, true, true),
+  ATTR_EXCL ("pure", true, true, true),
+  ATTR_EXCL ("returns_twice", true, true, true),
+  ATTR_EXCL ("warn_unused_result", true, true, true),
+  ATTR_EXCL (NULL, false, false, false),
+};
+
+static const struct attribute_spec::exclusions attr_returns_twice_exclusions[] =
+{
+  ATTR_EXCL ("noreturn", true, true, true),
+  ATTR_EXCL (NULL, false, false, false),
+};
+
+static const struct attribute_spec::exclusions attr_const_pure_exclusions[] =
+{
+  ATTR_EXCL ("const", true, true, true),
+  ATTR_EXCL ("noreturn", true, true, true),
+  ATTR_EXCL ("pure", true, true, true),
+  ATTR_EXCL (NULL, false, false, false)
+};
+
+/* Table of machine-independent attributes supported in libgccjit.  */
+const struct attribute_spec jit_attribute_table[] =
+{
+  /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
+       affects_type_identity, handler, exclude } */
+  { "noreturn",               0, 0, true,  false, false, false,
+                             handle_noreturn_attribute,
+                             attr_noreturn_exclusions },
+  { "leaf",                  0, 0, true,  false, false, false,
+                             handle_leaf_attribute, NULL },
+  /* The same comments as for noreturn attributes apply to const ones.  */
+  { "const",                  0, 0, true,  false, false, false,
+                             handle_const_attribute,
+                             attr_const_pure_exclusions },
+  { "malloc",                 0, 0, true,  false, false, false,
+                             handle_malloc_attribute, NULL },
+  { "pure",                   0, 0, true,  false, false, false,
+                             handle_pure_attribute,
+                             attr_const_pure_exclusions },
+  { "no vops",                0, 0, true,  false, false, false,
+                             handle_novops_attribute, NULL },
+  { "nonnull",                0, -1, false, true, true, false,
+                             handle_nonnull_attribute, NULL },
+  { "nothrow",                0, 0, true,  false, false, false,
+                             handle_nothrow_attribute, NULL },
+  { "patchable_function_entry", 1, 2, true, false, false, false,
+                             handle_patchable_function_entry_attribute,
+                             NULL },
+  { "returns_twice",          0, 0, true,  false, false, false,
+                             handle_returns_twice_attribute,
+                             attr_returns_twice_exclusions },
+  { "sentinel",               0, 1, false, true, true, false,
+                             handle_sentinel_attribute, NULL },
+  { "type generic",           0, 0, false, true, true, false,
+                             handle_type_generic_attribute, NULL },
+  { "fn spec",               1, 1, false, true, true, false,
+                             handle_fnspec_attribute, NULL },
+  { "transaction_pure",              0, 0, false, true, true, false,
+                             handle_transaction_pure_attribute, NULL },
+  /* For internal use only.  The leading '*' both prevents its usage in
+     source code and signals that it may be overridden by machine tables.  */
+  { "*tm regparm",            0, 0, false, true, true, false,
+                             ignore_attribute, NULL },
+  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+};
+
+/* Give the specifications for the format attributes, used by C and all
+   descendants.  */
+
+const struct attribute_spec jit_format_attribute_table[] =
+{
+  /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
+       affects_type_identity, handler, exclude } */
+  { "format",                 3, 3, false, true,  true, false,
+                             handle_format_attribute, NULL },
+  { "format_arg",             1, 1, false, true,  true, false,
+                             handle_format_arg_attribute, NULL },
+  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+};
+
+/* Attribute handlers.  */
+
+/* Handle a "noreturn" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_noreturn_attribute (tree *node, tree ARG_UNUSED (name),
+                          tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                          bool * ARG_UNUSED (no_add_attrs))
+{
+  tree type = TREE_TYPE (*node);
+
+  if (TREE_CODE (*node) == FUNCTION_DECL)
+    TREE_THIS_VOLATILE (*node) = 1;
+  else if (TREE_CODE (type) == POINTER_TYPE
+          && TREE_CODE (TREE_TYPE (type)) == FUNCTION_TYPE)
+    TREE_TYPE (*node)
+      = build_pointer_type
+       (build_type_variant (TREE_TYPE (type),
+                            TYPE_READONLY (TREE_TYPE (type)), 1));
+  else
+    gcc_unreachable ();
+
+  return NULL_TREE;
+}
+
+/* Handle a "leaf" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_leaf_attribute (tree *node, tree name,
+                      tree ARG_UNUSED (args),
+                      int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+  if (TREE_CODE (*node) != FUNCTION_DECL)
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }
+  if (!TREE_PUBLIC (*node))
+    {
+      warning (OPT_Wattributes, "%qE attribute has no effect on unit local functions", name);
+      *no_add_attrs = true;
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle a "const" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_const_attribute (tree *node, tree ARG_UNUSED (name),
+                       tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                       bool * ARG_UNUSED (no_add_attrs))
+{
+  if (TREE_CODE (*node) != FUNCTION_DECL
+      || !fndecl_built_in_p (*node))
+    inform (UNKNOWN_LOCATION, "%s:%s: %E: %E", __FILE__, __func__, *node, name);
+
+  tree type = TREE_TYPE (*node);
+
+  /* See FIXME comment on noreturn in c_common_attribute_table.  */
+  if (TREE_CODE (*node) == FUNCTION_DECL)
+    TREE_READONLY (*node) = 1;
+  else if (TREE_CODE (type) == POINTER_TYPE
+          && TREE_CODE (TREE_TYPE (type)) == FUNCTION_TYPE)
+    TREE_TYPE (*node)
+      = build_pointer_type
+       (build_type_variant (TREE_TYPE (type), 1,
+                            TREE_THIS_VOLATILE (TREE_TYPE (type))));
+  else
+    gcc_unreachable ();
+
+  return NULL_TREE;
+}
+
+
+/* Handle a "malloc" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_malloc_attribute (tree *node, tree ARG_UNUSED (name),
+                        tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                        bool * ARG_UNUSED (no_add_attrs))
+{
+  if (TREE_CODE (*node) == FUNCTION_DECL
+      && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node))))
+    DECL_IS_MALLOC (*node) = 1;
+  else
+    gcc_unreachable ();
+
+  return NULL_TREE;
+}
+
+
+/* Handle a "pure" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_pure_attribute (tree *node, tree ARG_UNUSED (name),
+                      tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                      bool * ARG_UNUSED (no_add_attrs))
+{
+  if (TREE_CODE (*node) == FUNCTION_DECL)
+    DECL_PURE_P (*node) = 1;
+  else
+    gcc_unreachable ();
+
+  return NULL_TREE;
+}
+
+
+/* Handle a "no vops" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_novops_attribute (tree *node, tree ARG_UNUSED (name),
+                        tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                        bool *ARG_UNUSED (no_add_attrs))
+{
+  gcc_assert (TREE_CODE (*node) == FUNCTION_DECL);
+  DECL_IS_NOVOPS (*node) = 1;
+  return NULL_TREE;
+}
+
+
+/* Helper for nonnull attribute handling; fetch the operand number
+   from the attribute argument list.  */
+
+static bool
+get_nonnull_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp)
+{
+  /* Verify the arg number is a constant.  */
+  if (!tree_fits_uhwi_p (arg_num_expr))
+    return false;
+
+  *valp = TREE_INT_CST_LOW (arg_num_expr);
+  return true;
+}
+
+/* Handle the "nonnull" attribute.  */
+
+static tree
+handle_nonnull_attribute (tree *node, tree ARG_UNUSED (name),
+                         tree args, int ARG_UNUSED (flags),
+                         bool * ARG_UNUSED (no_add_attrs))
+{
+  tree type = *node;
+
+  /* If no arguments are specified, all pointer arguments should be
+     non-null.  Verify a full prototype is given so that the arguments
+     will have the correct types when we actually check them later.
+     Avoid diagnosing type-generic built-ins since those have no
+     prototype.  */
+  if (!args)
+    {
+      gcc_assert (prototype_p (type)
+                 || !TYPE_ATTRIBUTES (type)
+                 || lookup_attribute ("type generic", TYPE_ATTRIBUTES (type)));
+
+      return NULL_TREE;
+    }
+
+  /* Argument list specified.  Verify that each argument number references
+     a pointer argument.  */
+  for (; args; args = TREE_CHAIN (args))
+    {
+      tree argument;
+      unsigned HOST_WIDE_INT arg_num = 0, ck_num;
+
+      if (!get_nonnull_operand (TREE_VALUE (args), &arg_num))
+       gcc_unreachable ();
+
+      argument = TYPE_ARG_TYPES (type);
+      if (argument)
+       {
+         for (ck_num = 1; ; ck_num++)
+           {
+             if (!argument || ck_num == arg_num)
+               break;
+             argument = TREE_CHAIN (argument);
+           }
+
+         gcc_assert (argument
+                     && TREE_CODE (TREE_VALUE (argument)) == POINTER_TYPE);
+       }
+    }
+
+  return NULL_TREE;
+}
+
+
+/* Handle a "nothrow" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_nothrow_attribute (tree *node, tree ARG_UNUSED (name),
+                         tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                         bool * ARG_UNUSED (no_add_attrs))
+{
+  if (TREE_CODE (*node) == FUNCTION_DECL)
+    TREE_NOTHROW (*node) = 1;
+  else
+    gcc_unreachable ();
+
+  return NULL_TREE;
+}
+
+
+/* Handle a "sentinel" attribute.  */
+
+static tree
+handle_sentinel_attribute (tree *node, tree ARG_UNUSED (name), tree args,
+                          int ARG_UNUSED (flags),
+                          bool * ARG_UNUSED (no_add_attrs))
+{
+  gcc_assert (stdarg_p (*node));
+
+  if (args)
+    {
+      tree position = TREE_VALUE (args);
+      gcc_assert (TREE_CODE (position) == INTEGER_CST);
+      if (tree_int_cst_lt (position, integer_zero_node))
+       gcc_unreachable ();
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle a "type_generic" attribute.  */
+
+static tree
+handle_type_generic_attribute (tree *node, tree ARG_UNUSED (name),
+                              tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                              bool * ARG_UNUSED (no_add_attrs))
+{
+  /* Ensure we have a function type.  */
+  gcc_assert (TREE_CODE (*node) == FUNCTION_TYPE);
+
+  /* Ensure we have a variadic function.  */
+  gcc_assert (!prototype_p (*node) || stdarg_p (*node));
+
+  return NULL_TREE;
+}
+
+/* Handle a "transaction_pure" attribute.  */
+
+static tree
+handle_transaction_pure_attribute (tree *node, tree ARG_UNUSED (name),
+                                  tree ARG_UNUSED (args),
+                                  int ARG_UNUSED (flags),
+                                  bool * ARG_UNUSED (no_add_attrs))
+{
+  /* Ensure we have a function type.  */
+  gcc_assert (TREE_CODE (*node) == FUNCTION_TYPE);
+
+  return NULL_TREE;
+}
+
+/* Handle a "returns_twice" attribute.  */
+
+static tree
+handle_returns_twice_attribute (tree *node, tree ARG_UNUSED (name),
+                               tree ARG_UNUSED (args),
+                               int ARG_UNUSED (flags),
+                               bool * ARG_UNUSED (no_add_attrs))
+{
+  gcc_assert (TREE_CODE (*node) == FUNCTION_DECL);
+
+  DECL_IS_RETURNS_TWICE (*node) = 1;
+
+  return NULL_TREE;
+}
+
+static tree
+handle_patchable_function_entry_attribute (tree *, tree, tree, int, bool *)
+{
+  /* Nothing to be done here.  */
+  return NULL_TREE;
+}
+
+/* Ignore the given attribute.  Used when this attribute may be usefully
+   overridden by the target, but is not used generically.  */
+
+static tree
+ignore_attribute (tree * ARG_UNUSED (node), tree ARG_UNUSED (name),
+                 tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                 bool *no_add_attrs)
+{
+  *no_add_attrs = true;
+  return NULL_TREE;
+}
+
+/* Handle a "format" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_format_attribute (tree * ARG_UNUSED (node), tree ARG_UNUSED (name),
+                        tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                        bool *no_add_attrs)
+{
+  *no_add_attrs = true;
+  return NULL_TREE;
+}
+
+
+/* Handle a "format_arg" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+tree
+handle_format_arg_attribute (tree * ARG_UNUSED (node), tree ARG_UNUSED (name),
+                            tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+                            bool *no_add_attrs)
+{
+  *no_add_attrs = true;
+  return NULL_TREE;
+}
+
+
+/* Handle a "fn spec" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_fnspec_attribute (tree *node ATTRIBUTE_UNUSED, tree ARG_UNUSED (name),
+                        tree args, int ARG_UNUSED (flags),
+                        bool *no_add_attrs ATTRIBUTE_UNUSED)
+{
+  gcc_assert (args
+             && TREE_CODE (TREE_VALUE (args)) == STRING_CST
+             && !TREE_CHAIN (args));
+  return NULL_TREE;
+}
+
+/* (end of attribute-handling).  */
+
 /* Language-dependent contents of a type.  */
 
 struct GTY(()) lang_type
@@ -269,6 +721,12 @@ jit_langhook_getdecls (void)
 #undef LANG_HOOKS_GETDECLS
 #define LANG_HOOKS_GETDECLS            jit_langhook_getdecls
 
+/* Attribute hooks.  */
+#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
+#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE jit_attribute_table
+#undef LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
+#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE jit_format_attribute_table
+
 #undef  LANG_HOOKS_DEEP_UNSHARING
 #define LANG_HOOKS_DEEP_UNSHARING      true
 
index ca8d3df419373e24a9e9fc6b7d4f7f88dee3e41c..632ab8cfb2e435c32e262f50e43ffb1ccce6df87 100644 (file)
@@ -74,6 +74,9 @@
 #undef create_code
 #undef verify_code
 
+/* test-builtin-unreachable.c: We don't add this one, since it touches
+   the optimization level of the context as a whole.  */
+
 /* test-calling-external-function.c */
 #define create_code create_code_calling_external_function
 #define verify_code verify_code_calling_external_function
diff --git a/gcc/testsuite/jit.dg/test-builtin-unreachable.c b/gcc/testsuite/jit.dg/test-builtin-unreachable.c
new file mode 100644 (file)
index 0000000..09e55e6
--- /dev/null
@@ -0,0 +1,49 @@
+#include <libgccjit.h>
+
+#include "harness.h"
+
+/* Verify that we can compile a function that uses __builtin_unreachable
+   (PR jit/95426).
+   
+   Compile the equivalent of this C:
+
+     int test_pr95426_unreachable (int i)
+     {
+       __builtin_unreachable ();
+       return i;
+     }
+*/
+
+void
+create_code (gcc_jit_context *ctxt, void *user_data)
+{
+  gcc_jit_context_set_int_option (ctxt,
+                                 GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
+                                 0);
+
+  gcc_jit_type *int_type = gcc_jit_context_get_type(ctxt, GCC_JIT_TYPE_INT);
+  gcc_jit_param *param_i
+    = gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
+  gcc_jit_function *test_fn
+    = gcc_jit_context_new_function (ctxt, NULL, GCC_JIT_FUNCTION_EXPORTED,
+                                   int_type,
+                                   "test_pr95426_unreachable",
+                                   1, &param_i, 0);
+
+  gcc_jit_block *bb = gcc_jit_function_new_block(test_fn, "start");
+  gcc_jit_function *func___builtin_unreachable
+    = gcc_jit_context_get_builtin_function(ctxt, "__builtin_unreachable");
+  gcc_jit_rvalue *call___builtin_unreachable
+    = gcc_jit_context_new_call(ctxt, NULL, func___builtin_unreachable,
+                              0, NULL);
+  gcc_jit_block_add_eval(bb, NULL, call___builtin_unreachable);
+  gcc_jit_block_end_with_return (bb, NULL, gcc_jit_param_as_rvalue(param_i));
+}
+
+void
+verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  CHECK_NON_NULL (result);
+  CHECK_NON_NULL (gcc_jit_result_get_code (result, "test_pr95426_unreachable"));
+  /* Don't actually run the code.  */
+}