Bug 44492 - auto-inc-dec pushes PRE_MODIFY/PRE_INC into inline asm operands
Summary: auto-inc-dec pushes PRE_MODIFY/PRE_INC into inline asm operands
Alias: None
Product: gcc
Classification: Unclassified
Component: middle-end (show other bugs)
Version: 4.6.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
Keywords: wrong-code
Depends on:
Reported: 2010-06-10 09:31 UTC by Jakub Jelinek
Modified: 2010-08-13 12:47 UTC (History)
2 users (show)

See Also:
Target: powerpc64-linux
Known to work:
Known to fail:
Last reconfirmed:

gcc46-pr44494.patch (1.92 KB, patch)
2010-06-10 17:06 UTC, Jakub Jelinek
Details | Diff
gcc46-pr44492.patch (2.95 KB, patch)
2010-06-11 13:22 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jakub Jelinek 2010-06-10 09:31:30 UTC
struct T { const char *p; };
struct S { T a, b, c; unsigned d; };
void bar (const T &, const T &);

foo (S &s, T e)
  const char *a = e.p;
  const char *b = s.b.p;
  __asm__ volatile ("/* %0 %1 */" : : "rm" (a), "rm" (b));
  bar (e, s.b);

has PRE_MODIFY and PRE_INC in MEM operands of ASM_OPERANDS.  I can't see how that can be right - there is no guarantee that the operand is used in the asm at all, and even when it is used, it would have to be used in a load or store with update insn.
Comment 1 Andrew Pinski 2010-06-10 09:33:57 UTC
I had saw this before and it was declared this was correct behavior but I cannot find the bug any more.
Comment 2 Jakub Jelinek 2010-06-10 09:44:35 UTC
struct T { unsigned long p; };
struct S { T a, b, c; unsigned d; };

bar (const T &x, const T &y)
  if (x.p != 0x2348 || y.p != 0x2346)
    __builtin_abort ();

foo (S &s, T e)
  unsigned long a = e.p;
  unsigned long b = s.b.p;
  __asm__ volatile ("" : : "rm" (a), "rm" (b));
  bar (e, s.b);

main ()
  S s = { { 0x2345 }, { 0x2346 }, { 0x2347 }, 6 };
  T t = { 0x2348 };
  foo (s, t);

Updated testcase, this one I believe (don't have a ppc64 around to runtest it) should crash at runtime.
If PRE_INC/PRE_MODIFY is allowed in asm operands with "m" constraint, then "m" couldn't be actually ever safely used in inline asm, at least on ppc64.
(mem:DI (plus:DI (reg:DI 4) (const_int 8))) is printed as 8(4) identically to
what (mem:DI (pre_inc:DI (reg:DI 4))) prints - 8(4).  How could the asm find out which one it is?
Comment 3 Andrew Pinski 2010-06-10 09:47:48 UTC
I think the real issue is that m is too generic from the point of the documentation.

Reading: http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Simple-Constraints.html#Simple-Constraints

Makes it sound like it could accept an auto increment/decrement pattern.  While using something like "o" would not.
Comment 4 Andreas Schwab 2010-06-10 10:12:07 UTC
You need to use the %U and %X modifiers together with "m".  If the insn does not support them (no update and/or indexed form) you cannot use "m".  Basically this means you can use "m" only with the normal load/store insns.
Comment 5 Jakub Jelinek 2010-06-10 10:25:07 UTC
And ensure that it is used exactly once in an inline asm pattern?  Even for "g" constraint?  That just can't be true.
Just look at how many of "m" and "=m" constraints e.g. glibc uses.
Only 8 occurrences of %U, but e.g.:
./iconvdata/iso-2022-kr.c:      asm ("" : "=m" (buf));                                              \
./iconvdata/iso-2022-cn.c:      asm ("" : "=m" (buf));                                                \
./nscd/nscd_helper.c:  asm ("" : "=m" (tvend));
./nptl/sysdeps/unix/sysv/linux/powerpc/pthread_once.c:                  : "=&r" (oldval), "=&r" (tmp), "=m" (*once_control)
./nptl/sysdeps/unix/sysv/linux/powerpc/pthread_once.c:                  : "r" (once_control), "r" (newval), "m" (*once_control)
./nptl/sysdeps/unix/sysv/linux/powerpc/lowlevellock.h:                 : "=&r" (__val), "=m" (*futex)                         \
./nptl/sysdeps/unix/sysv/linux/powerpc/lowlevellock.h:                 : "r" (futex), "r" (id), "m" (*futex)                  \
./string/test-memcmp.c:   asm ("" : "=g" (r) : "0" (r));
./string/test-strncmp.c:          asm ("" : "=g" (r) : "0" (r));
./string/test-strcmp.c:   asm ("" : "=g" (r) : "0" (r));
./debug/tst-longjmp_chk.c:  asm volatile ("" : "=m" (buf));
./math/math_private.h:#define math_force_eval(x) __asm __volatile ("" : : "m" (x))
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "=&r" (__val), "=m" (*mem)                          \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                  \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "=&r" (__val), "=m" (*mem)                          \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                  \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "=&b" (__val), "=&r" (__tmp), "=m" (*mem)           \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                  \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "=&b" (__val), "=m" (*mem)                          \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "b" (mem), "m" (*mem)                               \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "=&b" (__val), "=m" (*mem)                          \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                      : "b" (mem), "m" (*mem)                               \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                     : "=&b" (__val), "=&r" (__tmp), "=m" (*mem)            \
./sysdeps/powerpc/powerpc64/bits/atomic.h:                     : "b" (mem), "m" (*mem)                                \
./sysdeps/powerpc/bits/atomic.h:                      : "=&r" (__val), "=m" (*mem)                            \
./sysdeps/powerpc/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                    \
./sysdeps/powerpc/bits/atomic.h:                      : "=&r" (__val), "=m" (*mem)                            \
./sysdeps/powerpc/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                    \
./sysdeps/powerpc/bits/atomic.h:                      : "=&b" (__val), "=&r" (__tmp), "=m" (*mem)             \
./sysdeps/powerpc/bits/atomic.h:                      : "b" (mem), "r" (value), "m" (*mem)                    \
./sysdeps/powerpc/bits/atomic.h:                      : "=&b" (__val), "=m" (*mem)                            \
./sysdeps/powerpc/bits/atomic.h:                      : "b" (mem), "m" (*mem)                                 \
./sysdeps/powerpc/bits/atomic.h:                      : "=&b" (__val), "=m" (*mem)                            \
./sysdeps/powerpc/bits/atomic.h:                      : "b" (mem), "m" (*mem)                                 \
./sysdeps/powerpc/bits/atomic.h:                       : "=&b" (__val), "=&r" (__tmp), "=m" (*mem)            \
./sysdeps/powerpc/bits/atomic.h:                       : "b" (mem), "m" (*mem)                                \

Guess the Linux kernel has many more of these.
Comment 6 Andreas Schwab 2010-06-10 10:59:03 UTC
You can use them as many times as needed.
Comment 7 Andreas Schwab 2010-06-10 11:06:05 UTC
A matched constraint can never have side effects, and the powerpc specific uses are already correct (though they could take advantage of the "es" or "Z" constraints).
Comment 8 Jakub Jelinek 2010-06-10 11:36:48 UTC
Don't understand the last comment.
If matched constraint can't have side-effects, then this bug is valid and auto-inc-dec.c should just do if (asm_noperands (insn) >= 0) continue; similarly how it refuses to auto-inc into JUMP_INSNs.
If this bug is not valid, then even the powerpc specific uses can't be correct, they don't mention many of the "m" or "=m" operands at all.
If it would be ok to use "m" constraint only together with matching %U on ppc, use it at least once, but it would be fine to use it more than once, then
"lwz%U2%X2 %0,%2
lwz%U2%X2 %1,%2" would increment the reg in "m" operand 2 twice if it contains pre_inc/pre_modify in it.
Comment 9 Andreas Schwab 2010-06-10 11:50:53 UTC
You cannot use an "m" operand more than once, since it can include side effects.
Comment 10 Frank Ch. Eigler 2010-06-10 12:11:30 UTC
(In reply to comment #9)
> You cannot use an "m" operand more than once, since it can include side
> effects.

Nor less than once, apparently.
Comment 11 Jakub Jelinek 2010-06-10 12:24:36 UTC
I believe for GCC it shouldn't be hard to at least easily detect the used zero times case which happens very often in lost of code.
asm ("... %2 ... " : "=m" (*p) : "m" (*p), "r" (p));
is just very common, the "=m" and "m" just tells gcc that the memory is used and might change.
It would be even possible using a target hook to check whether it is used exactly once, furthermore in target specific proper way (say, on powerpc64 the check would
be that there is exactly one use of the operand with U modifier, at most one with X modifier and exactly one another use, on ia64 it could be that there is exactly one use with P modifier and exactly one use another use, on the targets which print incdec in *print_*address it could be by default check for exactly one use of the operand.
This is not going to be perfect, it would count uses in comments and also doesn't cover e.g. a single use in not always executed code (say the asm has conditional jump around a few insns and uses the mem in there), but could fix up significant amount of code in the wild.
Comment 12 Michael Matz 2010-06-10 12:26:26 UTC
I don't think it ever was intended that 'm' includes operands with embedded
side-effects.  I don't think so because making this work reliably is
relatively difficult.  In particular the tests that Jakub mentions would need
implementation (and probably other changes too), and the point is that such
things never were implemented.  Hence with enough work it's probably possible
to construct testcases also for much older versions of GCC that fail in
similar ways.

If that means to slightly change the definition of 'm' compared to what is
documented currently, well, so be it.  The other definition is unreliable
anyway, so any inline asm that uses 'm' and expects a side-effect is fishy
at best.

It is fishy because if the compiler forwards a side-effect into the operands
it would have to rewrite the inline asm string to actually contain an
instruction to calculate this side-effect, which obviously is bollocks.

So, yes, auto-inc-dec should of course _not_ push side-effects into inline
Comment 13 Andreas Schwab 2010-06-10 12:39:21 UTC
"m" is defined to be the most general memory constraint, and a pre/post modified memory operand is still a memory operand.
Comment 14 Andreas Schwab 2010-06-10 12:42:23 UTC
> asm ("... %2 ... " : "=m" (*p) : "m" (*p), "r" (p));

In this case the compiler should never use a side effect.
Comment 15 Andreas Schwab 2010-06-10 12:46:12 UTC
The %X modifier has nothing to do with side effects, it is used for indexed addressing modes.
Comment 16 Frank Ch. Eigler 2010-06-10 13:24:39 UTC
(In reply to comment #13)
> "m" is defined to be the most general memory constraint, and a pre/post
> modified memory operand is still a memory operand.

If this is to stand, please amend the documentation to warn about this.
Comment 17 Michael Matz 2010-06-10 13:34:25 UTC
> "m" is defined to be the most general memory constraint, and a pre/post
> modified memory operand is still a memory operand.

I know that this is the case, which is why I said: "If that means to slightly
change the definition of 'm' compared to what is documented currently, well,
so be it."

I.e. I'm arguing that the documentation should be amended to state something
that can actually be implemented in a reliable way.  I.e. include "without
side-effects" at the appropriate place.
Comment 18 Andreas Schwab 2010-06-10 15:09:59 UTC
One option is to add a sequence point before every asm statement.
Comment 19 Andreas Schwab 2010-06-10 15:19:08 UTC
Of course, there _is_ already a sequence point before every statement.  Doh.
Comment 20 Jakub Jelinek 2010-06-10 15:22:15 UTC
Another option is to disallow side-effects in inline asm memory operands and
let the user explicitly request that the side-effects are possible, through
some new constraint modifier (e.g. _ used as "_mi" or "=_g" would tell auto-inc-dec it can put side-effects into it).  When _ is present, the user
would promise that the mem operand is used exactly once in an insn that is executed exactly once in that inline asm and that any target dependent modifiers
that are needed for auto-update insns are used too (%P on ia64, %U on ppc, etc.).

Comment 21 Jakub Jelinek 2010-06-10 17:06:55 UTC
Created attachment 20883 [details]

WIP patch that implements that.  Still missing documentation, testcases and
likely also doing similar test in constrain_operands, for asm_noperands > 0 there only (just not sure how to find
out cheaply from constrain_operands that we are in inline asm).
Comment 22 Jakub Jelinek 2010-06-11 13:22:49 UTC
Created attachment 20888 [details]

Updated patch.
Comment 23 Jakub Jelinek 2010-06-24 17:48:32 UTC
Subject: Bug 44492

Author: jakub
Date: Thu Jun 24 17:48:16 2010
New Revision: 161328

URL: http://gcc.gnu.org/viewcvs?root=gcc&view=rev&rev=161328
	PR middle-end/44492
	* recog.h (struct recog_data): Add is_asm field.
	* recog.c (asm_operand_ok, constrain_operands): If neither < nor > is
	present in constraints of inline-asm operand and memory operand
	contains {PRE,POST}_{INC,DEC,MODIFY}, return 0.
	(extract_insn): Initialize recog_data.is_asm.
	* doc/md.texi (Constraints): Document operand side-effect rules.

	* g++.dg/torture/pr44492.C: New test.


Comment 24 Jakub Jelinek 2010-08-13 12:47:53 UTC