Was hier passiert ist wesentlich komplexer als bei inline asm.
Nehmen wir mal das Beispiel einer 16-Bit AND-Insn für AVR:
Code:
(define_insn "andhi3"
[(set (match_operand:HI 0 "register_operand" "=r,d,r")
(and:HI (match_operand:HI 1 "register_operand" "%0,0,0")
(match_operand:HI 2 "nonmemory_operand" " r,i,M")))
(clobber (match_scratch:QI 3 "=X,X,&d"))]
""
{
if (which_alternative==0)
return (AS2 (and,%A0,%A2) CR_TAB
AS2 (and,%B0,%B2));
else if (which_alternative==1)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int mask = INTVAL (operands[2]);
if ((mask & 0xff) != 0xff)
output_asm_insn (AS2 (andi,%A0,lo8(%2)), operands);
if ((mask & 0xff00) != 0xff00)
output_asm_insn (AS2 (andi,%B0,hi8(%2)), operands);
return "";
}
return (AS2 (andi,%A0,lo8(%2)) CR_TAB
AS2 (andi,%B0,hi8(%2)));
}
return (AS2 (ldi,%3,lo8(%2)) CR_TAB
AS2 (and,%A0,%3) CR_TAB
AS1 (clr,%B0));
}
[(set_attr "length" "2,2,3")
(set_attr "cc" "set_n,clobber,set_n")])
Diese Standard-Insn wird vom Middle-End emitiert, wenn ein 16-Bit
bitweises And gemacht werden soll. %0 und %1 liegen vor Pass LREG
höchstwahrscheinlich in Pseudo-Registern, %2 ist ein Immediate oder
ein Reg und %3 ist ein evtl. benötigtes Clobber-Register.
Vor Pass LREG hat das clobber nur eine Funktion was Matching der
Insn-Pattern angeht. Wichtig wird es erst im Reload-Pass, also in
GREG. GREG kümmert sich um die globale Register-Allokierung, d.h. es
bildet die (potentiell) unendlich vielen Pseudo-Regs auf die endlich
vielen Hard-Regs der Maschine ab, wobei noch Nebenbedingungen
berücksichtigt werden müssen, weil nicht jede Instruktion auf jedem
GPR erlaubt ist.
GREG hat 3 Möglichkeiten der Allokierung, beschrieben in den Constraints:
- #0 ist einfach: Ein- und Ausgabe liegen in Registern; das And wird mit
2 AND-Instruktionen erledigt und die Constraint zu %1 sorgt dafür,
daß %0 und %1 im gleichen Hard-Reg zu liegen kommen (dafür muss GREG
evtl. eine movhi-Insn emittieren).
- #1 ist auch einfach: Ausgabe ist ein REG aus Klasse "d" (LD_REGS) auf
das eine Konstante drauf-geundet wird. Für diese Register gibt's die
ANDI-Instruktion, und je nachdem, ob die Konstante schon zur
Compilezeit bekannt ist oder nicht (Symbole sind zB Konstanten, die
aber erst zur Locate-Zeit bekannt werden) werden eine oder zwei ANDI
ausgegeben.
- #2 %2 ist eine 16-Bit-Konstante und %0 gehört nicht zu LD_REGS
(regclass NO_LD_REGS). In diesem Falls ist es
nicht möglich, die ANDI-Instruktion zu verwenden. Falls %2 eine
8-Bit-Konstante ist (Contraint "M"), dann bewirkt das "=&d" bei %3,
daß GREG ein Register aus Klasse "d" zur Verfügung stellt, das wir
temporär in dieser insn verwenden können.
Globale Register-Allokierung ist einer der schwierigen und
komplexesten Teile jedes optimierenden Compilers.
Es ist zB denkbar, daß kein "d"-Reg mehr frei ist, und GCC muss erst
eines frei machen und extra Code dafür ausgeben, indem irgendein
"d"-Reg auf den Stack gesichert wird.
#2 erledigt den Reload der Konstanden in das Register %3 von Hand und führt
das AND auf dem unteren Byte von %0 aus. Das obere Byte ist 0, denn
die Konstante ist nur 8 Bit breit.
- Es verbleibt der Fall, daß %0 zu NO_LD_REGS gehört und die
Konstante keine 8-Bit-Konstante ist. In diesem Fall kümmert sich GREG
um den Reload, lädt also %2 in ein GPR und wird dann #0 anwenden
können. Schwierig daran ist dann der Reload von %2, weil man keine
Konstante nach NO_LD_REGS laden darf... Wie das gelöst wird, führt
jetzt zu weit.
In #0 und #1 sagt die Contraint "X" bei %3, daß kein Clobber-Reg
gebraucht wird.
Clobber macht also mehr, als zu sagen, daß der Registerinhalt
verändert wird (um das Register zu verändern, müssen wir es erst
*haben*!)
Es hat Einfluß auf Insn-Matching und die
Register-Allokierung. Beachte, daß das Ergebnis besser ist, als wenn
man über ein extra Pseudo arbeiten würde um die Konstante dahin zu
laden. Dann würde man einen Expander schreiben und dort die Konstante
in ein Pseudo laden. Dann würde man allerdings nicht in dieser Form
von der ANDI profitieren können.
Um das Pattern für #2 erzeugen zu lassen, muss man schon was
tricksen. Für avr-gcc 3.4.6 geht's mit folgender Quelle:
Code:
void foo (void);
int B;
void bar (void)
{
int b = B;
foo ();
asm volatile ("":::"16","17");
B = b & 0xf;
}
Das asm volatile dient nur dazu, daß GREG b nicht nach 16/17 legt
(würde zu #1 führen). Zum weiteren Verständnis wird's nicht gebraucht.
Code:
avr-gcc clobber.c -S -Os -da -dP -fverbose-asm
Code:
; (insn 14 12 15 (parallel [(set (reg/v:HI 14 r14 [orig:41 b ] [41])
; (and:HI (reg/v:HI 14 r14 [orig:41 b ] [41])
; (const_int 15 [0xf])))
; (clobber (reg:QI 24 r24))
; ]) 45 {andhi3} (insn_list 8 (nil))
; (expr_list:REG_UNUSED (reg:QI 24 r24)
; (nil)))
ldi r24,lo8(15) ; , ; 14 andhi3/3 [length = 3]
and r14,r24 ; b,
clr r15 ; b
Lesezeichen