aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Documentation/IR.rst2
-rw-r--r--builtin.c73
-rw-r--r--evaluate.c3
-rw-r--r--expression.h2
-rw-r--r--flow.c17
-rw-r--r--linearize.c69
-rw-r--r--linearize.h7
-rw-r--r--liveness.c7
-rw-r--r--pre-process.c6
-rw-r--r--ptrlist.c10
-rw-r--r--ptrlist.h24
-rw-r--r--show-parse.c2
-rw-r--r--simplify.c6
-rw-r--r--sparse.c13
-rw-r--r--validation/builtin-objsize-dyn.c22
-rw-r--r--validation/builtin-objsize0.c25
-rw-r--r--validation/builtin-objsize1.c21
-rw-r--r--validation/eval/assign-restricted-ok.c22
-rw-r--r--validation/linear/asm-out0.c25
-rw-r--r--validation/mem2reg/asm-reload0.c14
20 files changed, 304 insertions, 66 deletions
diff --git a/Documentation/IR.rst b/Documentation/IR.rst
index 0cc90ec0..d41bce87 100644
--- a/Documentation/IR.rst
+++ b/Documentation/IR.rst
@@ -408,7 +408,7 @@ Others
Extract a "slice" from an aggregate.
* .base: (pseudo_t) aggregate (alias .src)
- * .from, .len: offet & size of the "slice" within the aggregate
+ * .from: offset of the "slice" within the aggregate
* .target: result
* .type: type of .target
diff --git a/builtin.c b/builtin.c
index 5c7321ca..c7e7da3b 100644
--- a/builtin.c
+++ b/builtin.c
@@ -454,6 +454,77 @@ static struct symbol_op atomic_op = {
};
+///
+// expand __builtin_object_size()
+//
+// :note: type 1 and type 3 are not supported because the
+// needed information isn't available after evaluation.
+static int expand_object_size(struct expression *expr, int cost)
+{
+ struct expression *arg = first_expression(expr->args);
+ int type = get_expression_value_silent(ptr_list_nth(expr->args, 1));
+ unsigned long val = -1, off = 0;
+
+ while (arg) {
+ switch (arg->type) {
+ case EXPR_IMPLIED_CAST:
+ case EXPR_CAST:
+ // ignore those
+ arg = arg->cast_expression;
+ continue;
+ case EXPR_BINOP:
+ // a constant add is (maybe) an offset
+ if (!arg->right || arg->op != '+' || arg->right->type != EXPR_VALUE)
+ break;
+ off += arg->right->value;
+ arg = arg->left;
+ continue;
+ case EXPR_PREOP:
+ // a deref is just intermediate variable
+ // and so the offset needs to be zeroed.
+ if (arg->op == '*') {
+ arg = arg->unop;
+ off = 0;
+ switch (arg->type) {
+ case EXPR_SYMBOL:
+ arg = arg->symbol->initializer;
+ continue;
+ default:
+ break;
+ }
+ }
+ break;
+ case EXPR_SYMBOL:
+ // the symbol we're looking after
+ val = bits_to_bytes(arg->symbol->bit_size);
+ break;
+ case EXPR_CALL:
+ // use alloc_size() attribute but only after linearization.
+ return UNSAFE;
+ default:
+ break;
+ }
+ break;
+ }
+
+ if (val == -1)
+ val = (type & 2) ? 0 : val;
+ else if (type & 1)
+ return UNSAFE;
+ else
+ val -= off;
+
+ expr->flags |= CEF_SET_ICE;
+ expr->type = EXPR_VALUE;
+ expr->value = val;
+ expr->taint = 0;
+ return 0;
+}
+
+static struct symbol_op object_size_op = {
+ .expand = expand_object_size,
+};
+
/*
* Builtin functions
*/
@@ -598,7 +669,7 @@ static const struct builtin_fn builtins_common[] = {
{ "__builtin_nan", &double_ctype, 0, { &const_string_ctype }},
{ "__builtin_nanf", &float_ctype, 0, { &const_string_ctype }},
{ "__builtin_nanl", &ldouble_ctype, 0, { &const_string_ctype }},
- { "__builtin_object_size", size_t_ctype, 0, { &const_ptr_ctype, &int_ctype }},
+ { "__builtin_object_size", size_t_ctype, 0, { &const_ptr_ctype, &int_ctype }, .op = &object_size_op},
{ "__builtin_parity", &int_ctype, 0, { &uint_ctype }, .op = &parity_op },
{ "__builtin_parityl", &int_ctype, 0, { &ulong_ctype }, .op = &parity_op },
{ "__builtin_parityll", &int_ctype, 0, { &ullong_ctype }, .op = &parity_op },
diff --git a/evaluate.c b/evaluate.c
index 41871e18..eea6b7ad 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -1442,7 +1442,7 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
if (sclass & TYPE_FOULED && unfoul(s) == t)
goto Cast;
if (!restricted_value(*rp, target))
- return 1;
+ goto Cast;
if (s == t)
return 1;
} else if (!(sclass & TYPE_RESTRICT))
@@ -2170,7 +2170,6 @@ static struct symbol *evaluate_member_dereference(struct expression *expr)
}
expr->r_bitpos += bytes_to_bits(offset);
expr->type = EXPR_SLICE;
- expr->r_nrbits = member->bit_size;
expr->r_bitpos += member->bit_offset;
expr->ctype = member;
return member;
diff --git a/expression.h b/expression.h
index 3e9e9d85..f733c076 100644
--- a/expression.h
+++ b/expression.h
@@ -206,7 +206,7 @@ struct expression {
// EXPR_SLICE
struct /* slice */ {
struct expression *base;
- unsigned r_bitpos, r_nrbits;
+ unsigned r_bitpos;
};
// EXPR_CAST, EXPR_FORCE_CAST, EXPR_IMPLIED_CAST,
// EXPR_SIZEOF, EXPR_ALIGNOF and EXPR_PTRSIZEOF
diff --git a/flow.c b/flow.c
index b776375b..c5319ae3 100644
--- a/flow.c
+++ b/flow.c
@@ -471,12 +471,21 @@ static inline int distinct_symbols(pseudo_t a, pseudo_t b)
*/
int dominates(pseudo_t pseudo, struct instruction *insn, struct instruction *dom, int local)
{
- int opcode = dom->opcode;
-
- if (opcode == OP_CALL || opcode == OP_ENTRY)
+ switch (dom->opcode) {
+ case OP_CALL: case OP_ENTRY:
return local ? 0 : -1;
- if (opcode != OP_LOAD && opcode != OP_STORE)
+ case OP_LOAD: case OP_STORE:
+ break;
+ case OP_ASM:
+ if (dom->clobber_memory)
+ return -1;
+ if (dom->output_memory)
+ return -1;
+ return 0;
+ default:
return 0;
+ }
+
if (dom->src != pseudo) {
if (local)
return 0;
diff --git a/linearize.c b/linearize.c
index c1aeb159..7248fa56 100644
--- a/linearize.c
+++ b/linearize.c
@@ -466,7 +466,7 @@ const char *show_instruction(struct instruction *insn)
break;
case OP_SLICE:
- buf += sprintf(buf, "%s <- %s, %d, %d", show_pseudo(insn->target), show_pseudo(insn->base), insn->from, insn->len);
+ buf += sprintf(buf, "%s <- (%d) %s, %d", show_pseudo(insn->target), type_size(insn->orig_type), show_pseudo(insn->src), insn->from);
break;
case OP_NOT: case OP_NEG:
@@ -1235,8 +1235,8 @@ static pseudo_t linearize_slice(struct entrypoint *ep, struct expression *expr)
insn->target = new;
insn->from = expr->r_bitpos;
- insn->len = expr->r_nrbits;
- use_pseudo(insn, pre, &insn->base);
+ insn->orig_type = expr->base->ctype;
+ use_pseudo(insn, pre, &insn->src);
add_one_insn(ep, insn);
return new;
}
@@ -2123,43 +2123,55 @@ static pseudo_t linearize_range(struct entrypoint *ep, struct statement *stmt)
ALLOCATOR(asm_rules, "asm rules");
ALLOCATOR(asm_constraint, "asm constraints");
-static void add_asm_input(struct entrypoint *ep, struct instruction *insn, struct asm_operand *op)
+static void add_asm_rule(struct instruction *insn, struct asm_constraint_list **list, struct asm_operand *op, pseudo_t pseudo)
{
- pseudo_t pseudo = linearize_expression(ep, op->expr);
struct asm_constraint *rule = __alloc_asm_constraint(0);
-
+ rule->is_memory = op->is_memory;
rule->ident = op->name;
rule->constraint = op->constraint ? op->constraint->string->data : "";
use_pseudo(insn, pseudo, &rule->pseudo);
- add_ptr_list(&insn->asm_rules->inputs, rule);
+ add_ptr_list(list, rule);
+}
+
+static void add_asm_input(struct entrypoint *ep, struct instruction *insn, struct asm_operand *op)
+{
+ pseudo_t pseudo = linearize_expression(ep, op->expr);
+
+ add_asm_rule(insn, &insn->asm_rules->inputs, op, pseudo);
+}
+
+static void add_asm_output_address(struct entrypoint *ep, struct instruction *insn, struct asm_operand *op)
+{
+ pseudo_t pseudo;
+
+ if (!op->is_memory)
+ return;
+
+ pseudo = linearize_expression(ep, op->expr);
+ add_asm_rule(insn, &insn->asm_rules->outputs, op, pseudo);
+ insn->output_memory = 1;
}
static void add_asm_output(struct entrypoint *ep, struct instruction *insn, struct asm_operand *op)
{
struct access_data ad = { NULL, };
pseudo_t pseudo;
- struct asm_constraint *rule;
- if (op->is_memory) {
- pseudo = linearize_expression(ep, op->expr);
- } else {
- if (!linearize_address_gen(ep, op->expr, &ad))
- return;
- pseudo = alloc_pseudo(insn);
- linearize_store_gen(ep, pseudo, &ad);
- }
- rule = __alloc_asm_constraint(0);
- rule->is_memory = op->is_memory;
- rule->ident = op->name;
- rule->constraint = op->constraint ? op->constraint->string->data : "";
- use_pseudo(insn, pseudo, &rule->pseudo);
- add_ptr_list(&insn->asm_rules->outputs, rule);
+ if (op->is_memory)
+ return;
+
+ if (!linearize_address_gen(ep, op->expr, &ad))
+ return;
+ pseudo = alloc_pseudo(insn);
+ linearize_store_gen(ep, pseudo, &ad);
+
+ add_asm_rule(insn, &insn->asm_rules->outputs, op, pseudo);
}
static pseudo_t linearize_asm_statement(struct entrypoint *ep, struct statement *stmt)
{
struct instruction *insn;
- struct expression *expr;
+ struct expression *expr, *clob;
struct asm_rules *rules;
struct asm_operand *op;
@@ -2179,6 +2191,11 @@ static pseudo_t linearize_asm_statement(struct entrypoint *ep, struct statement
add_asm_input(ep, insn, op);
} END_FOR_EACH_PTR(op);
+ /* ... and the addresses for memory outputs */
+ FOR_EACH_PTR(stmt->asm_outputs, op) {
+ add_asm_output_address(ep, insn, op);
+ } END_FOR_EACH_PTR(op);
+
add_one_insn(ep, insn);
/* Assign the outputs */
@@ -2186,6 +2203,12 @@ static pseudo_t linearize_asm_statement(struct entrypoint *ep, struct statement
add_asm_output(ep, insn, op);
} END_FOR_EACH_PTR(op);
+ /* and finally, look if it clobbers memory */
+ FOR_EACH_PTR(stmt->asm_clobbers, clob) {
+ if (!strcmp(clob->string->data, "memory"))
+ insn->clobber_memory = 1;
+ } END_FOR_EACH_PTR(clob);
+
return VOID;
}
diff --git a/linearize.h b/linearize.h
index 18f1d80f..7909b01f 100644
--- a/linearize.h
+++ b/linearize.h
@@ -113,6 +113,7 @@ struct instruction {
};
struct /* unops */ {
pseudo_t src;
+ unsigned from; /* slice */
struct symbol *orig_type; /* casts */
};
struct /* memops */ {
@@ -127,10 +128,6 @@ struct instruction {
pseudo_t _src1, _src2; // alias .src[12]
struct symbol *itype; // input operands' type
};
- struct /* slice */ {
- pseudo_t base;
- unsigned from, len;
- };
struct /* setval */ {
struct expression *val;
};
@@ -150,6 +147,8 @@ struct instruction {
struct /* asm */ {
const char *string;
struct asm_rules *asm_rules;
+ unsigned int clobber_memory:1;
+ unsigned int output_memory:1;
};
};
};
diff --git a/liveness.c b/liveness.c
index 4fc16e3d..c79e8598 100644
--- a/liveness.c
+++ b/liveness.c
@@ -76,7 +76,8 @@ static void track_instruction_usage(struct basic_block *bb, struct instruction *
/* Uni */
case OP_UNOP ... OP_UNOP_END:
case OP_SYMADDR:
- USES(src1); DEFINES(target);
+ case OP_SLICE:
+ USES(src); DEFINES(target);
break;
case OP_SEL:
@@ -121,10 +122,6 @@ static void track_instruction_usage(struct basic_block *bb, struct instruction *
} END_FOR_EACH_PTR(pseudo);
break;
- case OP_SLICE:
- USES(base); DEFINES(target);
- break;
-
case OP_ASM:
asm_liveness(bb, insn, def, use);
break;
diff --git a/pre-process.c b/pre-process.c
index 7a1478f6..3fb25082 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -884,6 +884,12 @@ static void set_stream_include_path(struct stream *stream)
memcpy(m, stream->name, len);
m[len] = 0;
path = m;
+ /* normalize this path */
+ while (path[0] == '.' && path[1] == '/') {
+ path += 2;
+ while (path[0] == '/')
+ path++;
+ }
}
stream->path = path;
}
diff --git a/ptrlist.c b/ptrlist.c
index 0f0b3f6d..c5766002 100644
--- a/ptrlist.c
+++ b/ptrlist.c
@@ -154,10 +154,10 @@ void *ptr_list_nth_entry(struct ptr_list *list, unsigned int idx)
// @head: the list to be linearized
// @arr: a ``void*`` array to fill with @head's entries
// @max: the maximum number of entries to store into @arr
-// @return: the number of entries linearized.
+// @return: the number of entries in the list.
//
// Linearize the entries of a list up to a total of @max,
-// and return the nr of entries linearized.
+// and return the number of entries in the list.
//
// The array to linearize into (@arr) should really
// be ``void *x[]``, but we want to let people fill in any kind
@@ -170,14 +170,14 @@ int linearize_ptr_list(struct ptr_list *head, void **arr, int max)
do {
int i = list->nr;
+ nr += i;
+ if (max == 0)
+ continue;
if (i > max)
i = max;
memcpy(arr, list->list, i*sizeof(void *));
arr += i;
- nr += i;
max -= i;
- if (!max)
- break;
} while ((list = list->next) != head);
}
return nr;
diff --git a/ptrlist.h b/ptrlist.h
index 4bf8c709..5a3dcbeb 100644
--- a/ptrlist.h
+++ b/ptrlist.h
@@ -12,7 +12,7 @@
/* Silly type-safety check ;) */
#define CHECK_TYPE(head,ptr) (void)(&(ptr) == &(head)->list[0])
-#define TYPEOF(head) __typeof__(&(head)->list[0])
+#define PTRLIST_TYPE(head) __typeof__((head)->list[0])
#define VRFY_PTR_LIST(head) (void)(sizeof((head)->list[0]))
#define LIST_NODE_NR (13)
@@ -67,12 +67,28 @@ extern void **__add_ptr_list_tag(struct ptr_list **, void *, unsigned long);
(__typeof__(&(ptr))) __add_ptr_list_tag(head, ptr, tag);\
})
+#define pop_ptr_list(l) ({ \
+ PTRLIST_TYPE(*(l)) ptr; \
+ ptr = delete_ptr_list_last((struct ptr_list**)(l)); \
+ ptr; \
+ })
+
extern void __free_ptr_list(struct ptr_list **);
#define free_ptr_list(list) do { \
VRFY_PTR_LIST(*(list)); \
__free_ptr_list((struct ptr_list **)(list)); \
} while (0)
+#define ptr_list_nth(lst, nth) ({ \
+ struct ptr_list* head = (struct ptr_list*)(lst); \
+ (PTRLIST_TYPE(lst)) ptr_list_nth_entry(head, nth);\
+ })
+
+#define ptr_list_to_array(list, array, size) ({ \
+ struct ptr_list* head = (struct ptr_list*)(list); \
+ CHECK_TYPE(list, *array); \
+ linearize_ptr_list(head, (void**)array, size); \
+ })
////////////////////////////////////////////////////////////////////////
// API
@@ -247,7 +263,7 @@ extern void __free_ptr_list(struct ptr_list **);
extern void split_ptr_list_head(struct ptr_list *);
#define DO_INSERT_CURRENT(new, __head, __list, __nr) do { \
- TYPEOF(__head) __this, __last; \
+ PTRLIST_TYPE(__head) *__this, *__last; \
if (__list->nr == LIST_NODE_NR) { \
split_ptr_list_head((struct ptr_list*)__list); \
if (__nr >= __list->nr) { \
@@ -266,8 +282,8 @@ extern void split_ptr_list_head(struct ptr_list *);
} while (0)
#define DO_DELETE_CURRENT(__head, __list, __nr) do { \
- TYPEOF(__head) __this = __list->list + __nr; \
- TYPEOF(__head) __last = __list->list + __list->nr - 1; \
+ PTRLIST_TYPE(__head) *__this = __list->list + __nr; \
+ PTRLIST_TYPE(__head) *__last = __list->list + __list->nr - 1; \
while (__this < __last) { \
__this[0] = __this[1]; \
__this++; \
diff --git a/show-parse.c b/show-parse.c
index 3ab8ec8f..e2fc18bb 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -819,7 +819,7 @@ static int show_slice(struct expression *expr)
{
int target = show_expression(expr->base);
int new = new_pseudo();
- printf("\tslice.%d\t\tv%d,v%d,%d\n", expr->r_nrbits, target, new, expr->r_bitpos);
+ printf("\tslice.%d\t\tv%d,v%d,%d\n", expr->ctype->bit_size, target, new, expr->r_bitpos);
return new;
}
diff --git a/simplify.c b/simplify.c
index 584078dd..207af8ed 100644
--- a/simplify.c
+++ b/simplify.c
@@ -83,7 +83,7 @@ static struct basic_block *phi_parent(struct basic_block *source, pseudo_t pseud
// number of element, a positive number if there was
// more than expected and a negative one if less.
//
-// :note: we can't reuse a function like linearize_ptr_list()
+// :note: we can't reuse ptr_list_to_array() for the phi-sources
// because any VOIDs in the phi-list must be ignored here
// as in this context they mean 'entry has been removed'.
static int get_phisources(struct instruction *sources[], int nbr, struct instruction *insn)
@@ -108,7 +108,7 @@ static int get_phisources(struct instruction *sources[], int nbr, struct instruc
static int if_convert_phi(struct instruction *insn)
{
struct instruction *array[2];
- struct basic_block *parents[3];
+ struct basic_block *parents[2];
struct basic_block *bb, *bb1, *bb2, *source;
struct instruction *br;
pseudo_t p1, p2;
@@ -116,7 +116,7 @@ static int if_convert_phi(struct instruction *insn)
bb = insn->bb;
if (get_phisources(array, 2, insn))
return 0;
- if (linearize_ptr_list((struct ptr_list *)bb->parents, (void **)parents, 3) != 2)
+ if (ptr_list_to_array(bb->parents, parents, 2) != 2)
return 0;
p1 = array[0]->phi_src;
bb1 = array[0]->bb;
diff --git a/sparse.c b/sparse.c
index 151eaf4e..9d62d4fe 100644
--- a/sparse.c
+++ b/sparse.c
@@ -163,20 +163,9 @@ static void check_byte_count(struct instruction *insn, pseudo_t count)
/* OK, we could try to do the range analysis here */
}
-static pseudo_t argument(struct instruction *call, unsigned int argno)
-{
- pseudo_t args[8];
- struct ptr_list *arg_list = (struct ptr_list *) call->arguments;
-
- argno--;
- if (linearize_ptr_list(arg_list, (void *)args, 8) > argno)
- return args[argno];
- return NULL;
-}
-
static void check_memset(struct instruction *insn)
{
- check_byte_count(insn, argument(insn, 3));
+ check_byte_count(insn, ptr_list_nth(insn->arguments, 3));
}
#define check_memcpy check_memset
diff --git a/validation/builtin-objsize-dyn.c b/validation/builtin-objsize-dyn.c
new file mode 100644
index 00000000..276c9204
--- /dev/null
+++ b/validation/builtin-objsize-dyn.c
@@ -0,0 +1,22 @@
+void *alloc(unsigned long)__attribute__((alloc_size(1)));
+
+_Bool sta(void)
+{
+ void *ptr = alloc(4);
+ return __builtin_object_size(ptr, 0) == 4;
+}
+
+_Bool dyn(unsigned long n)
+{
+ void *ptr = alloc(n);
+ return __builtin_object_size(ptr, 0) == n;
+}
+
+/*
+ * check-name: builtin-objsize-dyn
+ * check-command: test-linearize -Wno-decl $file
+ * check-known-to-fail
+ *
+ * check-output-ignore
+ * check-output-returns: 1
+ */
diff --git a/validation/builtin-objsize0.c b/validation/builtin-objsize0.c
new file mode 100644
index 00000000..9aab2ddd
--- /dev/null
+++ b/validation/builtin-objsize0.c
@@ -0,0 +1,25 @@
+#define bos(O, T) __builtin_object_size(O, T)
+
+struct s {
+ char arr[8];
+ __INT32_TYPE__ i;
+ __INT32_TYPE__ padding;
+};
+
+static struct s s;
+static char *p = &s.arr[1];
+static int *q = &s.i;
+
+int obj_int0(void) { return bos(&s.i, 0) == 8; }
+int obj_arr0(void) { return bos(&s.arr[1], 0) == 15; }
+
+int ptr_int(struct s *p) { return bos(&p->i, 0) == -1; }
+int ptr_arr(struct s *p) { return bos(&p->arr[1], 0) == -1; }
+
+/*
+ * check-name: builtin-objsize0
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-returns: 1
+ */
diff --git a/validation/builtin-objsize1.c b/validation/builtin-objsize1.c
new file mode 100644
index 00000000..1f285fc5
--- /dev/null
+++ b/validation/builtin-objsize1.c
@@ -0,0 +1,21 @@
+#define bos(O, T) __builtin_object_size(O, T)
+
+struct s {
+ char arr[8];
+ __INT32_TYPE__ i;
+ __INT32_TYPE__ padding;
+};
+
+static struct s s;
+
+int obj_int1(void) { return bos(&s.i, 1) == 4; }
+int obj_arr1(void) { return bos(&s.arr[1], 1) == 7; }
+
+/*
+ * check-name: builtin-objsize1
+ * check-command: test-linearize -Wno-decl $file
+ * check-known-to-fail
+ *
+ * check-output-ignore
+ * check-output-returns: 1
+ */
diff --git a/validation/eval/assign-restricted-ok.c b/validation/eval/assign-restricted-ok.c
new file mode 100644
index 00000000..df94d8c9
--- /dev/null
+++ b/validation/eval/assign-restricted-ok.c
@@ -0,0 +1,22 @@
+#ifdef __CHECKER__
+#define __bitwise __attribute__((bitwise))
+#else
+#define __bitwise
+#endif
+
+typedef __INT16_TYPE__ __bitwise __be16;
+
+static __be16 foo(void)
+{
+ __be16 val = 0;
+ return val;
+}
+
+/*
+ * check-name: assign-restricted-ok
+ * check-command: test-linearize -fdump-ir $file
+ *
+ * check-output-ignore
+ * check-output-contains: store\\.16
+ * check-output-excludes: store\\.32
+ */
diff --git a/validation/linear/asm-out0.c b/validation/linear/asm-out0.c
new file mode 100644
index 00000000..8b0907b3
--- /dev/null
+++ b/validation/linear/asm-out0.c
@@ -0,0 +1,25 @@
+static void asm_out0(void)
+{
+ int mem;
+ asm volatile ("[%1] <= 0" : "=m" (mem));
+}
+
+/*
+ * check-name: asm-out0
+ * check-command: test-linearize -m64 -fdump-ir $file
+ *
+ * check-output-start
+asm_out0:
+.L0:
+ <entry-point>
+ symaddr.64 %r1 <- mem
+ asm "[%1] <= 0"
+ out: "=m" (%r1)
+ br .L1
+
+.L1:
+ ret
+
+
+ * check-output-end
+ */
diff --git a/validation/mem2reg/asm-reload0.c b/validation/mem2reg/asm-reload0.c
new file mode 100644
index 00000000..ce1829e0
--- /dev/null
+++ b/validation/mem2reg/asm-reload0.c
@@ -0,0 +1,14 @@
+static int asm_reload(void)
+{
+ int mem = 0;
+ asm volatile ("[%1] <= 1" : "=m" (mem));
+ return mem;
+}
+
+/*
+ * check-name: asm-reload0
+ * check-command: test-linearize $file
+ *
+ * check-output-ignore
+ * check-output-contains: load\\.
+ */