{
    Copyright (c) 2019 by Dmitry Boyarintsev

    This unit implements support import,export,link routines
    for the WASI 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_wasi;

{$i fpcdefs.inc}

interface

uses
  systems,

  globtype, globals,
  aasmbase,
  cfileutl, cutils, cclasses,

  import, export, aasmdata, aasmcpu,
  fmodule, ogbase, ogwasm,

  symconst, symsym, symdef, symcpu,

  link,

  i_wasi, tgcpu;

type

  { texportlibwasi }

  texportlibwasi=class(texportlib)
      procedure preparelib(const s : string);override;
      procedure exportprocedure(hp : texported_item);override;
      procedure exportvar(hp : texported_item);override;
      procedure generatelib;override;
    end;

  { timportlibwasi }
  timportlibwasi = class(timportlib)
      procedure generatelib;override;
    end;

  { tlinkerwasi }

  tlinkerwasi=class(texternallinker)
  public
    constructor Create;override;
    procedure SetDefaultInfo;override;

    procedure InitSysInitUnitName;override;

    function  MakeExecutable:boolean;override;
    function  MakeSharedLibrary:boolean;override;
  end;

  { TInternalLinkerWasi }

  TInternalLinkerWasi=class(tinternallinker)
  private
    function GetExeSectionSize(aExeOutput: TExeOutput; const aname:string): QWord;
  protected
    procedure DefaultLinkScript;override;

    function GetDataSize(aExeOutput: TExeOutput): QWord;override;
    function GetBssSize(aExeOutput: TExeOutput): QWord;override;
  public
    constructor create;override;

    procedure InitSysInitUnitName;override;
  end;


implementation

uses
  SysUtils,
  verbose,
  comprsrc,rescmn;

{ timportlibwasi }

  procedure timportlibwasi.generatelib;
    begin
    end;

{ tlinkerwasi }

constructor tlinkerwasi.Create;
begin
  inherited Create;
end;

procedure tlinkerwasi.SetDefaultInfo;
begin
  with Info do
    begin
      ExeCmd[1] := 'wasm-ld -m wasm32 $SONAME $GCSECTIONS $MAP -z stack-size=$STACKSIZE $OPT -o $EXE';
      DllCmd[1] := 'wasm-ld -m wasm32 $SONAME $GCSECTIONS $MAP -z stack-size=$STACKSIZE $OPT -o $EXE';
    end;
end;

procedure tlinkerwasi.InitSysInitUnitName;
begin
  if current_module.islibrary then
    sysinitunit:='si_dll'
  else
    sysinitunit:='si_prc';
end;

function tlinkerwasi.MakeExecutable:boolean;
const
  PageSize = 65536;
var
  GCSectionsStr  : ansistring;
  binstr, cmdstr : Tcmdstr;
  InitStr,
  FiniStr,
  SoNameStr      : string[80];
  mapstr,ltostr  : TCmdStr;
  success        : Boolean;

  tmp : TCmdStrListItem;
  tempFileName : ansistring;

  initialmem,
  maxmem : longint;
begin
  if not(cs_link_nolink in current_settings.globalswitches) then
    Message1(exec_i_linking,current_module.exefilename);

  { Create some replacements }
  mapstr:='';
  if (cs_link_map in current_settings.globalswitches) then
    mapstr:='-Map '+maybequoted(ChangeFileExt(current_module.exefilename,'.map'));
  if (cs_link_smart in current_settings.globalswitches) and
     create_smartlink_sections then
   GCSectionsStr:='--gc-sections'
  else
    GCSectionsStr:='';

  SoNameStr:='';
  SplitBinCmd(Info.ExeCmd[1],binstr,cmdstr);
  Replace(cmdstr,'$EXE',maybequoted(current_module.exefilename));

  tmp := TCmdStrListItem(ObjectFiles.First);
  while Assigned(tmp) do begin
    cmdstr := tmp.Str+ ' ' + cmdstr;
    tmp := TCmdStrListItem(tmp.Next);
  end;

//  if HasExports then
//    cmdstr := cmdstr + ' --export-dynamic'; //' --export-dynamic';

  cmdstr := cmdstr + ' --no-entry';

  initialmem:=align(heapsize,PageSize);
  maxmem:=align(maxheapsize,PageSize);

  if ts_wasm_threads in current_settings.targetswitches then
    begin
      cmdstr := cmdstr + ' --import-memory --shared-memory --global-base=1024';
    end;

  if initialmem>0 then
    cmdstr := cmdstr + ' --initial-memory=' + tostr(initialmem);

  if maxmem>0 then
    cmdstr := cmdstr + ' --max-memory=' + tostr(maxmem);

  if (cs_link_strip in current_settings.globalswitches) then
   begin
     { only remove non global symbols and debugging info for a library }
     cmdstr := cmdstr + ' --strip-all';
   end;

  Replace(cmdstr,'$OPT',Info.ExtraOptions);
  //Replace(cmdstr,'$RES',maybequoted(outputexedir+Info.ResName));
  //Replace(cmdstr,'$INIT',InitStr);
  //Replace(cmdstr,'$FINI',FiniStr);
  Replace(cmdstr,'$STACKSIZE',tostr(stacksize));
  Replace(cmdstr,'$SONAME',SoNameStr);
  Replace(cmdstr,'$MAP',mapstr);
  //Replace(cmdstr,'$LTO',ltostr);
  Replace(cmdstr,'$GCSECTIONS',GCSectionsStr);
  success:=DoExec(FindUtil(utilsprefix+binstr),cmdstr,true,false);

  MakeExecutable:=success;
end;

function tlinkerwasi.MakeSharedLibrary: boolean;
var
  GCSectionsStr  : ansistring;
  binstr, cmdstr : Tcmdstr;
  InitStr,
  FiniStr,
  SoNameStr      : string[80];
  mapstr,ltostr  : TCmdStr;
  success        : Boolean;

  tmp : TCmdStrListItem;
  tempFileName : ansistring;
begin
  Result:=false;
  if not(cs_link_nolink in current_settings.globalswitches) then
    Message1(exec_i_linking,current_module.sharedlibfilename);

  { Create some replacements }
  mapstr:='';
  if (cs_link_map in current_settings.globalswitches) then
    mapstr:='-Map '+maybequoted(ChangeFileExt(current_module.sharedlibfilename,'.map'));
  if (cs_link_smart in current_settings.globalswitches) and
     create_smartlink_sections then
   GCSectionsStr:='--gc-sections'
  else
    GCSectionsStr:='';

  SoNameStr:='';
  SplitBinCmd(Info.DllCmd[1],binstr,cmdstr);
  Replace(cmdstr,'$EXE',maybequoted(current_module.sharedlibfilename));

  tmp := TCmdStrListItem(ObjectFiles.First);
  while Assigned(tmp) do begin
    cmdstr := tmp.Str+ ' ' + cmdstr;
    tmp := TCmdStrListItem(tmp.Next);
  end;

//  if HasExports then
//    cmdstr := cmdstr + ' --export-dynamic'; //' --export-dynamic';

  cmdstr := cmdstr + ' --no-entry';

  if (cs_link_strip in current_settings.globalswitches) then
   begin
     { only remove non global symbols and debugging info for a library }
     cmdstr := cmdstr + ' --strip-all';
   end;

  Replace(cmdstr,'$OPT',Info.ExtraOptions);
  //Replace(cmdstr,'$RES',maybequoted(outputexedir+Info.ResName));
  //Replace(cmdstr,'$INIT',InitStr);
  //Replace(cmdstr,'$FINI',FiniStr);
  Replace(cmdstr,'$STACKSIZE',tostr(stacksize));
  Replace(cmdstr,'$SONAME',SoNameStr);
  Replace(cmdstr,'$MAP',mapstr);
  //Replace(cmdstr,'$LTO',ltostr);
  Replace(cmdstr,'$GCSECTIONS',GCSectionsStr);
  success:=DoExec(FindUtil(utilsprefix+binstr),cmdstr,true,false);

  MakeSharedLibrary:=success;
end;

{ texportlibwasi }

procedure texportlibwasi.preparelib(const s: string);
begin
  //nothing to happen. wasm files are modules
end;

procedure texportlibwasi.exportprocedure(hp: texported_item);
var
  nm : TSymStr;
  pd: tcpuprocdef;
begin
  pd:=tcpuprocdef(tprocsym(hp.sym).ProcdefList[0]);
  if eo_promising_first in hp.options then
    pd.add_promising_export(hp.name^,false)
  else if eo_promising_last in hp.options then
    pd.add_promising_export(hp.name^,true)
  else
    begin
      nm := pd.mangledname;
      current_asmdata.asmlists[al_exports].Concat(tai_export_name.create(hp.name^, nm, ie_Func));
    end;
end;

procedure texportlibwasi.exportvar(hp: texported_item);
begin
  //inherited exportvar(hp);
end;

procedure texportlibwasi.generatelib;
begin
  //inherited generatelib;
end;

{ TInternalLinkerWasi }

function TInternalLinkerWasi.GetExeSectionSize(aExeOutput: TExeOutput;
  const aname: string): QWord;
var
  sec: TExeSection;
begin
  sec:=aExeOutput.findexesection(aname);
  if assigned(sec) then
    Result:=sec.size
  else
    Result:=0;
end;

procedure TInternalLinkerWasi.DefaultLinkScript;
var
  s: TCmdStr;
begin
  while not ObjectFiles.Empty do
  begin
    s:=ObjectFiles.GetFirst;
    if s<>'' then
      LinkScript.Concat('READOBJECT ' + maybequoted(s));
  end;

  LinkScript.Concat('EXESECTION .wasm_globals');
  LinkScript.Concat('  SYMBOL __stack_pointer');
  if ts_wasm_threads in current_settings.targetswitches then
    begin
      LinkScript.Concat('  SYMBOL __tls_base');
      LinkScript.Concat('  SYMBOL __tls_size');
      LinkScript.Concat('  SYMBOL __tls_align');
    end;
  LinkScript.Concat('  OBJSECTION .wasm_globals.*');
  LinkScript.Concat('ENDEXESECTION');

  { WebAssembly is a Harvard architecture, with multiple address spaces, so it
    is important to keep the sections grouped, and keep the first section in
    each group intact (otherwise, TWasmExeOutput.MemPos_ExeSection in ogwasm.pas
    needs to be updated) }
  { tags (used by WebAssembly native exceptions) }
  ScriptAddGenericSections('.wasm_tags');
  { code }
  if ts_wasm_threads in current_settings.targetswitches then
    begin
      linkscript.Concat('EXESECTION .text');
      linkscript.Concat('  OBJSECTION .text*');
      { functions, generated by the linker: }
      linkscript.Concat('  SYMBOL __wasm_init_tls');
      linkscript.Concat('  SYMBOL __fpc_wasm_init_shared_memory');
      linkscript.Concat('ENDEXESECTION');
    end
  else
    ScriptAddGenericSections('.text');
  if ts_wasm_threads in current_settings.targetswitches then
    ScriptAddGenericSections('.tbss');
  ScriptAddGenericSections(
    { data (initialized data first, uninitialized data later) }
    '.rodata,.data,fpc.resources,fpc.reshandles,.bss,'+
    { debug info }
    '.debug_frame,.debug_info,.debug_line,.debug_abbrev,.debug_aranges,.debug_ranges,.debug_str');
end;

function TInternalLinkerWasi.GetDataSize(aExeOutput: TExeOutput): QWord;
begin
  Result:=GetExeSectionSize(aExeOutput,'.rodata') +
          GetExeSectionSize(aExeOutput,'.data') +
          GetExeSectionSize(aExeOutput,'fpc.resources');
end;

function TInternalLinkerWasi.GetBssSize(aExeOutput: TExeOutput): QWord;
begin
  Result:=GetExeSectionSize(aExeOutput,'.bss') +
          GetExeSectionSize(aExeOutput,'.tbss') +
          GetExeSectionSize(aExeOutput,'fpc.reshandles');
end;

constructor TInternalLinkerWasi.create;
begin
  inherited create;
  CExeOutput:=TWasmExeOutput;
  CObjInput:=TWasmObjInput;
end;

procedure TInternalLinkerWasi.InitSysInitUnitName;
begin
  if current_module.islibrary then
    sysinitunit:='si_dll'
  else
    sysinitunit:='si_prc';
end;

initialization
  RegisterTarget(system_wasm32_wasip1_info);
  RegisterImport(system_wasm32_wasip1, timportlibwasi);
  RegisterExport(system_wasm32_wasip1, texportlibwasi);
  RegisterTarget(system_wasm32_wasip1threads_info);
  RegisterImport(system_wasm32_wasip1threads, timportlibwasi);
  RegisterExport(system_wasm32_wasip1threads, texportlibwasi);
  RegisterTarget(system_wasm32_wasip2_info);
  RegisterImport(system_wasm32_wasip2, timportlibwasi);
  RegisterExport(system_wasm32_wasip2, texportlibwasi);
  RegisterLinker(ld_int_wasi,TInternalLinkerWasi);
  RegisterLinker(ld_wasi, tlinkerwasi);
  RegisterRes(res_wasm_info,TWinLikeResourceFile);
end.