[Ada Information Clearinghouse]
Ada '83 Rationale, Sec 10.5: The Implementation of Separate Compilation

"Rationale for the Design of the
Ada® Programming Language"

[Ada '83 Rationale, HTML Version]

Copyright ©1986 owned by the United States Government. All rights reserved.
Direct inquiries to the Ada Information Clearinghouse at adainfo@sw-eng.falls-church.va.us.

CHAPTER 10: Separate Compilation and Libraries

10.5 The Implementation of Separate Compilation

The Ada separate compilation facility can be implemented at a reasonable cost for the simple strategy where the quantum of change recognized by the compiler is the compilation or recompilation of a single unit. The model described below is similar to the technique used in compilers for the Lis language.

In this section...

10.5.1 Principle of Separate Compilation
10.5.2 Details of the Actions Performed by the Compiler
10.5.3 Treatment of Package Bodies
10.5.4 Summary of the Information Contained in a Library File

10.5.1 Principle of Separate Compilation

As mentioned before, the Ada separate compilation facility involves a program library that records information on compilation units and on dependence relations between them.

The library file associated with the program library can be organized as a collection of records: one for each compilation unit. If a compilation unit includes declarations that are potentially visible from other compilation units, the corresponding record must contain a description of these declarations - commonly called a symbol table. This need arises in the following cases:

These symbol tables are produced and managed by the compiler. For the compilation of a given unit, the compiler must first retrieve the symbol tables that describe the current context, and then assemble them as appropriate. In other words, the compiler must construct an integrated symbol table that describes visibility for the compilation unit as if the program were not split into separate texts.

In order to perform this task it is useful to consider the following forest structure (a collection of genealogical trees), which reflects the declaration of units and subunits:

  1. Each library unit is a root.

  2. The parent unit of a subunit is the compilation unit that contains the corresponding body stub.
This structure is necessary for the determination of visibility rules. Hence it must be recorded in the library file and updated as new body stubs are encountered, and as new units are compiled.

Finally, for each compilation unit, the list of library units that are mentioned by its context clause must be kept. The forest structure will help for determining the symbol tables to be retrieved, for checking the validity of context clauses and for determining the recompilations that need to be done as a consequence of previous recompilations. Naturally, the compiler may also use this information to assist the user with recompilations.

To check for required recompilations, the compiler may use a system of time-stamping that reflects the order in which compilations are submitted: a unique compilation date is associated with the symbol table of each compilation unit.

10.5.2 Details of the Actions Performed by the Compiler

The following major actions must be performed during the compilation of a compilation unit:

Determination of the compilation context

The context clause is analyzed and the name of the compilation unit is recognized. Using the full name of the subunit (given after the reserved word separate), the genealogy of a subunit can be found: its parent, grandparent, and so on up to the ancestor library unit. A combined with clause is formed by merging the with clauses of the genealogy.

Checking the validity of the compilation context

Any unit mentioned by a context clause must be a library unit.

The following checks must be performed:

Compilation may proceed only if all these checks succeed. Otherwise diagnostics, a list of required recompilations, and a recommended recompilation order may be printed by the compiler.

Table loading

The symbol tables of the library units named by the merged with clause may now be assembled. For a subunit the constitution of the current context also involves nesting the declarative parts of the units of the genealogy - layer by layer, from the ancestor to the immediate parent.

This table assembly may involve establishing some links between the individual symbol tables, since they may refer to each other (for example, an identifier declared in a given package may be of a type declared in another package).

Update of the forest structure, table unloading

At the end of the compilation of a compilation unit, the date of compilation must be updated. For a library unit, and for a unit that contains body stubs (and therefore has subunits), a new symbol table must be stored in the library file in a suitable format. Newly declared subunits must be entered in the forest. If a new library unit is compiled, a root must be added to the forest.

The forest structure can be used to mark units that have become invalid as a consequence of the current compilation and for which recompilation will therefore be needed.

10.5.3 Treatment of Package Bodies

For a given package, the two disjoint units (specification and body) must be viewed as defining complementary aspects of the same logical entity. Consequently it will be convenient for the user to have a single object module, and not two. In order to achieve this effect the code produced during the compilation of the package specification, if any, may be kept in some intermediate form in the record that is associated with the package in the program library. Later, when compiling the package body, this initial code may be recovered and the compilations may proceed as if the two units were concatenated. (The code produced for the specification must still be retained, in case the body is recompiled.)

10.5.4 Summary of the Information Contained in a Library File

The library file contains a representation of the forest structure discussed above. Each node of a tree corresponds to a subunit, except the root, which is always a library unit. A node contains: The record for a given node is created either when the stub for a subunit is analyzed (and then initialized in the state recompilation needed), or during compilation in the case of a library unit. This record is updated during compilations. The record for a subunit may be deleted from the library file upon recompilation of the parent unit if this unit no longer has a corresponding body stub.

Each individual symbol table should be kept in a format that simplifies establishment of the relations between different symbol tables when they are assembled. As an example, consider the two following packages:

package D is
  type T is ...
end D;

with D;
package E is
  use D;
  X :  T;
end E;

Given the symbol table entry for the declaration of X, it must be possible to find the symbol table entry for its type T.

If internal references are used to represent such relations, they must be relocated when the symbol tables are assembled. Methods involving relocation information, or a mapping into virtual memory can be used to support this table assembly.

Note, finally, that symbol tables may be transferred from the library file of one program to that of another program. The internal structure adopted for symbol tables should permit this.

Address any questions or comments to adainfo@sw-eng.falls-church.va.us.