atari-home.de - Foren

Software => Coding => Thema gestartet von: Mado am Mo 27.06.2022, 07:35:44

Titel: Erste Assembler-Gehversuche
Beitrag von: Mado am Mo 27.06.2022, 07:35:44
Ich hatte gestern ein kleines erstes Programm geschrieben, wo ich eine Assembler-Datei asmmul.s habe, die ich in ein C-Programm einbinden möchte. Die Assembler-Unterroutine soll nichts anderes machen, als zwei WORD-Werte zu einem LONG multiplizieren und dann über D0 wieder an das C-Programm übergeben. Ich erhalte aber ein komisches Ergebnis:

a x b = 24240
Vielleicht kann mir ein alter Hase bei meinen Gehversuchen helfen? Hier mein Code:

Makefile:

# For Linux
CC = m68k-atari-mint-gcc
CFLAGS = -mshort -O2 -Wall

OBJS = main.o asmmul.o

all: asmmul.prg
asmmul.prg: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)

main.o: main.c
$(CC) $(CFLAGS) -c $<

asmmul.o: asmmul.s
$(CC) $(CFLAGS) -c $<

C-Programm main.c:

#include <stdio.h>

extern long asmmul(int factor, int value);

int main(void) {
    int f = 5;
    int v = 10;
    long ret = 0;

    ret = asmmul(f, v);

    printf("a x b = %d\n", ret);

    return 0;
}

Und das Assembler-Stückchen assmul.s:

       .text
        .global _asmmul
_asmmul:
        clr.l   d1
        clr.l   d0
        move.w  2(sp),d1
        move.w  4(sp),d0
        muls    d1,d0

        rts
        .end

Da ich nur die Scratch-Register D0 und D1 an Registern verwende, save ich keine Register auf dem Stack.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am Mo 27.06.2022, 08:24:09
Der erste Parameter liegt bei 4(SP) auf dem Stack.

PS: Vorsicht mit -mshort. Die Standard-MiNTLib ist nicht damit compiliert. Eventuell weiß @Thorsten Otto, ob es eine MiNTLib dafür gibt. (libcmini kann man als "mshort"-Version bauen.)
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Mo 27.06.2022, 09:18:52
Ah, jetzt funktioniert es. So habe ich jetzt den Code angepasst:

       .text
        .global _asmmul
_asmmul:
        clr.l   d1
        clr.l   d0
        move.w  4(sp),d1
        move.w  6(sp),d0
        muls    d1,d0

        rts
        .end

Und beim printf im C-Programm muss es natürlich ein long bei der Ausgabe sein:

    printf("a x b = %ld\n", ret);
Beim Programmieren bin ich momentan komplett nur in TOS unterwegs. Soweit ich es sehen kann, ist im TOS jeder int und auch alle Übergaberegister etc 16 Bit weit. Wenn ich also auf 32 Bit aufbauen würde, müsste ich immer explizit short int (oder WORD???) angeben. Ebenfalls ist es ja so, dass bestimmte Assember-Direktiven in short schneller sind, als in long. Warum wurde dieser Weg gewählt?
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am Mo 27.06.2022, 09:57:37
Die TOS-Bindings sind natürlich in MiNTLib korrekt, d.h. es werden 16-Bit-Worte übergeben, wo dies nötig ist, ohne dass Du explizit "short" dran schreiben müsstest. Generell gilt beim Portieren von Unix-Software (was ja die Stärke der MiNTLib ist), dass das besser funktioniert, wenn ein "int" eben 32 Bit lang ist. (Nicht jede Software ist mit intNN_t und uintNN_t geschrieben.)

Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Mo 27.06.2022, 10:08:56
Ah, ok, verstanden. Ich wurschtel gerade im EmuTOS rum, also nicht im Mint. Das EmuTOS ist ja mit -mshort kompiliert. Ich möchte herausfinden, welche Grafikroutinen sich ggf. durch schnellere Assembler-Routinen ersetzen lassen. Ich komm irgendwie nicht drüber hinweg, dass ich NVDI installiere und teilweise um Faktoren schneller bin.

Ich habe mir mal einige Routinen als Assembly angeschaut und nur die Hände über dem Kopf zusammen geschlagen. Was der C-Compiler da baut, scheint mir nicht die vollen CISC-Features bzw. besonders leistungsfähige Kommandos des Prozessors auszunutzen.

Da ich m68k-Assembler immer nur im ganz kleinen Bereich genutzt habe und das auch schon 25 Jahre her ist, werd ich wohl ein ziemlich langes Weilchen brauchen, bis ich da überhaupt was zustande bringe. Siehst ja. ;-)
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am Mo 27.06.2022, 10:16:59
Natürlich ist es kein Problem für gcc etwas als "-mshort" zu compilieren. Ich schrieb ja bloß: Vorsicht beim Linken gegen MiNTLib.

Und was EmuTOS und Assembler-Routinen angeht: Beachte, dass EmuTOS auch auf ColdFire funktionieren muss. Somit hast Du in Assembler eigentlich immer #ifdef-Strecken, die die Wartbartkeit des Codes reduzieren.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Thorsten Otto am Mo 27.06.2022, 13:31:28
PS: Vorsicht mit -mshort. Die Standard-MiNTLib ist nicht damit compiliert. Eventuell weiß @Thorsten Otto, ob es eine MiNTLib dafür gibt. (libcmini kann man als "mshort"-Version bauen.)

Nein, mintlib gibt es nicht (mehr) für -mshort. Theoretisch könnte man die wohl bauen, aber das würde vermutlich nur zu noch mehr  Verwirrung führen, und auch wenig bringen, weil sie ja hauptsächlich zum port von unix-tools verwendet wird, und die würden mit 16bit-ints sowieso i.d.R nicht klar kommen. Auch müsste man dann alle anderen verwendeten Bibliotheken auch mit -mshort kompilieren.

Gemlib gibt es allerdings noch für -mshort.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Thorsten Otto am Mo 27.06.2022, 13:43:27
Ah, jetzt funktioniert es. So habe ich jetzt den Code angepasst:

       .text
        .global _asmmul
_asmmul:
        clr.l   d1
        clr.l   d0
        move.w  4(sp),d1
        move.w  6(sp),d0
        muls    d1,d0

        rts
        .end

Das wäre immer noch falsch. Der erste Parameter is bei 4(sp), aber das low-word davon is bei 6(SP). Der zweite Parameter ist bei 8(sp), das low-word davon bei 10(sp).

Im Zweifelsfall einmal kurz anschauen was GCC damit macht:

int asmmul(short x, short y)
{
return x * y;
}
$ m68k-atari-mint-gcc -fomit-frame-pointer -S -o - -O2 bla.c
       .text
        .even
        .globl  _asmmul
_asmmul:
        move.w 6(%sp),%d0
        muls.w 10(%sp),%d0
        rts

Wie man daran auch schön sieht, nutzt auch ein explizites "short" nichts, der Compiler pusht immer ein int auf den Stack.

Das ist auch der Grund für die ganzen komischen macros in osbind.h von GCC. Ohne die wäre GCC (ohne -mshort) nicht in der Lage, das für GEMDOS-Calls nötige Stack-layout zu erzeugen.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Thorsten Otto am Mo 27.06.2022, 13:51:48
Ich habe mir mal einige Routinen als Assembly angeschaut und nur die Hände über dem Kopf zusammen geschlagen. Was der C-Compiler da baut, scheint mir nicht die vollen CISC-Features bzw. besonders leistungsfähige Kommandos des Prozessors auszunutzen.

Ja, manchmal sieht der Code schon komisch aus. Aber insgesamt macht er schon einen ganz guten Job. Kannst ja mal probeweise ein paar benchmarks einmal mit Pure-C und einmal mit gcc übersetzen. I.d.R. ist da nicht viel Unterschied.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am Mo 27.06.2022, 15:42:20
Das wäre immer noch falsch. Der erste Parameter is bei 4(sp), aber das low-word davon is bei 6(SP). Der zweite Parameter ist bei 8(sp), das low-word davon bei 10(sp).

Siehe oben: Martin baut mit "-mshort". Da sind die Parameter auf dem Stack tatsächlich 16-bit aligned.

Kannst ja mal probeweise ein paar benchmarks einmal mit Pure-C und einmal mit gcc übersetzen. I.d.R. ist da nicht viel Unterschied.

??? Im Vergleich zwischen Pure-C und gcc gewinnt gcc meilenweit.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Mo 27.06.2022, 18:34:52
Kann mir jemand im Detail erklären, was die Assembler-Direktive lea macht? Ich habe hier ein Stückchen Beispiel-Code:

#ifdef __mcoldfire__
        lea     -12(sp),sp
        movem.l d1/a0-a1,(sp)
#else
        movem.l d1/a0-a1,-(sp)
#endif

Im Falle von Nicht-Coldfire werden die angegebenen Register auf den Stack gesichert, der Stackpointer wird jeweils vorher dekrementiert.

Ich verstehe das lea Kommando oben nicht, der Rest ist klar. Warum ist das nicht einfach ein sub #12,sp? lea lädt doch eine Adresse, aber warum sind im linken Teil dann Klammern um sp, das würde doch bedeuten, dass der Inhalt der Speicherzelle referenziert wird, auf die sp zeigt?

Und, was ist der Unterschied zwischen dem Laden einer Adresse und einer effektiven Adresse? Bei allen Büchern, die ich gelesen habe, habe ich es nicht verstanden.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: mfro am Mo 27.06.2022, 19:12:48
ein
suba.w   #12,spgibt's beim ColdFire nicht (mehr).
suba.l    #12,sp gibt's noch, der Befehl braucht aber (weil #-12 da ein Langwort ist) 3 Worte.

lea        -12(sp),sp macht effektiv dasselbe mit zwei Words.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Thorsten Otto am Di 28.06.2022, 06:16:56
lea kann man im Grunde mit dem Adress-operator (&) in C vergleichen. Es wird lediglich die berechnete Adresse ins Adress-Register geladen, aber nicht dereferenziert.

lea ist auf eigentlich auf allen Prozessoren schneller als ein suba (u.a. glaube ich weil dafür eine andere ALU benutzt wird als für Arithmetik-Operationen).
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Di 28.06.2022, 07:08:39
lea kann man im Grunde mit dem Adress-operator (&) in C vergleichen. Es wird lediglich die berechnete Adresse ins Adress-Register geladen, aber nicht dereferenziert.
Ah, danke, das war die entscheidende Erklärung, jetzt habe ich es verstanden. Es wird also in der Adressierungsart auf einen Inhalt verwiesen und die effektive Adresse ist dann die "Hausnummer".
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Di 28.06.2022, 07:10:32
lea        -12(sp),sp macht effektiv dasselbe mit zwei Words.
Das ist dann der nächste Schritt: Zu wissen, welcher Code schnell läuft und welcher langsam. Danke.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: mfro am Di 28.06.2022, 12:00:06
lea        -12(sp),sp macht effektiv dasselbe mit zwei Words.
Das ist dann der nächste Schritt: Zu wissen, welcher Code schnell läuft und welcher langsam. Danke.

Was Schnelligkeit angeht, ist es für ColdFire (fast) wurscht (beim 060 dürfte das genauso sein). lea und sub brauchen beide genau einen Taktzyklus (vorausgesetzt, der Instruction-Cache ist gefüllt und es tritt kein Pipeline-Stall auf).
Allerdings ist natürlich bei 3-Wort-Befehlen der Cache "schneller leer" als bei zweien und die Wahrscheinlichkeit eines unaligned access ist höher (dann gibt's u.U. "Straftakte").
 
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Fr 01.07.2022, 08:13:48
Oje. Ich habe eine komplette Routine aus dem EmuTOS-ROM in Assembler implementiert, vom Algorithmus der C-Implementierung folgend. Aber ich bin nicht schneller, als der C-Compiler mit Optimierung und einer kleinen Loop, die auch im C-Original als Inline-Assembler eingebettet ist. Frust - und viel gelernt.

Ich glaube, ich habe so ungefähr jeden Fehler gemacht, den man als Assembler-Anfänger machen kann. Aber immerhin habe ich meine Routine komplett zum Laufen gebracht. :-)

Ich habe:
- WORD-Arrays nur BYTEweise indiziert und damit nette kleine Address Errors provoziert,
- Ich habe mit MOVEM weniger Register gesichert, als ich brauchte und mich über komische Abstürze gewundert,
- Ich habe Register aus versehen doppelt verwendet
- Ich habe Schleifen von C nach Assembler falsch umgesetzt, C-For-Schleifen prüfen am Anfang!
- Ich habe vorzeichenbehaftete Zahlen so behandelt, wie welche ohne
- ... und vermutlich noch viele Sachen mehr, die ich schon wieder vergessen habe

Ich habe aber auch massiv viel gelernt. Letzte Woche hatte ich eine berufliche Fahrt nach Köln und habe mir während der Fahrt komplett "ATARI ST – Programmieren in Maschinensprache", das schwarze Buch vom SYBEX-Verlag, reingetan. Dafür, dass ich gerade erst anfange, bin ich eigentlich ganz zufrieden mit mir.

Was ich auch gelernt habe: Bis auf einige Mikro-Optimierungen, die man auch in C einbetten kann, lohnt sich bei den heutigen Compilern eine Programmierung in Assembler offenbar kaum noch, außer, man will hardware-nahe Interfaces machen, oder sowas.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: gh-baden am Fr 01.07.2022, 19:56:59
Was ich auch gelernt habe: Bis auf einige Mikro-Optimierungen, die man auch in C einbetten kann, lohnt sich bei den heutigen Compilern eine Programmierung in Assembler offenbar kaum noch,

Ganz so weit würde ich nicht gehen, aber es ist definitiv ein ganz anderer Unterschied zwischen heutigen Compilern und denen von 1986, und handgestricktem Assembler.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am Fr 01.07.2022, 20:42:58
Ich bin auch schon reingefallen mit Optimierungsversuchen, die das Gegenteil bewirkt haben. Was ich in einem Vierteljahrhundert Programmierung gelernt habe:

Ganz schlecht: C-Code angucken, denken "das kann keinen guten Code ergeben" und umständlicher hinschreiben. Compiler ‒ jedenfalls moderne ‒ sind gut im Optimieren, insbesondere, wenn man den Code eben nicht vorab händisch versucht zu verbessern.

Schon besser: Compiler-Output angucken und denken "was soll denn das?". Ja, vielleicht ist eine spezifische Stelle suboptimal übersetzt. Aber wenn diese Stelle nicht in einem "heißen" Teil des Codes ist, lohnt es die Mühe dennoch nicht, sie in (Inline-)Assembler umzuschreiben.

Tipp: Hatari hat auch einen Profiler. Zumindest für die ST/68000-Emulation ist er zyklusexakt. Damit kannst Du Dir z.B. eine VDI-Funktion vornehmen und sehen, welche Codestellen "heiß" sind.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am Sa 02.07.2022, 11:28:19
Ich hatte gedacht, wenn ich soweit es geht alles in Registern unterbringe, anstatt immer auf Speicherstellen im Stack-Frame zuzugreifen, dann müsste es richtig schnell werden. Aber offensichtlich macht der Compiler genau das gleiche bei entsprechenden Optimierungsoptionen, -fomit-frame-pointer etc ...

Und das mit dem "heißen Teil" ist wohl ganz wichtig.

Wenn das NVDI Dinge irgendwo doppet so schnell macht, wie EmuTOS, dann liegt es auf jeden Fall nicht an der Routine in vdi_line.c, die ich mir da gegriffen habe.

Was ich auch gespürt habe ist, dass C-Code wirklich wesentlich übersichtlicher ist, als Assembler. Ich glaube nicht, dass es nur Gewohnheit ist.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Thorsten Otto am Sa 02.07.2022, 15:47:54
Wenn das NVDI Dinge irgendwo doppet so schnell macht, wie EmuTOS, dann liegt es auf jeden Fall nicht an der Routine in vdi_line.c, die ich mir da gegriffen habe.

Auch die line-Routine ist in NVDI sicherlich schneller als in EmuTOS/TOS. Das liegt aber im wesentlichen daran, daß NVDI für sehr viele spezielle Fälle (insbesondere die die auch vom AES benutzt werden), spezielle Routinen benutzt, die entsprechend optimiert sind. Auch gibt es für jede Bit-Tiefe (1/2/4/8 plane etc) eigene Routinen in den jeweiligen Treibern. Sowas kann man in EmuTOS schon aus Platz-Gründen nicht implementieren.

Vermutlich könnte mit Assembler schon ein bisschen rausholen, aber vermutlich nur in Grössenordnungen die sich lediglich mit einem Benchmark messen lassen, und beim täglichen Gebrauch kaum auffallen, und das ganze auch nur mit erheblichem Aufwand.

Aber das soll dich nicht davon abhalten es weiterhin zu versuchen ;) Wenn nichts anderes, kann man dabei auch eine Menge lernen
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: czietz am So 03.07.2022, 17:57:08
Zum Thema NVDI: NVDI macht auch einige "dreckige" Annahmen, wie z.B., dass gewissen Variablen in den ersten 32 kiB RAM stehen und somit über den xxx.W-Adressierungsmodus erreichbar sind. Wir mussten einmal EmuTOS deswegen anpassen.


Zum Thema kluge Compiler: Ein Beispiel. Divisionen sind sehr langsame Operationen (selbst auf modernen CPUs). Programmierer versuchen sie zu vermeiden. Aber nehmen wir an, eine Division durch drei sei nun einmal nötig:

uint16_t divide_by_three(uint16_t a)
{
    uint16_t res;
    res = a / 3;
    return res;
}

Bestimmt 95% der Programmierer würden, wenn sie das händisch in Assembler formulieren müssten, in den sauren Apfel beißen und halt notgedrungen ein "DIVU.W #3,Dx" hinschreiben. Was gcc daraus macht, überrascht vielleicht den einen oder anderen: https://tinyurl.com/2cz88ktv

Auf einem 68000 braucht der naive Weg (mit DIVU) 136 Zyklen, gccs Code (obwohl mehrere Instruktionen) nur 76 Zyklen. Demnach gut 40% schneller.

Titel: Re: Erste Assembler-Gehversuche
Beitrag von: mfro am So 03.07.2022, 21:27:13
... und wer wissen will wie (und warum) das funktioniert:

https://gmplib.org/~tege/divcnst-pldi94.pdf
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: Mado am So 03.07.2022, 22:33:17
Hilfääääää!  ;)

@thh Naja, weiter versuchen – erstmal nicht. Ich könnte Loop-Unrolling machen und dann die Loop austauschen gegen eine Tabelle von Move-Anweisungen, in die ich dann ab der richtigen Anzahl Anweisungen rein springe etc, das hat aber wieder Nachteile in der Code-Größe und skaliert auch nicht auf immer größere Displays... Der Code ist schon gut so. ;) Dann lad ich mir für sowas halt NVDI (Deines).

Ach ja, den Code vereinfacht für nur ST-High hatte ich in C schon mal, hat aber kaum was gebracht, da die Verwaltung der Planes kaum Laufzeit braucht. War irgendwie 3-4% schneller. Dafür lohnt es sich nicht.
Titel: Re: Erste Assembler-Gehversuche
Beitrag von: simonsunnyboy am Mo 04.07.2022, 20:02:09
... und wer wissen will wie (und warum) das funktioniert:

https://gmplib.org/~tege/divcnst-pldi94.pdf

Echt interessant, nach dem heutigem Tag verdau ich das aber eher nicht. ;)
Muss ich irgendwann mal in Ruhe lesen.