PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : ASM clobber register?



matzeed7
05.09.2007, 09:15
Hallo,

ich hoffe Ihr könnt mir helfen. Ich beschäftige mich zur Zeit mit dem gcc, genauer gesagt mit der Zwischensprache des gcc also RTL(Register Transfer Language). In ihr kommt ein Befehl (clobber x) wobei x meist ein Register ist.
Laut gcc internals bewirkt dies eine speicherung bzw eine mögliche speicherung von x hier mal ein beispiel


(insn 9 8 10 1 (parallel [
(set (reg:SI 60)
(plus:SI (mem/c/i:SI (reg/f:SI 53 virtual-incoming-args) [0 a+0 S4 A32])
(reg:SI 61)))
(clobber (reg:CC 17 flags))
]) -1 (nil)
(nil))


kann mir wer sagen was nun genau dabei gemacht wird, also was der befehl clobber mit dem reg17 macht-

gruss matze

pctoaster
05.09.2007, 13:55
Dieses Register wird im Ram zwischengespeichert, damit darin später andere Werte gespeichert werden können. (z.B. via Inline Assembler Code).

Gruß
pctoaster

SprinterSB
05.09.2007, 17:57
(clobber (reg:CC 17 flags))


clobber sagt, daß REG 17 durch diese insn verändert wird; und zwar in einer Weise, die zu kompliziert ist, um sie hier genau zu beschreiben. Wichtig ist, daß REG 17 den Inhalt wechselt. REG 17 hat den Mode (enum machine_mode) CCmode (CC), enthält also den condition code der Maschine.

das parallel[] (eine zeitgleiche(!) Ausführung mehrerer insns) enthält ein set. In C-Syntax:

reg60 = (*(reg53)) + reg61

wobei die Operation als 32-Bit ausgeführt wird (SImode=single integer). Sie lädt also einen 32-Bit-Wert, und diese Ladeoperation hat eine nicht weiter spezifizierte Änderung von reg17 zur Folge.

Was weiter mit REG 17 geschieht, ist aus der insn nicht ersichtlich. Weiterverwendet werden kann REG 17 nicht -- was wollte man mit einem Register mit undefiniertem Inhalt auch anfangen?

Wie REG 17 auf Funktionsebene gehandhabt wird, hängt davon ab, ob es sich um ein Hard-Reg handelt oder um ein Pseudo-Reg (siehst du im Header ./gcc/config/target/target.h). Zu vermuten ist, daß die Maschine 16 GPRs hat und REG 17 ebenfalls zu den Hard-Regs gehört.

Dieser Beitrag passt übrigens nicht in dieses Forum, da es sich dabei nicht um Assembler-Programmierung handelt. (Die insn entstand ja nicht aus einem Inline-Assembler, und selbst wenn, ist es keine Frage von asm-Programmierung), sondern ist ein Pattern aus der Backend-Beschreibung der Maschine, die du findest in ./gcc/config/target/target.md

Dieses Thema gehört zu "Compilerbau", also am ehesten nach "Software, Algorithmen und KI".

matzeed7
06.09.2007, 08:14
danke erst mal für die schnelle antwort. der hauptgrund, warum ich nun in diesem forum meine frage stellte ist der das ich nun nicht rausbekommen hatte, was der befehl clobber nun genau macht. das einzige was ich fand war
der asm befehl. ich hoffte das es da einen zusammenhang gab zwischen beiden befehlen

was der befehl nun genau mit dem register macht kann man wo nicht sagen?
ich habe zwar das internal des gcc gelesen, kann aber immer noch nicht sagen was da nun geschied besonders in verbindung mit dem (scratch:m) befehl was nich immer für mich sinn macht



(clobber (scratch:SI))


zum beispiel ?

SprinterSB
06.09.2007, 11:19
Was hier passiert ist wesentlich komplexer als bei inline asm.

Nehmen wir mal das Beispiel einer 16-Bit AND-Insn für AVR:

(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:


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.

avr-gcc clobber.c -S -Os -da -dP -fverbose-asm


; (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

matzeed7
06.09.2007, 13:30
also ich bin mir nicht so sicher ob ich deine Antwort verstanden habe?



[(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"))]


also ich denke mal das die Constraints die in "" beschriebnen werte sind wie zB "=r,d,r" nur kann ich diese net so recht deuten! in der entsprechenden definition für den i386 sieht es wie folgt aus


(define_expand "andhi3"
[(set (match_operand:HI 0 "nonimmediate_operand" "")
(and:HI (match_operand:HI 1 "nonimmediate_operand" "")
(match_operand:HI 2 "general_operand" "")))
(clobber (reg:CC FLAGS_REG))] "TARGET_HIMODE_MATH"
"ix86_expand_binary_operator (AND, HImode, operands); DONE;")

hier werden keine solche angaben gemacht?

matzeed7
06.09.2007, 15:10
PS: wo kann man den diese Sachen nachlesen?

SprinterSB
06.09.2007, 15:27
In Expandern (define_expand) werden keine Constraints angegeben, man wird sogar angewarnt wenn man es tut. Ein Expander arbeitet ja auf Pseudo-Ebene. Constraints findest du zB bei Insns (define_insn, define_insn_and_split) und bei den Scratch-Regs für Peephole2 (define_peephole2).

RTL anhand von i386 zu lernen ist übel, weil das eines der komplexesten Maschinenbeschreibungen überhaupt ist; es kann Code für 32-Bit oder 64-Bit Maschinen ausgegeben werden, es werden unterschiedliche Assembler-Dialekte unterstützt, und es gibt *viele* Hacks.

reg 17 gehört zu den fixed Hard-Regs des i386, d.h. es wird nicht von gcc verwaltet. Von daher sind die Clobbers auf reg 17 nicht so spannend. Es heisst einfach, daß ein vorher in reg 17 vorhandener Wert nicht mehr verwendbar und futsch ist.


PS: wo kann man den diese Sachen nachlesen?

Gute Frage... Zum Einstieg ist das GCC Internals ganz ok. Teilweise Kommentare in den Quellen des Middle-End von gcc. Aber erschöpfende Auskunft und Spezifikation ist meiner Erfahrung nach Fehlanzeige. Zum Nachlesen ist das Genannte ganz ok, aber wenn man selber ein Backend implementiert stösst man fix an die Grenzen und man muss sich durchackern...

matzeed7
06.09.2007, 16:06
Ach du schande da habe ich mich natürlich mit meinem i386 ganz schon was eingebrockt!!!

Ich schreibe zZ eine Art Semesterarbeit über die Möglichkeit, welche Aussagen man, über die Infos die man aus dem gcc mit -da und dann aus der *.c.00.expand datei erhält, treffen kann . Ziel ist also die Erstellung eines RTL parsers (den habe ich schon fast fertig) und dann halt noch die Umwandlung der einzelnen RTL Konstrukte (set, mem, plus....) in eine Art Pseudocode. Dafür brauch ich aber die Bedeutung der einzelnen Konstrukte!!!

Danke erst mal !!!!!!!

zZ hänge ich an der Fragestellung, wie ich den mem am besten darstellen kann, der er ja zum lesen und schreiben benutzt wird, zB

(set(reg:SI 61)(mem ...))

und dann halt noch

(set (mem ...)(reg:SI 66))

matzeed7
06.09.2007, 16:07
PS: diese Arbeit soll dann mal als Grundlage für gewisse tests dienen.

SprinterSB
06.09.2007, 17:29
RTL umwandeln in Pseudo-Code? :-k

RTL ist doch bereits Pseudo-Code... Du kannst auch nicht hingehen, und ein Programm in RTL schreiben und gcc das weiterbearbeiten lassen.

Welche Aussagen/Tests sollen denn ausgeführt werden? Geht's dabei um die Verifikation des von gcc emittierten RTL gegen die (preprozesste) C/C++/Java/Fortran/Ada-Quelle? Jedenfalls solltest du bedenken, daß nicht die gesamte Information eines Programmes (bzw. eines Moduls) in RTL vorliegt, auch nicht das gesamte "Wissen", daß gcc intern über die Quelle hat.

Ob ein RTL sinnvoll/gültig ist, ist stark maschinenabhängig und hängt auch davon ab, ob du nonstrict-RTL betrachtest (darauf scheinst du dich ja beschränken zu wollen) oder strict-RTL.

Für eine Einteilung und einen Überblick schau mal ins ./gcc/rtl.def (http://crypto.riken.go.jp/pub/NetBSD/NetBSD-release-3-1/src/gnu/dist/toolchain/gcc/rtl.def)

matzeed7
07.09.2007, 10:29
Also wie ich eben gelesen habe, bezieht sich RTL auf eine abstrakte Maschine!


Kennst du links oder dokumente wo man nachlesen kann wie diese funktionieren soll!

Ich habe eben ein weinig Probleme die Funktionsweise zu verstehen.


zum Beispiel



(insn 8 6 9 1 (set (reg:SI 61)
(mem/c/i:SI (plus:SI (reg/f:SI 53 virtual-incoming-args)
(const_int 4 [0x4])) [0 b+0 S4 A32])) -1 (nil) (nil))


wird ja der Wert der Variable b (die als zweites Arg. dieser Funktion) in das Reg61 geladen!




(insn 9 8 10 1 (parallel [
(set (reg:SI 60)
(plus:SI (mem/c/i:SI (reg/f:SI 53 virtual-incoming-args) [0 a+0 S4 A32])
(reg:SI 61)))
(clobber (reg:CC 17 flags))
]) -1 (nil)
(nil))



in diesem schritt wird dann der Wert der Variable a zum wert der Variable b addiert und dann in Reg60 gespeichert


nun meine Frage:
ist also die Funktionsweise dieser "RTL" Maschine gleich dem Funktionsprinzip der Maschine die dem Assemblercode zugrunde liegt. Also was ich wissen möchte ist, ob sie nur auf einem virtuellem stack arbeitet, so wie man im Assembler mit push and pop arbeitet, oder ex da unterschiede??


Gruss Matze

matzeed7
07.09.2007, 11:02
kann man den annehmen das



push %ebp


und



(insn 10 9 12 1 (set (mem/c/i:SI (plus:SI (reg/f:SI 54 virtual-stack-vars)
(const_int -4 [0xfffffffc])) [0 sum+0 S4 A32])
(reg:SI 60)) -1 (nil)
(nil)))


die selbe Funtionsweise in bezug zu einem Stack zugrundeliegt?[/quote]

SprinterSB
07.09.2007, 15:04
Daß die Funktionsweise identisch ist, kann man dann annehmen, wenn man eine genaue Umschreibung dessen hat, was push bedeuten soll.

Genau diesem Zweck dient RTL: Algebraische Umschreibungen der Maschine (bzw. ihres Instruktionssatzes) zu ermöglichen.

Auf RTL sind dann Transormationen möglch, die zB gewisse Insns bzw. Insn-Sequenzen als äquivalent betrachten.

Die Zuordnung einer asm-Instruktionssequenz zu einer Insn geschieht wie du weißt im md-File. Ob Transormationen erlaubt sind bzw. äquivalente RTXe ergeben, sieht man nicht unbedingt am md. Hier geht auch ganz viel über das h- und das c-File, zB was gültige Register-Operanden und Adressen sind, welche Pseudos auf welche Hard-Regs abgebildet werden dürfen, etc. Und ob zB ein CALL korrekt in asm umgesetzt wurde, lässt sich am RTL nicht erkennen, weil die argumente eines CALL nicht in RTL auftauchen.

Mir ist jetzt nicht ganz klar, was du mit "virtuellem Stack" meinst.

Der Maschine sind einige besondere "virtuelle" Hard-Register bekannt wie
virtual-incoming-args, virtual-stack-vars, virtual-stack-dynamic, virtual-outgoing-args, virtual-cfa, ... die für besondere Zwecke verwendet werden und irgendwann auf Hard-Register abgebildet oder eliminiert werden.

Wenn einer Funktion zB Argumente über den Stack übergeben werden, zeigt virtual-incoming-args auf das erste Argument; virtual-incoming-args wird aber für praktisch alle Maschinen auf den Frame-Pointer oder den Stack-Pointer abgebildet (und der Frame-Pointer dann auch auf den Stack-Pointer), wobei natürlich noch die richtigen Offsets drauf müssen.

Das einzige Dokument, daß dies ansazuweise zum Thema hat, ist m.W das gcc internals. Ansonsten helfen nur die GCC-Quellen.

matzeed7
07.09.2007, 15:46
Wow Danke Du hast mir schon weiter geholfen!!!!!

Gibt es denn irgendwo eine Aussage wie die Abbildung von den pseudoregistern in die entsprechenden Hardregister definiert ist??

ich habe in den dump dateien gelesen



Registers live at start: 6 [bp] 7 [sp] 16 [argp] 20 [frame]
Registers live at end: 0 [ax] 6 [bp] 7 [sp] 16 [argp] 20 [frame]


das doch eine gewisse Zuordung nachvollziebar sein könnte, bin mir aber nicht sicher ob diese immer so ist

SprinterSB
09.09.2007, 22:13
Gibt es denn irgendwo eine Aussage wie die Abbildung von den pseudoregistern in die entsprechenden Hardregister definiert ist??

So ganz verstehe ich deine Frage nicht.

Für eine Maschine ist dies angegeben in den Comstraints zu dem jeweiligen Operanden, wobei in den Constraint noch mehr steht: Constraints beziehen sich ja auf Operanden allgemein, also nicht nur auf REG-Operanden, sondern auch auf Adressen, MEM, Konstanten, und Teilmengen/Vereinigungen davon, etc. AUsserdem stehen in den Constrains Dinge wie: early clobber, ob Operanden Kommutieren, Kosten von Constraints, ob eine Constraint Nebeneffekte hat, ...

Für eine konkrete Insn wird die Abbildung notiert, sobald sie erfolgt ist; hier eine Insn von avr-gcc 3.4.6 nach .25.greg:

(insn 15 39 17 1 (set (reg:HI 24 r24 [orig:41 <result> ] [41])
(plus:HI (reg/v:HI 24 r24 [orig:42 a ] [42])
(reg/v:HI 22 r22 [orig:43 b ] [43]))) 25 {*addhi3} (nil)
(nil))
Pseudo 41 enthält den Return-Wert (<result>), der aber schon auf Hard-Reg 24 (durch REGISTER_NAMES[] benamt zu "r24") abgebildet wurde. Variable "a" in 42 lebt auch in 24 und b lebt in 43, jetzt in 22.

Hier siehst du schon ein Problem: Sowohl <result> als auch a leben in r24. Das geht ok weil a nach der Insn nicht mehr gebraucht wird. Eine REG_DEAD Note für 24 gibt's an der Insn aber keine, weil 24 nach der Insn nicht tot ist (danach lebt <result> drin).

In .24.lreg gabt's noch Dead-Notes für a in insn 15:

(insn 15 39 17 1 (set (reg:HI 41 [ <result> ])
(plus:HI (reg/v:HI 42 [ a ])
(reg/v:HI 43 [ b ]))) 25 {*addhi3} (nil)
(expr_list:REG_DEAD (reg/v:HI 42 [ a ])
(expr_list:REG_DEAD (reg/v:HI 43 [ b ])
(nil))))


Wo a wirklich lebt, sieht man aber erst in der Assembler-Ausgabe (-S -dP -fverbose-asm), denn nach .26.postreload kommen noch einige Passes.

; (insn 15 39 47 (set (reg:HI 24 r24 [orig:41 <result> ] [41])
; (plus:HI (reg/v:HI 24 r24 [orig:42 a ] [42])
; (reg/v:HI 22 r22 [orig:43 b ] [43]))) 25 {*addhi3} (nil)
; (expr_list:REG_DEAD (reg/v:HI 22 r22 [orig:43 b ] [43])
; (nil)))
add r24,r22 ; <result>, b ; 15 *addhi3/1 [length = 2]
adc r25,r23 ; <result>, b

Auch wenn dir das alles weiterhelfen mag, möchte ich nochmals erwähnen, daß es nicht sonderlich sinnvoll ist, Informationen aus den -da Dateien rauszuziehen, um sie auszuwerten! Diese Dateien dienen den gcc-Entwicklern u.a. zum Auffinden von Bugs und beim Implementieren neuer Features. Wenn man wirklich Informationen haben will, sollte man eher versuchen, GCC zu instrumentieren als seinen -da-Ausgaben hinterherzulaufen. Von daher ist wohl ein Wörtchen mit deinem Auftraggeber angebracht, der dich diese Studienarbeit schreiben lässt.

Es bringt dir nähmlich mehr, etwas von den Interna vom gcc zu lernen/verstehen, als dich mit dem lisp-Parser rumzuärgern für ne Ausgabe, die du nicht klar spezifiziert bekommst. Un wenn du es hinbekommst, dann rekonstruierst du lediglich die Informationen, die gcc intern schon (sicherlich vollständiger) hat!

Was den lisp-Parser angeht, so gibt's den schon in ./gcc/read-rtl.c, dem Gegenstück zu ./gcc/print-rtl.c. Der RTL-Reader dient aber AFAIK zum Lesen des md-Files.

matzeed7
10.09.2007, 06:45
ja danke erst mal!!!!

gruss matze