Porting to PlayStation1 Guide

From Free Pascal wiki
Jump to navigationJump to search

Overview

This is a step by step tutorial based on the example of my FPC port to the PlayStation 1.

To see the port in action look at the PlayStation 1 compilation manual.

This Guide assumes we are working under Linux.

Forking and Merging

The first step is to create an GitLab account and Fork the compiler sources from

https://gitlab.com/freepascal.org/fpc/source

when done, create a new branch. I called it PS1.

Now when Your are done with the work, just do a merge request. The FPC Team will review Your changes and merge them into the main branch.

Preparing the Assembler and Linker

As we don't going to write our own internal Assembler and Linker we use GNU BinUtils. In our case we need the MIPSEL one.

Download a recent version from https://ftp.gnu.org/gnu/binutils/ . Then run shell commands:

./tar -xf <binutils.tar.gz>
./cd <binutils>
./mkdir build
./cd build
../configure --prefix=/usr/local/mipsel-unknown-elf --target=mipsel-unknown-elf --with-float=soft
make -j$(nproc)
sudo make install
export PATH="/usr/local/mipsel-unknown-elf/bin:$PATH"

Now you can go two ways:

1) Let the binaries as they are and always use the compiler option

-XP/usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-

this means we going to build the compiler with the option CROSSOPT="-XP/usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-"

2) Create symlinks

sudo ln -s /usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-as /usr/bin/mipsel-ps1-as
sudo ln -s /usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-ld /usr/bin/mipsel-ps1-ld
sudo ln -s /usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-objcopy /usr/bin/mipsel-ps1-objcopy

this means we don't need any extra compile options

Setup the make files

1) add the new build target to fpcmake:

- You need to extend the ./utils/fpcm/fpcmmain.pp:

insert the new Target in

type TOS=(..., ps1); (about line 100)

section.

don't forget to insert it also to OSStr(about line 135) and OSSufix(about line 144) constants.

add at the end of the table OSCpuPossible a new os/cpu entry (about line 207)

- now expand the ./packages/fpmkunit/src/fpmkunit.pp

TOS=(.., ps1) (about line 166)

and in the OSCPUSupported table like with the fpcmmain above


2) rebuild your native compiler:

make all
sudo make install

now fpcmake can generate make files.


3) add to ./compiler/Makefile.fpc:

NO_NATIVE_COMPILER_OS_LIST (about line 44)

your target system, ps1 this indicates, there is no native compiler for your target


4) time to rebuild all make files:

you have to run the fpcmake in all folders of the fpc sources after that to build the rtl make files you have to execute ./regenmakefiles.sh in ./rtl to build the packages you have to execute ./regenmakefiles.sh in ./packages

to simplify all those steps execute my script:

#!/bin/sh
set -e
FPCMAKE=/usr/local/bin/fpcmake
find . -name Makefile.fpc | xargs $FPCMAKE -Tall 
cd ./rtl
./regenmakefiles.sh
cd ./..
cd ./packages
./regenmakefiles.sh
cd ./..
cd ./utils/build/
$FPCMAKE -Tall -s -o Makefile.pkg Makefile.fpc
cd ./../..
cd ./packages/build/
$FPCMAKE -Tall -s -o Makefile.pkg Makefile.fpc
cd ./../..

Inform the compiler about the new Target

1) ppu signature

./compiler/utils/pputils/ppudump.pp (about line 252)

2) info screen of the compiler

./compiler/msg/errore.msg under option_help_pages (about line 4369)

BTW. this file:

I included an error message in the section about linking (about line 3224)

link_f_executable_too_big_exceeds_X_by_Y_bytes=09224_F_Executable image size is too big for $1 target (exceeds $2 by $3 bytes).
% The result File is too big

I mentioned this because You have to keep track of the message number. In this case it is 09224. You can display this message by invoking:

Message3(link_f_executable_too_big_exceeds_X_by_Y_bytes, 'PS1', '2097152', tostr(filesize(f) - $200000));

3) to get the set of systems what support the new target add in

./compiler/systems.pas (about line 430, in our case after systems_jvm) the ps1 target

./compiler/systems.inc: (about line 218) in type tsystem

4) register new linker in ./compiler/systems.inc (about line 339) in tlink

ld_ps1

5) set to your target CPU (in this case MIPS) the new platform:

./compiler/mips/cputarg.pas (about line 58):

{$ifndef NOTARGETPS1}
 ,t_ps1
{$endif}

6) set the Target CPU to MIPS1

./compiler/options.pas (about line 5555):

 system_mipsel_PS1:
  begin
	{ set default cpu type to MIPS1 with no FPU }
	if not option.CPUSetExplicitly then
		          init_settings.cputype:=cpu_mips1;
	if not option.OptCPUSetExplicitly then
		          init_settings.optimizecputype:=cpu_mips1;
	if not option.FPUSetExplicitly then
		          init_settings.fputype:=fpu_none;
 end;

pay attention that we going with no FPU for now. Later we will add the Software FPU support. We are using the the -Cfnone option.

So we comment out in ./compiler/options.pas (about line 4656):

def_system_macro('FPC_HAS_TYPE_DOUBLE');
def_system_macro('FPC_HAS_TYPE_SINGLE');
def_system_macro('FPC_INCLUDE_SOFTWARE_INT64_TO_DOUBLE');

7) As we support only external assembler (GNU)

./compiler/mips/cpugas.pas (about line 283)

add to as_MIPSEL_as_info the new target : system_mipsel_ps1

Create ./compiler/systems/i_ps1.pas

This unit implements support information structures for ps1.

unit i_ps1;

{$i fpcdefs.inc}

interface
uses systems;

const
       system_mipsel_ps1_info : tsysteminfo =
          (
            system       : system_mipsel_ps1;
            name         : 'PlayStation 1 for MIPSEL';
            shortname    : 'ps1';
            flags        : [tf_no_pic_supported, tf_smartlink_sections, tf_files_case_sensitive, tf_requires_proper_alignment, tf_emit_stklen];
            cpu          : cpu_mipsel;
            unit_env     : '';
            extradefines : '';
            exeext       : '';
            defext       : '.def';
            scriptext    : '.sh';
            smartext     : '.sl';
            unitext      : '.ppu';
            unitlibext   : '.ppl';
            asmext       : '.s';
            objext       : '.o';
            resext       : '.res';
            resobjext    : '.or';
            sharedlibext : '.so';
            staticlibext : '.a';
            staticlibprefix : 'libp';
            sharedlibprefix : 'lib';
            sharedClibext : '.so';
            staticClibext : '.a';
            staticClibprefix : 'lib';
            sharedClibprefix : 'lib';
            importlibprefix : 'libimp';
            importlibext : '.a';
            Cprefix      : '';
            newline      : #10;
            dirsep       : '/';
            assem        : as_gas;
            assemextern  : as_gas;
            link         : ld_none;
            linkextern   : ld_ps1;
            ar           : ar_gnu_ar;
            res          : res_elf;
            dbg          : dbg_none;
            script       : script_unix;
            endian       : endian_little;
            alignment    :
              (
                procalign       : 8;
                loopalign       : 8;
                jumpalign       : 8;
                jumpalignskipmax    : 8;
                coalescealign   : 0;
                coalescealignskipmax: 8;
                constalignmin   : 0;
                constalignmax   : 8;
                varalignmin     : 0;
                varalignmax     : 8;
                localalignmin   : 0;
                localalignmax   : 8;
                recordalignmin  : 0;
                recordalignmax  : 8;
                maxCrecordalign : 8
              );
            first_parm_offset : 0;
            stacksize    : 262144;
            stackalign   : 8;
            abi : abi_default;
            llvmdatalayout : 'e-p:32:32:32-i1:8:8-i8:8:32-i16:16:32-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-n32-S64';
          );

implementation

initialization

{$IFDEF CPUMIPSEL}
  {$IFDEF PS1}
    set_source_info(system_mipsel_ps1_info);
  {$ENDIF}
{$ENDIF}

end.



Create ./compiler/systems/t_ps1.pas

For now we going with the minimal implementation

unit t_ps1;

{$i fpcdefs.inc}

interface
uses link;

type
    TLinkerPS1=class(TExternalLinker)
    private
      Function  WriteResponseFile(isdll:boolean) : Boolean;
    public
      constructor Create;override;
      procedure SetDefaultInfo;override;
      function  MakeExecutable:boolean;override;
      procedure InitSysInitUnitName; override;
    end;


implementation
uses
    SysUtils,
    cutils,cfileutl,cclasses,
    verbose,systems,globtype,globals,
    fmodule,
    aasmbase,
    ogelf,owar,
    i_ps1
    ;


Constructor TLinkerPS1.Create;
begin
  Inherited Create;
end;


procedure TLinkerPS1.SetDefaultInfo;
begin
  with Info do
   begin
     ExeCmd[1]:= 'ld $OPT $RES';
   end;
end;


Function TLinkerPS1.WriteResponseFile(isdll: boolean): Boolean;
begin
  result:= True;
end;


function TLinkerPS1.MakeExecutable: boolean;
begin
  result:= true;
end;

procedure TLinkerPS1.InitSysInitUnitName;
begin
  sysinitunit:='si_prc';
end;



initialization
  RegisterLinker(ld_ps1, TLinkerPS1);
  RegisterTarget(system_mipsel_ps1_info);
end.


Create a minimal RTL

In this step we are using the minimal RTL setup.

./rtl/ps1/system.pas:

unit system;
interface

const
  MaxLongint  = $7fffffff;
  
type
  TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, tkSet,
    tkMethod, tkSString, tkLString, tkAString, tkWString, tkVariant, tkArray,
    tkRecord, tkInterface, tkClass, tkObject, tkWChar, tkBool, tkInt64, tkQWord,
    tkDynArray, tkInterfaceRaw, tkProcVar, tkUString, tkUChar, tkHelper, tkFile,
    tkClassRef, tkPointer);

{$I typedefs.inc}

{$I ../mips/setjumph.inc}

type
PExceptAddr = ^TExceptAddr;
  TExceptAddr = record 
    buf: Pjmp_buf;
    next: PExceptAddr;
    {$IFDEF CPU16}
    frametype: SmallInt;
    {$ELSE CPU16}
    frametype: LongInt;
    {$ENDIF CPU16}
  end;

  PGuid = ^TGuid;
  TGuid = packed record
    case Integer of
    1:
     (Data1: DWord;
      Data2: word;
      Data3: word;
      Data4: array [0 .. 7] of byte;
    );
    2:
     (D1: DWord;
      D2: word;
      D3: word;
      D4: array [0 .. 7] of byte;
    );
    3:
    ( { uuid fields according to RFC4122 }
      time_low: DWord; // The low field of the timestamp
      time_mid: word; // The middle field of the timestamp
      time_hi_and_version: word;
      // The high field of the timestamp multiplexed with the version number
      clock_seq_hi_and_reserved: byte;
      // The high field of the clock sequence multiplexed with the variant
      clock_seq_low: byte; // The low field of the clock sequence
      node: array [0 .. 5] of byte; // The spatially unique node identifier
    );
  end;

var
  ExitCode: hresult = 0; export name 'operatingsystem_result';

procedure fpc_initializeunits; compilerproc;

procedure fpc_do_exit; compilerproc;

implementation

{$I ../mips/setjump.inc}


procedure fpc_initializeunits;
begin
end;

procedure fpc_do_exit;
begin
end;

end.

./rtl/ps1/typedefs.inc:

Type
  { The compiler has all integer types defined internally. Here
    we define only aliases }
  DWord    = LongWord;
  Cardinal = LongWord;
  Integer  = SmallInt;
  UInt64   = QWord;

  SizeInt = Longint;
  SizeUInt = DWord;
  PtrInt = Longint;
  PtrUInt = DWord;
  ValSInt = Longint;
  ValUInt = Cardinal;
  CodePointer = Pointer;
  CodePtrInt = PtrInt;
  CodePtrUInt = PtrUInt;
  TExitCode = Longint;

  { NativeInt and NativeUInt are Delphi compatibility types. Even though Delphi
    has IntPtr and UIntPtr, the Delphi documentation for NativeInt states that
    'The size of NativeInt is equivalent to the size of the pointer on the
    current platform'. Because of the misleading names, these types shouldn't be
    used in the FPC RTL. Note that on i8086 their size changes between 16-bit
    and 32-bit according to the memory model, so they're not really a 'native
    int' type there at all. }
  NativeInt  = Type PtrInt;
  NativeUInt = Type PtrUInt;

  Int8    = ShortInt;
  Int16   = SmallInt;
  Int32   = Longint;
  IntPtr  = PtrInt;
  UInt8   = Byte;
  UInt16  = Word;
  UInt32  = Cardinal;
  UIntPtr = PtrUInt;

{ Zero - terminated strings }

{$IF DECLARED(AnsiChar)}
// Compiler defines AnsiChar and WideChar, not AnsiChar
{$IFDEF UNICODERTL}
  Char = WideChar;
{$ElSE}
  Char = AnsiChar;
{$ENDIF}

{$ELSE}
  // Compiler defines Char, we make AnsiChar an alias
  AnsiChar = char;
{$ENDIF}

  PChar               = ^Char;
  PPChar              = ^PChar;
  PPPChar             = ^PPChar;

  TAnsiChar           = AnsiChar;
  PAnsiChar           = ^AnsiChar;
  PPAnsiChar          = ^PAnsiChar;
  PPPAnsiChar         = ^PPAnsiChar;

  UTF8Char = AnsiChar;
  PUTF8Char = PAnsiChar;

  UCS4Char            = 0..$10ffff;
  PUCS4Char           = ^UCS4Char;
{$ifdef CPU16}
  TUCS4CharArray      = array[0..32767 div sizeof(UCS4Char)-1] of UCS4Char;
{$else CPU16}
  TUCS4CharArray      = array[0..$effffff] of UCS4Char;
{$endif CPU16}
  PUCS4CharArray      = ^TUCS4CharArray;
{$ifdef FPC_HAS_FEATURE_DYNARRAYS}
  UCS4String          = array of UCS4Char;
{$endif}


Comp = type Int64;


PCurrency           = ^Currency;

  UTF8String          = type ansistring;
  PUTF8String         = ^UTF8String;

  RawByteString       = ansistring;

  HRESULT             = type Longint;
  TError               = type Longint;

  PSmallInt           = ^Smallint;
  PShortInt           = ^Shortint;
  PInteger            = ^Integer;
  PByte               = ^Byte;
  PWord               = ^word;
  PDWord              = ^DWord;
  PLongWord           = ^LongWord;
  PLongint            = ^Longint;
  PCardinal           = ^Cardinal;
  PQWord              = ^QWord;
  PInt64              = ^Int64;
  PUInt64             = ^UInt64;
  PPtrInt             = ^PtrInt;
  PPtrUInt            = ^PtrUInt;
  PSizeInt            = ^SizeInt;
  PSizeUInt           = ^SizeUInt;

  PPByte              = ^PByte;
  PPLongint           = ^PLongint;

  PPointer            = ^Pointer;
  PPPointer           = ^PPointer;

  PCodePointer        = ^CodePointer;
  PPCodePointer       = ^PCodePointer;

  PBoolean            = ^Boolean;

{$IFNDEF VER3_0}
  PBoolean8           = ^Boolean8;
{$ENDIF VER3_0}
  PBoolean16          = ^Boolean16;
  PBoolean32          = ^Boolean32;
  PBoolean64          = ^Boolean64;

  PByteBool           = ^ByteBool;
  PWordBool           = ^WordBool;
  PLongBool           = ^LongBool;
  PQWordBool          = ^QWordBool;

  PNativeInt 	      = ^NativeInt;
  PNativeUInt	      = ^NativeUint;
  PInt8   	      = PShortInt;
  PInt16  	      = PSmallint;
  PInt32  	      = PLongint;
  PIntPtr 	      = PPtrInt;
  PUInt8  	      = PByte;
  PUInt16 	      = PWord;
  PUInt32 	      = PDWord;
  PUintPtr	      = PPtrUInt;

  PShortString        = ^ShortString;
  PAnsiString         = ^AnsiString;
  PRawByteString      = ^RawByteString;

{$ifndef FPUNONE}
  PDate               = ^TDateTime;
  PDateTime           = ^TDateTime;
{$endif}
  PError              = ^TError;

{$ifdef FPC_HAS_FEATURE_VARIANTS}
  PVariant            = ^Variant;
  POleVariant         = ^OleVariant;
{$endif FPC_HAS_FEATURE_VARIANTS}

  PWideChar           = ^WideChar;
  PPWideChar          = ^PWideChar;
  PPPWideChar         = ^PPWideChar;
  WChar               = Widechar;
  UCS2Char            = WideChar;
  PUCS2Char           = PWideChar;
  PWideString         = ^WideString;

  UnicodeChar         = WideChar;
  PUnicodeChar        = ^UnicodeChar;
  PUnicodeString      = ^UnicodeString;

  PMarshaledString    = ^PWideChar;
  PMarshaledAString   = ^PAnsiChar;

  MarshaledString     = PWideChar;
  MarshaledAString    = PAnsiChar;

  TSystemCodePage     = Word;

  TFileTextRecChar    = {$if defined(FPC_ANSI_TEXTFILEREC) or not(defined(FPC_HAS_FEATURE_WIDESTRINGS))}AnsiChar{$else}UnicodeChar{$endif};
  PFileTextRecChar    = ^TFileTextRecChar;

  TTextLineBreakStyle = (tlbsLF,tlbsCRLF,tlbsCR);

{ opaque data type and related opaque pointer }
  TOpaqueData = record end;
  POpaqueData = ^TOpaqueData;
  OpaquePointer = type POpaqueData;

{ procedure type }
  TProcedure  = Procedure;

./rtl/ps1/objpas.pp:

{$Mode OBJFPC}
{$I-,S-}
unit objpas;

  interface

    { first, in object pascal, the integer type must be redefined }
    const
       MaxInt  = MaxLongint;
    type
       Integer  = longint;

  implementation

end.

./rtl/ps1/si_prc.pp:

{$SMARTLINK OFF}
unit si_prc;
interface

implementation

procedure PascalMain; external name 'PASCALMAIN';

function _FPC_proc_start: longint; cdecl; public name '_start';
begin
	PascalMain;
end; 

begin
end.

Build and Compile the first time

1) Now we can build the compiler itself:

make compiler_cycle CPU_TARGET=mipsel OS_TARGET=ps1 OPT="-O- -CR -glv" CROSSOPT="-O- -g-" ALLOW_WARNINGS=1

2) Build the minimal RTL:

./compiler/ppcrossmipsel ./rtl/ps1/system.pp -O- -Tps1 -Fi./rtl/mipsel -Fi./rtl/inc -Fi./rtl/ps1 -a -XP/usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf- -Cfnone
./compiler/ppcrossmipsel ./rtl/inc/fpintres.pp -O- -Tps1 -Fu./rtl/ps1 -Fi./rtl/mipsel -Fi./rtl/inc -Fi./rtl/ps1 -a -XP/usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf- -Cfnone 

3) Create the first test.pas:

begin
 asm
	li $a1, 123
 end;
end.

4) Compile it:

./compiler/ppcrossmipsel test.pas -Cn -O- -Fu./rtl/ps1 -Fu./rtl/inc -Tps1 -Cfnone -a -XP/usr/local/mipsel-unknown-elf/bin/mipsel-unknown-elf-

Congratulations!


Building the full RTL

1) Add requered RTL units to the makefile:

./rtl/ps1/Makefile.fpc

...

units=$(SYSTEMUNIT) $(OBJPASUNIT) $(FPINTRESUNIT) $(ISO7185UNIT) $(CTYPESUNIT) \
  $(SYSCONSTUNIT) $(UUCHARUNIT) $(STRINGSUNIT) \
  libstd libetc libgte libgpu libapi libcd libcomb libgs libsnd libspu libds libgun libmcrd libtap libpad libmcx libpress libsn libmcgui libhmd \
  si_prc

...

# PSY-Q-SDK units
libstd$(PPUEXT) : psy-q-sdk/libstd.pas $(OBJPASUNIT)$(PPUEXT) $(SYSTEMUNIT)$(PPUEXT)
        $(COMPILER) $<

libetc$(PPUEXT) : psy-q-sdk/libetc.pas $(OBJPASUNIT)$(PPUEXT) $(SYSTEMUNIT)$(PPUEXT)
        $(COMPILER) $<

libgte$(PPUEXT) : psy-q-sdk/libgte.pas $(OBJPASUNIT)$(PPUEXT) $(SYSTEMUNIT)$(PPUEXT)
        $(COMPILER) $<
...


# Loaders
si_prc$(PPUEXT) : $(SYSTEMUNIT)$(PPUEXT)
        $(COMPILER) si_prc.pp

the listed units gonna be compiled an put automatically into the units folder of the fpc installation.