Variablenbezüge in Assembler innerhalb Pascal

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

wie wäre es mit

Code: Alles auswählen

asm
   #blah
   call @weiter
   #blah?
   call @hier_könnte_ihr_Name_stehen
   #blah.
   jmp @The_end
   
  @weiter
   #blah!
   ret

  @hier_könnte_ihr_Name_stehen
   #mir_fällt_nix_ein
   ret

  @The_end
   #Wuga!
end;
dann hättest du das auch mit call gelöst. und Pascalunterprogramme kannst du auch von Assembler aus aufrufen - musst dich eben nur selbst darum kümmern die Parameter in der richtigen reihenfolge auf den Stack zu schieben (die waren "afair afair" von rechts nach Links)

Ähdit: siehe elgoog.de

es geht übrigens auch Procedure aloha;assembler;

@wobo
Die Subroutinen müssen vor den Routinen stehen, die die Subroutinen aufrufen.
Afair gab es in Pascal auch eine möglichkeit Unterprogramme vorzudefinieren, d.h. da stand dann erstmal nur die Definition (z.b. procedure wasauchimmer(is,mir,wurscht:String); ) und später wurde erst das unterprogramm genau definiert - ist praktisch für verschachtelte Unterprogramme

... ok ich habs, einfach forward hinter dem interfaceteil deines Unterprogrammes schreiben
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von freecrac »

wobo hat geschrieben:Ja, man muss für jede Subroutine eine eigene Pascal-Procedure schreiben (Du bist ja auch unter Pascal),
....
Die Subroutinen müssen vor den Routinen stehen, die die Subroutinen aufrufen.

Turbo Pascal ist für 80286, d.h. kann grundsätzlich nur 16-bit/286-instructions. "Pushad" etc. geht also grundsätzlich nicht.
Kennt Pascal die asm-Anweisung Define Byte (zb: DB 0,1,2,3,4)?
Weil dann könnte man doch auch 32 Bit-Register/Befehle von Hand coden und dort einbauen.
Parameter kommen auf den Stack und werden von der Subroutine beim Verlassen auch wieder entfernt.
Lokale Variablen werden wie unter Pascal angelegt und ebenfalls automatisch beim Verlassen der procedure vom Stack entfernt.
Alles andere, was Du auf den Stack schmeißt, mußt Du auch wieder selbst entfernen (z.B.: die pusha/popa oben).

Irgendwo habe ich mal gelesen, dass man den in Pascal integrierten Assembler als logischen Assembler bezeichnet, da man grundsätzlich keine Adressen berechnen muss. Wenn mir also einfällt, ich brauchte noch 6 Variablen, dann kann ich sie gefahrlos vor (und natürlich auch nach) der bereits angelegten lokalen Variable "Summe" anlegen, ohne dass ich am Code was ändern müßte.
Also z.B.:

Code: Alles auswählen

procedure Haupt( w1, w2 : word);
var   ich, bin, so, ver, gess, lich : byte;
      Summe: word;
begin
   Summe := w1+w2;
   asm
     mov ax, Summe
     pusha
     push ax
     call Sub02
     popa
     mov Summe, ax
   end;
   writeln( Summe );
end;
Ok, das habe ich nun verstanden.
Leider habe ich mit VESA noch nichts gemacht. Wie kommt man denn an die Bankumschaltungsroutine und welche Parameter erwartet diese?
Ab VBE 2 macht man es so wie folgt:
Zuerst rufen wir VBE-Function 00 auf. Dafür reservieren wir einen Buffer von 512 Byte.

Function 00h - Return VBE Controller Information
Input: AX = 4F00h Return VBE Controller Information
ES:DI = Pointer to buffer in which to place VbeInfoBlock structure

VbeInfoBlock (512 Byte):
....
VideoModePtr dd ? ; VbeFarPtr to VideoModeList = VbeInfoBlock+0Eh
.....

Dann holen wir aus diesem VbeInfoBlock+0Eh den Pointer der auf die Vesa-Modeliste zeigt.
Nun holen wir darüber die erste Modenummer und rufen damit VBE-Function 01 auf, um damit zu überprüfen ob dieser Mode die gewünschte Auflösung und Anzahl der Farben hat. Für Function 01 brauchen wir einen weiteren Buffer(ModeInfoBlock) von 256 Bytes, die wir ebenfalls reservieren.

Function 01h - Return VBE Mode Information
Input: AX = 4F01h Return VBE mode information
CX = Mode number
ES:DI = Pointer to ModeInfoBlock structure

ModeInfoBlock: (256 Byte: Auflösung, Farbtiefe... )
...
WinFuncPtr dd ? ; real mode pointer to window function = Modeinfo-Buffer+0Ch
...

Haben wir unsere gewünschte Auflösung und damit unsere Modenummer gefunden, dann können wir nun den far call-Pointer zur Bankumschalktungsroutine aus ModeInfoBlock+0Ch holen und in unseren Pixelsetzer schreiben.

Wenn die Auflösung etc. des ersten Mode in der Modeliste nicht stimmt, dann müssen wir die nächste Modenummer aus der Liste holen und die Überprüfung mit Funktion 01 erneut wiederholen, bis wir die gewünschte Auflosung gefunden haben, oder bis die Modeliste mit FFFF endet und damit kein Mode mit den gewünschten Eigenschaften gefunden wurde.

Wenn wir unseren Mode gefunden haben:

Code: Alles auswählen


	  mov      di, OFFSET ModeInfoBlock
	  mov      eax, DWORD PTR[di+0Ch] ; Zeiger auf Bankumschaltungs-Routine holen
	  and      eax, eax
	  jz  FEHLER                   ; Funktion wird nicht unterstützt
	  mov     DWORD PTR cs:[BANKSET], eax


Pixelsetzer:
               ; Berechnung der Adresse des Pixels; OFFSET nach DI; Banknummer nach DX

	  cmp      dx, bp            ; BP = alte Banknummer; DX = neue Banknummer
	  jz  short NOBA             ; wenn identisch, dann braucht nicht geschaltet werden
	  xor      bx, bx            ; VESA:Bankumschaltung (Fenster A)
	  mov      bp, dx            ; neue Banknummer retten
;--------------------------
PIXEL:  DB 9Ah                         ; CALL FAR: BANKSET
BANKSET DD ?                           ; Zeiger auf Bankumschaltungs-Routine (OFFSET, SEGMENT)
;--------------------------
NOBA:     mov     DWORD PTR fs:[di], 15 ; Pixel setzen (FS = 0A000h)
Für 80286 müsste man statt EAX zu verwenden nur AX benutzen und dann zwei Lese- und Schreibzugriffe machen, damit Segment und Offset von der Bankumschaltungsroutine auch in unseren Code richtig eingetragen wird.

Für den far call zur Bankumschaltunfsroutine: Ausser die Funktionsnummer(in AX) die wir mit dem Direktansprung nicht mehr brauchen, sind es die selben Input-Parameter wie bei der Function 05, die wir auch für den far call zur Bankumschaltungsroutine benötigen.

Function 05h - Display Window Control
Input: AX = 4F05h VBE Display Window Control ; brauchen wir nicht beim far call
(16-bit) BH = 00h Set memory window
= 01h Get memory window
BL = Window number
= 00h Window A
= 01h Window B
DX = Window number in video memory in window

Alle diese VBE-Funktionen sollten im AX-Register 004Fh zurück geben, weil sonst ist ein Fehler augetreten.
Wenn noch etwas unklar ist, dann einfach noch einmal nachfragen. Ich erkläre es so lange, bis es jeder versteht der es verstehen möchte.

Genauere Details findet man im "vbe3.pdf" auf vesa.org (Registrieren/Anmelden erforderlich).
.... Der Programmierer muss gucken, dass der Stack groß genug ist. Pascal entfernt nur nach Verlassen einer Subroutine die übergebenen Parameter und die angelegten lokalen Variablen sowie die Rücksprungadresse.
Ok, was ich aber noch nicht weiss, wo wird die Stackgrösse angegeben?

Dirk
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von freecrac »

Brueggi hat geschrieben:Ich nehme an, du möchtest das via MUL und DIV rechnen? Würde ich - je nach dem, für welche CPU dein Programm ist, aber lassen oder nur eingeschränkt verwenden, denn das kann (gerade am 286) viel Takte kosten.

Teilst/Multiplizierst Du durch/mit einem Word, dann verwendet der Prozessor automatisch DX:AX / wort, wobei dann AX=Ergebnis und DX glaub ich der Rest ist, bei der Multiplikation ist DX:AX=AX*Wort.
Ich vermute das beim Anfang einer ASM-Anweisung in einem TP-Listing alle Register auf Null gesetzt werden. Stimmt das?
Falls es irgendwie möglich ist, würde ich aber lieber eine Tabelle anlegen, in welcher zumindest die wichtigsten Werte schon vorab ausgerechnet zu finden sind. Das kostet am wenigsten Zeit (wobei das dann am Pentium oder so egal sein dürfte).
Ja genau, am besten man berechnet schon alles was es zu berechen gibt im voraus. Entweder ein einziges Mal aus einer dafür entwickelten Rechen-Anwendung und dann speichert man die Ergebnisse in ein Datenfile.
Unsere andere Anwendung läd dann zum Anfang das Datenfile in den Speicher, um dann nur noch die schon fertig berechneten Ergebnisse weiter zu verarbeiten. Oder man berechnet alle Berechnungen ganz am Anfang in unserer Anwendung. Vermutlich wird das auch nicht so viel Zeit dauern, denn wenn das Datenfile von Diskette geladen werden muss, dann dauert das auch seine Zeit. Und falls die Ergebnisse doch zu umfangreich werden und dann ggf. zu viel Platz belegen würden, dann wird man es wohl doch besser erst dann berechnen, wenn das jeweilige Ergbniss auch wirklich schon weiterverarbeitet werden soll.
Ich habe jetzt die Takte nicht im Kopf, meine aber, das DIV und MUL erst ab dem 80386 richtig sparsam sind, vorher aber richtig viel Zeit "verbraten".
Besonders DIV braucht heftig viele Takte.
Wenn es irgendwie geht den Wert zu zerlegen, dann sollte man das auch tun.

Beispiel: Wert mit 10 multiplizieren (ohne MUL-Befehl)!
a) Wert in ein zweites Register retten,
b) das eine Register mit "shl Register, 3" mit 8 multiplizieren
c) das andere Register mit "shl Register, 1" mit 2 multiplizieren
d) beide Register zusammen addieren

Die Shift-Befehle sind im Vergleich zu MUL/DIV sehr schnell, es ist aber leider nicht immer möglich sie zu verwenden.
Und auch Berechnungen mit dem LEA-Befehl sind (besonders ab 80386 mit 32 Bit, da mehr Register-Kombinationen und scaling dort möglich ist) sehr schnell. Aber auch beim 80286 wird ein "lea bx, [bx+si]" vermutlich schneller ausgeführt als ein "add bx, si" und ein "lea bx, [bx+1]" wird schneller ausgeführt als ein "inc bx".

Auch sehr wichtig ist es auf das Daten-Alignment zu achten, so das Word-Zugriffe bestenfalls nur auf gerade Adressen erfolgen und DWORD-Zugriffe auf Adressen die durch 4 teilbar sind usw..

Code-Optimierung ab den Pentium: Der Pentium hat schon zwei getrennt arbeitende Befehlsausführungseinheiten integriert die im günstigsten Fall zwei Integer-Befehle paralell ausführen können, wenn es zwischen den verwendeten Befehlen keine Abhängigkeiten gibt. Je nachdem wie viele Befehle die beiden cpu prefetch queues aufnehmen können (beim Pentium 2 x 64 Byte) und dann im günstigsten Fall auch aufgefüllt sind, können die Befehle verteilt jeweils mit einem komplexen Befehl und mit einem nicht komplexen Befehl zusammen, oder zwei nicht komplexe Befehle zusammen parallel ausgeführt werden.
Zwei komplexe Befehle können aber nicht zusammen parallel ausgeführt werden. Komplexe Befehle können nur im ersten
queues ausgeführt werden, ist dort aber gerade besetzt, dann kommt die ganze Paralelisierung aus dem Takt, bis wieder beide queues mit passenden Kombinationen von Befehlen aufgefüllt worden sind.

Dann solte man auch schon darauf achten, dass wenn man in ein Register etwas geschrieben hat und man möchte dieses Register unmittelbar danach wieder lesen, oder beschreiben, das dann eine Verzögerung auftriit, weil hier eine Abhängigkeit zwischen den Befehlen besteht. Beim zu erst lesen eines Registers und unmittelbar danach es zu beschreiben tritt keine Verzögerung auf. Wir merken uns: Schreiben nach dem Lesen macht kein Problem, aber das Lesen nach dem Schreiben und auch das Schreiben nach dem Schreiben in ein Register. In diesem Fall sollten wir nach Möglichkeit andere Befehle dazwischen plazieren und den Lesebefehl (oder den nachfolgeneden Schreibbefehl) so weit wie möglich vom vorherigen Schreibbefehl auf das selbe Register auseinander stellen. Das macht leider den Code für uns nicht gerade leichter zu lesen, sondern eher noch schwerer. Die Ausführungsgeschwindigkeit profitiert aber davon erheblich, wenn wir Abhängigkeiten zwischen den Befehlen so gut wie möglich vermeiden.

Dirk
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von freecrac »

wobo hat geschrieben:Das Problem dürfte - falls nicht noch andere Fehler vorhanden sind - aber auch sein, dass ich eingangs bei der Erstellung der Notenfaktortabelle runde, und nun bei der Division den Rest unter den Tisch fallen lasse. Vielleicht wissen ja die Profis hier (Brueggi,DosFeratu,Freecrac (A-Z-Order!)} Rat, wie man die Genaugikeit verbessern kann.
Bei der Genaugikeit bin ich überfragt, wenn ich es richtig vertanden habe werden die Werte mit 1024 multipliziert.
Damit wird doch schon die dritte Nachkommastelle berücksichtigt, wenn ich das richtig sehe. Habe ich das jetzt richtig verstanden? Weil Ich habe bei Mathe immer ein Brett vor dem Kopf, bzw. massive Denkblockaden.

Ich konnte keinen Fehler im Code finden.

Code: Alles auswählen

  asm
    mov bx, NotenNr     
    lea si, Notenf1024     { Tabelle liegt im Datensegment ds } 
    lea bx, [bx+bx]        { Notenf1024 - Tabelle ist array of word, d.h. *2 }
    mov ax, [si+bx]	     { ax := Notenf1024[NotenNr] }
    mul WORD[C3Freq]
    div WORD[MixSpeed]
    mov StepSpeed, ax
  end;
Dirk
Brueggi

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Brueggi »

Also DB gibt es in PASCAL nicht, aber INLINE, man kann also "illegale" Intruktionen per Hand einfügen.
Ich vermute mal, dass die Register nicht auf 0 gesetzt werden, da man z.B. auch selbst sorge dafür tragen muss, dass z. B. BP und DS nach Abschluss der ASM-Routine korrekt wiederhergestellt werden. Daher vermute ich, dass Pascal sich darum selbst erstmal nicht kümmert und das bei Eintritt in ein Unterprogramm bzw. bei Eintritt in eine ASM-Routine die Register ebenso sind, wie sie aktuell zu genau diesem Zeitpunkt belegt sind.

Zum Thema VESA werde ich dich demnächst mal etwas ausquetschen - ich möchte damit nämlich auch mal experimentieren :-)

Das mit den Optimierungen ist ein sehr interessantes Thema - für mich aktuell aber nicht soooo interessant, da ich mich (vorerst) auf 80286er beschränke - auch wenn die Software dann auch mal auf meinem Pentium I läuft - das geht aber immernoch so schnell (im Vergleich zu meinem 486er), dass man garnicht mitkommt :-) Ich habe nur in meinem Assemblerbuch gesehen, das der 8086 eine echter "Taktfresser" ist - sogar SHL/R braucht mit Angabe in CL fast genauso viel Takte, wie entsprechend untereinander geschrieben, also statt

MOV CL,5
SHL AX,CL

könnte man da auch gleich

SHL AX
SHL AX
SHL AX
SHL AX
SHL AX

schreiben. Ich meine, der 80286 hat ja dann auch erst einen Shifter, der mehrere Verschiebungen "in einem Rutsch" ermöglicht - deshalb ist es am 80286 wieder deutlich schneller, einen Befehl statt mehrere zu nehmen. Ich muss manchmal noch immer grübeln, wie ich am besten vorgehe - vom C64 (6510) war ich es gewöhnt, möglichst viele Anweisungen zu verwenden und ja keine Schleifenkonstrukte oder sowas zu verwenden, da beim 6510 eben mehr weniger ist (da ist es wirklich besser, mehr Befehle zu verwenden, um hinterher schnelleren Code und weniger Takte zu haben).

Die Frage ist halt dann auch, wo fängts an und wo hörts auf - auch aus diesen (und auch nostalgischen) Gründen kann ich mit einem Pentium nichts anfangen - denn das bringt mir nicht viel, wenn ich dann auf einem 133er PI 20% Rechenzeit spare, das gleiche aber am 386 dann (übertrieben gesagt) 20% mehr Zeit braucht, der Prozessoer aber eh nur mit 16 oder 33 MHz läuft.

Ich hab mich damals schon gefreut wie ein Schneekönig, als ich am CPC mein erstes Programm (hand assembliert) in Z80-Assembler geschrieben habe, und das Kopieren einer Grafik nicht mehr 2 Minuten gedauert hat (BASIC), sondern nichtmal eine Sekunde :-) Und das waren ja gerade mal 4 MHz :-) Genauso freue ich mich am 486er - wobei es da nicht mehr so auffällt, denn die EXE-Files von z.B. Pascal sind ja schon sau schnell. Einzig kompilierte QBASIC-Programme sind auf dem 486er lahm wie nix, während sie am Pentium super laufen...

Nochmal zurück zum Thema Optimierung: Kannst Du erläutern, warum diese Verzögerung bei Register-Zugriffen entsteht? Am Cache kann das ja dann nicht liegen, wenn ein Register betroffen ist? Wie sieht das dann in der Praxis aus, wenn ich Abhängigkeiten vermeiden möchte? Hast Du da mal ein konkretes Beispiel?
Brueggi

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Brueggi »

Bei der Gelegenheit hätte ich noch eine Frage: Warum bricht eine CPU wie Pentium oder 486er so extremst ein, wenn ich den Cache abschalte? Normalerweise müsste es doch Aufgrund der MHz schon so sein, dass der Prozessor - wenn auch nicht mehr so flott - trotzdem noch rasend schnell ist. Allerdings läuft dann mein DX-2 grad mal so schnell wie ein 286er-16 (gefühlt), wenn ich den Cache abschalte.
wobo
DOS-Guru
Beiträge: 614
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

freecrac hat geschrieben:
wobo hat geschrieben: Turbo Pascal ist für 80286, d.h. kann grundsätzlich nur 16-bit/286-instructions. "Pushad" etc. geht also grundsätzlich nicht.
Kennt Pascal die asm-Anweisung Define Byte (zb: DB 0,1,2,3,4)?
Weil dann könnte man doch auch 32 Bit-Register/Befehle von Hand coden und dort einbauen.
Die Anweisung Define Byte kennt auch der integrierte Assembler, d.h. mittels z.B.: "db $66; mov ax, word ptr _32bit_Variable;" kannst Du ein "mov eax, _32bit_Variable" realisieren, mittels z.B. "DB $65; DB $66; DB $67; mov [di],ax" ein "mov gs:[edi],eax".

Frag mal Dosferatu - der weiß da mehr. Er schreibt, glaube ich, nur noch DB-Anweisungen ("Einbyten") und gar keinen richtigen Code mehr (;-))...
freecrac hat geschrieben: Ich vermute das beim Anfang einer ASM-Anweisung in einem TP-Listing alle Register auf Null gesetzt werden. Stimmt das?
Nein. Die Register sind grundsätzlich undefiniert. Ihr Inhalt bestimmt sich danach, was Pascal vorher compiliert hat. Die Anweisung "ASM" erzeugt selbst grundsätzlich keinen Code (imho). Auf null gesetzt wird jedenfalls keines der Register. Vordefiniert sind m.W. nur CS, IP, BP, SP, SS, DS. Der Inhalt der anderen Register ist bei Beginn der ASM-Anweisung quasi "zufällig".

freecrac hat geschrieben:Ok, was ich aber noch nicht weiss, wo wird die Stackgrösse angegeben?
Die Stack-Größe wird entweder innerhalb der IDE angegeben, oder als (vorrangiger) Compiler-Schalter im Listing, d.h. {$M StackSize, HeapMin, HeapMax }, wobei StackSize ein Wert zwischen 1024 und 65520 sein muß. Voreingestellt, d.h. ohne einen solchen Compilerschalter oder eine solche Einstellung in der IDE, sind grundsätzlich 16kb für den Stack.


@Dosenware
Dosenware hat geschrieben: @wobo
Die Subroutinen müssen vor den Routinen stehen, die die Subroutinen aufrufen.
Afair gab es in Pascal auch eine möglichkeit Unterprogramme vorzudefinieren, d.h. da stand dann erstmal nur die Definition (z.b. procedure wasauchimmer(is,mir,wurscht:String); ) und später wurde erst das unterprogramm genau definiert - ist praktisch für verschachtelte Unterprogramme

... ok ich habs, einfach forward hinter dem interfaceteil deines Unterprogrammes schreiben
Danke für die Info. "forward" kannte ich bisher noch gar nicht!
wobo
DOS-Guru
Beiträge: 614
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Brueggi hat geschrieben:.
Ich vermute mal, dass die Register nicht auf 0 gesetzt werden, da man z.B. auch selbst sorge dafür tragen muss, dass z. B. BP und DS nach Abschluss der ASM-Routine korrekt wiederhergestellt werden. Daher vermute ich, dass Pascal sich darum selbst erstmal nicht kümmert und das bei Eintritt in ein Unterprogramm bzw. bei Eintritt in eine ASM-Routine die Register ebenso sind, wie sie aktuell zu genau diesem Zeitpunkt belegt sind.
Richtig.
Das mit den Optimierungen ist ein sehr interessantes Thema ....
Nochmal zurück zum Thema Optimierung: Kannst Du erläutern, warum diese Verzögerung bei Register-Zugriffen entsteht? Am Cache kann das ja dann nicht liegen, wenn ein Register betroffen ist? Wie sieht das dann in der Praxis aus, wenn ich Abhängigkeiten vermeiden möchte? Hast Du da mal ein konkretes Beispiel?
Vielleicht sollten wir dazu einen neuen Thread aufmachen? Der hier explodiert ja schon fast ;-)
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von zatzen »

Hey das ist mein Thread ;-)


Aber sehr ergiebig, er wird sich lesen wie ein Lexikon.



Noch ne kleine Frage.

In Pascal, mit asm - end; anweisung.

Kommt da direkt der code mitten rein in das letztlliche EXE Compilat
oder generiert die Klammerung nochmal Stacksicherungen?

Vereinfachtes Beispiel:


asm
NOP
end;

(und das ganze entweder im hauptprogramm oder in einer subroutine)

Steht dann einfach in der EXE-Datei ein einzelnes 0x90 Byte oder frickelt pascal da noch mehr drumherum?

So könnte man sich auch in nem Pascal Proggie markierungen setzen, um mal in nem Hex-Editor
zu gucken, was der Pascal compiler aus diesem oder jenem macht, wäre mal interessant.

Leute ich muss euch echt sagen, seit ich mich jetzt von Windos nicht mehr so einschüchtern
lasse habe ich mit der guten säuberlichen DOS-Programmierung noch einen von Milliarden
Lebensinnen mehr! :-)
Zuletzt geändert von zatzen am Di 1. Mai 2012, 11:33, insgesamt 1-mal geändert.
mov ax, 13h
int 10h

while vorne_frei do vor;
wobo
DOS-Guru
Beiträge: 614
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

zatzen hat geschrieben: Diese DWORD-Berechnungen würden recht selten auftreten.
Immer nur pro Zeile in einem Pattern, und ich hab 8 Kanäle.
Im Durchschnitt hätte ich vielleicht 40 solche Berechnungen
pro Sekunde...

Notenfaktor wäre eine Tabelle mit 108 WORD Werten,
die ich mir aus einer Tabelle zur realen temperierten
Stimmung generiere. A= 440 Hz, C=523,251 Hz usw...
(Der Tracker aus dem ich konvertiere kann 108 Noten).
108 Noten sind ganz schön viel. Wie gesagt der Noisetracker hatte m.W. ursprünglich nur 3 Oktaven.
Die anderen beiden Werte sind beliebig und müssen daher immer
neu in die Rechnung mit einbezogen werden.
Theoretisch könnte ich aber die Mixfrequenz noch "rauskürzen",
indem ich z.B. 3 Tabellen anlege für die Mixfrequenzen
11025, 22050 und 44100 Hz, das müsste eigentlich flexibel
genug sein. Evtl. noch 32000 Hz, das klingt nämlich in der
Praxis genauso "edel" wie 44100.
Oder, anders auch, einfach einen Divisor bzw. Multiplikator mit einbeziehen.

C-3 Frequenz kann theoretisch alles zwischen 1 und 65535 HZ sein,
wird aber in der Praxis irgendwo zwischen 4 kHz und 44100 Hz liegen,
wobei ich aus Sparsamkeit wohl nie höher als 22050 Hz gehen werde.
Aber für klitzekleine Hihats oder so schaden 44100 Hz nicht ;-)
Die Idee mit fixen MixSpeeds und/oder fixen Shiftfaktoren hat mir gut gefallen. Allerdings ist mir beim Lesen der anderen Beiträge über die Vorausberechnungen in Tabellen aufgefallen, dass das doch auch bei Dir klappen müßte.

Du hast vielleicht max. 100 Instrumente pro Song, d.h. Du hast pro Song auch max. 100 verschiedene C-3-Referenzen. Wenn Du jetzt beim Laden des Songs für jedes Instrument die jeweilige Notenfaktortabelle unter Berücksichtigung der spezifischen C-3-Frequenz erstellst, dann ginge das ganze in eine Tabelle von 100 * 108 = 10800 word, also knapp 20kb. Du müßtest also die Berechnungen nur immer beim Laden eines Songs ausführen und wärst dann auch mit der MixSpeed komplett variabel, d.h. Du müßtest diese nicht "herauskürzen"..
Zuletzt geändert von wobo am Di 1. Mai 2012, 19:53, insgesamt 1-mal geändert.
wobo
DOS-Guru
Beiträge: 614
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

zatzen hat geschrieben:Hey das ist mein Thread ;-)
Denkste, die Anarchisten hier im Forum erklären immer alles sofort zum Allgemeingut ;-)
Noch ne kleine Frage.

In Pascal, mit asm - end; anweisung.

Kommt da direkt der code mitten rein in das letztlliche EXE Compilat
oder generiert die Klammerung nochmal Stacksicherungen?

Vereinfachtes Beispiel:
asm
NOP
end;

(und das ganze entweder im hauptprogramm oder in einer subroutine)

Steht dann einfach in der EXE-Datei ein einzelnes 0x90 Byte oder frickelt pascal da noch mehr drumherum?

So könnte man sich auch in nem Pascal Proggie markierungen setzen, um mal in nem Hex-Editor
zu gucken, was der Pascal compiler aus diesem oder jenem macht, wäre mal interessant.
Also eine Stacksicherung erfolgt mit Sicherheit nicht. Ich kann mir auch nicht vorstellen, was TP da hineinfrickeln sollte. M.W. kommen die Anweisung im ASM-Block alle 1:1 in die Exe.
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von freecrac »

Brueggi hat geschrieben:Also DB gibt es in PASCAL nicht, aber INLINE, man kann also "illegale" Intruktionen per Hand einfügen.
Ich vermute mal, dass die Register nicht auf 0 gesetzt werden, da man z.B. auch selbst sorge dafür tragen muss, dass z. B. BP und DS nach Abschluss der ASM-Routine korrekt wiederhergestellt werden. Daher vermute ich, dass Pascal sich darum selbst erstmal nicht kümmert und das bei Eintritt in ein Unterprogramm bzw. bei Eintritt in eine ASM-Routine die Register ebenso sind, wie sie aktuell zu genau diesem Zeitpunkt belegt sind.
Oh, dann sollte man für einen DIV-Befehl auf jeden Fall das DX-Register im Auge behalten. und ggf. DS und BP retten.
Zum Thema VESA werde ich dich demnächst mal etwas ausquetschen - ich möchte damit nämlich auch mal experimentieren :-)
Gerne.
Das mit den Optimierungen ist ein sehr interessantes Thema - für mich aktuell aber nicht soooo interessant, da ich mich (vorerst) auf 80286er beschränke - auch wenn die Software dann auch mal auf meinem Pentium I läuft - das geht aber immernoch so schnell (im Vergleich zu meinem 486er), dass man garnicht mitkommt :-) Ich habe nur in meinem Assemblerbuch gesehen, das der 8086 eine echter "Taktfresser" ist - sogar SHL/R braucht mit Angabe in CL fast genauso viel Takte, wie entsprechend untereinander geschrieben, also statt

MOV CL,5
SHL AX,CL

könnte man da auch gleich

SHL AX
SHL AX
SHL AX
SHL AX
SHL AX

schreiben. Ich meine, der 80286 hat ja dann auch erst einen Shifter, der mehrere Verschiebungen "in einem Rutsch" ermöglicht - deshalb ist es am 80286 wieder deutlich schneller, einen Befehl statt mehrere zu nehmen.
Da ich nur für kurze Zeit einen 80286 hatte, deswegen habe ich mich gar nicht erst mit noch älteren Architekturen beschäftigt. Und beginnend mit dem 80386 hatte ich dann auch keinen Grund mehr auf 32Bit zu verzichten.

Zum Thema Optimierung findet man hier etwas:
"Intel® Architecture Optimization Reference Manual":
http://www.intel.com/design/pentiumii/m ... 245127.htm

....

Software Optimization Guide for AMD64 Processors:
http://support.amd.com/us/Processor_TechDocs/25112.PDF

AMD Developer Central:
http://developer.amd.com/documentation/ ... fault.aspx
Ich muss manchmal noch immer grübeln, wie ich am besten vorgehe - vom C64 (6510) war ich es gewöhnt, möglichst viele Anweisungen zu verwenden und ja keine Schleifenkonstrukte oder sowas zu verwenden, da beim 6510 eben mehr weniger ist (da ist es wirklich besser, mehr Befehle zu verwenden, um hinterher schnelleren Code und weniger Takte zu haben).
Daran kann ich mich jetzt gar nicht mehr errinern. Verdammt, ich kannte den C64 mal ganz genau bis in alle Details
und auch wie man alle Komponennten programmiert. Heute kann ich mich kaum noch an etwas erinnern.
Die Frage ist halt dann auch, wo fängts an und wo hörts auf - auch aus diesen (und auch nostalgischen) Gründen kann ich mit einem Pentium nichts anfangen - denn das bringt mir nicht viel, wenn ich dann auf einem 133er PI 20% Rechenzeit spare, das gleiche aber am 386 dann (übertrieben gesagt) 20% mehr Zeit braucht, der Prozessoer aber eh nur mit 16 oder 33 MHz läuft.
Für mich gibt es keine x86-CPU die mir zu schnell ist, ganz im Gegenteil, von mir aus könnten die x86-CPUs gerne millionenfach schneller werden, weil mir alle CPUs die es gibt noch viel zu langsam sind.
Ich hab mich damals schon gefreut wie ein Schneekönig, als ich am CPC mein erstes Programm (hand assembliert) in Z80-Assembler geschrieben habe, und das Kopieren einer Grafik nicht mehr 2 Minuten gedauert hat (BASIC), sondern nichtmal eine Sekunde :-) Und das waren ja gerade mal 4 MHz :-) Genauso freue ich mich am 486er - wobei es da nicht mehr so auffällt, denn die EXE-Files von z.B. Pascal sind ja schon sau schnell. Einzig kompilierte QBASIC-Programme sind auf dem 486er lahm wie nix, während sie am Pentium super laufen...
Genauso habe ich mich auch schon beim C64 gefreut als wir unseren ersten Pixelsetzer programmiert haben.
Zu sehen war ein kleiner weisser Punkt auf schwarzen Hintergrund. Bedenken muss man dazu auch, das meine Freunde und ich noch keine Computer von der Schule oder so her kannten und wir auch nicht mit solchen Technik von Kind auf an mit in Berührung kamen und es für unsere Generation eher Neuland war und wir damals auch noch kein Internet hatten und wir uns alles aus gedrukten Unterlagen zusammen suchen mussten. Auch waren meine Englisch-Kenntnisse damals noch nicht so augeprägt. Eine Vielzahl an unbekannten Fachbegriffen wollten auch erstmal verstanden werden, bevor man sich mit den dortigen Beziehungskisten aueinandersetzen konnte.
Nochmal zurück zum Thema Optimierung: Kannst Du erläutern, warum diese Verzögerung bei Register-Zugriffen entsteht? Am Cache kann das ja dann nicht liegen, wenn ein Register betroffen ist? Wie sieht das dann in der Praxis aus, wenn ich Abhängigkeiten vermeiden möchte? Hast Du da mal ein konkretes Beispiel?
Das liegt daran, dass jeder einzelne Befehl in der Pipeline mehrere Abarbeitungsstufen benötigt und während der Befelhl durch diese Stufen noch ausgeführt wird, sich auch noch andere Befehle in der Pipeline befinden deren Ausführung ebenfalls schon beginnt und zur gleichen Zeit paralell erfolgen können.

Solche Ausführungstufen muss jeder Befehl durchlaufen bis er in der letzten Stufe als vollständig ausgeführt die Pipeline verlässt.
So sehen diese Stufen beispielweise aus:
http://de.wikipedia.org/wiki/Pipeline-Architektur
A – Befehlscode laden (IF, Instruction Fetch)
In der Befehlsbereitstellungsphase wird der Befehl, der durch den Befehlszähler adressiert ist, aus dem Arbeitsspeicher geladen. Der Befehlszähler wird anschließend hochgezählt.
B – Instruktion dekodieren und Laden der Daten (ID, Instruction Decoding)
In der Dekodier- und Ladephase wird der geladene Befehl dekodiert (1. Takthälfte) und die notwendigen Daten aus dem Arbeitsspeicher und dem Registersatz geladen (2. Takthälfte).
C – Befehl ausführen (EX, Execution)
In der Ausführungsphase wird der dekodierte Befehl ausgeführt. Das Ergebnis wird durch den Pipeline-latch gepuffert.
D – Ergebnisse zurückgeben (WB, Write Back)
Hier ein Bild einer Pipeline wo im oberen Teil alle Stufen mit Befehlen gefüllt sind:
Bild

Wenn nun zwei Befehle in das selbe Register etwas schreiben wollen, dann können diese Befehle nicht gleichzeitig von den Ausführungstufen verarbeitet werden und ein nachfolgender Befehl muss warten bis der erste Befehl mit dem Schreibzugriff auf das selbe Register vollständig abgearbeitet wurde. Es kommt dann zu eine Lücke in der Pipeiline und die Parallelisierung kann erst dann wieder erfolgen, wenn die Pipeline wieder lückenlos aufgefüllt ist.

Auf der nach einer aufgeteilten Liste für komlexe und einfache x86-Befehle habe ich diese Seiten gefunden:
Guide to Assembly Code Optimization:
http://www.lxhp.in-berlin.de/lhpk6opt.html

From the Pentium Processor User's Manual:
http://www.gamedev.net/page/resources/_ ... rules-r212

Pairing Pentium Instructions:
http://www.emboss.co.nz/pentopt/pairing.html

Multiplication Tables/ Integer multiplying by constants:
http://www.azillionmonkeys.com/qed/amultl2.html

Beginnt mit 80386 + Pairing Pentium Instructions:
http://www.rmcet.com/Study%20Material/P ... %20sol.pdf

Dirk
Brueggi

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Brueggi »

Hui - das nenn ich mal eine ausführliche Antwort. Da muss ich mich in Ehrfurcht verneigen - da schreibt ein echter Profi! Hut ab! Die vielen Infos und Links müssen sich jetzt erstmal "setzen" sprich ich muss das "verdauen" - bei Fragen rühre ich mich wieder, allerdings dann im eigenen Thread :-)

Ja, und Computer von der Schule kannten wir damals auch nicht - es fing alles an, dass ich meine Mutter genervt habe, ich will etwas zum Zocken. Und da hab ich den Schneider CPC 464 bekommen. Und da gings dann los mit der Programmiererei. :-) Aber ich glaub das wird jetzt zu viel OT hier. - Schade dass solche Leute nicht hier in meiner Nähe wohnen, denn sowas wäre eine gutes Thema bei einem schönen kalten Bierchen ;-)
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von zatzen »

wobo hat geschrieben:
zatzen hat geschrieben: Notenfaktor wäre eine Tabelle mit 108 WORD Werten,
die ich mir aus einer Tabelle zur realen temperierten
Stimmung generiere. A= 440 Hz, C=523,251 Hz usw...
(Der Tracker aus dem ich konvertiere kann 108 Noten).

108 Noten sind ganz schön viel. Wie gesagt der Noisetracker hatte m.W. ursprünglich nur 3 Oktaven.
nunja, ich habe praktisch mit dem Noisetracker-Format angefangen,
und bin da nicht selten an die Grenzen des Tonumfangs gestoßen.
Wenn ich mir meine heutigen Trackerfiles angucke, dann sehe ich,
dass die ca. zu 20% durchaus mehr als 5 Oktaven beanspruchen.
108 würde sich ja nur auf die Tabellengröße auswirken.
Nun gut, man könnte sich vielleicht noch mit 63 Notenwerten abfinden,
da ich in diesem kompakten Format meist kurze Samples verwenden
will, die man sehr hoch gespielt schon fast gar nicht mehr wahrnehmen würde.
wobo hat geschrieben: Du hast vielleicht max. 100 Instrumente pro Song, d.h. Du hast pro Song auch max. 100 verschiedene C-3-Referenzen. Wenn Du jetzt beim Laden des Songs für jedes Instrument die jeweilige Notenfaktortabelle unter Berücksichtigung der spezifischen C-3-Frequenz erstellst, dann ginge das ganze in eine Tabelle von 100 * 108 = 10800 word, also knapp 20kb. Du müßtest also die Berechnungen nur immer beim Laden eines Songs ausführen und wärst dann auch mit der MixSpeed komplett variabel, d.h. Du müßtest diese nicht "herauskürzen"..
So eine Tabelle wäre schon praktisch, allerdings ist so viel Speicherbedarf schon schmerzlich irgendwie.
Ich könnte das ganze aber dynamisch anlegen, d.h. nur so viel im Speicher anlegen wieviel Samples
auch tatsächlich im File drin sind.
Ansonsten bleibt weiterhin die Frage, ob der Player seine vielleicht 10 Schrittweitenberechnungen
pro Puffer nicht doch hinbekommt. Notfalls sogar auch in Hochsprache, d.h. Pascal.
Ich gehe von Rechnern jenseits oder ab 486 aus.
mov ax, 13h
int 10h

while vorne_frei do vor;
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von freecrac »

Brueggi hat geschrieben:Hui - das nenn ich mal eine ausführliche Antwort. Da muss ich mich in Ehrfurcht verneigen - da schreibt ein echter Profi! Hut ab!
Als Profi würde ich mich nicht bezeichnen. Ich habe aber in den verschiedenen Newsgroups über Assembler mehrere Jahre hinweg einige erfahrene X86-Programmier kennengelernt die ihr Wissen teilen. Echte Profis haben dafür vermutlich nie wirklich Zeit. Auch gab es einmal über Optimierung eine Serie von Artikeln in der Presse(CT Magazin?) die ich aufmerksam verfolgt habe.
Die vielen Infos und Links müssen sich jetzt erstmal "setzen" sprich ich muss das "verdauen" - bei Fragen rühre ich mich wieder, allerdings dann im eigenen Thread :-)
Ich habe noch ein kleines Beispiel(ich mache es kurz).
So eine Befehlsreihenfolge:
mov ax, bx ; Schreibzugriff auf AX
add ax, cx ; erneuter Schreibzugriff auf AX
mov bx, dx ; so auch mit BX, CX und SI
add bx, si
mov cx, di
add cx, ax
mov si, bx
add si, cx

...ändert man bessser in so eine Befehlsreihenfolge(für Pentium+):

mov ax, bx ; Schreibzugriff auf AX
mov bx, dx ; Schreibzugriff auf BX
mov cx, di ; Schreibzugriff auf CX
mov si, bx ; Schreibzugriff auf SI + Lesezugriff auf BX
add ax, cx ; erneuter Schreibzugriff auf AX + Lesezugriff auf CX
add bx, si
add cx, ax
;-----------------> warten
add si, cx

Dieser letzte Befehl muss leider trotzdem warten bis der Befehl unmittelbar davor fertig ist.
Es sei denn wir können nachfolgende Befehle dazwischen plazieren, die nicht CX betreffen und auch nicht SI beschreiben.

Dirk
Antworten