In order to have a nicely aligned assembly listing, we have separated
multiple assembler statements in the Asm template string with linefeed
(ASCII.LF) and horizontal tab (ASCII.HT) characters.
The resulting section of the assembly output file is:

#APP
pushfl
popl %eax
movl %eax, -40(%ebp)
#NO_APP

It would have been legal to write the Asm invocation as:

Asm ("pushfl popl %%eax movl %%eax, %0")

but in the generated assembler file, this would come out as:

#APP
pushfl popl %eax movl %eax, -40(%ebp)
#NO_APP

which is not so convenient for the human reader.

We use Ada comments
at the end of each line to explain what the assembler instructions
actually do. This is a useful convention.

When writing Inline Assembler instructions, you need to precede each register
and variable name with a percent sign. Since the assembler already requires
a percent sign at the beginning of a register name, you need two consecutive
percent signs for such names in the Asm template string, thus %%eax.
In the generated assembly code, one of the percent signs will be stripped off.

Names such as %0, %1, %2, etc., denote input or output
variables: operands you later define using Input or Output
parameters to Asm.
An output variable is illustrated in
the third statement in the Asm template string:

movl %%eax, %0

The intent is to store the contents of the eax register in a variable that can
be accessed in Ada. Simply writing movl %%eax, Flags would not
necessarily work, since the compiler might optimize by using a register
to hold Flags, and the expansion of the movl instruction would not be
aware of this optimization. The solution is not to store the result directly
but rather to advise the compiler to choose the correct operand form;
that is the purpose of the %0 output variable.

Information about the output variable is supplied in the Outputs
parameter to Asm:

Outputs => Unsigned_32'Asm_Output ("=g", Flags));

The output is defined by the Asm_Output attribute of the target type;
the general format is

Type'Asm_Output (constraint_string, variable_name)

The constraint string directs the compiler how
to store/access the associated variable. In the example

Unsigned_32'Asm_Output ("=m", Flags);

the "m" (memory) constraint tells the compiler that the variable
Flags should be stored in a memory variable, thus preventing
the optimizer from keeping it in a register. In contrast,

Unsigned_32'Asm_Output ("=r", Flags);

uses the "r" (register) constraint, telling the compiler to
store the variable in a register.

If the constraint is preceded by the equal character (=), it tells
the compiler that the variable will be used to store data into it.

In the Get_Flags example, we used the "g" (global) constraint,
allowing the optimizer to choose whatever it deems best.

There are a fairly large number of constraints, but the ones that are
most useful (for the Intel x86 processor) are the following:

=

output constraint

g

global (i.e. can be stored anywhere)

m

in memory

I

a constant

a

use eax

b

use ebx

c

use ecx

d

use edx

S

use esi

D

use edi

r

use one of eax, ebx, ecx or edx

q

use one of eax, ebx, ecx, edx, esi or edi

The full set of constraints is described in the gcc and as
documentation; note that it is possible to combine certain constraints
in one constraint string.

You specify the association of an output variable with an assembler operand
through the %n notation, where n is a non-negative
integer. Thus in

As a variation on the Get_Flags example, we can use the constraints
string to direct the compiler to store the eax register into the Flags
variable, instead of including the store instruction explicitly in the
Asm template string: