Re: A89: More Help
[Prev][Next][Index][Thread]
Re: A89: More Help
Hi!
> > Rather than let them explain it, I refer to the GCC documentation
> > (which FYI I have bookmarked): ...
>
> I downloaded the whole GCC doc last night, so that's covered ;)
> Couldn't make much sense of it...but I can't say my brain was
> working to full capacity either...
TIGCCLIB release 2.2 (planed for the next week) documentation will
contain not only the documentation for the library, but for the
compiler itself. These informations will be a restructured informations
from the original GNU C manual, but restructured to be more particular
to TIGCC, TI calculators, and MC68000. Here is what I wrote about
"asm" instruction (I think that it will be useful to read, although it
is only somewhat modified GNU C infos, but more MC68000 specific
examples are added). This will show to a lot of uninformed users how
powerful TIGCC built-in assembler is. This section assumes that you
already know something about other GNU extensions (will be included
in TIGCC documentation too). Sorry, the section "Operand Constraints"
is not written yet, but it will be. Please, if someone experienced
find some errors in what I am written below, inform me. Also, inform
me about unclear facts which needs to be explain better.
Cheers,
Zeljko Juric
=================================================================
The built-in assembler
----------------------
One of GNU C extensions is the <CODE>asm</CODE> keyword, which allows
the access to the inline assembler. The simplest form of it is
asm ("string");
where "string" contains one or more assembly instrutcions, which are
separated by semi-colons. In TIGCC, all register names must be
preceeded by a percent sign, to avoid confusion with an eventual C
variable named as one of the CPU registers, for example:
asm ("move.l 0xC8,%a0; move.l (%a0,1656); jsr (%a0)");
Notice that hex constants must be written in according to C syntax
(like '0xC8'), in opposite to the notation '$C8' which is common in
various assemblers. Note that something like
asm ("move.l 0xC8,a0");
will be interpreted quite differently: 'a0' will be regarded as a C
language variable, not a register!
Newlines as separators are also accepted in the "string", so the
following code is valid too:
asm ("move.l 0xC8,%a0
move.l (%a0,1656)
jsr (%a0)");
But this is not all. The GNU built-in assembler is much more
powerful. In an assembler instruction using 'asm', you can specify
the operands of the instruction using C expressions! This means you
need not guess which registers or memory locations will contain the
data you want to use. You must specify an assembler instruction
template, plus an operand constraint string for each operand.
For the begining, here will be given a very ilustrative example how
to use the 68881's 'fsinx' instruction (note that this example is
not applicable with TI calculators, because the MC68000 processor
which is built into the TI-89 and TI-92+ does not have this
instruction, but the example is ilustrative anyway):
asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
Here 'angle' is the C expression for the input operand while 'result'
is that of the output operand. Each has "f" as its operand constraint,
saying that a floating point register is required. The '=' in "=f"
indicates that the operand is an output; all output operands
constraints must use '='. The constraints use the same language used
in the machine description (see section "Operand Constraints"). In
this example, the compiler will convert this instruction into a
sequence of assembly instructions which will ensure that the
evaluated expression 'angle' is really stored in some of 68881's
floating point registers, and which will store the result of the
'fsinx' (which is in some another floating point register) into the
expression 'result' (note that 'result' need not to be a variable;
it can be any lvalue expression, for example a dereferenced pointer).
Each operand is described by an operand-constraint string followed by
the C expression in parentheses. A colon separates the assembler
template from the first output operand and another separates the last
output operand from the first input, if any. Commas separate the
operands within each group. The total number of operands is limited to
ten or to the maximum number of operands in any instruction pattern in
the machine description, whichever is greater.
If there are no output operands but there are input operands, you must
place two consecutive colons surrounding the place where the output
operands would go.
Output operand expressions must be lvalues; the compiler can check
this. The input operands need not be lvalues. The compiler cannot
check whether the operands have data types that are reasonable for the
instruction being executed. It does not parse the assembler instruction
template and does not know what it means or even whether it is valid
assembler input. The extended 'asm' feature is most often used for
machine instructions the compiler itself does not know exist. If
the output expression cannot be directly addressed (for example, it is
a bit field), your constraint must allow a register. In that case,
GNU C will use the register as the output of the 'asm', and then store
that register into the output.
Here is an another example, which is applicable with TIGCC. Suppose
that we want to rotate the value of the expression 'input' for one bit
left, and to store the result into the expression 'output' (which is
a lvalue). We can write the following code:
asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0"
: "=g" (output) : "g" (input));
Note that when extended asm constructions are used, the percent sign
before the register names must be doubled. It is important to say that
in above example 'input' and 'output' may be any valid expressions; in
simple case when both of them are just variables, simple asm construction
like
asm ("move.l input,%d0; rol #1,%d0; move.l %d0,output");
would be quite enough. Extended asm constructions allows encapsulating
them in macros which look like functions, like in the following example,
which defines macro 'rotate' which acts like a void function:
#define rotate(input, output) \
({ asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0" \
: "=g" (output) : "g" (input)); })
All examples given above have a serious drawback: they clobbers the
register 'd0'. If the compiler keep something important in it (which
is very likely), this may cause a lot of troubles. Of course, you can
save it on the stack at the begining and restore it at the end, but
there exist much better solution which will save clobbered registers
only when necessary. To describe clobbered registers, write a third
colon after the input operands, followed by the names of the
clobbered registers (given as strings), separated by commas:
#define rotate(input, output) \
({ asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0" \
: "=g" (output) : "g" (input) : "d0"); })
Whenever you refer to a particular hardware register from the
assembler code, you will probably have to list the register after
the third colon to tell the compiler the register's value is modified.
Alternatively, you can force the compiler to use any data register
instead of 'd0', using the following trick:
#define rotate(input, output) \
({ register long __temp; \
asm ("move.l %1,%2; rol #1,%2; move.l %2,%0" \
: "=g" (output) : "g" (input), "d" (__temp)); })
Here, "d" constraint ensures that '__temp' will be stored in the
data register (note that 'rol' is applicable only to data registers).
In fact, there is no any need for a temporary register if we forced
the output to be in the data register, which can be implemented as
in following example:
#define rotate(input, output) \
({ asm ("move.l %1,%0; rol #1,%0" : "=d" (output) : "g" (input)); })
If your assembler instruction can alter the condition code register,
add 'cc' to the list of clobbered registers. GNU C on some machines
represents the condition codes as a specific hardware register;
'cc' serves to name this register. On other machines, the
condition code is handled differently, and specifying 'cc' has no
effect. But it is valid no matter what the machine.
If your assembler instruction modifies memory in an unpredictable
fashion, add 'memory' to the list of clobbered registers. This will
cause GNU C to not keep memory values cached in registers across
the assembler instruction.
The ordinary output operands must be write-only; GNU C will assume that
the values in these operands before the instruction are dead and need
not be generated. That's why the following version of 'rotate' macro
which accept just one argument (and which rotates it one byte left)
is quite unreliable:
#define rotate(inout) ({ asm ("rol #1,%0" : "=d" (inout)); })
To solve such difficulties, extended asm also supports input-output
or read-write operands. Use the constraint character '+' to indicate
such an operand and list it with the output operands.
When the constraints for the read-write operand (or the operand in
which only some of the bits are to be changed) allows a register, you
may, as an alternative, logically split its function into two separate
operands, one input operand and one write-only output operand. The
connection between them is expressed by constraints which say they need
to be in the same location when the instruction executes. You can use
the same C expression for both operands, or different expressions. For
example, here the (fictitious) 'combine' instruction will be used with
'bar' as its read-only source operand and 'foo' as its read-write
destination:
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
The constraint "0" for operand 1 says that it must occupy the same
location as operand 0. A digit in constraint is allowed only in an
input operand and it must refer to an output operand.
Only a digit in the constraint can guarantee that one operand will be
in the same place as another. The mere fact that 'foo' is the value
of both operands is not enough to guarantee that they will be in the
same place in the generated assembler code. The following would not
work reliably:
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
Various optimizations or reloading could cause operands 0 and 1 to be
in different registers; GNU C knows no reason not to do so. For
example, the compiler might find a copy of the value of 'foo' in one
register and use it for operand 1, but generate the output operand 0
in a different register (copying it afterward to foo's own address).
Of course, since the register for operand 1 is not even mentioned in
the assembler code, the result will not work, but GNU C can't tell that.
You may not write a clobber description in a way that overlaps with
an input or output operand. For example, you may not have an operand
describing a register class with one member if you mention that
register in the clobber list. There is no way for you to specify that
an input operand is modified without also specifying it as an output
operand. Note that if all the output operands you specify are for this
purpose (and hence unused), you will then also need to specify
'volatile' for the 'asm' construct, as described below, to prevent
GNU C from deleting the 'asm' statement as unused.
As already mentioned above, you can put multiple assembler
instructions together in a single 'asm' template, separated either
with newlines or with semicolons (GNU assembler supports semicolons).
The input operands are guaranteed not to use any of the clobbered
registers, and neither will the output operands addresses, so you can
read and write the clobbered registers as many times as you like. Here
is an example of multiple instructions in a template; it assumes the
subroutine '_foo' accepts arguments in registers a0 and a1:
asm ("move.l %0,a0; move.l %1,a1; jsr _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "a0", "a1");
Unless an output operand has the '&' constraint modifier, GNU C
may allocate it in the same register as an unrelated input operand,
on the assumption the inputs are consumed before the outputs are
produced. This assumption may be false if the assembler code actually
consists of more than one instruction. In such a case, use '&' for
each output operand that may not overlap an input. See section
"Constraint Modifier Characters".
If you want to test the condition code produced by an assembler
instruction, you must include a branch and a label in the 'asm'
construct, as follows:
asm ("clr.l %0; test.l %1; beq 0f; moveq #1,%0; 0:"
: "g" (result) : "g" (input));
This assumes your assembler supports local labels, which is true with
the GNU assembler (it uses suffix 'f' for forward and 'b' for backward
local labels, which are digits always).
Speaking of labels, jumps from one 'asm' to another are not supported.
The compiler's optimizers do not know about these jumps, and
therefore they cannot take account of them when deciding how to
optimize.
As already mentioned, usually the most convenient way to use these
'asm' instructions is to encapsulate them in macros that look like
functions. Here is an example how to define a function-looking macro
with non-void return type:
#define rotate(x) \
({ unsigned long __result, __arg = (x); \
asm ("move.l %1,%0; rol #1,%0": "=d" (__result): "g" (__arg)); \
__result; })
This macro acts nearly exactly like the function which takes one
'unsigned long' argument, and which returns an 'unsigned long' result.
Here the variable '__arg' is used to make sure that the instruction
operates on a proper 'unsigned long' value, and to accept only those
arguments 'x' which can convert automatically to a 'unsigned long'.
Another way to make sure the instruction operates on the correct
data type is to use a cast in the 'asm'. This is different from using
a variable '__arg' in that it converts more different types. For
example, if the desired type were 'int', casting the argument to
'int' would accept a pointer with no complaint, while assigning the
argument to an 'int' variable named '__arg' would warn about
using a pointer unless the caller explicitly casts it.
If an 'asm' has output operands, GNU C assumes for optimization
purposes the instruction has no side effects except to change
the output operands. This does not mean instructions with a side
effect cannot be used, but you must be careful, because the
compiler may eliminate them if the output operands aren't used, or
move them out of loops, or replace two with one if they constitute
a common subexpression. Also, if your instruction does have a side
effect on a variable that otherwise appears not to change, the old
value of the variable may be reused later if it happens to be found
in a register. If you are not happy with such behaviour, you can
prevent an 'asm' instruction from being deleted, moved significantly,
or combined, by writing the keyword 'volatile' after the 'asm'.
For example:
#define get_and_set_priority(new) \
({ int __old; \
asm volatile ("get_and_set_priority %0, %1" \
: "=g" (__old) : "g" (new)); \
__old; })
If you write an 'asm' instruction with no outputs, GNU C will
know the instruction has side-effects and will not delete the
instruction or move it outside of loops. If the side-effects of
your instruction are not purely external, but will affect variables
in your program in ways other than reading the inputs and
clobbering the specified registers or memory, you should write the
'volatile' keyword to prevent future versions of GNU C from moving
the instruction around within a core region.
An 'asm' instruction without any operands or clobbers (and "old
style" 'asm') will not be deleted or moved significantly, regardless,
unless it is unreachable, the same wasy as if you had written a
'volatile' keyword.
Note that even a volatile 'asm' instruction can be moved in ways
that appear insignificant to the compiler, such as across jump
instructions. You can't expect a sequence of volatile 'asm'
instructions to remain perfectly consecutive. If you want
consecutive output, use a single 'asm'.
You can write write __asm__ instead of <CODE>asm</CODE>.
See section "Alternate Keywords".
More about register variables
-----------------------------
You can specify the name to be used in the assembler code for a C
function or variable by writing the 'asm' keyword after the
declarator as follows:
int foo asm ("myfoo") = 2;
This specifies that the name to be used for the variable 'foo' in
the assembler code should be 'myfoo' rather than the usual 'foo'.
You cannot use 'asm' in this way in a function definition; but you
can get the same effect by writing a declaration for the function
before its definition and putting 'asm' there, like this:
extern func () asm ("FUNC");
func (int x, int y)
...
It is up to you to make sure that the assembler names you choose do
not conflict with any other assembler symbols. Also, you must not
use a register name; that would produce completely invalid assembler
code. GNU C does not as yet have the ability to store static variables
in registers.
GNU C allows you to put a few global variables into specified hardware
registers. You can also specify the register in which an ordinary
register variable should be allocated.
Global register variables reserve registers throughout the program.
This may be useful in programs such as programming language
interpreters which have a couple of global variables that are accessed
very often.
Local register variables in specific registers do not reserve the
registers. The compiler's data flow analysis is capable of determining
where the specified registers contain live values, and where they are
available for other uses. Stores into local register variables may be
deleted when they appear to be dead according to dataflow analysis.
References to local register variables may be deleted or moved or
simplified.
These local variables are sometimes convenient for use with the
extended 'asm' feature, if you want to write one output of the
assembler instruction directly into a particular register (this will
work provided the register you specify fits the constraints
specified for that operand in the 'asm'.)
You can define a global register variable in GNU C like this:
register int *foo asm ("a5");
Here 'a5' is the name of the register which should be used. Choose a
register which is normally saved and restored by function calls on
your machine, so that library routines will not clobber it (naturally,
the register name is cpu-dependent, so you would need to conditionalize
your program according to cpu type; the register 'a5' would be a good
choice on a 68000 for a variable of pointer type).
Defining a global register variable in a certain register reserves
that register entirely for this use, at least within the current
compilation. The register will not be allocated for any other purpose
in the functions in the current compilation. The register will not be
saved and restored by these functions. Stores into this register are
never deleted even if they would appear to be dead, but references
may be deleted or moved or simplified.
It is not safe to access the global register variables from interrupt
handlers, because the system library routines may temporarily use the
register for other things (unless you recompile them specially for
the task at hand).
It is not safe for one function that uses a global register variable
to call another such function 'foo' by way of a third function
'lose' that was compiled without knowledge of this variable (i.e. in a
different source file in which the variable wasn't declared). This is
because 'lose' might save the register and put some other value there.
For example, you can't expect a global register variable to be
available in the comparison-function that you pass to qsort, since
it might have put something else in that register (if you are prepared
to recompile it with the same global register variable, you can solve
this problem).
If you want to recompile other source files which do not actually use
your global register variable, so that they will not use that
register for any other purpose, then it suffices to specify the
compiler option '-ffixed-reg'. You need not actually add a global
register declaration to their source code.
A function which can alter the value of a global register variable
cannot safely be called from a function compiled without this variable,
because it could clobber the value the caller expects to find there on
return. Therefore, the function which is the entry point into the part
of the program that uses the global register variable must explicitly
save and restore the value which belongs to its caller.
Be aware if you use longjmp with global register variables, because it
will restore to each global register variable the value it had at the
time of the setjmp.
All global register variable declarations must precede all function
definitions. If such a declaration could appear after function
definitions, the declaration would be too late to prevent the register
from being used for other purposes in the preceding functions.
Global register variables may not have initial values, because an
executable file has no means to supply initial contents for a
register.
On the MC 68000, a2...a5 should be suitable for global register
variables, as should d2...d7. Of course, it will not do to use more
than a few of those.
You can define a local register variable with a specified register
like this:
register int *foo asm ("a5");
Here '>a5' is the name of the register which should be used. Note
that this is the same syntax used for defining global register
variables, but for a local variable it would appear within a
function. Naturally the register name is cpu-dependent (do not worry
about it, because you use TIGCC for exactly one processor, MC 68000).
Specific registers are most often useful with explicit assembler
instructions. Defining such a register variable does not reserve the
register; it remains available for other uses in places where flow
control determines the variable's value is not live. However, these
registers are made unavailable for use in the reload pass; excessive
use of this feature leaves the compiler too few available registers
to compile certain functions.
This option does not guarantee that GNU C will generate code that
has this variable in the register you specify at all times. You may
not code an explicit reference to this register in an 'asm' statement
and assume it will always refer to this variable.
Stores into local register variables may be deleted when they appear
to be dead according to dataflow analysis. References to local
register variables may be deleted or moved or simplified.