- 3D-Druck Einstieg und Tipps         
Seite 3 von 3 ErsteErste 123
Ergebnis 21 bis 26 von 26

Thema: Register sichern in eigenen Assembler Funktionen

  1. #21
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Anzeige

    E-Bike
    Ein Programm unterteile ich in Module, wie man das eben so macht. Zu jedem Modul gibt es (statische) Daten. Diese Daten lege ich nicht einzeln an als Bytes oder Worte, sondern in Strukturen. Damit ist jedes Modul grob so was wie ein Objekt in einer OO Sprache (natürlich viel schwächer).

    Soweit ist das ein alter Hut.

    Nehmen wir mal so eine Objekt:
    Code:
    foo_t foo;
    Funktionen lesen und schreiben diese Daten. Ein Zugriff auf z.B.
    Code:
    foo.bar = blah;
    ist dabei 4 Byte lang (lds, sts):
    Code:
    sts foo+6, r24
    Man kann nun auf die Idee kommen, in Bereichen, die viel auf foo rumnudeln, indirekt auf foo zuzugreifen, denn ein indirekter Zugriff mit ld(d) oder st(d) kostet nur 2 Bytes und ist gleichschnell:
    Code:
    foo_t * pfoo = &foo;
    pfoo->bar = blah;
    avr-gcc macht daraus:
    Code:
    sts foo+6, r24
    Shit! GCC kann zur Compilezeit die Adresse von foo bestimmen und greift direkt nach foo, aber nicht indirekt, wie wir gerne hätten. Meistens ist das auch ok, Adressregister sind begehrt, wie du vom Assembler her weisst. Zudem muss für nen indirekten Zugriff das Adress-Register erst mit der Adresse vorgeladen werden, und das kostet. Für wenige Instruktionen trifft avr-gcc also die beste Entscheidung, zumal wenn zwischen den Zugriffen noch Funktionsaufrufe passieren. Da müsste als Register eines der call-saved genommen werden, und das kann nur Y sein (ABI von avr-gcc, Link von oben), wo dann im Prolog/Epilog noch Stack-Zugriffe hinzukommen.

    Wir brauchen also einen Weg, um zu erzwingen, daß die Adresse von foo im Register bleibt. Das geht, indem wir die Information, daß es sich dabei um die Adresse von foo handelt, wieder vernichten! Dazu habe ich ein Inline-Asm geschrieben (Geht für alle gcc, nicht nur für avr-gcc):
    Code:
    #define RELOAD(reg,var) \
    	__asm volatile (";RELOAD " reg " with " #var : "=" reg (var) : "0" (var))
    Nachdem der Präprozessor darüber gerauscht ist, etwa über
    Code:
    RELOAD ("z", pfoo);
    sieht's nicht mehr ganz so kryptisch aus. Im wesentlichen bleibt davon übrig
    Code:
    __asm volatile (";RELOAD z with pfoo" : "=z" (pfoo) : "0" (pfoo));
    was sagt, daß pfoo in ein Register der Klasse "z" (also Z) geladen werden soll. Dann wird das asm in den Code eingefügt (Kommentar) und Z bekommt für GCC einen nicht nachvollziehbaren Wert, der aber der gleiche ist wie vorher, weil ja nix passiert im asm.

    Code:
    foo_t * pfoo = &foo;
    RELOAD ("z", pfoo);
    pfoo->bar = blah;
    gibt dann
    Code:
    	ldi r30,lo8(foo)
    	ldi r31,hi8(foo)
    /* #APP */
    	;RELOAD z with pfoo
    /* #NOAPP */
    	std Z+6, r24
    Aus Systemsicht segmentiert man das RAM und benutzt Z bzw. Y als Data Segment Pointer. Zum Glück hat man die Wahl, ich würd nicht freiwillig auf ner segmentierten Arechitektur proggen. Für AVR war ursprünglich sogar ein segmentiertes Layout geplant, aber das erwies sich als zu schwer handhabbar für Hochsprachen.

    Hier mal ein Beispiel aus dem wahren Leben
    Code:
    dcf_error_t dcf_check_time()
    {
    	// Eine neue Minute hat angefangen:
    	// DCF-Zeit auf Fehler prüfen und
    	// in time_dcf-Struktur kopieren, wenn ok.
    	
    	dcf_t *z = &dcf;
    	RELOAD ("z", z);
    
    	if (parity_even_bit (z->time_bits.minute ^ z->time_bits.minute_parity))
    		return BAD_PARITY_M;
    
    	if (parity_even_bit (z->time_bits.hour ^ z->time_bits.hour_parity))
    		return BAD_PARITY_H;
     
    	if (parity_even_bit (z->time_bits.date_parity 
    			^ z->time_bits.day	^ z->time_bits.day_of_week
    			^ z->time_bits.month ^ z->time_bits.year
    		))
    		return BAD_PARITY_D;
    
    	z->time.second      = 0;
    	z->time.minute      = z->time_bits.minute;
    	z->time.hour        = z->time_bits.hour;
    	z->time.year        = z->time_bits.year;
    	z->time.day         = z->time_bits.day;
    	z->time.day_of_week = z->time_bits.day_of_week;
    	z->time.month       = z->time_bits.month;
    	
    	return DCF_OK;
    }
    time und time_bits sind unterschiedlich aufgebaut und können daher nicht mit z->time = z->time_bits kopiert werden, ausserdem befinden sich schon viele Werte in Registern.

    Mit RELOAD hat das 130 Byte, ohne RELOAD sind es 158 Byte, das sind über 20% mehr!

    Natürlich hätte man auch die Adresse von foo als Parameter übergeben können, aber das hilft auch nicht immer, weil GCC einfach zu schlau ist...

    Bei den meisten Funktionen schreibe ich die Zugriffe also mit -> , das ist nur eine Zeile Code mehr, um die Adresse zu besorgen. Der generierte Code sieht aber genauso aus, wie mit direktem Zugriff wie oben erklärt, und man vergibt sich nix dabei. Ein Blick ins asm erklärt schnell, ob sich ein explizites RELOAD lohnt.

    Hab eben mal die Statistik zu meinem momentanen Projekt geschaut: Es hat über 3900 Zeilen spartanisch kommentierten C-Code, braucht aber nur knapp 7kByte Flash des ATmega8. Und da kommt noch einiges mehr rein! (ein grösserer µC kommt nicht in Frage).
    Disclaimer: none. Sue me.

  2. #22
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    08.05.2005
    Ort
    Issum
    Alter
    53
    Beiträge
    2.236
    Danke Sprinter,
    mit so einer detalierten Beschreibung habe ich nicht gerechnet, tolle Sache mit den Strukturen, super Programmierstill,
    wenn man es näher überlegt, ist es auch irgendwie logisch, daß man auf diese
    Weise schnellen und knappen Code produziert.
    Es ist mir nicht aufgefallen, daß man beim Zugriff auf (alle?) Devices von Linux sehr viel mit Strukturen arbeitet, jetzt wird mir klar warum...

    Ich hoffe, daß ich es auch schaffe das alles für mich umzusetzen.

    Gruß Sebastian

  3. #23
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Mit Kompusiten (also structs und unions) bze Objekten zu arbeiten, ist der einzige Weg, überhaupt sinnvoll programmieren zu können. Wenn z.B. eine Struktuv verschickt werden soll, will man nicht jede Variable einzeln angeben.

    Soll z.B ein foo_t verschickt werden -- wie auch immer -- dann interessiert der interne Aufbau von foo_t überhaupt nicht. Das einzige was interessiert ist, wieviele Bytes es hat.

    Wenn es also eine Sendefunktion gibt
    Code:
    send_n_bytes (const uint8_t * anfangs_addr, const size_t anzahl_bytes)
    dann verschickt man es einfach mit
    Code:
    send_n_bytes ((const uint8_t *) &foo, sizeof (foo_t));
    Ausserdem kann man auf ein foo zugreifen indem man eine Adresse übergibt, und nicht, indem man für jedes Element von foo was übergeben muss. Ausserdem kann man nachträglich die Struktur erweitern, ohne daß sich Interfaces ändern (also die Prototypen von Funktionen, etc).
    Disclaimer: none. Sue me.

  4. #24
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    08.05.2005
    Ort
    Issum
    Alter
    53
    Beiträge
    2.236
    Hallo,
    Ich habe gestern mein LCD Programm in C umgesetzt, dabei auch mit Strukturen und Zeigern darauf gearbeitet (man will ja was aus diesem Thread lernen)
    Fazit:
    Der C Code ist um 10 Bytes kleiner, als mein Assembler also ein Hoch auf gcc, und die tollen Leute, die daran arbeiten.

    Gruß Sebastian

  5. #25
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Da sag noch einer, Hochsprachen führten zu schlechterem Code (wobeo, so "hoch" ist C ja nun auch wieder nicht).

    Aber wundern tut's mich schon. Mit der 4er Version von GCC wir der Code noch mal dichter, wenn SSA voll zuschlägt (und die 4er Version stabiler und einsetzbar ist).
    Disclaimer: none. Sue me.

  6. #26
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    08.05.2005
    Ort
    Issum
    Alter
    53
    Beiträge
    2.236
    Aber wundern tut's mich schon
    Naja, manche Sachen hab ich etwas anders gemacht, vielleicht hab ich teilweise etwas zu kompliziert gedacht, aber Du kennst alle pro und Kontra für und gegen Assembler, es dauert etwas zu lange, um alles 100% zu lösen.
    Ja die GCC 4.xx.xx Version...
    Dazu ließt man zu viele negative Beiträge in den Foren, bleibt abzuwarten bis sie mal irgendwann stable ist.

    Gruß Sebastian

Seite 3 von 3 ErsteErste 123

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

fchao-Sinus-Wechselrichter AliExpress