[PATCH] doc: c: c++: Document the C/C++ extended asm empty input constraints

Segher Boessenkool segher@kernel.crashing.org
Tue Feb 23 22:24:38 GMT 2021


Hi!

On Tue, Feb 23, 2021 at 12:05:34AM +0000, Neven Sajko wrote:
> On Mon, 22 Feb 2021 at 16:30, Segher Boessenkool
> <segher@kernel.crashing.org> wrote:
> > On Mon, Feb 15, 2021 at 11:22:52PM +0000, Neven Sajko via Gcc-patches wrote:
> > > There is a long-standing, but undocumented GCC inline assembly feature
> > > that's part of the extended asm GCC extension to C and C++: extended
> > > asm empty input constraints.
> >
> > There is no such thing.  *All* empty constraints have the same
> > semantics: anything whatsoever will do.  Any register, any constant, any
> > memory.
> 
> What I was trying to express is that input operand constraints are
> unlike output operand constraints in that they can be empty. I now
> realize I ended up being slightly confusing, though.

Ah, that is actually true for inline asm, yes; as long as you use only
a single alternative.  If you have more alternatives you can use an
empty constraint in an output just fine (with just one constraint you
will run into the "operand is not directly addressable" check, which
acdtually should *pass* for empty constraints).  Also, all this (except
that check, which is for inline asm only) is identical for the RTL that
GCC uses internally, and there empty constraints are quite common (for
situations where we really do not care what the operand is; almost
always the operands are constrained some other way already then).

So
  int f(void) { int x; asm("" : "=,r"(x)); }
works fine.

> > A length zero string is allowed as well.  This could be made more
> > explicit sure; OTOH, it isn't very often useful.  So your example
> > (using it for making a dependency) is certainly useful to have.  But
> > it is not a special case at all.
> 
> Syntactically, it's not a special case; but I definitely think the
> semantics could be better documented. Proof:
> 
> * There's a relevant Stack Overflow question.

There are SO questions for anything not obvious to some.  And you cannot
trust most answers there, either.

> If I didn't know better
> I'd conclude from the discussion there that empty input constraints
> are undocumented and unsupported,

They are not *usable* for almost anything.

> and there would surely be an answer
> if the documentation on the GCC side was a bit better:

Sure.  On the other hand, this is just one case of a much more general
issue: it is easy to write incorrect inline asm.

> * Clang erroneously doesn't support empty constraints for many years
> now (even though their internal documentation still says empty input
> constraints are supported, and external documentation says they
> support all the same constraints as GCC does).

Yes, they do not support many other features of inline asm either, and
they do not implement the same semantics for basic constructs.

> > > An empty input
> > > +constraint can be used to create an artificial dependency on a C or C++
> > > +variable (the variable that appears in the expression associated with the
> > > +constraint) without incurring unnecessary costs to performance.
> >
> > It still needs a register (or memory) reserved there (or sometimes a
> > constant can be used, but you have no dependency in that case!)
> 
> Yeah, this is a bit more complicated than I perhaps implied. An asm
> volatile can tell the compiler "I need this value calculated at this
> point",

No, that is not what asm volatile is or does.

"asm volatile" means the asm has an unspecified needed side effect.  In
other words, it has to be executed on the real machine exactly like on
the abstract machine; as often, and in the same order.

A volatile asm can be moved out of loops, or even out of functions, and
other similar things, just fine.

(All inline asms without outputs are always volatile (they could just
always be deleted if that was not true), you do not often need to say
"asm volatile" explicitly; mostly if the asm changes some machine state
the compiler does not know about, which you do not see in user code a
lot, just in OS code etc.)

> but the compiler may still choose to eliminate the calculation
> from the generated code if it can perform it itself at compilation
> time.

The compiler can *never* do what an asm does.  The compiler can never
know what a piece of assembler code (if that is what it is!) means!

If the compiler knows it will not need any outputs from the asm, and
there are no other side effects needed either, then it can delete the
asm.

> > > Specific applications may include direct
> > > +interaction with hardware features; or things like testing, fuzzing and
> > > +benchmarking.
> >
> > What does this mean?
> 
> The manual already has examples for "direct interaction with hardware features".

I could not find anything with non-trivial length substrings of that in
the manual.  Do you have an URL maybe?  Something under
  https://gcc.gnu.org/onlinedocs/gcc/
preferably.

> > Here is a simple example showing why this isn't as simple to use as
> > you imply here:
> >
> > ===
> > void f(int x)
> > {
> >         asm volatile("" :: ""(x));
> > }
> >
> > void g(void)
> > {
> >         return f(42);
> > }
> > ===
> >
> > Both function compile to (taking aarch64 as example) just "ret".  But,
> > if you look at what the compiler does, you see in the "dfinish" pass it
> > has for f:
> >
> > (insn:TI 6 3 20 (asm_operands/v ("") ("") 0 [
> >             (reg:SI 0 x0 [93])
> >         ]
> >          [
> >             (asm_input:SI ("") zlc.c:3)
> >         ]
> >          [] zlc.c:3) "zlc.c":3:2 -1
> >      (expr_list:REG_DEAD (reg:SI 0 x0 [93])
> >         (nil)))
> >
> >
> > (so it has register x0 as input), while function g has
> >
> > (insn:TI 5 2 16 (asm_operands/v ("") ("") 0 [
> >             (const_int 42 [0x2a])
> >         ]
> >          [
> >             (asm_input:SI ("") zlc.c:3)
> >         ]
> >          [] zlc.c:3) "zlc.c":3:2 -1
> >      (nil))
> >
> > which has no dependency, gets fed the constant 42 instead, because
> > *anything at all* is allowed by an empty constraint.
> >
> > You can also make this clear by using
> >
> >         asm volatile("# %0" :: ""(x));
> >
> > which gives
> >         # x0
> > resp.
> >         # 42
> >
> > or, with -fverbose-asm:
> >         # x0            // tmp93
> > and
> >         # 42            //
> >
> > which is clear as mud, but it means in f there was a variable as input
> > to the asm, and in g there wasn't.
> 
> Thank you for the example.

Let me work it out a little bit more then.

Say you have f as before, but g now is a loop calling f:

void f(int x)
{
	asm volatile("" :: ""(x));
}

void g(int n)
{
	for (int j = 0; j < n; j++)
		f(j);
}

and suppose this loop is loop peeled (a very common optimisation: the
first few iterations of the loop are unrolled).  Making this in effect:

void g(int n)
{
	if (n < 1)
		return;
	f(0);
	for (int j = 1; j < n; j++)
		f(j);
}

This  f(0);  does *not* get the dependency on j.


***  Empty constraints do not provide dependencies in most cases.  ***


"X" can be surprising in exactly this same way.

Always just use "r" or "rm" if you need a dependency.

> I would be very satisfied if the wording from the end of Jonathan's
> message made it to the documentation, though perhaps there should be
> an additional warning about the issue that Segher pointed to: that GCC
> may still eliminate a calculation if it can perform it at compilation
> time.

That is not the problem.  The weakness of using empty constraints (or
anything not register or memory) for dependencies is that it does NOT
create a dependency.


Segher


More information about the Gcc-patches mailing list