!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" To: Cc: ; 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. ****************************************************************