As more fully described in [1], the goals of this
To provide an easily usable teaching tool.
To provide high-quality diagnostics to the user.
To provide fast compilation at the expense, if necessary,
To faithfully implement Pascal 6000\-3.4 as described in [5], and
to be as compatible as possible with the
implementation of the language.
The Pascal execution profiler, hereafter referred to simply as
results from the second design goal.
The system, however, would benefit
greatly from a more complete debugging facility.
interactive and post-mortem debugger
for the system exists [6], but
has not been implemented as of this writing.
Placement of statement counters
Execution profiling is quite simple in concept.
counters at a sufficient number of points in the program to allow
the determination of the number of times each statement has been executed.
Knuth, in [7], gives an algorithm for determining the minimum number
of counters necessary to gather complete information.
Pascal code is interpreted.
Thus the addition of statement counters
does not tend to have a significant degrading effect on the speed of execution.
Even in heavily travelled loops, a statement counter is of low enough
execution cost that techniques for, e.g., moving counters out of
loops were determined to be unnecessary.
More complicated techniques
such as determining the number of calls on a
by adding together the call counts for all calling sites would
not materially decrease the time in execution and would add significantly
to the complexity of the post-processing.
It was thus decided not to do
any counting optimization of this type.
A more subtle consideration involved non-local
analysis, which was not available in the one-pass compiler scheme being
used, there is always the possibility that an arbitrary
call will not return to the calling site.
accurate counts and a simple scheme when non-local
would likely involve the placement of a counter after each such
The presence of these extra counters could easily multiply the number
required for typical programs by a factor of 5 or more.
statements which cut across levels of control tend to be used
only in infrequently occurring error situations, it was decided not to place
counters to allow for non-local
Counters are not placed after each
if a call fails to return, the counts will be inaccurate to
An option to allow for correct counts in the presence of non-local
was considered, but rejected.
such an option would rarely, or never, be used.
It is the author's belief that
summary data flow information indicating possible non-local
control transfers would be necessary if the placement of counters were
to be done appropriately.
Thus, it was decided to place counters at the following program points:
At the entry point to each
statement is counted by subtracting the count for the
part from the pre-statement count.
statement, with one counter
for each group of case labels.
Before any statement which has a
The counts are made completely accurate in the presence of local
by placing an extra counter after each
statement which contains a
statement in such a statement,
the count after the statement is taken to be the count before the statement.
It was later suggested to the author that one could count the frequency
of each individual label in a
statement, rather than keeping one count for each group.
This might provide useful information for some programs,
but has not been implemented
A final consideration related to the placement of counters over parts
This would surely be desirable if gathering profile data were expensive.
user might be able to restrict his cost by specifying which
parts of his code were to be counted.
that the profile listing would be constructed by reparsing
the source text of the program and combining it with the profile data,
it seemed clear that the savings from such partial counting would not
materially affect the overall cost of producing a profile.
It was therefore decided to allow selectivity of profile output
rather than selectivity of count gathering at compile or
Given the collected data in the form of an array of statement counts,
one then wishes to produce a listing of all or part of the program
with the count information appended in an easily understandable form.
It seems clear that a system which presents the count information
to the user with associated source text from his program will be
superior to one which merely produces a table of counts for identified
debugging system [8] produced such a listing by
a saved internal form of the source program.
this internal form was also used to provide symbolic flow-trace
As we did not propose to do symbolic statement tracing in our
debugger design [6], there was no need for an internal form of the
source program to be available at run time.
Given the fact that our operating system is primarily disk-space
limited, it was decided that reparsing the source text of the program
to produce the execution profile was a reasonable approach.
This allows the profiler to use the existing source text of the
program without requiring a potentially large intermediate form.
This also allowed the profiler to use the existing parser and to
receive as its input a well defined parse tree as described in [1].
Thus the execution of a program normally produces a file named
which contains an array of counters.
The code generated need contain
only the instructions required to prepare these counts.
is very conservative of disk storage resources.
In cases where the execution of a Pascal program terminates abnormally,
or has to be terminated due to apparent infinite looping, it is often
desirable to obtain a profile to help discern the cause of the error.
The Pascal runtime system will create the
file in these cases, and profiling is still possible.\u\s-2\*(dg\s0\d
\*(dg If the Pascal runtime system terminates due to a fault,
either because of a bug in the runtime system,
a severe misuse of pointers,
or an untested out-of-range subscript,
a core-image file will be produced.
to extract the profile information from this core image,
allowing profiling in this case also.
the information so gathered is not as usable as that which could be
garnered easily by using a debugger such as
since variable values are not available.
A final point to be noted is that the counts are inaccurate
near the suspended control points whenever the program terminates abnormally.
This is explained more fully in [3].
debugging system design included the marking in the profile of the
point of abnormal termination in a fashion similar to that used by
Satterthwaite [9], but this has not yet been implemented.
Formatting of the program
As already noted, easy comprehension by the user of the the profile
requires that the profile be in a readable form.
One possibility would be an annotated source listing, using the
form of the original source text.
This has the advantage that
the listing is in a form familiar to the user.
in this approach is the fact that the format of the source may not
leave room for easy placement of all of the profile information.
There have been a number of systems designed or constructed which
provide automatically restructured listings of programs
Such programs have often been called ``pretty printers.''
It is not hard to see that the production of a readable profile
from a well-structured listing would not be difficult given
the framework of a pretty printer.
It was therefore decided to produce such a restructured listing
and to annotate it with the profile information.
An option to use the execution profiler as a pretty printer was
Comments and output compression
One problem with the evolving approach was the treatment of comments.
The interface from the parser to the semantic routines in the compiler,
which was the available and highly suitable framework, had no
provision for the placement of comment text anywhere in the parse tree.
For the purposes of profiling this could be easily tolerated.
An effort can be made when profiling to suppress output which is not absolutely
necessary in the profile.
In particular, comments, declarations, and the bodies of
unexecuted procedures and functions could be normally suppressed.
In fact, early versions of
In the current implementation, however,
the default is that only the bodies of unexecuted
functions and procedures are suppressed.
All such suppression is controllable through options as described in
The design of the comment formatting facility for
is presented in section 3.
The author feels that significantly better approaches to program
maintenance and formatting are possible
Some possibilities are discussed in section 4.
Many different formats for presenting Pascal structure are possible.
The author's personal programming style largely determined
the structure of programs produced by
In particular, the author was bothered by one feature of other ``pretty
Many other pretty printers
construct in a fashion similar
\*belse\fR \*bif\fR condition2
This could be termed ``wandering across the page.''
In structured programs, especially in a language which contains
or other escape statement, it is easy to get ``buried''
in many levels of conditions.
While the merits of escape-less programming are debatable, it seems
important to present the structure of the above construct as
The author feels that a more appropriate way to present
this statement is often the following:
\*bif\fR condition1 \*bthen\fR
\*belse\fR \*bif\fR condition2 \*bthen\fR
Begin\-end pairs and well\-bracketing
Another aspect of modern programming languages which are not
pairs, which, if mismatched, can cause problems,
if they escape detection at compile time.
With an automatically reformatted listing, the user
no longer needs to worry that his listing may textually represent
the structure of the program in a way different from the true structure.
Given this situation, the author feels that the words
convey no information to the user that is not already reflected in
a more convenient form by the textual indentation.
how to represent the ``bushy''
blocks and choosing between:
\*bif\fR condition1 \*bthen\fR
\*belse\fR \*bif\fR condition2 \*bthen\fR
\*bif\fR condition1 \*bthen\fR \*bbegin\fR
\*bend\fR \*belse\fR \*bif\fR condition2 \*bthen\fR \*bbegin\fR
the author chose the latter, which he prefers.
It should be noted that this is essentially the syntax of
and is similar to Wirth's
\*bif\fR condition \*bthen\fR
\*belsif\fR condition2 \*bthen\fR
Indenting of structured levels
Another issue of style arises in choosing how many spaces to indent
each structured statement of the source text.
While 8 spaces per level is very
one level then corresponds to one
4 spaces seemed to work best with the execution profile when using
an adaptation of the format of Satterthwaite [8].
Thus, options were provided to allow a level to be defined as
any small number of column positions, with 4 columns the default.
The printing of expressions presented yet another problem.
It was felt that a format which reflected the true structure of an expression
For this reason, a fully parenthesized expression format was first tried.
This turned out to be a very poor choice as it made complicated
expressions very hard to read.
The author then implemented a minimal-parenthesis format as the default
with full parenthesization as an option.
It is probably true that many users would prefer to have their
parenthesization preserved even when the parentheses deleted are, in fact,
This would be even more true in a language which had a large number
of precedence levels with a large number of operators.
It is not as necessary in Pascal since there are only a small number
of levels here, but such an option would have been useful in any case.
This option is not, however, included in the current
definitions which are nested
within one another would be indented one structured statement
level by default to improve readability.
An option was included to omit this indenting if desired.
is labelled with the name of the
This is done also for the opening
definitions occur within the
When first designing the statement formats, the author felt that
statement should be formatted so that cases with one and cases with
more than one statement would line up for easy readability.
At the time, 8 spaces per level was the default format and the choice
col := (col + 8) \*bmod\fR 8;
col := (col + 8) \*bmod\fR 8;
With the eventual choice of 4 spaces per structured level as the
default indentation, the difference between the corresponding formats
In retrospect, this was not
necessarily sufficiently valuable to justify the amount of time spent on the
as different strategies were needed
for each indenting option, and the format was optimized
to be ``pretty'' in each case subject to the alignment constraint.
uses only the simpler first strategy.
Labels are place on a separate line, aligned with the header
Placed this way, they are easy to locate.
the text editing facilities on the
Thus it is extremely convenient to have statements on single
For this reason the format of the programs produced by
the pretty printer gives section keywords like
In this way deleting the first declaration
of such a section is made easier \- there is no need to split
the keyword away from the first declaration.
Other options, such as an option to underline all of the keywords
in the program, are provided by
These are detailed more fully in [3].
In summary, the profiler format reflects the taste of
the author in Pascal formatting at the time this program was
It largely succeeds in producing readable Pascal texts.
Treatment of long statements and placement of multiple statements per line
and a design for some of these is described in section 2.
They were omitted from the original
design only because the author was not initially sure of a reasonable way
to proceed in these areas, and felt that these were more important
in the context of a pretty printer.
was intended primarily as an execution profiler.
Even without the features of section 3, earlier versions of
were useful in showing students who had trouble formatting their programs
one acceptable way of writing Pascal.
Profile Data Presentation
The basic format for the profile is essentially that of Satterthwaite [8] [9].
Examples are given in [3].
The following pieces of information are included with the profile
in addition to the basic count information.
The version number and date of the instance of
which produced the profile.
The version of the program being profiled (taken to be the time at which
the file it is contained in was last modified.)
The time at which the profile data was gathered.
This information serves to identify uniquely the program involved.
The algorithm for count interpretation is given in [3] and is essentially
the same as that given by Satterthwaite in [9].
Two options for compressing profile data are available.
One gives a table of the procedures and functions with their activation
This requires only a very small amount of data even for large programs.
More easily readable than a simple table are profiles of parts of a
These can be obtained without modifying the source text.
line, and profiles will be enabled for these and their contained
procedures and functions only.
This ability to extract selective information and to be able to do it
without modifying the source code is critical.
Editing the source code (at least twice!) for each profiling pass
would not only be tedious, but could easily be done incorrectly.
The command line syntax is simple and relatively foolproof.
This feature is fully described in [3].