{
    Copyright (c) 1998-2002 by Peter Vreman

    Contains the base stuff for binary object file writers

    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 ogbase;

{$i fpcdefs.inc}

interface
    uses
       { common }
       cclasses,
       { targets }
       systems,
       { outputwriters }
       owbase,owar,
       { assembler }
       aasmbase,aasmtai;

    type
       tobjectoutput = class
       protected
         { writer }
         FWriter    : tobjectwriter;
         function  writedata(data:TAsmObjectData):boolean;virtual;abstract;
       public
         constructor create(smart:boolean);
         destructor  destroy;override;
         function  newobjectdata(const n:string):TAsmObjectData;virtual;
         function  startobjectfile(const fn:string):boolean;
         function  writeobjectfile(data:TAsmObjectData):boolean;
         procedure exportsymbol(p:tasmsymbol);
         property Writer:TObjectWriter read FWriter;
       end;

       tobjectinput = class
       protected
         { reader }
         FReader    : tobjectreader;
       protected
         function  readobjectdata(data:TAsmObjectData):boolean;virtual;abstract;
       public
         constructor create;
         destructor  destroy;override;
         function  newobjectdata(const n:string):TAsmObjectData;virtual;
         function  readobjectfile(const fn:string;data:TAsmObjectData):boolean;virtual;
         property Reader:TObjectReader read FReader;
       end;

       texesection = class(tnamedindexitem)
       public
         available : boolean;
         secsymidx,
         datasize,
         datapos,
         memsize,
         mempos    : longint;
         flags     : cardinal;
         secdatalist : TLinkedList;
         constructor create(const n:string);
         destructor  destroy;override;
       end;

       texeoutput = class
       private
         procedure Sections_FixUpSymbol(s:tnamedindexitem;arg:pointer);
       protected
         { writer }
         FWriter : tobjectwriter;
         procedure MapObjectdata(var datapos:longint;var mempos:longint);
         function  writedata:boolean;virtual;abstract;
       public
         { info for each section }
         sections     : tdictionary;
         { global symbols }
         externalsyms : tsinglelist;
         commonsyms   : tsinglelist;
         globalsyms   : tdictionary;
         { list of all data of the object files to link }
         objdatalist  : tlinkedlist;
         constructor create;
         destructor  destroy;override;
         function  newobjectinput:tobjectinput;virtual;
         procedure GenerateExecutable(const fn:string);virtual;abstract;
         function  writeexefile(const fn:string):boolean;
         function  CalculateSymbols:boolean;
         procedure CalculateMemoryMap;virtual;abstract;
         procedure addobjdata(objdata:TAsmObjectData);
         procedure FixUpSymbols;
         procedure FixUpRelocations;
         procedure addglobalsym(const name:string;ofs:longint);
         property Writer:TObjectWriter read FWriter;
       end;

    var
      exeoutput : texeoutput;


implementation

    uses
      cutils,globtype,globals,verbose,fmodule,ogmap;



{****************************************************************************
                                tobjectoutput
****************************************************************************}

    constructor tobjectoutput.create(smart:boolean);
      begin
      { init writer }
        if smart and
           not(cs_asm_leave in aktglobalswitches) then
          FWriter:=tarobjectwriter.create(current_module.staticlibfilename^)
        else
          FWriter:=tobjectwriter.create;
      end;


    destructor tobjectoutput.destroy;
      begin
        FWriter.free;
      end;


    function tobjectoutput.newobjectdata(const n:string):TAsmObjectData;
      begin
        result:=TAsmObjectData.create(n);
      end;


    function tobjectoutput.startobjectfile(const fn:string):boolean;
      begin
        result:=false;
        { start the writer already, so the .a generation can initialize
          the position of the current objectfile }
        if not FWriter.createfile(fn) then
         Comment(V_Fatal,'Can''t create object '+fn);
        result:=true;
      end;


    function tobjectoutput.writeobjectfile(data:TAsmObjectData):boolean;
      begin
        if errorcount=0 then
         result:=writedata(data)
        else
         result:=true;
        { close the writer }
        FWriter.closefile;
      end;


    procedure tobjectoutput.exportsymbol(p:tasmsymbol);
      begin
        { export globals and common symbols, this is needed
          for .a files }
        if p.currbind in [AB_GLOBAL,AB_COMMON] then
         FWriter.writesym(p.name);
      end;


{****************************************************************************
                                texesection
****************************************************************************}

    constructor texesection.create(const n:string);
      begin
        inherited createname(n);
        mempos:=0;
        memsize:=0;
        datapos:=0;
        datasize:=0;
        secsymidx:=0;
        available:=false;
        flags:=0;
        secdatalist:=TLinkedList.Create;
      end;


    destructor texesection.destroy;
      begin
      end;


{****************************************************************************
                                texeoutput
****************************************************************************}

    constructor texeoutput.create;
      begin
        { init writer }
        FWriter:=tobjectwriter.create;
        { object files }
        objdatalist:=tlinkedlist.create;
        { symbols }
        globalsyms:=tdictionary.create;
        globalsyms.usehash;
        globalsyms.noclear:=true;
        externalsyms:=tsinglelist.create;
        commonsyms:=tsinglelist.create;
        sections:=tdictionary.create;
      end;


    destructor texeoutput.destroy;
      begin
        sections.free;
        globalsyms.free;
        externalsyms.free;
        commonsyms.free;
        objdatalist.free;
        FWriter.free;
      end;


    function texeoutput.newobjectinput:tobjectinput;
      begin
        result:=tobjectinput.create;
      end;


    function texeoutput.writeexefile(const fn:string):boolean;
      begin
        result:=false;
        if FWriter.createfile(fn) then
         begin
           { Only write the .o if there are no errors }
           if errorcount=0 then
             result:=writedata
           else
             result:=true;
           { close the writer }
           FWriter.closefile;
         end
        else
         Comment(V_Fatal,'Can''t create executable '+fn);
      end;


    procedure texeoutput.addobjdata(objdata:TAsmObjectData);
      begin
        objdatalist.concat(objdata);
      end;


    procedure texeoutput.MapObjectdata(var datapos:longint;var mempos:longint);
{$ifdef needrewrite}
      var
        sec : TSection;
        s   : TAsmSection;
        alignedpos : longint;
        objdata : TAsmObjectData;
      begin
        { calculate offsets of each objdata }
        for sec:=low(TSection) to high(TSection) do
         begin
           if sections[sec].available then
            begin
              { set start position of section }
              sections[sec].datapos:=datapos;
              sections[sec].mempos:=mempos;
              { update objectfiles }
              objdata:=TAsmObjectData(objdatalist.first);
              while assigned(objdata) do
               begin
                 s:=objdata.sects[sec];
                 if assigned(s) then
                  begin
                    { align section }
                    mempos:=align(mempos,$10);
                    if assigned(s.data) then
                     begin
                       alignedpos:=align(datapos,$10);
                       s.dataalignbytes:=alignedpos-datapos;
                       datapos:=alignedpos;
                     end;
                    { set position and size of this objectfile }
                    s.mempos:=mempos;
                    s.datapos:=datapos;
                    inc(mempos,s.datasize);
                    if assigned(s.data) then
                     inc(datapos,s.datasize);
                  end;
                 objdata:=TAsmObjectData(objdata.next);
               end;
              { calculate size of the section }
              sections[sec].datasize:=datapos-sections[sec].datapos;
              sections[sec].memsize:=mempos-sections[sec].mempos;
            end;
         end;
{$endif needrewrite}
      begin
      end;


    procedure texeoutput.Sections_FixUpSymbol(s:tnamedindexitem;arg:pointer);
{$ifdef needrewrite}
      var
        secdata : TAsmSection;
        hsym    : TAsmSymbol;
      begin
        with texesection(s) do
          begin
            if assigned(exemap) then
              exemap.AddMemoryMapExeSection(TExeSection(s));
            secdata:=TAsmSection(secdatalist.first);
            while assigned(secdata) do
             begin
               if assigned(exemap) then
                 exemap.AddMemoryMapObjectSection(secdata);
               hsym:=tasmsymbol(secdata.owner.symbols.first);
               while assigned(hsym) do
                 begin
                   { process only the symbols that are defined in this section
                     and are located in this module }
                   if ((hsym.section=secdata) or
                       ((secdata.sectype=sec_bss) and (hsym.section.sectype=sec_common))) then
                     begin
                       if hsym.currbind=AB_EXTERNAL then
                         internalerror(200206303);
                       inc(hsym.address,secdata.mempos);
                       if assigned(exemap) then
                         exemap.AddMemoryMapSymbol(hsym);
                     end;
                   hsym:=tasmsymbol(hsym.indexnext);
                 end;
               secdata:=TAsmSection(secdata.indexnext);
             end;
          end;
      end;
{$endif needrewrite}
      begin
      end;


    procedure texeoutput.FixUpSymbols;
      var
        sym : tasmsymbol;
      begin
        {
          Fixing up symbols is done in the following steps:
           1. Update addresses
           2. Update common references
           3. Update external references
        }
        { Step 1, Update addresses }
        if assigned(exemap) then
          exemap.AddMemoryMapHeader;
        sections.foreach(@sections_fixupsymbol,nil);
        { Step 2, Update commons }
        sym:=tasmsymbol(commonsyms.first);
        while assigned(sym) do
         begin
           if sym.currbind=AB_COMMON then
            begin
              { update this symbol }
              sym.currbind:=sym.altsymbol.currbind;
              sym.address:=sym.altsymbol.address;
              sym.size:=sym.altsymbol.size;
              sym.section:=sym.altsymbol.section;
              sym.typ:=sym.altsymbol.typ;
              sym.owner:=sym.altsymbol.owner;
            end;
           sym:=tasmsymbol(sym.listnext);
         end;
        { Step 3, Update externals }
        sym:=tasmsymbol(externalsyms.first);
        while assigned(sym) do
         begin
           if sym.currbind=AB_EXTERNAL then
            begin
              { update this symbol }
              sym.currbind:=sym.altsymbol.currbind;
              sym.address:=sym.altsymbol.address;
              sym.size:=sym.altsymbol.size;
              sym.section:=sym.altsymbol.section;
              sym.typ:=sym.altsymbol.typ;
              sym.owner:=sym.altsymbol.owner;
            end;
           sym:=tasmsymbol(sym.listnext);
         end;
      end;


    procedure texeoutput.FixUpRelocations;
      var
        objdata : TAsmObjectData;
      begin
        objdata:=TAsmObjectData(objdatalist.first);
        while assigned(objdata) do
         begin
           objdata.fixuprelocs;
           objdata:=TAsmObjectData(objdata.next);
         end;
      end;


    procedure texeoutput.addglobalsym(const name:string;ofs:longint);
      var
        sym : tasmsymbol;
      begin
        sym:=tasmsymbol(globalsyms.search(name));
        if not assigned(sym) then
         begin
           sym:=tasmsymbol.create(name,AB_GLOBAL,AT_FUNCTION);
           globalsyms.insert(sym);
         end;
        sym.currbind:=AB_GLOBAL;
        sym.address:=ofs;
      end;


    function TExeOutput.CalculateSymbols:boolean;
      var
        commonobjdata,
        objdata : TAsmObjectData;
        sym,p : tasmsymbol;
      begin
        commonobjdata:=nil;
        CalculateSymbols:=true;
        {
          The symbol calculation is done in 3 steps:
           1. register globals
              register externals
              register commons
           2. try to find commons, if not found then
              add to the globals (so externals can be resolved)
           3. try to find externals
        }
        { Step 1, Register symbols }
        objdata:=TAsmObjectData(objdatalist.first);
        while assigned(objdata) do
         begin
           sym:=tasmsymbol(objdata.symbols.first);
           while assigned(sym) do
            begin
              if not assigned(sym.owner) then
               internalerror(200206302);
              case sym.currbind of
                AB_GLOBAL :
                  begin
                    p:=tasmsymbol(globalsyms.search(sym.name));
                    if not assigned(p) then
                      globalsyms.insert(sym)
                    else
                      begin
                        Comment(V_Error,'Multiple defined symbol '+sym.name);
                        result:=false;
                      end;
                  end;
                AB_EXTERNAL :
                  externalsyms.insert(sym);
                AB_COMMON :
                  commonsyms.insert(sym);
              end;
              sym:=tasmsymbol(sym.indexnext);
            end;
           objdata:=TAsmObjectData(objdata.next);
         end;
        { Step 2, Match common symbols or add to the globals }
        sym:=tasmsymbol(commonsyms.first);
        while assigned(sym) do
         begin
           if sym.currbind=AB_COMMON then
            begin
              p:=tasmsymbol(globalsyms.search(sym.name));
              if assigned(p) then
               begin
                 if p.size<>sym.size then
                  internalerror(200206301);
               end
              else
               begin
                 { allocate new symbol in .bss and store it in the
                   *COMMON* module }
                 if not assigned(commonobjdata) then
                  begin
                    if assigned(exemap) then
                      exemap.AddCommonSymbolsHeader;
                    { create .bss section and add to list }
                    commonobjdata:=TAsmObjectData.create('*COMMON*');
                    commonobjdata.createsection(sec_bss,'',0,[aso_alloconly]);
                    addobjdata(commonobjdata);
                  end;
                 p:=TAsmSymbol.Create(sym.name,AB_GLOBAL,AT_FUNCTION);
                 commonobjdata.writesymbol(p);
                 if assigned(exemap) then
                   exemap.AddCommonSymbol(p);
                 { make this symbol available as a global }
                 globalsyms.insert(p);
               end;
              sym.altsymbol:=p;
            end;
           sym:=tasmsymbol(sym.listnext);
         end;
        { Step 3 }
        sym:=tasmsymbol(externalsyms.first);
        while assigned(sym) do
         begin
           if sym.currbind=AB_EXTERNAL then
            begin
              p:=tasmsymbol(globalsyms.search(sym.name));
              if assigned(p) then
               begin
                 sym.altsymbol:=p;
               end
              else
               begin
                 Comment(V_Error,'Undefined symbol: '+sym.name);
                 CalculateSymbols:=false;
               end;
            end;
           sym:=tasmsymbol(sym.listnext);
         end;
      end;


{****************************************************************************
                                tobjectinput
****************************************************************************}

    constructor tobjectinput.create;
      begin
        { init reader }
        FReader:=tobjectreader.create;
      end;


    destructor tobjectinput.destroy;
      begin
        FReader.free;
      end;


    function tobjectinput.newobjectdata(const n:string):TAsmObjectData;
      begin
        result:=TAsmObjectData.create(n);
      end;


    function tobjectinput.readobjectfile(const fn:string;data:TAsmObjectData):boolean;
      begin
        result:=false;
        { start the reader }
        if FReader.openfile(fn) then
         begin
           result:=readobjectdata(data);
           FReader.closefile;
         end;
      end;


end.
