A89: placing data into C variables from ASM(" ") constructs


[Prev][Next][Index][Thread]

A89: placing data into C variables from ASM(" ") constructs




TurboSoft@aol.com writes:

 > l can use the following code to put values into an ASM(" ") construct like 
 > this...
 > function(x)
 > int x;
 > {
 > move.w 8(%sp),%d0   // puts 'x' into d0
 > }
 > 
 > But how do l get values out of an asm construct? (like register d0 into 'x')

It is advisable to read the gcc manual, gcc has some interesting ways 
to deal with assembly - C interaction.

The following is *not* a detailed description and does not deal with
all possible aspects of referencing variables from either side.

Let us have a simple assembly code which (for the sake of simplicity)
adds two numbers together.

func()
{
int a, b, c;

  /* This will add a and b, store the result in c */
  
  asm volatile ( "
	
	 move.l %2,%0
	 add.l  %1,%0
	 
    " : "=&d" (c) : "g" (a), "g" (b) );
	
What does it do:

In the move.l and add.l operands %0, %1 and %2 refer to the formal 
parameters to the assembly text. They will be substituted with the 
relevant register or memory references. The formal parameters are 
numbered from 0 to 9, left to right. 

The colon separates the assembly code from the formal parameter list
section. This start with the output parameter description list. 
An assembly function may have none, one or more output parameters. 
The nature of the parameter is specified between the ""-s.
The first symbol is the = which indicates an output parameter. The
next symbol is an optional &, see later. The next is a letter or a
series of letters indicating the type of the operand. The most often
used ones are d, for a data register, a, for address register, m for
memory and g for any of the above. Thus, in our example formal 
parameter 0 is qualified by "=&d", that is, it is an output operand
which must be a data register. It also has the '&' qualifier, which
will soon be discussed. Following this definition is the (c)
part. This tells the compiler that after executing the routine it
should assign the result in that data register to the C variable 'c'. 
Actually, anything can be in the parens which can be on the left hand
side of an = operator, so you can have things like this:

	... " : "=&da" (*(( long *) my_pointer)) : ...

which will move the result of the assembly routine, which result
should be in an address or in a data register, as a longword, to the
location pointed by my_pointer. 

If you have more than one output, you simply list them, separated by
commas, like this:

   ... " : "=&d" (x), "=&a" (ptr) : ...
   
This was the output specification section.

The following : indicates the end of the description of the output
operands and the start of the input operands. The input operand
descriptors are similar to the output ones, with some differences. On
one hand, they do not start with = for they are input operands. For
that reason, the c expression in the parens can be anything that can
be on the right hand side of an =, including constants, any complex
expression, function calls and whatever.

Now comes the interesting bit. Gcc treats the whole assembly issue in
such a way that it assumes that the assembly stub consumes all of its
input operands before producing its output. This means that gcc may
allocate the same register for an output operand as an input one. In
our example, if %0 and %1 are the same, then we are in trouble. This
is what the '&' is for in the output specification. It tells gcc that
the particular output operand can not be assignedto the same register 
as an input operand.
Sometimes you want the opposite, you want to tell the compiler that
an input operand must be allocated into the same register as a
particular output operand. You do it by specifying the reference
number of the output operand in the input section, as in the example
below:

   asm volatile ( "
     
	   add.l %1,%0
	   
   "=&d" (c) : "d" (a), "0" (b) );
   
The only difference is from the previous version is that as you can
see the type specifier for the (b) operand is "0". This means the 0-th 
formal parameter, that is the same place that will be allocated to
(c). This way we could save the move.l %2,%0 since the compiler will
know that these registers are the same.

If your assembly routine destroys specific registers, you can list
them after the input section. Again, a : should be put there then you
just list the registers you destroy, each of them in ""-s, like this:

   "=&d" (c) : "d" (a), "0" (b) " "d0", "d1", "a5" );

if you destroy d0, d1 and a5. If you clobber memory, then list 
"memory" as well. It is guaranteed that gcc will not allocate these
registers to input or output operands.

This leaves one more thing: what about destroying input operands. Gcc
assumes, (unless you specifically assigned an input and an output
parameter to the same formal parameter) that your routine does not
change the input parameters. If you do change it, then you are in
trouble - if the compiler later needs the value it stuffed into the
regsiter, it vill assume that it's still there. If you changed it,
then your code will blow.

To avoid this, you can transfer the input operands which you wish to
change into registers which then are listed in the clobbered section.

There are other tips & tricks with regards to the C and asm
interaction in gcc, but hopefully the above is enough to get you
going. For anything more you should consult the gcc info file, which
you should have received with the compiler package.

Regards,

Zoltan


Follow-Ups: References: