{
    Copyright (c) 2005-2022 by Free Pascal Compiler team

    This unit implements support import, export, link routines
    for the Amstrad CPC Target

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 ****************************************************************************
}
unit t_amstradcpc;

{$i fpcdefs.inc}

interface


implementation

    uses
       SysUtils,
       cutils,cfileutl,cclasses,
       globtype,globals,systems,verbose,comphook,cscript,fmodule,i_amstradcpc,link,
       cpuinfo,ogbase,ogrel,owar;

    const
       DefaultOrigin=23800;

    type

       { sdld - the sdld linker from the SDCC project ( http://sdcc.sourceforge.net/ ) }
       { vlink - the vlink linker by Frank Wille (http://sun.hasenbraten.de/vlink/ ) }

       TLinkerAmstradCPC=class(texternallinker)
       private
          FOrigin: Word;
          Function  WriteResponseFile_Sdld: Boolean;
          Function  WriteResponseFile_Vlink: Boolean;

          procedure SetDefaultInfo_Sdld;
          procedure SetDefaultInfo_Vlink;
          function  MakeExecutable_Sdld: boolean;
          function  MakeExecutable_Vlink: boolean;
       public
          procedure SetDefaultInfo; override;
          function  MakeExecutable: boolean; override;
          procedure InitSysInitUnitName; override;

          function postprocessexecutable(const fn : string;isdll:boolean): boolean;
       end;

       { TInternalLinkerAmstradCPC }

       TInternalLinkerAmstradCPC=class(tinternallinker)
       private
         FOrigin: Word;
       protected
         procedure DefaultLinkScript;override;
       public
         constructor create;override;
         procedure InitSysInitUnitName;override;
         function MakeExecutable: boolean; override;
         function postprocessexecutable(const fn : string): boolean;
       end;


{*****************************************************************************
                          TLinkerAmstradCPC
*****************************************************************************}

function TLinkerAmstradCPC.WriteResponseFile_Sdld: Boolean;
  Var
    linkres  : TLinkRes;
    s        : TCmdStr;
    prtobj: string[80];
  begin
    result:=False;
    prtobj:='prt0';

    { Open link.res file }
    LinkRes:=TLinkRes.Create(outputexedir+Info.ResName,true);

    { Write the origin (i.e. the program load address) }
    LinkRes.Add('-b _CODE='+tostr(FOrigin));

    if not (target_info.system in systems_internal_sysinit) and (prtobj <> '') then
      begin
        s:=FindObjectFile(prtobj,'',false);
        LinkRes.AddFileName(s);
      end;

    while not ObjectFiles.Empty do
     begin
      s:=ObjectFiles.GetFirst;
      if s<>'' then
       begin
        if not(cs_link_on_target in current_settings.globalswitches) then
         s:=FindObjectFile(s,'',false);
        LinkRes.AddFileName((maybequoted(s)));
       end;
     end;

    { Write staticlibraries }
    if not StaticLibFiles.Empty then
     begin
      while not StaticLibFiles.Empty do
       begin
        S:=StaticLibFiles.GetFirst;
        LinkRes.Add('-l'+maybequoted(s));
       end;
     end;

    { Write and Close response }
    linkres.writetodisk;
    linkres.free;

    result:=True;
  end;

function TLinkerAmstradCPC.WriteResponseFile_Vlink: Boolean;
  Var
    linkres  : TLinkRes;
    s        : TCmdStr;
    prtobj: string[80];
  begin
    result:=false;
    prtobj:='prt0';

    { Open link.res file }
    LinkRes:=TLinkRes.Create(outputexedir+Info.ResName,true);
    if (source_info.dirsep <> '/') then
      LinkRes.fForceUseForwardSlash:=true;

    LinkRes.Add('INPUT (');

    if not (target_info.system in systems_internal_sysinit) and (prtobj <> '') then
      begin
        s:=FindObjectFile(prtobj,'',false);
        LinkRes.AddFileName(maybequoted(s));
      end;

    while not ObjectFiles.Empty do
      begin
        s:=ObjectFiles.GetFirst;
        if s<>'' then
          begin
            s:=FindObjectFile(s,'',false);
            LinkRes.AddFileName(maybequoted(s));
          end;
      end;

    while not StaticLibFiles.Empty do
      begin
        S:=StaticLibFiles.GetFirst;
        LinkRes.AddFileName(maybequoted(s));
      end;

    LinkRes.Add(')');

    with LinkRes do
      begin
        Add('');
        Add('SECTIONS');
        Add('{');
        Add('  . = 0x'+hexstr(FOrigin,4)+';');
        Add('  .text : { *(.text .text.* _CODE _CODE.* ) }');
        Add('  .data : { *(.data .data.* .rodata .rodata.* .fpc.* ) }');
        Add('  .bss  : { *(_BSS _BSS.*) *(.bss .bss.*) *(_BSSEND _BSSEND.*) *(_HEAP _HEAP.*) *(.stack .stack.*) *(_STACK _STACK.*) }');
        Add('}');
      end;

    { Write and Close response }
    linkres.writetodisk;
    linkres.free;

    result:=true;
  end;

procedure TLinkerAmstradCPC.SetDefaultInfo_Sdld;
  const
    ExeName='sdldz80';
  begin
    if ImageBaseSetExplicity then
      FOrigin:=ImageBase
    else
      FOrigin:=DefaultOrigin;
    with Info do
     begin
       ExeCmd[1]:=ExeName+' -n $OPT -i $MAP $EXE -f $RES'
     end;
  end;

procedure TLinkerAmstradCPC.SetDefaultInfo_Vlink;
  const
    ExeName='vlink';
  begin
    if ImageBaseSetExplicity then
      FOrigin:=ImageBase
    else
      FOrigin:=DefaultOrigin;
    with Info do
     begin
       ExeCmd[1]:=ExeName+' -bihex $GCSECTIONS -e $STARTSYMBOL $STRIP $OPT $MAP -o $EXE -T $RES'
     end;
  end;

procedure TLinkerAmstradCPC.SetDefaultInfo;
  begin
    if not (cs_link_vlink in current_settings.globalswitches) then
      SetDefaultInfo_Sdld
    else
      SetDefaultInfo_Vlink;
  end;

function TLinkerAmstradCPC.MakeExecutable_Sdld: boolean;
  var
    binstr,
    cmdstr,
    mapstr: TCmdStr;
    success : boolean;
    StaticStr,
    //GCSectionsStr,
    DynLinkStr,
    StripStr,
    FixedExeFileName: string;
  begin
    { for future use }
    StaticStr:='';
    StripStr:='';
    mapstr:='';
    DynLinkStr:='';
    FixedExeFileName:=maybequoted(ScriptFixFileName(ChangeFileExt(current_module.exefilename,'.ihx')));

    if (cs_link_map in current_settings.globalswitches) then
     mapstr:='-mw';

  { Write used files and libraries }
    WriteResponseFile_Sdld();

  { Call linker }
    SplitBinCmd(Info.ExeCmd[1],binstr,cmdstr);
    Replace(cmdstr,'$OPT',Info.ExtraOptions);

    Replace(cmdstr,'$EXE',FixedExeFileName);
    Replace(cmdstr,'$RES',(maybequoted(ScriptFixFileName(outputexedir+Info.ResName))));
    Replace(cmdstr,'$STATIC',StaticStr);
    Replace(cmdstr,'$STRIP',StripStr);
    Replace(cmdstr,'$MAP',mapstr);
    //Replace(cmdstr,'$GCSECTIONS',GCSectionsStr);
    Replace(cmdstr,'$DYNLINK',DynLinkStr);

    success:=DoExec(FindUtil(utilsprefix+BinStr),cmdstr,true,false);

  { Remove ReponseFile }
    if success and not(cs_link_nolink in current_settings.globalswitches) then
     DeleteFile(outputexedir+Info.ResName);

  { Post process }
    if success and not(cs_link_nolink in current_settings.globalswitches) then
      success:=PostProcessExecutable(FixedExeFileName,false);

    result:=success;   { otherwise a recursive call to link method }
  end;

function TLinkerAmstradCPC.MakeExecutable_Vlink: boolean;
  var
    binstr,
    cmdstr: TCmdStr;
    success: boolean;
    GCSectionsStr,
    StripStr,
    StartSymbolStr,
    MapStr,
    FixedExeFilename: string;
  begin
    GCSectionsStr:='-gc-all -mtype';
    StripStr:='';
    MapStr:='';
    StartSymbolStr:='start';
    FixedExeFileName:=maybequoted(ScriptFixFileName(ChangeFileExt(current_module.exefilename,'.ihx')));

    if (cs_link_map in current_settings.globalswitches) then
      MapStr:='-M'+maybequoted(ScriptFixFileName(current_module.mapfilename));

  { Write used files and libraries }
    WriteResponseFile_Vlink();

  { Call linker }
    SplitBinCmd(Info.ExeCmd[1],binstr,cmdstr);
    Replace(cmdstr,'$OPT',Info.ExtraOptions);

    Replace(cmdstr,'$EXE',FixedExeFileName);
    Replace(cmdstr,'$RES',(maybequoted(ScriptFixFileName(outputexedir+Info.ResName))));
    Replace(cmdstr,'$MAP',MapStr);
    Replace(cmdstr,'$STRIP',StripStr);
    Replace(cmdstr,'$STARTSYMBOL',StartSymbolStr);
    Replace(cmdstr,'$GCSECTIONS',GCSectionsStr);

    success:=DoExec(FindUtil(utilsprefix+BinStr),cmdstr,true,false);

  { Remove ReponseFile }
    if success and not(cs_link_nolink in current_settings.globalswitches) then
     DeleteFile(outputexedir+Info.ResName);

  { Post process }
    if success and not(cs_link_nolink in current_settings.globalswitches) then
      success:=PostProcessExecutable(FixedExeFileName,false);

    result:=success;
  end;

function TLinkerAmstradCPC.MakeExecutable: boolean;
  begin
    if not (cs_link_vlink in current_settings.globalswitches) then
      result:=MakeExecutable_Sdld
    else
      result:=MakeExecutable_Vlink;
  end;


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

function TLinkerAmstradCPC.postprocessexecutable(const fn: string; isdll: boolean): boolean;
  begin
    result:=DoExec(FindUtil(utilsprefix+'ihxutil'),' '+fn,true,false);
  end;


{*****************************************************************************
                          TInternalLinkerAmstradCPC
*****************************************************************************}

procedure TInternalLinkerAmstradCPC.DefaultLinkScript;
  var
    s        : TCmdStr;
    prtobj: string[80];
  begin
    prtobj:='prt0';

    if not (target_info.system in systems_internal_sysinit) and (prtobj <> '') then
      LinkScript.Concat('READOBJECT ' + maybequoted(FindObjectFile(prtobj,'',false)));

    while not ObjectFiles.Empty do
      begin
        s:=ObjectFiles.GetFirst;
        if s<>'' then
          begin
            if not(cs_link_on_target in current_settings.globalswitches) then
              s:=FindObjectFile(s,'',false);
            LinkScript.Concat('READOBJECT ' + maybequoted(s));
          end;
      end;

    LinkScript.Concat('GROUP');
    { Write staticlibraries }
    if not StaticLibFiles.Empty then
      begin
        while not StaticLibFiles.Empty do
          begin
            S:=StaticLibFiles.GetFirst;
            if s<>'' then
              LinkScript.Concat('READSTATICLIBRARY '+MaybeQuoted(s));
          end;
      end;
    LinkScript.Concat('ENDGROUP');

    LinkScript.Concat('IMAGEBASE '+tostr(FOrigin));

    LinkScript.Concat('EXESECTION .text');
    LinkScript.Concat('  OBJSECTION _CODE');
    LinkScript.Concat('ENDEXESECTION');
    LinkScript.Concat('EXESECTION .data');
    LinkScript.Concat('  OBJSECTION _DATA');
    LinkScript.Concat('ENDEXESECTION');
    LinkScript.Concat('EXESECTION .bss');
    LinkScript.Concat('  OBJSECTION _BSS');
    LinkScript.Concat('  OBJSECTION _BSSEND');
    LinkScript.Concat('  OBJSECTION _HEAP');
    LinkScript.Concat('  OBJSECTION _STACK');
    LinkScript.Concat('ENDEXESECTION');

    LinkScript.Concat('ENTRYNAME start');
  end;

constructor TInternalLinkerAmstradCPC.create;
  begin
    inherited create;
    CArObjectReader:=TArObjectReader;
    CExeOutput:=TZXSpectrumIntelHexExeOutput;
    CObjInput:=TRelObjInput;
    if ImageBaseSetExplicity then
      FOrigin:=ImageBase
    else
      FOrigin:=DefaultOrigin;
  end;

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

function TInternalLinkerAmstradCPC.MakeExecutable: boolean;
  begin
    result:=inherited;
    { Post process }
    if result and not(cs_link_nolink in current_settings.globalswitches) then
      result:=PostProcessExecutable(current_module.exefilename);
  end;

function TInternalLinkerAmstradCPC.postprocessexecutable(const fn: string): boolean;
  var
    exitcode: longint;
    FoundBin: ansistring;
    Found: Boolean;
    utilexe: TCmdStr;
  begin
    result:=false;

    utilexe:=utilsprefix+'ihxutil';
    FoundBin:='';
    Found:=false;
    if utilsdirectory<>'' then
      Found:=FindFile(utilexe,utilsdirectory,false,Foundbin);
    if (not Found) then
      Found:=FindExe(utilexe,false,Foundbin);

    if Found then
      begin
        Message1(exec_t_using_util,FoundBin);
        exitcode:=RequotedExecuteProcess(foundbin,' '+fn);
        result:=exitcode<>0;
      end
    else
      begin
        Message1(exec_e_util_not_found,utilexe);
      end;
  end;

{*****************************************************************************
                                     Initialize
*****************************************************************************}

initialization
{$ifdef z80}
{  RegisterLinker(ld_int_amstradcpc,TInternalLinkerAmstradCPC);}
  RegisterLinker(ld_amstradcpc,TLinkerAmstradCPC);
  RegisterTarget(system_z80_amstradcpc_info);
{$endif z80}
end.
