/* Convert function calls to rtl insns, for GNU C compiler.
- Copyright (C) 1989-2019 Free Software Foundation, Inc.
+ Copyright (C) 1989-2020 Free Software Foundation, Inc.
This file is part of GCC.
#include "tree-ssa-strlen.h"
#include "intl.h"
#include "stringpool.h"
+#include "hash-map.h"
+#include "hash-traits.h"
#include "attribs.h"
#include "builtins.h"
#include "gimple-fold.h"
{
tree name_decl = DECL_NAME (fndecl);
- if (fndecl && name_decl
- && IDENTIFIER_LENGTH (name_decl) <= 11
- /* Exclude functions not at the file scope, or not `extern',
- since they are not the magic functions we would otherwise
- think they are.
- FIXME: this should be handled with attributes, not with this
- hacky imitation of DECL_ASSEMBLER_NAME. It's (also) wrong
- because you can declare fork() inside a function if you
- wish. */
- && (DECL_CONTEXT (fndecl) == NULL_TREE
- || TREE_CODE (DECL_CONTEXT (fndecl)) == TRANSLATION_UNIT_DECL)
- && TREE_PUBLIC (fndecl))
+ if (maybe_special_function_p (fndecl)
+ && IDENTIFIER_LENGTH (name_decl) <= 11)
{
const char *name = IDENTIFIER_POINTER (name_decl);
const char *tname = name;
return true;
/* GCC post 3.4 passes *all* variable sized types by reference. */
- if (!TYPE_SIZE (type) || TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST)
+ if (!TYPE_SIZE (type) || !poly_int_tree_p (TYPE_SIZE (type)))
return true;
/* If a record type should be passed the same as its first (and only)
return pass_by_reference (NULL, function_arg_info (type, /*named=*/false));
}
+/* Decide whether ARG, which occurs in the state described by CA,
+ should be passed by reference. Return true if so and update
+ ARG accordingly. */
+
+bool
+apply_pass_by_reference_rules (CUMULATIVE_ARGS *ca, function_arg_info &arg)
+{
+ if (pass_by_reference (ca, arg))
+ {
+ arg.type = build_pointer_type (arg.type);
+ arg.mode = TYPE_MODE (arg.type);
+ arg.pass_by_reference = true;
+ return true;
+ }
+ return false;
+}
+
/* Return true if ARG, which is passed by reference, should be callee
copied instead of caller copied. */
bool
get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
{
+ if (!exp)
+ return false;
+
if (tree_fits_uhwi_p (exp))
{
/* EXP is a constant. */
if (!get_attr_nonstring_decl (arg))
{
c_strlen_data lendata = { };
+ /* Set MAXBOUND to an arbitrary non-null non-integer
+ node as a request to have it set to the length of
+ the longest string in a PHI. */
+ lendata.maxbound = arg;
get_range_strlen (arg, &lendata, /* eltsize = */ 1);
maxlen = lendata.maxbound;
}
if (!get_attr_nonstring_decl (arg))
{
c_strlen_data lendata = { };
+ /* Set MAXBOUND to an arbitrary non-null non-integer
+ node as a request to have it set to the length of
+ the longest string in a PHI. */
+ lendata.maxbound = arg;
get_range_strlen (arg, &lendata, /* eltsize = */ 1);
maxlen = lendata.maxbound;
}
error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason);
}
+/* Used to define rdwr_map below. */
+struct rdwr_access_hash: int_hash<int, -1> { };
+
+/* A mapping between argument number corresponding to attribute access
+ mode (read_only, write_only, or read_write) and operands. */
+typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
+
+/* Initialize a mapping for a call to function FNDECL declared with
+ attribute access. Each attribute positional operand inserts one
+ entry into the mapping with the operand number as the key. */
+
+static void
+init_attr_rdwr_indices (rdwr_map *rwm, tree fntype)
+{
+ if (!fntype)
+ return;
+
+ for (tree access = TYPE_ATTRIBUTES (fntype);
+ (access = lookup_attribute ("access", access));
+ access = TREE_CHAIN (access))
+ {
+ /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
+ is the attribute argument's value. */
+ tree mode = TREE_VALUE (access);
+ gcc_assert (TREE_CODE (mode) == TREE_LIST);
+ mode = TREE_VALUE (mode);
+ gcc_assert (TREE_CODE (mode) == STRING_CST);
+
+ const char *modestr = TREE_STRING_POINTER (mode);
+ for (const char *m = modestr; *m; )
+ {
+ attr_access acc = { };
+
+ switch (*m)
+ {
+ case 'r': acc.mode = acc.read_only; break;
+ case 'w': acc.mode = acc.write_only; break;
+ default: acc.mode = acc.read_write; break;
+ }
+
+ char *end;
+ acc.ptrarg = strtoul (++m, &end, 10);
+ m = end;
+ if (*m == ',')
+ {
+ acc.sizarg = strtoul (++m, &end, 10);
+ m = end;
+ }
+ else
+ acc.sizarg = UINT_MAX;
+
+ acc.ptr = NULL_TREE;
+ acc.size = NULL_TREE;
+
+ /* Unconditionally add an entry for the required pointer
+ operand of the attribute, and one for the optional size
+ operand when it's specified. */
+ rwm->put (acc.ptrarg, acc);
+ if (acc.sizarg != UINT_MAX)
+ rwm->put (acc.sizarg, acc);
+ }
+ }
+}
+
+/* Returns the type of the argument ARGNO to function with type FNTYPE
+ or null when the typoe cannot be determined or no such argument exists. */
+
+static tree
+fntype_argno_type (tree fntype, unsigned argno)
+{
+ if (!prototype_p (fntype))
+ return NULL_TREE;
+
+ tree argtype;
+ function_args_iterator it;
+ FOREACH_FUNCTION_ARGS (fntype, argtype, it)
+ if (argno-- == 0)
+ return argtype;
+
+ return NULL_TREE;
+}
+
+/* Helper to append the "rdwr" attribute specification described
+ by ACCESS to the array ATTRSTR with size STRSIZE. Used in
+ diagnostics. */
+
+static inline void
+append_attrname (const std::pair<int, attr_access> &access,
+ char *attrstr, size_t strsize)
+{
+ /* Append the relevant attribute to the string. This (deliberately)
+ appends the attribute pointer operand even when none was specified. */
+ size_t len = strlen (attrstr);
+
+ const char *atname
+ = (access.second.mode == attr_access::read_only
+ ? "read_only"
+ : (access.second.mode == attr_access::write_only
+ ? "write_only" : "read_write"));
+
+ const char *sep = len ? ", " : "";
+
+ if (access.second.sizarg == UINT_MAX)
+ snprintf (attrstr + len, strsize - len,
+ "%s%s (%i)", sep, atname,
+ access.second.ptrarg + 1);
+ else
+ snprintf (attrstr + len, strsize - len,
+ "%s%s (%i, %i)", sep, atname,
+ access.second.ptrarg + 1, access.second.sizarg + 1);
+}
+
+/* Iterate over attribute access read-only, read-write, and write-only
+ arguments and diagnose past-the-end accesses and related problems
+ in the function call EXP. */
+
+static void
+maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
+{
+ tree fndecl = NULL_TREE;
+ tree fntype = NULL_TREE;
+ if (tree fnaddr = CALL_EXPR_FN (exp))
+ {
+ if (TREE_CODE (fnaddr) == ADDR_EXPR)
+ {
+ fndecl = TREE_OPERAND (fnaddr, 0);
+ fntype = TREE_TYPE (fndecl);
+ }
+ else
+ fntype = TREE_TYPE (TREE_TYPE (fnaddr));
+ }
+
+ if (!fntype)
+ return;
+
+ /* A string describing the attributes that the warnings issued by this
+ function apply to. Used to print one informational note per function
+ call, rather than one per warning. That reduces clutter. */
+ char attrstr[80];
+ attrstr[0] = 0;
+
+ for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it)
+ {
+ std::pair<int, attr_access> access = *it;
+
+ /* Get the function call arguments corresponding to the attribute's
+ positional arguments. When both arguments have been specified
+ there will be two entries in *RWM, one for each. They are
+ cross-referenced by their respective argument numbers in
+ ACCESS.PTRARG and ACCESS.SIZARG. */
+ const int ptridx = access.second.ptrarg;
+ const int sizidx = access.second.sizarg;
+
+ gcc_assert (ptridx != -1);
+ gcc_assert (access.first == ptridx || access.first == sizidx);
+
+ /* The pointer is set to null for the entry corresponding to
+ the size argument. Skip it. It's handled when the entry
+ corresponding to the pointer argument comes up. */
+ if (!access.second.ptr)
+ continue;
+
+ tree argtype = fntype_argno_type (fntype, ptridx);
+ argtype = TREE_TYPE (argtype);
+
+ tree size;
+ if (sizidx == -1)
+ {
+ /* If only the pointer attribute operand was specified
+ and not size, set SIZE to the size of one element of
+ the pointed to type to detect smaller objects (null
+ pointers are diagnosed in this case only if
+ the pointer is also declared with attribute nonnull. */
+ size = size_one_node;
+ }
+ else
+ size = rwm->get (sizidx)->size;
+
+ tree ptr = access.second.ptr;
+ tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) };
+ if (get_size_range (size, sizrng, true)
+ && tree_int_cst_sgn (sizrng[0]) < 0
+ && tree_int_cst_sgn (sizrng[1]) < 0)
+ {
+ /* Warn about negative sizes. */
+ bool warned = false;
+ location_t loc = EXPR_LOCATION (exp);
+ if (tree_int_cst_equal (sizrng[0], sizrng[1]))
+ warned = warning_at (loc, OPT_Wstringop_overflow_,
+ "%Kargument %i value %E is negative",
+ exp, sizidx + 1, size);
+ else
+ warned = warning_at (loc, OPT_Wstringop_overflow_,
+ "%Kargument %i range [%E, %E] is negative",
+ exp, sizidx + 1, sizrng[0], sizrng[1]);
+ if (warned)
+ {
+ append_attrname (access, attrstr, sizeof attrstr);
+ /* Avoid warning again for the same attribute. */
+ continue;
+ }
+ }
+
+ if (tree_int_cst_sgn (sizrng[0]) >= 0)
+ {
+ if (COMPLETE_TYPE_P (argtype))
+ {
+ /* Multiple SIZE by the size of the type the pointer
+ argument points to. If it's incomplete the size
+ is used as is. */
+ size = NULL_TREE;
+ if (tree argsize = TYPE_SIZE_UNIT (argtype))
+ if (TREE_CODE (argsize) == INTEGER_CST)
+ {
+ const int prec = TYPE_PRECISION (sizetype);
+ wide_int minsize = wi::to_wide (sizrng[0], prec);
+ minsize *= wi::to_wide (argsize, prec);
+ size = wide_int_to_tree (sizetype, minsize);
+ }
+ }
+ }
+ else
+ size = NULL_TREE;
+
+ if (sizidx >= 0
+ && integer_zerop (ptr)
+ && tree_int_cst_sgn (sizrng[0]) > 0)
+ {
+ /* Warn about null pointers with positive sizes. This is
+ different from also declaring the pointer argument with
+ attribute nonnull when the function accepts null pointers
+ only when the corresponding size is zero. */
+ bool warned = false;
+ location_t loc = EXPR_LOCATION (exp);
+ if (tree_int_cst_equal (sizrng[0], sizrng[1]))
+ warned = warning_at (loc, OPT_Wnonnull,
+ "%Kargument %i is null but the corresponding "
+ "size argument %i value is %E",
+ exp, ptridx + 1, sizidx + 1, size);
+ else
+ warned = warning_at (loc, OPT_Wnonnull,
+ "%Kargument %i is null but the corresponding "
+ "size argument %i range is [%E, %E]",
+ exp, ptridx + 1, sizidx + 1,
+ sizrng[0], sizrng[1]);
+ if (warned)
+ {
+ append_attrname (access, attrstr, sizeof attrstr);
+ /* Avoid warning again for the same attribute. */
+ continue;
+ }
+ }
+
+ tree objsize = compute_objsize (ptr, 0);
+
+ tree srcsize;
+ if (access.second.mode == attr_access::write_only)
+ {
+ /* For a write-only argument there is no source. */
+ srcsize = NULL_TREE;
+ }
+ else
+ {
+ /* For read-only and read-write attributes also set the source
+ size. */
+ srcsize = objsize;
+ if (access.second.mode == attr_access::read_only)
+ {
+ /* For a read-only attribute there is no destination so
+ clear OBJSIZE. This emits "reading N bytes" kind of
+ diagnostics instead of the "writing N bytes" kind. */
+ objsize = NULL_TREE;
+ }
+ }
+
+ /* Clear the no-warning bit in case it was set in a prior
+ iteration so that accesses via different arguments are
+ diagnosed. */
+ TREE_NO_WARNING (exp) = false;
+ check_access (exp, NULL_TREE, NULL_TREE, size, /*maxread=*/ NULL_TREE,
+ srcsize, objsize);
+
+ if (TREE_NO_WARNING (exp))
+ /* If check_access issued a warning above, append the relevant
+ attribute to the string. */
+ append_attrname (access, attrstr, sizeof attrstr);
+ }
+
+ if (!*attrstr)
+ return;
+
+ if (fndecl)
+ inform (DECL_SOURCE_LOCATION (fndecl),
+ "in a call to function %qD declared with attribute %qs",
+ fndecl, attrstr);
+ else
+ inform (EXPR_LOCATION (fndecl),
+ "in a call with type %qT and attribute %qs",
+ fntype, attrstr);
+
+ /* Set the bit in case if was cleared and not set above. */
+ TREE_NO_WARNING (exp) = true;
+}
+
/* Fill in ARGS_SIZE and ARGS array based on the parameters found in
CALL_EXPR EXP.
/* Array for up to the two attribute alloc_size arguments. */
tree alloc_args[] = { NULL_TREE, NULL_TREE };
+ /* Map of attribute read_only, write_only, or read_write specifications
+ for function arguments. */
+ rdwr_map rdwr_idx;
+ init_attr_rdwr_indices (&rdwr_idx, fntype);
+
/* I counts args in order (to be) pushed; ARGPOS counts in order written. */
for (argpos = 0; argpos < num_actuals; i--, argpos++)
{
tree type = TREE_TYPE (args[i].tree_value);
int unsignedp;
- machine_mode mode;
/* Replace erroneous argument with constant zero. */
if (type == error_mark_node || !COMPLETE_TYPE_P (type))
/* If TYPE is a transparent union or record, pass things the way
we would pass the first field of the union or record. We have
already verified that the modes are the same. */
- if ((TREE_CODE (type) == UNION_TYPE || TREE_CODE (type) == RECORD_TYPE)
- && TYPE_TRANSPARENT_AGGR (type))
+ if (RECORD_OR_UNION_TYPE_P (type) && TYPE_TRANSPARENT_AGGR (type))
type = TREE_TYPE (first_field (type));
/* Decide where to pass this arg.
with those made by function.c. */
/* See if this argument should be passed by invisible reference. */
- function_arg_info orig_arg (type, argpos < n_named_args);
- if (pass_by_reference (args_so_far_pnt, orig_arg))
+ function_arg_info arg (type, argpos < n_named_args);
+ if (pass_by_reference (args_so_far_pnt, arg))
{
bool callee_copies;
tree base = NULL_TREE;
- callee_copies = reference_callee_copied (args_so_far_pnt, orig_arg);
+ callee_copies = reference_callee_copied (args_so_far_pnt, arg);
/* If we're compiling a thunk, pass through invisible references
instead of making a copy. */
"argument must be passed"
" by copying");
}
+ arg.pass_by_reference = true;
}
unsignedp = TYPE_UNSIGNED (type);
- mode = promote_function_mode (type, TYPE_MODE (type), &unsignedp,
- fndecl ? TREE_TYPE (fndecl) : fntype, 0);
+ arg.type = type;
+ arg.mode
+ = promote_function_mode (type, TYPE_MODE (type), &unsignedp,
+ fndecl ? TREE_TYPE (fndecl) : fntype, 0);
args[i].unsignedp = unsignedp;
- args[i].mode = mode;
+ args[i].mode = arg.mode;
targetm.calls.warn_parameter_passing_abi (args_so_far, type);
- function_arg_info arg (type, mode, argpos < n_named_args);
args[i].reg = targetm.calls.function_arg (args_so_far, arg);
if (args[i].reg && CONST_INT_P (args[i].reg))
if (args[i].reg == 0 || args[i].partial != 0
|| reg_parm_stack_space > 0
|| args[i].pass_on_stack)
- locate_and_pad_parm (mode, type,
+ locate_and_pad_parm (arg.mode, type,
#ifdef STACK_PARMS_IN_REG_PARM_AREA
1,
#else
/* The argument is passed entirely in registers. See at which
end it should be padded. */
args[i].locate.where_pad =
- BLOCK_REG_PADDING (mode, type,
+ BLOCK_REG_PADDING (arg.mode, type,
int_size_in_bytes (type) <= UNITS_PER_WORD);
#endif
promoted_mode used for function_arg above. However, the
corresponding handling of incoming arguments in function.c
does pass the promoted mode. */
- function_arg_info arg_to_skip (type, TYPE_MODE (type),
- argpos < n_named_args);
- targetm.calls.function_arg_advance (args_so_far, arg_to_skip);
+ arg.mode = TYPE_MODE (type);
+ targetm.calls.function_arg_advance (args_so_far, arg);
/* Store argument values for functions decorated with attribute
alloc_size. */
alloc_args[0] = args[i].tree_value;
else if (argpos == alloc_idx[1])
alloc_args[1] = args[i].tree_value;
+
+ /* Save the actual argument that corresponds to the access attribute
+ operand for later processing. */
+ if (attr_access *access = rdwr_idx.get (argpos))
+ {
+ if (POINTER_TYPE_P (type))
+ {
+ access->ptr = args[i].tree_value;
+ gcc_assert (access->size == NULL_TREE);
+ }
+ else
+ {
+ access->size = args[i].tree_value;
+ gcc_assert (access->ptr == NULL_TREE);
+ }
+ }
}
if (alloc_args[0])
/* Detect passing non-string arguments to functions expecting
nul-terminated strings. */
maybe_warn_nonstring_arg (fndecl, exp);
+
+ /* Check read_only, write_only, and read_write arguments. */
+ maybe_warn_rdwr_sizes (&rdwr_idx, exp);
}
/* Update ARGS_SIZE to contain the total size for the argument block.
poly_int64 size = 0;
HOST_WIDE_INT const_size = 0;
rtx_insn *before_arg = get_last_insn ();
+ tree type = TREE_TYPE (args[i].tree_value);
+ if (RECORD_OR_UNION_TYPE_P (type) && TYPE_TRANSPARENT_AGGR (type))
+ type = TREE_TYPE (first_field (type));
/* Set non-negative if we must move a word at a time, even if
just one word (e.g, partial == 4 && mode == DFmode). Set
to -1 if we just use a normal move insn. This value can be
gcc_assert (partial % UNITS_PER_WORD == 0);
nregs = partial / UNITS_PER_WORD;
}
- else if (TYPE_MODE (TREE_TYPE (args[i].tree_value)) == BLKmode)
+ else if (TYPE_MODE (type) == BLKmode)
{
/* Variable-sized parameters should be described by a
PARALLEL instead. */
- const_size = int_size_in_bytes (TREE_TYPE (args[i].tree_value));
+ const_size = int_size_in_bytes (type);
gcc_assert (const_size >= 0);
nregs = (const_size + (UNITS_PER_WORD - 1)) / UNITS_PER_WORD;
size = const_size;
if (GET_CODE (reg) == PARALLEL)
use_group_regs (call_fusage, reg);
else if (nregs == -1)
- use_reg_mode (call_fusage, reg,
- TYPE_MODE (TREE_TYPE (args[i].tree_value)));
+ use_reg_mode (call_fusage, reg, TYPE_MODE (type));
else if (nregs > 0)
use_regs (call_fusage, REGNO (reg), nregs);
}
preferred_unit_stack_boundary = preferred_stack_boundary / BITS_PER_UNIT;
+ if (flag_callgraph_info)
+ record_final_call (fndecl, EXPR_LOCATION (exp));
+
/* We want to make two insn chains; one for a sibling call, the other
for a normal call. We will select one of the two chains after
initial RTL generation is complete. */
for (unsigned int i = 0; count < nargs; i++, count++)
{
rtx val = args[i].first;
- machine_mode mode = args[i].second;
+ function_arg_info arg (args[i].second, /*named=*/true);
int unsigned_p = 0;
/* We cannot convert the arg value to the mode the library wants here;
must do it earlier where we know the signedness of the arg. */
- gcc_assert (mode != BLKmode
- && (GET_MODE (val) == mode || GET_MODE (val) == VOIDmode));
+ gcc_assert (arg.mode != BLKmode
+ && (GET_MODE (val) == arg.mode
+ || GET_MODE (val) == VOIDmode));
/* Make sure it is a reasonable operand for a move or push insn. */
if (!REG_P (val) && !MEM_P (val)
- && !(CONSTANT_P (val) && targetm.legitimate_constant_p (mode, val)))
+ && !(CONSTANT_P (val)
+ && targetm.legitimate_constant_p (arg.mode, val)))
val = force_operand (val, NULL_RTX);
- function_arg_info orig_arg (mode, /*named=*/true);
- if (pass_by_reference (&args_so_far_v, orig_arg))
+ if (pass_by_reference (&args_so_far_v, arg))
{
rtx slot;
- int must_copy = !reference_callee_copied (&args_so_far_v, orig_arg);
+ int must_copy = !reference_callee_copied (&args_so_far_v, arg);
/* If this was a CONST function, it is now PURE since it now
reads memory. */
}
else
{
- slot = assign_temp (lang_hooks.types.type_for_mode (mode, 0),
+ slot = assign_temp (lang_hooks.types.type_for_mode (arg.mode, 0),
1, 1);
emit_move_insn (slot, val);
}
slot),
call_fusage);
- mode = Pmode;
+ arg.mode = Pmode;
+ arg.pass_by_reference = true;
val = force_operand (XEXP (slot, 0), NULL_RTX);
}
- mode = promote_function_mode (NULL_TREE, mode, &unsigned_p, NULL_TREE, 0);
- function_arg_info arg (mode, /*named=*/true);
- argvec[count].mode = mode;
- argvec[count].value = convert_modes (mode, GET_MODE (val), val, unsigned_p);
+ arg.mode = promote_function_mode (NULL_TREE, arg.mode, &unsigned_p,
+ NULL_TREE, 0);
+ argvec[count].mode = arg.mode;
+ argvec[count].value = convert_modes (arg.mode, GET_MODE (val), val,
+ unsigned_p);
argvec[count].reg = targetm.calls.function_arg (args_so_far, arg);
argvec[count].partial
|| argvec[count].partial != 0
|| reg_parm_stack_space > 0)
{
- locate_and_pad_parm (mode, NULL_TREE,
+ locate_and_pad_parm (arg.mode, NULL_TREE,
#ifdef STACK_PARMS_IN_REG_PARM_AREA
1,
#else
/* The argument is passed entirely in registers. See at which
end it should be padded. */
argvec[count].locate.where_pad =
- BLOCK_REG_PADDING (mode, NULL_TREE,
- known_le (GET_MODE_SIZE (mode), UNITS_PER_WORD));
+ BLOCK_REG_PADDING (arg.mode, NULL_TREE,
+ known_le (GET_MODE_SIZE (arg.mode),
+ UNITS_PER_WORD));
#endif
targetm.calls.function_arg_advance (args_so_far, arg);
before_call = get_last_insn ();
+ if (flag_callgraph_info)
+ record_final_call (SYMBOL_REF_DECL (orgfun), UNKNOWN_LOCATION);
+
/* We pass the old value of inhibit_defer_pop + 1 to emit_call_1, which
will set inhibit_defer_pop to that value. */
/* The return type is needed to decide how many bytes the function pops.
return false;
/* If the type has variable size... */
- if (TREE_CODE (TYPE_SIZE (arg.type)) != INTEGER_CST)
+ if (!poly_int_tree_p (TYPE_SIZE (arg.type)))
return true;
/* If the type is marked as addressable (it is required
return targetm.calls.must_pass_in_stack (arg);
}
+/* Return true if FIELD is the C++17 empty base field that should
+ be ignored for ABI calling convention decisions in order to
+ maintain ABI compatibility between C++14 and earlier, which doesn't
+ add this FIELD to classes with empty bases, and C++17 and later
+ which does. */
+
+bool
+cxx17_empty_base_field_p (const_tree field)
+{
+ return (DECL_FIELD_ABI_IGNORED (field)
+ && DECL_ARTIFICIAL (field)
+ && RECORD_OR_UNION_TYPE_P (TREE_TYPE (field))
+ && !lookup_attribute ("no_unique_address", DECL_ATTRIBUTES (field)));
+}
+
/* Tell the garbage collector about GTY markers in this source file. */
#include "gt-calls.h"