Version 1.1 of acs/ac-00027.txt

Unformatted version of acs/ac-00027.txt version 1.1
Other versions for file acs/ac-00027.txt

!standard 11.04.01(01)          02-02-19 AC95-00027/01
!class amendment 02-02-19
!status received no action 02-02-19
!subject Profiling
!summary
!appendix

From: Nick Roberts
Sent: Friday, January 25, 2002  10:02 AM

Could I suggest a counter-proposal?

Suppose we introduce a standard package, e.g.:

   package Ada.Profiling is

      pragma Remote_Call_Interface;

      type Line_Number is [imp def integer];
      type Page_Number is [imp def integer];
      type Category_Number is 1..[imp def at least 8];

      type Profile_Procedure is access procedure (
            File:         in String; -- full pathname
            Line:         in Line_Number;
            Page:         in Page_Number;
            Unit:         in String; -- full name
            Body_Version: in String;
            Category:     in Category_Number );

      function Profiler (Category: in Category_Number)
         return Profile_Procedure;

      procedure Set_Profiler (Category: in Category_Number;
                              Profiler: in Profile_Procedure);

   end Ada.Profiling;

and then introduce a standard pragma, e.g.:

   pragma Profile (category{, unit_name});

which would apply either to the unit(s) named (all overloadings) or, if no
names are given, to the immediately enclosing program unit, and would
associate a 'profiling category' with each of the units to which it applies.
(A unit can be placed in several profiling categories.) By default, a unit
is in no profiling categories.

There is a 'profile procedure' for each profiling category (an integer from
1 up to an implementation defined limit, which must be no less than 8). By
default, the profile procedure for each category is null. The execution of a
null profile procedure does nothing. The Ada.Profiling.Set_Profiler
procedure enables a specific procedure to be installed as the profiling
procedure for a certain profiling category. Ada.Profiling.Profiler function
returns the current profiling procedure for a particular category.

Whenever a program unit which is in a certain profiling category is called,
an implicit call is first made to the profiling procedure of that category
(before any other action associated with the call takes place, including
evaluation or testing of the input parameters).

The full pathname (however that is defined on the host architecture), and
the page and line numbers within it, of the place of the call are passed
into the profiling procedure, as well as the full name (in dotted notation
and in upper case) of the unit being called, and the body version of that
unit (as would be yielded by the Body_Version attribute). The category
number is also passed in (allowing one actual procedure to service several
categories, and distinguish between them).

No concurrency exclusion mechanism is defined for the calling or execution
of profiling procedures (nor for the execution of the Profiler or
Set_Profiler subprograms in Ada.Profiling). When it is possible that
concurrent execution of profiling procedues may occur, the user must ensure:
(a) the profiling procedures are fully reentrant; (b) some effective
exclusion mechanism is in place; (c) separate tasks cause the execution of
different profiling procedures, which do not interfere with one another.
Probably (a) is preferable wherever it is possible.

Profiling procedures could use Ada.Calendar to obtain the time of the call.
It is recommended that implementations provide (the option of) a printout
which lists all the units in each profiling category (grouped by category).
Information such as the file name, page number, and line number of (the
start of) each such unit should be provided. Implementations may wish to
provide an option to disable profiling (the implicit calls made to profiling
procedures).

A further pragma, e.g.:

   pragma Deep_Profile (category{, unit_name});

could be defined, which has the same effect of the Profile pragma, but also,
for each unit U to which it applies, of applying the Deep_Profile pragma to
each of the units within U.

It would need to be an error to attempt to profile a profiling procedure!

I believe this facility would subsume Bernard's requirements, since all he
need do is declare a logging procedure somewhere, and then profile it, e.g.:

   procedure Log_Profiler (
         File:         in String
         Line:         in Line_Number;
         Page:         in Page_Number;
         Unit:         in String
         Body_Version: in String;
         Category:     in Category_Number );

   procedure Log_Execution is begin null end;

   pragma Inline(Log_Execution);
   pragma Profile(1,Log_Execution);

   ...

   Ada.Profiling.Set_Profiler(1,Log_Profiler'Access);

I also posit that the facility would be useful for traditional profiling
purposes, and probably quite a few other uses too. It could, of course, be
badly abused, but it would be easy to automatically search code for uses of
the facility; thus management could viably require programmers/teams to
obtain a 'licence' to use it.

My apologies if this suggestion treads on already trodden ground.

****************************************************************

From: Robert Duff
Sent: Friday, January 25, 2002

> Could I suggest a counter-proposal?

I still don't think the feature is worth the trouble.
But if we *are* to go ahead with it, I think Nick's design
(hook procedures that can be installed) is on the right track.
It seems like a better approach than the others we've seen.

This is similar to Common Lisp's "advice" functions.

Perhaps a hook should be a tagged object, rather than an
access-to-procedure.

Please don't use the term "unit".  Ada has "compilation unit", "library
unit", and "program unit" -- I get confused when people make me guess
which one they mean.

I don't think it makes sense to call the hook *before* evaluating the
actual parameters.  Eg, P(F(X)) calls F before P, and the hooks should
be called in that order.  Don't you want a hook after the call, too?
I would think that the implementation model would be to compile in the
hook calls into the *body* of each profiled procedure.

> There is a 'profile procedure' for each profiling category (an integer from
> 1 up to an implementation defined limit, which must be no less than 8). By
> default, the profile procedure for each category is null. The execution of a
> null profile procedure does nothing.

You don't mean null.  If it were null, it would raise C_E when called.
Perhaps the installer should complain about null hooks.

> It would need to be an error to attempt to profile a profiling
> procedure!

No -- I suggest that the implementation would take care of turning off
profiling while in a hook.

I'm sure there are other details that would have to be worked out if we
go forward with this...

****************************************************************

From: Nick Roberts
Sent: Saturday, January 26, 2002  6:00 PM

(Take Two ;-)  (Reply to Bob Duff at end)

=====

Profiling Proposal
Nick Roberts
January 2002
(Version 2)


(1) Introduce a new language-defined package:

   with Ada.Finalization;

   package Ada.Profiling is

      pragma Preelaborate(Profiling);

      subtype Page_Number   is [imp def integer type] range 1..[imp def];
      subtype Line_Number   is [imp def integer type] range 1..[imp def];
      subtype Column_Number is [imp def integer type] range 1..[imp def];

      type Calltarget_ID is private;

      function "=" (Left, Right: in Calltarget_ID) return Boolean;

      function File (ID: in Calltarget_ID) return String;
      function Page (ID: in Calltarget_ID) return Page_Number;
      function Line (ID: in Calltarget_ID) return Line_Number;
      function Col  (ID: in Calltarget_ID) return Column_Number;
      function Name (ID: in Calltarget_ID) return String;

      function Body_Version (ID: in Calltarget_ID) return String;

      type Callpoint_ID is private;

      function "=" (Left, Right: in Callpoint_ID) return Boolean;

      function File (ID: in Callpoint_ID) return String;
      function Page (ID: in Callpoint_ID) return Page_Number;
      function Line (ID: in Callpoint_ID) return Line_Number;
      function Col  (ID: in Callpoint_ID) return Column_Number;
      function Name (ID: in Callpoint_ID) return String;

      function Target (ID: in Callpoint_ID) return Calltarget_ID;

      type Root_Profiler_Type is abstract
         new Ada.Finalization.Limited_Controlled with private;

      procedure Pre_Call  (ID: in Callpoint_ID);
      procedure Post_Call (ID: in Callpoint_ID);

   private
      ... -- imp def
   end Ada.Profiling;

Every callable entity (see RM95 6 (2)) represents a @i(calltarget). Each
calltarget has a unique identifying value, its @i(calltarget identifier), of
the type Ada.Profiling.Calltarget_ID. The functions File, Page, Line, and
Col which take a parameter of this type relate to the source location of the
associated callable construct (see RM95 6 (2)). The function Body_Version
returns the same value that the Body_Version attribute (see RM95 E.3) would
yield when applied to: for a subprogram, the subprogram; for an entry, the
task unit or protected unit within which it is declared. The function Name
returns the fully qualified name (in upper case) of the callable entity. The
name of a subprogram is the name of the declaration of the subprogram; the
name of an entry is the name of its entry declaration.

Every call (see RM95 6 (2)) represents a @i(callpoint). Each callpoint has a
unique identifying value, its @i(callpoint identifier), of the type
Ada.Profiling.Callpoint_ID. The functions File, Page, Line, and Col which
take a parameter of this type relate to the source location of the call. The
function Name returns the fully qualified name (in upper case) of the call.
The function Target returns the calltarget identifier of the callable entity
called by the call.

The functions File, Page, Line, and Col relate to the source location of a
syntactic category as follows: they all relate to the first character of the
first lexical element of the text matching the syntactic category; File
returns the fullest name (however that is best fulfilled according to the
programming environment) of the source file (or whatever the container of
the source text may be); Page returns the page number; Line returns the line
number; Col returns the column number. Page, line and column numbers should
correspond to those defined for Ada.Text_IO as closely as possible; in
particular, if there are no pages in the source text, consider the whole
text to be page 1.

A @i(profiler) is an object of a type derived from
Ada.Profiling.Root_Profiler_Type. Such an object must be declared at the
library level if it is mentioned in a Profile or Deep_Profile pragma (see
below).

The procedures Pre_Call and Post_Call are @i(profiling procedures). Any
subprogram that overrides a profiling procedure is also a profiling
procedure.


(2) Introduce a new language-defined pragma:

   pragma Profile (profiler, callable_entity_name{, callable_entity_name});

This pragma @i(attaches) the named callable entities to the given profiler.
A callable entity name is a name, and must resolve as either a subprogram or
an entry. The callable entity must not be a profiling procedure.

The callable entries mentioned in this pragma must either: all be declared
at the library level, in which case the pragma must also occur at the
library level; all be declared in the same declarative region, in which case
the pragma must occur immediately in the same declarative region. In every
case, the pragma musy occur within the scope of the given profiler.

Whenever a particular call to a callable entity which is attached to a
particular profiler is executed or evaluated, the following extra actions
occur: at some point after any previous implicit call to any Post_Call
procedure has completed (normally or abnormally), and before the execution
the call (see RM95 6.4) could raise an exception, an implicit (dispatching)
call is made to the profiler's Pre_Call procedure, passing it the callpoint
identifier of the call, in the ID parameter; after the normal completion of
the execution of the call, and before any other actions which could raise an
exception, an implicit (dispatching) call is made to the profiler's
Post_Call procedure, also passing it the callpoint identifier of the call,
in the ID parameter.

The foregoing paragraph is described in terms of the single thread of
execution associated with one task. (No concurrency control is automatically
provided; if it may be needed it must be provided by the user.)

With regard to exceptions, the principle of the timings of the implicit
calls to Pre_Call and Post_Call is: Pre_Call is guaranteed to be called
regardless of whether an exception is propagated by the evaluation of the
parameters, the evaluation of the target of the call, or by the execution of
the target; conversely, Post_Call is guaranteed to be called only if no such
exception is propagated. The leeway of RM95 11.6 does not apply to these
timings. (Any exception propagated out of an implicit call to any Pre_Call
or Post_Call behaves as normal.)

A callable entity may be attached to several profilers at the same time (by
being affected by several Profile or Deep_Profile pragmas). In this case,
the extra actions described above will take place once for each profiler.
The order in which the actions take place will conform to the above
description, but is otherwise undefined. (If a callable entity is attached
several times to the same profiler, the actions will nevertheless occur only
once for that callable entity and that profiler.)

The Pre_Call and Post_Call procedures are declared as non-abstract
operations of the abstract type Root_Profiler_Type. These (default)
procedures are defined as doing nothing. Thus, for convenience, one or other
may be left un-overridden. It is likely that, if multi-level derivation of
profilers is used, a 'chain' of calls will be required: the body of Pre_Call
should call the Pre_Call of its parent level before taking its own actions;
the body of Post_Call should call the Post_Call of its parent level after
taking its own actions. (This may even be done by the child levels of
Root_Profiler_Type, redundantly but harmlessly, to preserve the sense of the
idiom, and/or to ease a change of level for that type. There is an analogy
here with the Ada.Finalization package.)

The type Root_Profiler_Type is controlled. This makes it straightforward for
a 'commodity' profiling type, derived from it, to look after its own
initialisation and finalisation. It means that derived types must be
declared at the library level, but I don't anticipate this ever being a
problem in practice. I've also made the type hierarchy limited; I think this
will spare implementors of profiler types the bother associated with
assignment and equality testing, based on the assumption that it is terribly
unlikely either of these operations will be required by anyone.


(3) Introduce a new language-defined pragma:

   pragma Deep_Profile (profiler, program_unit_name{, program_unit_name});

If the given profiler is P, then for each program unit U mentioned in the
pragma which is a callable entity, this pragma has the same effect, and must
obey the same rules, as:

   pragma Profile(P,U);

(In particular, U must not be a profiling procedure.)

In any case the pragma (Deep_Profile) also has the effect, for every program
unit U mentioned in it, of declaring:

   pragma Deep_Profile(P,C);

for (and as if located in the scope of) every program unit C that is
declared within U and is not a profiling procedure.


(4) Recommend implementations provide (an option to obtain) an alphabetic
list of each object which is a profiler (and which has at least one callable
entity attached to it), and, for each, an alphabetic list of the callable
entities actually attached to it.


(5) Introduce the language-defined attribute Calltarget_ID, which applies to
a callable entity (a subprogram or entry), and yields the calltarget
identifier associated with the callable entity.


=====

Apologies for my funny ('modern' British) spelling of certain words, if
anyone finds it off-putting. It's what I was taught (and I'm too old now to
change it :-)


----- Original Message -----
From: "Robert A Duff" <bobduff@TheWorld.com>
To: <ada-comment@ada-auth.org>
Cc: <ada-comment@ada-auth.org>; <nickroberts@ukf.net>
Sent: Friday, January 25, 2002 11:03 PM
Subject: Re: [Ada-Comment] Profiling (was: Ada unit information symbols)


> Perhaps a hook should be a tagged object, rather than an
> access-to-procedure.

Yes. Done!

> Please don't use the term "unit".  Ada has "compilation unit", "library
> unit", and "program unit" -- I get confused when people make me guess
> which one they mean.

Sorry. My proposal was actually a bit hasty, and not meant to be very strict
or formal. Nevertheless, sloppy language leads to sloppy thinking, sloppy
thinking leads to sloppy design, and too many aphorisms lead to headaches.

> I don't think it makes sense to call the hook *before* evaluating the
> actual parameters.  Eg, P(F(X)) calls F before P, and the hooks should
> be called in that order.  Don't you want a hook after the call, too?

Actually, I think it does make sense. The example code you give will (in
essence) compile to something like:

   evaluate X -> R1
   R1 -> parameter slot P1
   call F, result -> R2
   R2 -> parameter slot P1
   call P

I agree with having before-and-after hooks, and these would be inserted
thus:

   call Pre_Call(F)
   evaluate X -> R1
   R1 -> parameter slot P1
   call F, result -> R2
   call Post_Call(F)
   call Pre_Call(P)
   R2 -> parameter slot P1
   call P
   call Post_Call(P)

Thus you get the hook calling sequence:

   Pre_Call(F)
   Post_Call(F)
   Pre_Call(P)
   Post_Call(P)

which (to my way of thinking, at least) is just what is required. You'll
notice I've couched things now specifically in terms of exceptions, because
it was exceptions I was thinking of when making this rule.

> I would think that the implementation model would be to compile in the
> hook calls into the *body* of each profiled procedure.

No, the idea is to (make it possible to) 'trace' the actual calls of the
profiled subprograms, and this requires the compiler to insert code at each
call point. You'll see I've made provision for profiling procedures to
obtain meta-information about both the call and the called entity.

> > There is a 'profile procedure' for each profiling category (an integer
> > from 1 up to an implementation defined limit, which must be no less than
> > 8). By default, the profile procedure for each category is null. The
> > execution of a null profile procedure does nothing.
>
> You don't mean null.  If it were null, it would raise C_E when called.
> Perhaps the installer should complain about null hooks.

Okay, this is changed to work like Finalization, with default procedures
that are defined to do nothing.

> > It would need to be an error to attempt to profile a profiling
> > procedure!
>
> No -- I suggest that the implementation would take care of turning off
> profiling while in a hook.

Okay, accepted. This makes it easy to apply one Deep_Profile pragma to a
whole program, which includes profiling procedures, and let Ada sort it out.

> I'm sure there are other details that would have to be worked out if we
> go forward with this...

I've a suspicion that there will indeed be a fair few subtleties,
pathological cases, etc. C'est la vie!

****************************************************************

From: Nick Roberts
Sent: Sunday, January 27, 2002  10:53 PM

Reviewing my last post, I immediately noticed the following rather
fundamental error. The procedures Pre_Call and Post_Call should be declared
thus:

      procedure Pre_Call (Profiler: in out Root_Profiler_Type;
                          ID:       in     Callpoint_ID);

      procedure Post_Call (Profiler: in out Root_Profiler_Type;
                           ID:       in     Callpoint_ID);

Anyone who had already worked this out, please award yourself 10 points. I'm
off for my third trepanning operation: I find it's nice to get a bit of air
inside my skull. Sorry.

****************************************************************

Questions? Ask the ACAA Technical Agent