+/* Determine the offset *INDEX of the first byte of an array element of
+ TYPE (possibly recursively) into which the byte offset OFF points.
+ On success set *INDEX to the offset of the first byte and return type.
+ Otherwise, if no such element can be found, return null. */
+
+static tree
+array_elt_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+ gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+
+ tree eltype = type;
+ while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
+ eltype = TREE_TYPE (eltype);
+
+ if (TYPE_MODE (TREE_TYPE (eltype)) != TYPE_MODE (char_type_node))
+ eltype = TREE_TYPE (eltype);
+
+ if (eltype == type)
+ {
+ *index = 0;
+ return type;
+ }
+
+ HOST_WIDE_INT typsz = int_size_in_bytes (type);
+ HOST_WIDE_INT eltsz = int_size_in_bytes (eltype);
+ if (off < typsz * eltsz)
+ {
+ *index = (off / eltsz) * eltsz;
+ return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+ }
+
+ return NULL_TREE;
+}
+
+/* Determine the offset *INDEX of the first byte of a struct member of TYPE
+ (possibly recursively) into which the byte offset OFF points. On success
+ set *INDEX to the offset of the first byte and return true. Otherwise,
+ if no such member can be found, return false. */
+
+static bool
+field_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+ gcc_assert (RECORD_OR_UNION_TYPE_P (type));
+
+ for (tree fld = TYPE_FIELDS (type); fld; fld = TREE_CHAIN (fld))
+ {
+ if (TREE_CODE (fld) != FIELD_DECL || DECL_ARTIFICIAL (fld))
+ continue;
+
+ tree fldtype = TREE_TYPE (fld);
+ HOST_WIDE_INT fldoff = int_byte_position (fld);
+
+ /* If the size is not available the field is a flexible array
+ member. Treat this case as success. */
+ tree typesize = TYPE_SIZE_UNIT (fldtype);
+ HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (typesize)
+ ? tree_to_uhwi (typesize)
+ : off);
+
+ if (fldoff + fldsize < off)
+ continue;
+
+ if (TREE_CODE (fldtype) == ARRAY_TYPE)
+ {
+ HOST_WIDE_INT idx = 0;
+ if (tree ft = array_elt_at_offset (fldtype, off, &idx))
+ fldtype = ft;
+ else
+ break;
+
+ *index += idx;
+ fldoff -= idx;
+ off -= idx;
+ }
+
+ if (RECORD_OR_UNION_TYPE_P (fldtype))
+ {
+ *index += fldoff;
+ return field_at_offset (fldtype, off - fldoff, index);
+ }
+
+ *index += fldoff;
+ return true;
+ }
+
+ return false;
+}
+
+/* For an expression X of pointer type, recursively try to find the same
+ origin (object or pointer) as Y it references and return such an X.
+ When X refers to a struct member, set *FLDOFF to the offset of the
+ member from the beginning of the "most derived" object. */
+
+static tree
+get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
+{
+ if (!x)
+ return NULL_TREE;
+
+ switch (TREE_CODE (x))
+ {
+ case ADDR_EXPR:
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, off);
+
+ case ARRAY_REF:
+ {
+ tree offset = TREE_OPERAND (x, 1);
+ HOST_WIDE_INT idx = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+ tree eltype = TREE_TYPE (x);
+ if (TREE_CODE (eltype) == INTEGER_TYPE)
+ {
+ if (off)
+ *off = idx;
+ }
+ else if (idx < HOST_WIDE_INT_MAX)
+ *fldoff += idx * int_size_in_bytes (eltype);
+ else
+ *fldoff = idx;
+
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, NULL);
+ }
+
+ case MEM_REF:
+ if (off)
+ {
+ tree offset = TREE_OPERAND (x, 1);
+ *off = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+ }
+
+ x = TREE_OPERAND (x, 0);
+
+ if (off)
+ {
+ tree xtype
+ = (TREE_CODE (x) == ADDR_EXPR
+ ? TREE_TYPE (TREE_OPERAND (x, 0)) : TREE_TYPE (TREE_TYPE (x)));
+
+ /* The byte offset of the most basic struct member the byte
+ offset *OFF corresponds to, or for a (multidimensional)
+ array member, the byte offset of the array element. */
+ HOST_WIDE_INT index = 0;
+
+ if ((RECORD_OR_UNION_TYPE_P (xtype)
+ && field_at_offset (xtype, *off, &index))
+ || (TREE_CODE (xtype) == ARRAY_TYPE
+ && TREE_CODE (TREE_TYPE (xtype)) == ARRAY_TYPE
+ && array_elt_at_offset (xtype, *off, &index)))
+ {
+ *fldoff += index;
+ *off -= index;
+ }
+ }
+
+ return get_origin_and_offset (x, fldoff, NULL);
+
+ case COMPONENT_REF:
+ {
+ tree fld = TREE_OPERAND (x, 1);
+ *fldoff += int_byte_position (fld);
+
+ get_origin_and_offset (fld, fldoff, off);
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+
+ case SSA_NAME:
+ {
+ gimple *def = SSA_NAME_DEF_STMT (x);
+ if (is_gimple_assign (def))
+ {
+ tree_code code = gimple_assign_rhs_code (def);
+ if (code == ADDR_EXPR)
+ {
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+
+ if (code == POINTER_PLUS_EXPR)
+ {
+ tree offset = gimple_assign_rhs2 (def);
+ if (off)
+ *off = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, NULL);
+ }
+ else if (code == VAR_DECL)
+ {
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+ }
+ else if (gimple_nop_p (def) && SSA_NAME_VAR (x))
+ x = SSA_NAME_VAR (x);
+ }
+
+ default:
+ break;
+ }
+
+ return x;
+}
+
+/* If ARG refers to the same (sub)object or array element as described
+ by DST and DST_FLD, return the byte offset into the struct member or
+ array element referenced by ARG. Otherwise return HOST_WIDE_INT_MIN
+ to indicate that ARG and DST do not refer to the same object. */
+
+static HOST_WIDE_INT
+alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
+{
+ /* See if the argument refers to the same base object as the destination
+ of the formatted function call, and if so, try to determine if they
+ can alias. */
+ if (!arg || !dst || !ptr_derefs_may_alias_p (arg, dst))
+ return HOST_WIDE_INT_MIN;
+
+ /* The two arguments may refer to the same object. If they both refer
+ to a struct member, see if the members are one and the same. */
+ HOST_WIDE_INT arg_off = 0, arg_fld = 0;
+
+ tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off);
+
+ if (arg_orig == dst && arg_fld == dst_fld)
+ return arg_off;
+
+ return HOST_WIDE_INT_MIN;
+}
+