Workshops / Hintergrundberichte

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 <<
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ß.

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:

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.

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: