CONTENTS | PREV | NEXT

2. Programmierung des PowerPC mit WarpOS (von Steffen Häuser)

»Seit dem Erscheinen der PowerUP-Karten erschienen auf dem Aminet bereits 
einige Programme, die den PPC unterstützen. Es gibt jedoch zahlreiche 
Autoren, die ihre Programme noch nicht mit PPC-Support versehen, obwohl 
dies durch die Verwendung von StormC und WarpOS eigentlich sehr einfach 
ist. Dieser Artikel beschreibt, wie man ein Programm mit PPC-Support 
versieht.

Dabei wäre es sogar durchaus denkbar, ein Programm jetzt für den 68k zu 
entwickeln - da weder PPC-Karte noch PPC-Compiler verfügbar -, jedoch mit 
Blick auf eine leichte PPC-Portierbarkeit, und dann später die wenigen noch 
nötigen Schritte, um eine PPC-Version zu generieren.
Dieses Vorgehen dürfte vor allem für Spiele-Entwickler interessant sein. Im 
Zweifelsfall, falls ein Rechner gesucht wird, auf dem ein bereits 
fertiggestelltes PPC-Programm compiliert werden kann, ohne gleich eine PPC-
Karte kaufen zu müssen, stehe ich auch gerne zum Recompilieren zur 
Verfügung (obwohl es sicher praktikabler wäre, einen eigenen Rechner hier 
zu haben). Das Programmtesting kann im Übrigen prinzipiell auch mit der 
68k-Version erfolgen, da eigentlich keine größeren Änderungen erforderlich 
sind.

Prinzipiell erfolgt die Anpassung in folgenden Phasen (um die Entwicklung 
zu vereinfachen, ist empfohlen, dies schon während der Entwicklung der 68k- 
Version durchzuführen):

A) Umschreiben aller 68k-Assembler-Teile nach C oder C++ oder PPC-ASM
B) ANSI/StormC-Anpassung
C) PPC-Anpassung
D) Kontextswitch-Optimierung
E) Weitere Anpassung

Im Gegensatz zu einer PPC-Anpassung auf ppc.library hat man bei WarpOS nach 
Schritt A) eigentlich schon fast "gewonnen", die letztlichen PPC-
Anpassungen sind (zumindest bei Sourcen ohne Assembler-Optimierung) 
minimal. Ich verzichte darauf, auf Teil A) einzugehen, wer unbedingt 
Assembler-Projekte auf PPC portieren möchte, sollte E-Mail-Kontakt mit mir 
aufnehmen, da es evtl. ein besser geeignetes Verfahren der Portierung als 
das hier beschriebene gibt (und zudem vor einer Riesenarbeit geprüft werden 
sollte, ob es sich überhaupt lohnt).

Ich empfehle, zumindest Schritt B) bereits während der Entwicklung der 68k- 
Version vorzunehmen, um später nicht mehr durch den ganzen Source
durchgehen zu müssen.

B. StormC - ein strenger ANSI-Compiler
--------------------------------------

Der wildeste Part bei der Anpassung eines PPC-Programms ist nicht etwa die 
Anpassung von 68k auf PPC, sondern von SAS/C oder GNU C auf StormC.
StormC ist ein strenger ANSI-Compiler, daher sind Standard-C-Funktionen, 
die im ANSI-Standard nicht verfügbar sind, nicht zulässig. Einige der
Funktionen können über die offiziell noch nicht releaste UnixLib emuliert
werden.

Die Liste der nicht erlaubten SAS/C-Funktionen wäre (keine dieser 
Funktionen ist im ANSI-Standard enthalten...):

astcsma    isascii    iscsym     iscsymf    toascii    scdir      stcpm
stcpma     stcsma     stccpy     stpcpy     stcis      stcisn     stclen
stpbrk     stpchr     stpchrn    strcmpi    strnset
strset     stcarg     stpsym     stptok     stpblk     strbpl     strdup
strins     strmid     stcd_i     stcd_l     ecvt       fcvt       gcvt
stch_i     stch_l     stci_d     stci_h     stci_o     stcl_d     stcl_h
stcl_o     stco_i     stco_l     stcu_d     stcul_d    toascii    stpdate
stptime    __datecvt  __timecvt  utpack     utunpk     cot        iabs
max        min        pow2       __emit     getreg     putreg     geta
isatty     ovlyMgr    dqsort     fqsort     lqsort     sqsort     strsrt
tqsort     drand48    erand48    jrand8     lcong48    lrand48    mrand8
nrand48    seed48     srand48    __autoopenfail        chkabort   Chk_Abort
_CXBRK     __exit     onexit     _XCEXIT    forkl      forkv      onbreak
wait       waitm      bldmem     rstmem     sizmem     chkml      getmem
getml      halloc     lsbrk      sbrk       _MemCleanup rbrk      rlsmem
rlsml      memccpy    movmem     repmem     setmem     swmem      except
__matherr  poserr     datecmp    timer      __tzset    getch      fgetchar
fputchar   _dread     _dwrite    read       write      clrerr     close
_dclose    fcloseall  creat      _dcreat    _dcreatx   fdopen     fileno
fmode      iomode     open       _dopen     flushall   mkstemp    mktemp
setnbf     _dseek     lseek      tell       access     chkufb     chmod
fstat      getfa      getft      stat       stcgfe     stcgfn     stcgfp
strmfe     strmfn     strmfp     strsfn     unlink     argopt     chgclk
dos_packet getclk     getasn     getdfs     putenv     rawcon    stackavail
stacksize  stackused  chdir      closedir   dfind      dnext      findpath
getcd      getcwd     getfnl     getpath    mkdir      opendir    readdir
rmdir      seekdir    rewinddir  telldir    readlocale scr_beep   scr_bs
scr_cdelete scr_cinsert scr_clear scr_cr   scr_curs  scr_cursrt  scr_cursup
scr_eol    scl_home   scr_ldelete scr_lf    scr_linsert scr_tab   _CXFERR
_CXOVF     _EPILOG    _PROLOG

Die Liste sieht lang aus, aber man muß bedenken, daß die meisten 
aufgelisteten Funktionen "extrem exotische Funktionen" sind, die vermutlich 
vielen Programmierern gar nicht bekannt sind. Im ANSI-Standard sind sie 
jedenfalls nicht enthalten.

Nach den Ersetzungen (die wichtigsten Ersetzungen laufen auf das Ersetzen 
von open/close/read/write hinaus...) gibt es noch einen anderen Aspekt von 
ANSI zu bedenken:

Schreibweisen wie:

char *string=malloc(300);

die bei SAS/C nur eine WARNING produzieren, produzieren bei strengen ANSI-
C-Compilern einen Fehler. Derartiger Code sollte so aussehen:

char *string=(char *)malloc(300);

ANSI C verlangt eine STRENGE TYPISIERUNG. Dies gilt übrigens auch für 
Zeiger auf Funktionen. Ein guter "Trick", um einen nicht ANSI-konformen 
Source rasch zu konvertieren, ist:

1. Einfach mal durchcompilieren und dabei auf die Warnings/Fehler achten.
2. Alles, was nach Zeiger aussieht, und nicht streng typisiert ist, auf
   void * casten. Alles, was nicht nach Zeiger aussieht, und falsch         
   typisiert ist, je nach Anwendung auf int, long oder double casten.       
   Zeiger auf Funktionen auf void * casten, z.B.:

   void *funktion=(void *)meine_funktion;

Programmierer, die keinen StormC besitzen, können das "Test-Compilieren" 
auch mit SAS/C im "STRICT_ANSI"-Modus machen, der meines Wissens ziemlich 
ähnlich wie StormC reagiert.

Zudem sollte man noch jedes Auftreten von K&R-Syntax durch die normale C- 
Syntax ersetzen, z.B.

void main(argv,argc)
int argv;
char **argc;

durch

void main(int argv,char **argc);

ersetzen, da der PPC-Compiler keine K&R-Syntax mag.

Falls ein Source - wie etwa der Doom-Source - relativ häufig Funktionen aus 
der Unix-Welt verwendet, so hat es sich als hilfreich erwiesen, eine auf 
StormC umgeschriebene Version der AmiTCP/IP-Includes zu verwenden, so daß 
Datentypen wie z.B. dev_t zur Verfügung stehen. Ein Source, der von Anfang 
an auf ANSI-Kompatibilität getrimmt ist, sollte solche Typen jedoch nicht 
nötig haben. Wer dies dennoch nötig hat, soll einfach mal bei mir mit Bitte
um Hilfestellung anfragen (Kontaktadresse siehe unten).

Ein letzter Aspekt der Compiler-Unterschiede wären spezifische Unterschiede
zwischen SAS/C und StormC. Ich habe mich hierbei auf die Beschreibung 
"normaler" Sources beschränkt, wer gerne PPC-Shared Libraries machen 
möchte, soll mich kontaktieren, und ich werde auch hier beschreiben, was 
die Unterschiede zwischen SAS/C und StormC sind.

Einige Schlüsselworte von SAS/C entfallen einfach, indem man sie auf "leer"
definiert:

#define __stdargs
#define __regargs
#define __asm

__far sollte durch das Schlüsselwort FAR ersetzt werden, __inline durch 
inline, in diesen Dingen orientiert sich StormC ebenfalls mehr an ANSI. 
__chip, __fast und __interrupt funktionieren nicht so wie bei SAS/GNU, hier 
muß man den Weg über die entsprechenden OS-Funktionen gehen.

Registerparameter funktionieren (bis auf das Weglassen des Schlüsselwortes 
__asm) genau wie beim SAS/C, jedoch sollte man für eine PPC-Version darauf 
verzichten, da der PPC ja z.B. kein Register d0 hat (das ist ein 68k-
Register). Man kann natürlich

register int a;

schreiben, das funktioniert. Der PPC sucht dann selbst nach einem 
geeigneten Register, bzw. der Compiler tut das.

C. PPC-Anpassung
----------------

So, den wildesten Part haben wir nun, es folgen eigentlich nur noch 
Kleinigkeiten.
Da die eigentliche PPC-Anpassung ja noch gar nicht erfolgte (bis auf die 
Kleinigkeit mit den Registern...), folgt, daß man mit WarpOS die 68k- und 
die PPC-Version sehr leicht parallel entwickeln kann.

Der erste wesentliche Unterschied betrifft die OS-Includes. Ein

#include <proto/exec.h>

oder

#include <clib/exec_protos.h>
#include <pragma/exec_protos.h>

schreibt sich unter PPC als:

#include <clib/exec_protos.h>

Die Pragmas entfallen komplett, und man sollte auch keine Protos includen.

Für die Parallelentwicklung sieht das Ganze so aus:

#include <clib/exec_protos.h>
#ifndef __PPC__
#include <pragma/exec_lib.h>
#endif

Der Define __PPC__ ist immer entsprechend der Compiler-Optionen gesetzt.

Ein weiterer Unterschied fällt bei der Verwendung von Subtasks an. In 
dieser einfachen Variante der PPC-Portierung verwenden wir ja keine Mixed 
Binaries (über die Mixed Binary schreibe ich vielleicht später noch einen 
anderen Artikel), daher sind alle Tasks unseres Programms PPC-Native. 
Sollte man nun einen PPC-Native-Subtask starten, so muß dieser natürlich 
mit CreateTaskPPC() der powerpc.library gestartet werden, nicht etwa mit 
CreateTask(), weil das 68k-AmigaOS natürlich keine PPC-Tasks starten kann. 
Der Aufruf entspricht jedoch bei praktisch allen "Exec-ähnlichen" 
Funktionen der powerpc.library 1:1 dem Vorgehen unter dem 68k-AmigaOS. Eine 
Alternative wäre natürlich die Verwendung eines Mixed Binary, aber das 
sollte man vermeiden, da es die Performance senkt.

Ein weiterer Unterschied betrifft noch die Tag-List-Schreibweisen mancher 
OS-Funktionen. So beherrscht der PPC-Compiler zwar OpenScreenTagList, aber 
nicht OpenScreenTags. Man muß den Source also entsprechend anpassen.

Ein weiterer Unterschied betrifft die Funktion BeginIO der alib_protos.h. 
Diese Funktion ist unter PPC nur über ein (im Beispiel für das 
audio.device)

#include <libraries/powerpc.h>
#include <ppcamiga.h>

void BeginIOAudioPPC(struct IORequest *arg1)
{
        extern struct Library *AudioBase;
        ULONG regs[16];
        regs[9] = (ULONG) arg1;
        __CallLibrary(AudioBase,-30,regs);
}

verfügbar. Wie man sieht, muß man unter PPC grundsätzlich die LibBase des 
Devices auslesen. Ein Beispielaufruf könnte wie folgt aussehen (Code-
Beispiel aus ZhaDoomPPC...):

  AudioBase = (struct Library *)audio_io->ioa_Request.io_Device;
  c = &channel_info[cnum];
  c->audio_io->ioa_Request.io_Command = CMD_WRITE;
  c->audio_io->ioa_Request.io_Flags = ADIOF_PERVOL;
  c->audio_io->ioa_Data = &chip_cache_info[cache_chip_data 
(id)].chip_data[8];
  c->audio_io->ioa_Length = lengths[id] - 8;
  c->audio_io->ioa_Period = period_table[pitch];
  c->audio_io->ioa_Volume = vol << 2;
  c->audio_io->ioa_Cycles = 1;
#ifdef PPC
  BeginIOAudioPPC((struct IORequest *)c->audio_io);
#else
  BeginIO ((struct IORequest *)c->audio_io);
#endif

Einige Leser fragen sich vielleicht inzwischen, wo der berühmte 
Kontextswitch bleibt. Ganz einfach: Er bleibt. StormC verfügt nämlich über 
das Feature des "vollautomatischen Kontextswitches", was bedeutet, der User 
muß sich um nichts mehr kümmern, da dies bereits der Compiler für ihn 
erledigt. Es müssen lediglich Stub-Dateien oder Defines für die per 
Kontextswitch aufzurufenden Funktionen vorliegen. Diese sind für alle 
AmigaOS-Funktionen sowie für die 68k-Funktionen der rtgmaster.library (die 
jedoch auch PPC-Native-Funktionen besitzt) bereits in die ppcamiga.lib 
integriert. Für Funktionen, die nicht integriert sind, generiert man die 
Stubs mittels

genppcstub mylib_protos.h mylib.fd VERBOSE

wobei die Proto- und die FD-Datei vorliegen müssen. Das Ergebnis wird dann
einfach ins Projekt integriert (es handelt sich um einen C-Source). Im 
Falle eines "Mixed Binary" geht übrigens der Wechsel zwischen 68k und PPC 
wieder automatisch, ohne daß etwas Zusätzliches gemacht werden muß.

D) Kontextswitch-Optimierung
----------------------------

Unter WarpOS braucht ein Kontextswitch etwa 0.5 Millisekunden (hängt aber 
auch von der verwendeten PPC-Karte ab, 0.5 Millisekunden braucht es bei 
einer 200-MHz-Karte). Es sollte vermieden werde, "viele Kontextswitches pro 
Sekunde" zu erzeugen.

Beispiele, was zu vermeiden ist:

- Byteweises Einladen von Files mit fgetc (statt dessen mit fread in einen
  Fast-RAM-Buffer einladen, und dann nachbearbeiten)
- Pixelweises Darstellen auf dem Bildschirm (statt dessen in einen Fast-    
  RAM-Buffer zeichnen und den dann mit einem Befehl zeichnen)
- In häufig pro Sekunde aufgerufenen Schleifen viele OS-Calls


Die Grafik kann auch mittels der PPC-Version der rtgmaster.library komplett
PPC-Native abgearbeitet werden.

Ein Beispiel der Effekte von Kontextswitches:

ZhaDoomPPC auf einem 150-MHz-Rechner ohne Sound: 36 fps
ZhaDoomPPC auf einem 150-MHz-Rechner mit Sound : 32 fps

Der Sound bewirkt bei ZhaDoomPPC derzeit 9 Kontextswitches. Eine Lösung des
Problems wäre die Auslagerung der kompletten Sound-Funktion in einen 68k-
Part, den man dann über einen manuellen Aufruf des Kontextswitchers (siehe 
Dokumentation der powerpc.library) oder über ein Mixed Binary aufruft. 
Damit würde der Mehraufwand auf einen Kontextswitch reduziert. Im Falle von 
ZhaDoomPPC war das leider nicht so einfach machbar, da die Kontextswitches 
in verschiedenen Funktionen lagen, und so genau habe ich mir das noch nicht 
angeschaut.

Es sind zudem noch Optimierungen durch Ausnutzung der BAT-Register des PPC 
möglich, hier möchte ich jedoch einfach auf die Dokumentation von WarpOS 
verweisen.

E) Weitere Anpassung
--------------------

Es ist natürlich möglich, Teile des Codes in PPC-Assembler zu übersetzen, 
aber im Normalfall ist das eigentlich gar nicht nötig. Der PPC ist 
eigentlich schnell genug, um (fast) alles in C oder C++ zu machen 
(rtgmaster PPC hat trotzdem einige Assembler-Parts, da dies bei einem 
Grafiktreiber Sinn macht).

In einigen Newsgruppen wurde diskutiert, Programmteile parallel auf dem 68k 
und dem PPC laufen zu lassen. Dies würde man mittels des Message-Systems 
von WarpOS implementieren (Stichwort AllocXMsg in der powerpc.library- 
Dokumentation), man sollte es jedoch aus zwei Gründen vermeiden:

1) Ein solches Programm würde bei (zukünftigen) PPC-only Maschinen nicht in
idealster Weise profitieren, da Teile immer noch als 68k-Code abgearbeitet
würden. Und ich denke, auf lange Sicht werden solche Maschinen kommen.

2) Die Hardware ist schlichtweg nicht für den Multiprozessor-Betrieb 
geeignet, ganz gleich, wie man die Software anstellt. Ich werde hierauf 
nicht näher eingehen, ist in den Newsgruppen genug zu Tode diskutiert 
worden.

Solange die Tasks nicht miteinander kommunizieren, dürfte es 
unproblematisch sein, aber dann machen sie auch vermutlich nicht viel Sinn. 
Dies ist ein prinzipielles Problem der Hardware.

Aus oben genannten Gründen kann ich nur empfehlen, "synchron" zu bleiben. 
Wobei zukünftige Kompatibilität bei optimaler Geschwindigkeit da wohl 
beinahe das Wichtigere ist.

Kontaktadresse bei Rückfragen:
------------------------------

Steffen Häuser
Limburgstr. 127
73265 Dettingen/Teck
Tel. 07021-51787
email: MagicSN@Birdland.es.bawue.de

Informationen zur Person:

Informatik-Student.

Haupt-Aktivitäten im Amiga-Bereich:

- rtgmaster.library
- ZhaDoomPPC (Ein PPC-Doom-Port unter WarpOS)
- WarpView (Ein Image-Viewer für PPC unter WarpOS und 68k)
- WarpAMP (ein Port des AMP-MPEG-3-Players für WarpOS)
- Crystal3D (ein Port einer Spiele-artigen 3D-Engine)
- Seit kurzem Coder bei der Demo-Gruppe "Giants"
- ehemals Autor der "Amiga Texturemapping FAQ"
- "Berater" in Grafikkarten/PPC-Fragen bei einigen Amiga-Spiele-Firmen
- ppctut.guide (Einführung in PPC-Assembler-Programmierung)«