{
    $Id$
    This file is part of the Free Pascal run time library.
    Copyright (c) 1993,97 by Pierre Muller,
    member of the Free Pascal development team.

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    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.

 **********************************************************************}

Unit profile;

{$I os.inc}

interface

uses go32,dpmiexcp;

type header = record
             low,high,nbytes : longint;
             end;

{/* entry of a GPROF type file
*/}
type MTABE = record
             from,_to,count : longint;
             end;

     pMTABE = ^MTABE;
     ppMTABE = ^pMTABE;
{/* internal form - sizeof(MTAB) is 4096 for efficiency
*/ }
type
  PMTAB = ^M_TAB;
  M_TAB  = record
          calls : array [0..340] of MTABE;
          prev : PMTAB;
          end;
var
   h : header;
   histogram : ^integer;
const
   mcount_skip : longint = 1;
var
   histlen : longint;
   oldexitproc : pointer;

const
   mtab : PMTAB = nil;

{/* called by functions.  Use the pointer it provides to cache
** the last used MTABE, so that repeated calls to/from the same
** pair works quickly - no lookup.
*/ }
procedure mcount;

implementation

type plongint = ^longint;

var starttext, endtext : longint;

const cache : pMTABE = nil;

  { ebp contains the frame of mcount)
    (ebp) the frame of calling (to_)
    ((ebp)) the frame of from }

    { problem how to avoid mcount calling itself !! }
  procedure mcount;  [public, alias : 'MCOUNT'];
    var
       m : pmtab;
       i,to_,ebp,from,mtabi : longint;

    begin
       { optimisation !! }
       asm
          pushal
          movl 4(%ebp),%eax
          movl %eax,to_
          movl (%ebp),%eax
          movl 4(%eax),%eax
          movl %eax,from
       end;
       if endtext=0 then
         asm
            popal
            leave
            ret
         end;
       mcount_skip := 1;
       if (to_ > endtext) or (from > endtext) then runerror(255);
       if ((cache<>nil) and
         (cache^.from=from) and
         (cache^._to=to_)) then
         begin
       {/* cache paid off - works quickly */}
       inc(cache^.count);
       mcount_skip:=0;
       asm
          popal
          leave
          ret
       end;
    end;

  {/* no cache hit - search all mtab tables for a match, or an empty slot */}
  mtabi := -1;
  m:=mtab;
  while m<>nil do
    begin
       for i:=0 to 340 do
         begin
           if m^.calls[i].from=0 then
             begin
                {/* empty slot - end of table */ }
                mtabi := i;
                break;
             end;
           if ((m^.calls[i].from = from) and
               (m^.calls[i]._to = to_)) then
             begin
                {/* found a match - bump count and return */}
                inc(m^.calls[i].count);
                cache:=@(m^.calls[i]);
                mcount_skip:=0;
                asm
                   popal
                   leave
                   ret
                end;
             end;
        end;
      m:=m^.prev;
   end;
  if (mtabi<>-1) then
    begin
       {/* found an empty - fill it in */}
       mtab^.calls[mtabi].from := from;
       mtab^.calls[mtabi]._to := to_;
       mtab^.calls[mtabi].count := 1;
       cache := @(mtab^.calls[mtabi]);
       mcount_skip := 0;
       asm
          popal
          leave
          ret
       end;
    end;
  {/* lob off another page of memory and initialize the new table */}
  getmem(m,sizeof(M_TAB));
  fillchar(m^, sizeof(M_TAB),#0);
  m^.prev := mtab;
  mtab := m;
  m^.calls[0].from := from;
  m^.calls[0]._to := to_;
  m^.calls[0].count := 1;
  cache := @(m^.calls[0]);
  mcount_skip := 0;
  asm
     popal
     leave
     ret
  end;
  end;

  var new_timer,old_timer : tseginfo;

{ from itimer.c
/* Copyright (C) 1995 Charles Sandmann (sandmann@clio.rice.edu)
   setitimer implmentation - used for profiling and alarm
   BUGS: ONLY ONE AT A TIME, first pass code
   This software may be freely distributed, no warranty. */ }

{ static void timer_action(int signum) }
{
  if(reload)
    __djgpp_timer_countdown = reload;
  else
    stop_timer();
  raise(sigtype);
}
var reload : longint;
const invalid_mcount_call : longint = 0;
      mcount_nb : longint = 0;
      doublecall : longint = 0;

function mcount_tick(x : longint) : longint;forward;

function timer(x : longint) : longint;
begin
   if reload>0 then
     asm
       movl _RELOAD,%eax
       movl %eax,___djgpp_timer_countdown
     end;

   mcount_tick(x);
   { _raise(SIGPROF); }
end;

{/* this is called during program exit (installed by atexit). */}
procedure mcount_write;
 var m : PMTAB;
     i : longint;
     f : file;
{
  MTAB *m;
  int i, f;
  struct itimerval new_values;

  mcount_skip = 1;

  /* disable timer */
  new_values.it_value.tv_usec = new_values.it_interval.tv_usec = 0;
  new_values.it_value.tv_sec = new_values.it_interval.tv_sec = 0;
  setitimer(ITIMER_PROF, &new_values, NULL); }
  begin
  mcount_skip:=1;
  signal(SIGTIMR,@SIG_IGN);
  signal(SIGPROF,@SIG_IGN);
  set_pm_interrupt($8,old_timer);
  reload:=0;
  exitproc:=oldexitproc;
  writeln('Writing profile output');
  writeln('histogram length = ',histlen);
  writeln('Nb of double calls = ',doublecall);
  if invalid_mcount_call>0 then
    writeln('nb of invalid mcount : ',invalid_mcount_call,'/',mcount_nb)
  else
    writeln('nb of mcount : ',mcount_nb);
  assign(f,'gmon.out');
  rewrite(f,1);
  blockwrite(f, h, sizeof(header));
  blockwrite(f, histogram^, histlen);
  m:=mtab;
  while m<>nil do
    begin
       for i:=0 to 340 do
         begin
            if (m^.calls[i].from = 0) then
              break;
            blockwrite(f, m^.calls[i],sizeof(MTABE));
{$ifdef DEBUG}
            if m^.calls[i].count>0 then
              writeln('  0x',hexstr(m^.calls[i]._to,8),' called from ',hexstr(m^.calls[i].from,8),
                ' ',m^.calls[i].count,' times');
{$endif DEBUG}
         end;
       m:=m^.prev;
    end;
  close(f);
end;

(* extern unsigned start __asm__ ("start");
#define START (unsigned)&start
extern int etext;

/* ARGSUSED */
static void *)


function mcount_tick(x : longint) : longint;
  var bin : longint;
begin
   if mcount_skip=0 then
     begin
        {bin = __djgpp_exception_state->__eip;}
        bin := djgpp_exception_state^.__eip;
        if (djgpp_exception_state^.__cs=get_cs) and
           (bin >= starttext) and (bin <= endtext) then
          begin
             {bin := (bin - starttext) div 4;}	{/* 4 EIP's per bin */}
             bin := (bin - starttext) div 16;
             inc(histogram[bin]);
          end
        else
          inc(invalid_mcount_call);
        inc(mcount_nb);
     end
   else
     inc(doublecall);
   mcount_tick:=0;
end;

{/* this is called to initialize profiling before the program starts */}
procedure _mcount_init;

{struct itimerval new_values;}

  function djgpp_timer_hdlr : pointer;
    begin
       asm
          movl $___djgpp_timer_hdlr,%eax
          movl %eax,__RESULT
       end;
    end;

  procedure set_old_timer_handler;
    begin
       asm
          movl $_OLD_TIMER,%eax
          movl $___djgpp_old_timer,%ebx
          movl (%eax),%ecx
          movl %ecx,(%ebx)
          movw 4(%eax),%ax
          movw %ax,4(%ebx)
       end;

    end;
begin

       asm
          movl $_etext,_ENDTEXT
          movl $start,_STARTTEXT
       end;
  h.low := starttext;
  h.high := endtext;
  histlen := ((h.high-h.low) div 16) * 2; { must be even }
  h.nbytes := sizeof(header) + histlen;
  getmem(histogram,histlen);
  fillchar(histogram^, histlen,#0);

  oldexitproc:=exitproc;
  exitproc:=@mcount_write;

  {/* here, do whatever it takes to initialize the timer interrupt */}
  signal(SIGPROF,@mcount_tick);
  signal(SIGTIMR,@timer);

  get_pm_interrupt($8,old_timer);
  set_old_timer_handler;
{$ifdef DEBUG}
       writeln(stderr,'ori pm int8  '+hexstr(old_timer.segment,4)+':'
           +hexstr(longint(old_timer.offset),8));
       flush(stderr);
{$endif DEBUG}
  new_timer.segment:=get_cs;
  new_timer.offset:=djgpp_timer_hdlr;
  reload:=3;
{$ifdef DEBUG}
       writeln(stderr,'new pm int8  '+hexstr(new_timer.segment,4)+':'
           +hexstr(longint(new_timer.offset),8));
       flush(stderr);
{$endif DEBUG}
  set_pm_interrupt($8,new_timer);
  reload:=1;
     asm
       movl _RELOAD,%eax
       movl %eax,___djgpp_timer_countdown
     end;
  mcount_skip := 0;
  end;

begin
_mcount_init;
end.
{
  $Log$
  Revision 1.1  1998-03-25 11:18:42  root
  Initial revision

  Revision 1.4  1998/01/26 11:57:39  michael
  + Added log at the end

  Revision 1.3  1998/01/16 16:54:22  pierre
    + logs added at end
    + dxeload and emu387 added in makefile

}

{
  $Log$
  Revision 1.1  1998-03-25 11:18:42  root
  Initial revision

  Revision 1.4  1998/01/26 11:57:39  michael
  + Added log at the end


  
  Working file: rtl/dos/go32v2/profile.pp
  description:
  ----------------------------
  revision 1.3
  date: 1998/01/16 16:54:22;  author: pierre;  state: Exp;  lines: +5 -2
    + logs added at end
    + dxeload and emu387 added in makefile
  ----------------------------
  revision 1.2
  date: 1997/12/01 12:26:09;  author: michael;  state: Exp;  lines: +14 -3
  + added copyright reference in header.
  ----------------------------
  revision 1.1
  date: 1997/11/27 08:33:52;  author: michael;  state: Exp;
  Initial revision
  ----------------------------
  revision 1.1.1.1
  date: 1997/11/27 08:33:52;  author: michael;  state: Exp;  lines: +0 -0
  FPC RTL CVS start
  =============================================================================
}
