From: Kyrylo Tkachov <[email protected]>
set_mem_attributes derives MEM_NOTRAP from tree_could_trap_p. That predicate
describes a load. A store to readonly or otherwise unwritable memory can
therefore be marked as nontrapping. RTL exception analysis can then omit an
edge for a store that can raise a non-call exception.
Pass the access direction through set_mem_attributes and use
lhs_could_trap_p for stores. Propagate store semantics from EXPAND_WRITE,
assignment destinations, and built-in string destinations. Make every
get_memory_rtx caller state its access direction.
Some expanders use EXPAND_MEMORY because they require a MEM result. Apply
store semantics explicitly to asm outputs and to destination copies of strub
watermarks. The partial-load expander instead used EXPAND_WRITE for a load,
so use EXPAND_MEMORY there.
Bootstrapped and tested on aarch64-unknown-linux-gnu. Tested on
x86_64-pc-linux-gnu.
gcc/ChangeLog:
* builtins.cc (get_memory_rtx): Add STORE_P. Pass it to
set_mem_attributes.
(expand_builtin_memory_copy_args): Mark destination memory as a store
and source memory as a load.
(expand_movstr): Likewise.
(expand_builtin_strncpy): Likewise.
(expand_builtin_memset_args): Likewise.
(expand_builtin_memcmp): Mark both memory operands as loads.
(expand_builtin_strcmp): Likewise.
(expand_builtin_strncmp): Likewise.
(expand_builtin_strub_enter): Expand the watermark as a store.
(expand_builtin_strub_update): Apply store semantics to destination
copies of the watermark.
(inline_string_cmp): Mark the memory operand as a load.
* builtins.h (get_memory_rtx): Add STORE_P.
* cfgexpand.cc (expand_asm_stmt): Apply store semantics to memory
outputs.
* emit-rtl.cc (set_mem_attributes_minus_bitpos): Add STORE_P. Use
lhs_could_trap_p for stores.
(set_mem_attributes): Add STORE_P and pass it through.
* emit-rtl.h (set_mem_attributes): Add STORE_P.
(set_mem_attributes_minus_bitpos): Likewise.
* expr.cc (expand_assignment): Mark destination memory as a store.
(expand_expr_real_1): Use lhs_could_trap_p for store MEMs. Pass the
access direction to memory attribute generation.
* internal-fn.cc (expand_partial_load_optab_fn): Expand the source as
memory, not as a store.
(expand_RAWMEMCHR): Mark the memory operand as a load.
gcc/testsuite/ChangeLog:
* g++.dg/eh/noncall-store-2.C: New test.
* g++.dg/opt/mem-notrap-store-asm.C: New test.
* g++.dg/opt/mem-notrap-store-builtin.C: New test.
* gcc.target/i386/mem-notrap-mask-load.c: New test.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/builtins.cc | 46 +++++++++++--------
gcc/builtins.h | 2 +-
gcc/cfgexpand.cc | 5 +-
gcc/emit-rtl.cc | 12 +++--
gcc/emit-rtl.h | 11 +++--
gcc/expr.cc | 15 ++++--
gcc/internal-fn.cc | 4 +-
gcc/testsuite/g++.dg/eh/noncall-store-2.C | 39 ++++++++++++++++
.../g++.dg/opt/mem-notrap-store-asm.C | 15 ++++++
.../g++.dg/opt/mem-notrap-store-builtin.C | 22 +++++++++
.../gcc.target/i386/mem-notrap-mask-load.c | 23 ++++++++++
11 files changed, 156 insertions(+), 38 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/eh/noncall-store-2.C
create mode 100644 gcc/testsuite/g++.dg/opt/mem-notrap-store-asm.C
create mode 100644 gcc/testsuite/g++.dg/opt/mem-notrap-store-builtin.C
create mode 100644 gcc/testsuite/gcc.target/i386/mem-notrap-mask-load.c
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 056fd20c4c6..d9471340e30 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see
#include "tree.h"
#include "memmodel.h"
#include "gimple.h"
+#include "tree-eh.h"
#include "predict.h"
#include "tm_p.h"
#include "stringpool.h"
@@ -1364,10 +1365,10 @@ expand_builtin_prefetch (tree exp)
/* Get a MEM rtx for expression EXP which is the address of an operand
to be used in a string instruction (cmpstrsi, cpymemsi, ..). LEN is
the maximum length of the block of memory that might be accessed or
- NULL if unknown. */
+ NULL if unknown. STORE_P is true when EXP is the destination. */
rtx
-get_memory_rtx (tree exp, tree len)
+get_memory_rtx (tree exp, tree len, bool store_p)
{
tree orig_exp = exp, base;
rtx addr, mem;
@@ -1401,7 +1402,7 @@ get_memory_rtx (tree exp, tree len)
from the original address we got, and build an all-aliasing
unknown-sized access to that one. */
if (is_gimple_mem_ref_addr (TREE_OPERAND (exp, 0)))
- set_mem_attributes (mem, exp, 0);
+ set_mem_attributes (mem, exp, 0, store_p);
else if (TREE_CODE (TREE_OPERAND (exp, 0)) == ADDR_EXPR
&& (base = get_base_address (TREE_OPERAND (TREE_OPERAND (exp, 0),
0))))
@@ -1414,7 +1415,7 @@ get_memory_rtx (tree exp, tree len)
size_zero_node,
NULL)),
exp, build_int_cst (ptr_type_node, 0));
- set_mem_attributes (mem, exp, 0);
+ set_mem_attributes (mem, exp, 0, store_p);
/* Since we stripped parts make sure the offset is unknown and the
alignment is computed from the original address. */
clear_mem_offset (mem);
@@ -3770,7 +3771,7 @@ expand_builtin_memory_copy_args (tree dest, tree src,
tree len,
if (expected_align < dest_align)
expected_align = dest_align;
- dest_mem = get_memory_rtx (dest, len);
+ dest_mem = get_memory_rtx (dest, len, true);
set_mem_align (dest_mem, dest_align);
len_rtx = expand_normal (len);
determine_block_size (len, len_rtx, &min_size, &max_size,
@@ -3804,7 +3805,7 @@ expand_builtin_memory_copy_args (tree dest, tree src,
tree len,
return dest_mem;
}
- src_mem = get_memory_rtx (src, len);
+ src_mem = get_memory_rtx (src, len, false);
set_mem_align (src_mem, src_align);
/* Copy word part most expediently. */
@@ -3873,8 +3874,8 @@ expand_movstr (tree dest, tree src, rtx target, memop_ret
retmode)
if (!targetm.have_movstr ())
return NULL_RTX;
- dest_mem = get_memory_rtx (dest, NULL);
- src_mem = get_memory_rtx (src, NULL);
+ dest_mem = get_memory_rtx (dest, NULL, true);
+ src_mem = get_memory_rtx (src, NULL, false);
if (retmode == RETURN_BEGIN)
{
target = force_reg (Pmode, XEXP (dest_mem, 0));
@@ -4150,7 +4151,7 @@ expand_builtin_strncpy (tree exp, rtx target)
dest_align, false))
return NULL_RTX;
- dest_mem = get_memory_rtx (dest, len);
+ dest_mem = get_memory_rtx (dest, len, true);
store_by_pieces (dest_mem, tree_to_uhwi (len),
builtin_strncpy_read_str,
const_cast<char *> (p), dest_align, false,
@@ -4709,7 +4710,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
len_rtx = expand_normal (len);
determine_block_size (len, len_rtx, &min_size, &max_size,
&probable_max_size);
- dest_mem = get_memory_rtx (dest, len);
+ dest_mem = get_memory_rtx (dest, len, true);
val_mode = TYPE_MODE (unsigned_char_type_node);
if (TREE_CODE (val) != INTEGER_CST
@@ -4893,8 +4894,8 @@ expand_builtin_memcmp (tree exp, rtx target, bool
result_eq)
if (arg1_align == 0 || arg2_align == 0)
return NULL_RTX;
- rtx arg1_rtx = get_memory_rtx (arg1, len);
- rtx arg2_rtx = get_memory_rtx (arg2, len);
+ rtx arg1_rtx = get_memory_rtx (arg1, len, false);
+ rtx arg2_rtx = get_memory_rtx (arg2, len, false);
rtx len_rtx = expand_normal (fold_convert_loc (loc, sizetype, len));
/* Set MEM_SIZE as appropriate. */
@@ -4987,8 +4988,8 @@ expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx
target)
arg1 = builtin_save_expr (arg1);
arg2 = builtin_save_expr (arg2);
- rtx arg1_rtx = get_memory_rtx (arg1, NULL);
- rtx arg2_rtx = get_memory_rtx (arg2, NULL);
+ rtx arg1_rtx = get_memory_rtx (arg1, NULL, false);
+ rtx arg2_rtx = get_memory_rtx (arg2, NULL, false);
/* Try to call cmpstrsi. */
if (cmpstr_icode != CODE_FOR_nothing)
@@ -5144,8 +5145,8 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx
target,
len = fold_convert_loc (loc, sizetype, len);
len = fold_build2_loc (loc, MIN_EXPR, TREE_TYPE (len), len, len3);
}
- rtx arg1_rtx = get_memory_rtx (arg1, len);
- rtx arg2_rtx = get_memory_rtx (arg2, len);
+ rtx arg1_rtx = get_memory_rtx (arg1, len, false);
+ rtx arg2_rtx = get_memory_rtx (arg2, len, false);
rtx arg3_rtx = expand_normal (len);
result = expand_cmpstrn_or_cmpmem (cmpstrn_icode, target, arg1_rtx,
arg2_rtx, TREE_TYPE (len), arg3_rtx,
@@ -5545,7 +5546,7 @@ expand_builtin_strub_enter (tree exp)
tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
build_int_cst (TREE_TYPE (wmptr), 0));
- rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_WRITE);
emit_move_insn (wmark, stktop);
@@ -5623,7 +5624,9 @@ expand_builtin_strub_update (tree exp)
do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
ptr_mode, NULL_RTX, lab, NULL,
profile_probability::very_likely ());
- emit_move_insn (wmark, stktop);
+ rtx wmark_store = shallow_copy_rtx (wmark);
+ MEM_NOTRAP_P (wmark_store) = !lhs_could_trap_p (wmtree);
+ emit_move_insn (wmark_store, stktop);
/* If this is an inlined strub function, also bump the watermark for the
enclosing function. This avoids a problem with the following scenario: A
@@ -5647,7 +5650,9 @@ expand_builtin_strub_update (tree exp)
do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
ptr_mode, NULL_RTX, lab, NULL,
profile_probability::very_likely ());
- emit_move_insn (wmark, stktop);
+ wmark_store = shallow_copy_rtx (wmark);
+ MEM_NOTRAP_P (wmark_store) = !lhs_could_trap_p (wmtree);
+ emit_move_insn (wmark_store, stktop);
}
emit_label (lab);
@@ -7602,7 +7607,8 @@ inline_string_cmp (rtx target, tree var_str, const char
*const_str,
{
HOST_WIDE_INT offset = 0;
rtx var_rtx_array
- = get_memory_rtx (var_str, build_int_cst (unsigned_type_node,length));
+ = get_memory_rtx (var_str, build_int_cst (unsigned_type_node, length),
+ false);
rtx var_rtx = NULL_RTX;
rtx const_rtx = NULL_RTX;
rtx result = target ? target : gen_reg_rtx (mode);
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 9cca88c647c..97b79908d0d 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -162,7 +162,7 @@ extern char target_percent_s[3];
extern char target_percent_c[3];
extern char target_percent_s_newline[4];
extern bool target_char_cst_p (tree t, char *p);
-extern rtx get_memory_rtx (tree exp, tree len);
+extern rtx get_memory_rtx (tree exp, tree len, bool store_p);
extern internal_fn associated_internal_fn (combined_fn, tree);
extern internal_fn associated_internal_fn (tree);
diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc
index 245ba357b5e..7256c5235aa 100644
--- a/gcc/cfgexpand.cc
+++ b/gcc/cfgexpand.cc
@@ -3688,7 +3688,10 @@ expand_asm_stmt (gasm *stmt)
op = expand_expr (val, NULL_RTX, VOIDmode,
!allows_reg ? EXPAND_MEMORY : EXPAND_WRITE);
if (MEM_P (op))
- op = validize_mem (op);
+ {
+ op = validize_mem (op);
+ MEM_NOTRAP_P (op) = !lhs_could_trap_p (val);
+ }
if (! allows_reg && !MEM_P (op))
{
diff --git a/gcc/emit-rtl.cc b/gcc/emit-rtl.cc
index 4a23eaefe02..8eb42c4facc 100644
--- a/gcc/emit-rtl.cc
+++ b/gcc/emit-rtl.cc
@@ -1959,11 +1959,12 @@ get_mem_align_offset (rtx mem, unsigned int align)
/* Given REF (a MEM) and T, either the type of X or the expression
corresponding to REF, set the memory attributes. OBJECTP is nonzero
if we are making a new object of this type. BITPOS is nonzero if
- there is an offset outstanding on T that will be applied later. */
+ there is an offset outstanding on T that will be applied later. STORE_P
+ is true when REF is the destination of a store. */
void
set_mem_attributes_minus_bitpos (rtx ref, tree t, int objectp,
- poly_int64 bitpos)
+ poly_int64 bitpos, bool store_p)
{
poly_int64 apply_bitpos = 0;
tree type;
@@ -2059,7 +2060,8 @@ set_mem_attributes_minus_bitpos (rtx ref, tree t, int
objectp,
t = TREE_OPERAND (t, 0);
/* Note whether this expression can trap. */
- MEM_NOTRAP_P (ref) = !tree_could_trap_p (t);
+ MEM_NOTRAP_P (ref)
+ = !(store_p ? lhs_could_trap_p (t) : tree_could_trap_p (t));
base = get_base_address (t);
if (base)
@@ -2191,9 +2193,9 @@ set_mem_attributes_minus_bitpos (rtx ref, tree t, int
objectp,
}
void
-set_mem_attributes (rtx ref, tree t, int objectp)
+set_mem_attributes (rtx ref, tree t, int objectp, bool store_p)
{
- set_mem_attributes_minus_bitpos (ref, t, objectp, 0);
+ set_mem_attributes_minus_bitpos (ref, t, objectp, 0, store_p);
}
/* Set the alias set of MEM to SET. */
diff --git a/gcc/emit-rtl.h b/gcc/emit-rtl.h
index 0b572580a98..f50db283657 100644
--- a/gcc/emit-rtl.h
+++ b/gcc/emit-rtl.h
@@ -552,13 +552,16 @@ extern rtx offset_address (rtx, rtx, unsigned
HOST_WIDE_INT);
/* Given REF, a MEM, and T, either the type of X or the expression
corresponding to REF, set the memory attributes. OBJECTP is nonzero
- if we are making a new object of this type. */
-extern void set_mem_attributes (rtx, tree, int);
+ if we are making a new object of this type. STORE_P is true when REF is
+ the destination of a store. */
+extern void set_mem_attributes (rtx, tree, int, bool = false);
/* Similar, except that BITPOS has not yet been applied to REF, so if
we alter MEM_OFFSET according to T then we should subtract BITPOS
- expecting that it'll be added back in later. */
-extern void set_mem_attributes_minus_bitpos (rtx, tree, int, poly_int64);
+ expecting that it'll be added back in later. STORE_P is true when REF is
+ the destination of a store. */
+extern void set_mem_attributes_minus_bitpos (rtx, tree, int, poly_int64,
+ bool = false);
/* Return OFFSET if XEXP (MEM, 0) - OFFSET is known to be ALIGN
bits aligned for 0 <= OFFSET < ALIGN / BITS_PER_UNIT, or
diff --git a/gcc/expr.cc b/gcc/expr.cc
index feaa8e90470..fd735ac8f93 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -64,6 +64,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "rtx-vector-builder.h"
#include "tree-pretty-print.h"
+#include "tree-eh.h"
#include "flags.h"
#include "internal-fn.h"
#include "gimple-range.h"
@@ -6328,7 +6329,7 @@ expand_assignment (tree to, tree from, bool nontemporal)
/* If the field is at offset zero, we could have been given the
DECL_RTX of the parent struct. Don't munge it. */
to_rtx = shallow_copy_rtx (to_rtx);
- set_mem_attributes_minus_bitpos (to_rtx, to, 0, bitpos);
+ set_mem_attributes_minus_bitpos (to_rtx, to, 0, bitpos, true);
if (volatilep)
MEM_VOLATILE_P (to_rtx) = 1;
}
@@ -11759,6 +11760,9 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode
tmode,
else
decl_rtl = copy_rtx (decl_rtl);
+ if (exp && MEM_P (decl_rtl) && modifier == EXPAND_WRITE)
+ MEM_NOTRAP_P (decl_rtl) = !lhs_could_trap_p (exp);
+
/* Record writes to register variables. */
if (modifier == EXPAND_WRITE
&& REG_P (decl_rtl)
@@ -12077,7 +12081,7 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode
tmode,
op0 = addr_for_mem_ref (exp, as, true);
op0 = memory_address_addr_space (mode, op0, as);
temp = gen_rtx_MEM (mode, op0);
- set_mem_attributes (temp, exp, 0);
+ set_mem_attributes (temp, exp, 0, modifier == EXPAND_WRITE);
set_mem_addr_space (temp, as);
align = get_object_alignment (exp);
if (modifier != EXPAND_WRITE
@@ -12164,7 +12168,7 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode
tmode,
op0 = memory_address_addr_space (mode, op0, as);
}
temp = gen_rtx_MEM (mode, op0);
- set_mem_attributes (temp, exp, 0);
+ set_mem_attributes (temp, exp, 0, modifier == EXPAND_WRITE);
set_mem_addr_space (temp, as);
if (TREE_THIS_VOLATILE (exp))
MEM_VOLATILE_P (temp) = 1;
@@ -12733,7 +12737,7 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode
tmode,
we should just honor its original memory attributes. */
if (!(TREE_CODE (tem) == SSA_NAME
&& (MEM_P (orig_op0) || CONSTANT_P (orig_op0))))
- set_mem_attributes (op0, exp, 0);
+ set_mem_attributes (op0, exp, 0, modifier == EXPAND_WRITE);
if (REG_P (XEXP (op0, 0)))
mark_reg_pointer (XEXP (op0, 0), MEM_ALIGN (op0));
@@ -12865,7 +12869,8 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode
tmode,
if (op0 == orig_op0)
op0 = copy_rtx (op0);
- set_mem_attributes (op0, treeop0, 0);
+ set_mem_attributes (op0, treeop0, 0,
+ modifier == EXPAND_WRITE);
if (REG_P (XEXP (op0, 0)))
mark_reg_pointer (XEXP (op0, 0), MEM_ALIGN (op0));
diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
index 0138c6f7ef0..ceddc3787c0 100644
--- a/gcc/internal-fn.cc
+++ b/gcc/internal-fn.cc
@@ -3144,7 +3144,7 @@ expand_partial_load_optab_fn (internal_fn ifn, gcall
*stmt, convert_optab optab)
icode = convert_optab_handler (optab, TYPE_MODE (type),
TYPE_MODE (TREE_TYPE (maskt)));
- mem = expand_expr (rhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
+ mem = expand_expr (rhs, NULL_RTX, VOIDmode, EXPAND_MEMORY);
gcc_assert (MEM_P (mem));
/* The built MEM_REF does not accurately reflect that the load
is only partial. Clear it. */
@@ -3404,7 +3404,7 @@ expand_RAWMEMCHR (internal_fn, gcall *stmt)
create_call_lhs_operand (&ops[0], lhs_rtx, lhs_mode);
tree mem = gimple_call_arg (stmt, 0);
- rtx mem_rtx = get_memory_rtx (mem, NULL);
+ rtx mem_rtx = get_memory_rtx (mem, NULL, false);
create_fixed_operand (&ops[1], mem_rtx);
tree pattern = gimple_call_arg (stmt, 1);
diff --git a/gcc/testsuite/g++.dg/eh/noncall-store-2.C
b/gcc/testsuite/g++.dg/eh/noncall-store-2.C
new file mode 100644
index 00000000000..c095c71ca93
--- /dev/null
+++ b/gcc/testsuite/g++.dg/eh/noncall-store-2.C
@@ -0,0 +1,39 @@
+// Direct stores through reference and this parameters can throw non-call
+// exceptions.
+
+// { dg-do compile }
+// { dg-options "-O2 -fnon-call-exceptions -fdump-rtl-expand" }
+
+void
+store_ref (int &value)
+{
+ try
+ {
+ value = 1;
+ }
+ catch (...)
+ {
+ __builtin_trap ();
+ }
+}
+
+struct S
+{
+ int value;
+ void store ();
+};
+
+void
+S::store ()
+{
+ try
+ {
+ value = 1;
+ }
+ catch (...)
+ {
+ __builtin_trap ();
+ }
+}
+
+// { dg-final { scan-rtl-dump-times {REG_EH_REGION \(const_int [1-9][0-9]*} 2
"expand" } }
diff --git a/gcc/testsuite/g++.dg/opt/mem-notrap-store-asm.C
b/gcc/testsuite/g++.dg/opt/mem-notrap-store-asm.C
new file mode 100644
index 00000000000..9fcd13e9f61
--- /dev/null
+++ b/gcc/testsuite/g++.dg/opt/mem-notrap-store-asm.C
@@ -0,0 +1,15 @@
+// A memory asm output is a store even when expansion must return a MEM.
+
+// { dg-do compile }
+// { dg-options "-O2 -fnon-call-exceptions -fdump-rtl-expand" }
+
+extern const int output;
+
+void
+store ()
+{
+ asm volatile ("" : "=m" (const_cast<int &> (output)));
+}
+
+// { dg-final { scan-rtl-dump {\(set[ \t\n]+\(mem(?:/[a-z])*:} "expand" } }
+// { dg-final { scan-rtl-dump-not {\(set[
\t\n]+\(mem(?:/[a-z])*/c(?:/[a-z])*:} "expand" } }
diff --git a/gcc/testsuite/g++.dg/opt/mem-notrap-store-builtin.C
b/gcc/testsuite/g++.dg/opt/mem-notrap-store-builtin.C
new file mode 100644
index 00000000000..a048648784a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/opt/mem-notrap-store-builtin.C
@@ -0,0 +1,22 @@
+// Keep an inline memcpy source nontrapping, but not its destination.
+
+// { dg-do compile }
+// { dg-options "-O2 -fnon-call-exceptions -finline-stringops=memcpy
-fdump-rtl-expand" }
+// { dg-require-effective-target ptr32plus }
+
+typedef unsigned char block[16384];
+
+extern const block destination;
+extern block source;
+
+void
+copy ()
+{
+ __builtin_memcpy (const_cast<unsigned char *> (destination), source,
+ sizeof (block));
+}
+
+// { dg-final { scan-rtl-dump {\(mem(?:/[a-z])*/c(?:/[a-z])*:} "expand" } }
+// { dg-final { scan-rtl-dump {\(set[ \t\n]+\(mem(?:/[a-z])*:} "expand" } }
+// { dg-final { scan-rtl-dump-not {\(set[
\t\n]+\(mem(?:/[a-z])*/c(?:/[a-z])*:} "expand" } }
+// { dg-final { scan-assembler-not {\mmemcpy\M} } }
diff --git a/gcc/testsuite/gcc.target/i386/mem-notrap-mask-load.c
b/gcc/testsuite/gcc.target/i386/mem-notrap-mask-load.c
new file mode 100644
index 00000000000..bc510e6ad4f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/mem-notrap-mask-load.c
@@ -0,0 +1,23 @@
+/* A masked load is a read even though its expander requires a MEM. */
+
+/* { dg-do compile } */
+/* { dg-options "-O -fgimple -mavx2 -mno-avx512f -fnon-call-exceptions
-fdump-tree-optimized -fdump-rtl-expand" } */
+
+typedef int v4si __attribute__ ((vector_size (16)));
+
+extern const v4si values;
+
+v4si __GIMPLE (ssa)
+load (v4si mask)
+{
+ v4si result;
+
+ __BB(2):
+ result_2 = .MASK_LOAD (&values, _Literal (v4si *) 128,
+ mask_1(D),
+ _Literal (v4si) { 0, 0, 0, 0 });
+ return result_2;
+}
+
+/* { dg-final { scan-tree-dump {\.MASK_LOAD} "optimized" } } */
+/* { dg-final { scan-rtl-dump-times {\(mem/u/c:V4SI} 1 "expand" } } */
--
2.50.1 (Apple Git-155)