From 44564c4c811f4751daf363ca019a9f9bed702f4f Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Sun, 31 May 2020 13:28:41 -0400 Subject: [PATCH] jit: fix __builtin_unreachable [PR 95426] 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 | 460 +++++++++++++++++- gcc/testsuite/jit.dg/all-non-failing-tests.h | 3 + .../jit.dg/test-builtin-unreachable.c | 49 ++ 3 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/jit.dg/test-builtin-unreachable.c diff --git a/gcc/jit/dummy-frontend.c b/gcc/jit/dummy-frontend.c index 6c7b7992a4d..aa64a6eb7a0 100644 --- a/gcc/jit/dummy-frontend.c +++ b/gcc/jit/dummy-frontend.c @@ -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 +/* 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 diff --git a/gcc/testsuite/jit.dg/all-non-failing-tests.h b/gcc/testsuite/jit.dg/all-non-failing-tests.h index ca8d3df4193..632ab8cfb2e 100644 --- a/gcc/testsuite/jit.dg/all-non-failing-tests.h +++ b/gcc/testsuite/jit.dg/all-non-failing-tests.h @@ -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 index 00000000000..09e55e608da --- /dev/null +++ b/gcc/testsuite/jit.dg/test-builtin-unreachable.c @@ -0,0 +1,49 @@ +#include + +#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, ¶m_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. */ +} -- 2.30.2