Compiler

The Amzi! Prolog compiler transforms Prolog source code files (.pro) into object code files (.plm). Source files must be plain text with no embedded control characters other than the newline.

You may use an extension other than .pro for your source files, but then you must type it in each time, rather than using the default.

Advantages

Amzi! Prolog offers a compiler which optimizes a great deal of the searching procedure used on dynamic (or interpreted) predicates. General pattern matches are replaced by specific test-and-branch sequences of code, and procedure calls are flattened with the result that compiled programs run in much less space than their interpreted counterparts, and groups of clauses are cross-indexed to provide rapid table look up.

The net result of all of this work is that compiled code can run twenty or thirty times faster than equivalent interpreted code, occupies less space in memory and consumes much less space to execute.

We strongly recommend that as much code as possible be compiled once it has been developed.

Disadvantages

When code is compiled the code loses some features which may be important in some applications: Both of these restrictions can be ameliorated by selective intermixing of compiled and interpreted code in the same application.

Requirements

The compiler assumes that all clauses for a given predicate are contiguous and located in the same file. If they are not, then the source code containing the clauses must include the 'multifile' directive for all predicates that are defined in multiple files, and/or the 'discontiguous' directive for predicates whose clauses are not contiguous. (This is so the compiler can optimize the code for the various clauses.)

The compiler does not unintentional (no discontiguous or multifile specified) split definitions of predicates. When you try to load the compiled program, however, you will get an error message indicating you have attempted to redefine a compiled predicate.

Directives

Interpreted code is simply loaded into the dynamic database as it is 'consult'ed. The compiler, however, processes the code before compiling and may require you to specify certain directives on how you want it to proceed. The directives are summarized here and explained in greater detail in the following sections:

Running from the IDE

Normally the compiler is invoked when you build a project by either pressing the BLD button or choosing Build / Build from the menu. This will compile and link all the modified files in the project. (See the Projects section for more details.)

To compile a single file, select Build / Compile. The standard "file open" dialog box is displayed that allows you to select a source file to compile.

The compiler output and messages are displayed in a scrolling window for your perusal when compilation is completed.

Running from the Command-Line

There are two ways of supplying the names of files to be compiled to the compiler: The command-line can take three arguments. To use compile in prompting mode, do not specify any arguments. For example: or

Messages

When the compiler begins compiling, it indicates which clause of which predicate is being compiled by using a display of the form: If an error occurs a message will be printed to the screen and the error handler will be invoked. Most errors will be syntax errors. In the case of a syntax error, the offending term will be listed along with the error message.

Note that when the compiler reads in clauses it does so by reading in all clauses for a given predicate and the first clause of the following predicate. Thus it is possible for a read error to occur in a clause which is not in the predicate being compiled but is in the next predicate to be compiled.

Errors in a clause will cause the compilation of that clause to be skipped; compilation continues with the next clause.

The compiler output is especially useful for tracking down errors due to unintentional split-definition clauses, or "missing" predicates lost by a misplaced period. By viewing the listing you can see if all the clause definitions are contiguous, and if all the predicates you thought were in the file actually were.

Errors

Other than syntax errors, the most common cause of errors will be caused by trying to compile clauses or predicates which are too complex. In these cases you will likely receive a fatal error of the form "XXX stack full" where XXX is heap or control or local or trail. The easiest way around this trouble is to use 32-bit mode to compile the program, and set your stack sizes large enough in the .cfg file.

You might also run into difficulties loading programs that contain predicates with large numbers of clauses. The .cfg file parameters maxclauses, destbuf and srcbuf can all be used to increase the capacity of the Prolog engine when loading a compiled program.

Complex Clauses

The complexity of a clause is determined approximately by the number of variables in the clause. If this number gets too large then the compiler may fill a stack and cause a fatal error. To correct this it may be necessary to take a clause and split it into a number of smaller clauses.

The compiler optimizes its processing of clauses with only one or zero goals in the body. This permits such clauses to be compiled more quickly. This optimization has no effect on the resulting compiled code (it just arrives at it sooner).

The downside of this is a slight chance that such a clause will cause a stack to overflow on compilation. This is usually due to having very complex clauses, e.g.,

To resolve this problem, cast the clause in the following form: By forcing the clause to have a more complex body (and a simpler head) the optimization technique described above will not be used by the compiler.

Complex Predicates

Complex predicates generally occur because there are too many clauses in a given predicate. The precise number depends on the relative complexity of the individual clauses. The Amzi! Prolog Compiler can compile predicates with many hundreds of clauses, so in practice we do not expect this to be much of a problem.

Where it is a problem try dividing predicates into two or more predicates, e.g.,

can be replaced with a slight performance penalty by:

Dynamic Clauses in Compiled Code

Sometimes it is desireable to have some predicates in an application be compiled and others maintained in the dynamic database (interpreted). The dynamic directive lets you tell the compiler which clauses are to remain in the dynamic database, so they can be manipulated dynamically by the application.

It's syntax is:

You can have multiple dynamic directives if you prefer.

For example, in this code fragment the loc/2 predicate will be interpreted while the other predicates are compiled:

Multifile Directive

Sometimes it is desireable to have the clauses that define a predicate to be split between several files. If this is the case, you must let the compiler know with the multifile directive.

It's syntax is:

You can have multiple multifile directives if you prefer.

Discontiguous Directive

Sometimes it is desireable to have the clauses of some predicates defined in different places in a source file. In that case, the predicates must be listed in a discontiguous directive to avoid load errors.

It's syntax is:

You can have multiple discontiguous directives if you prefer.

Latent Expressions

A latent expression is a term which again represents a goal. However latent expressions are executed as the file is loaded. A latent expression is indicated using the prefix :-. The compiler translates :- body. to the special goal latent_exp :- body. The Prolog loader knows that as it encounters latent_exp :- .. clauses they are to be executed and not added to the database.

For example consider:

This file is compiled. When it is loaded (either as part of an application or as a single Prolog Object file) it will print out three lines at the terminal: while loading the two clauses. Then the listener will be entered.

The one exception to this treatment is if the body of :- is the single term op/3. In this case not only is latent expression latent_exp :- op(..) produced but op(..) is also executed as a compiler directive. This function is provided as a convenience (see above).

Main Predicate

The Prolog system treats the predicate main/0 in a special way. When Prolog initializes it looks to see if there is a definition for the predicate main/0. If there is a definition, then main/0 is proved instead of entering the listener (see the section on alis). Thus for an application to "load and run" it should contain a definition: Then when the application loads it will immediately try to prove the user-written predicate run_user_program.

Using Modules

Amzi! Prolog supports the concept of modules. A module is a compiled file in which certain predicates are visible outside the file and other predicates are visible only inside the file. This permits reusable code modules to be written safe in the knowledge that the definition of predicates in one file will not interfere with the definition of a like-named predicate in another file when both are loaded into the Prolog runtime system.

A module is a compiled file which contains at least one of the following declarations in the source code:

We refer to these declarations as import and export lists. There may be any number of import and export lists in one module.

Modules are an optional feature of Amzi! Prolog. If there are no import/export lists then all predicates in the file are 'visible' to predicates in all other files.

Importing Predicates

The declaration: in a compiled file indicates that the predicates listed are defined externally to this file but are used in the body of some clauses within this file. The file is made into a module upon compilation.

It is not necessary to explicitly import any predicate contained in the Prolog runtime and consequently documented in this manual, although declaring such a predicate to be imported causes no harm.

Exporting Predicates

The declaration: in a compiled file indicates that the predicates listed are defined internally in the file but are to be made visible externally. A module which wishes to use any of the predicates would have to, in turn, import them. The occurrence of the export declaration causes the file to be made into a module upon compilation.

There is no difference between import and export. Both directives make predicates visible to other modules. Both names are provided for clarity and readability.

Hidden Predicates

Predicates defined inside modules which are not on the export list of the module are said to be hidden. This means that the predicate is invisible outside of the module in which it is defined. Indeed there may be several definitions of a predicate with a given Name/Arity in different modules, and these definitions will never conflict if the files are linked or loaded together.

Note, this is not the case when the source file is loaded into the interpreter. The predicates only stay hidden in the compiled version of the file.

This allows you to construct library modules and link or load them together in applications without having to worry about support-predicates hidden inside each module colliding. Typical offenders in this regard are append/3 and member/2 which are often needed to accomplish some goal within the definition of a "bigger" predicate, but are not required outside of these definitions.

It is an error for a goal to appear in the body of a clause in a module where the predicate of that goal is neither imported (explicitly or implicitly) nor defined within the module. Attempting to load such a module (or run an application containing this module) will cause an error to occur at load timea message will be printed out and you will be returned to the operating system.

 There are some predicates which should always be exported even if they are used only inside a compiled module. These are any predicates which are arguments to call/1 (the interpreter), bagof/3, findall/3 or setof/3 in the file.

Modules Example

As an example, let's consider three files:
 
:- export a1/0.
a1 :- b.
b :- write('First file\n').
 
:- export a2/0, b/0.
a2 :- b.
b :- write('Second file\n').
 
:- export doit.
:- import a1/0, a2/0, b/0.
doit :- a1, a2, b.

Compile all three files and load then into the runtime system. Now give the goal:

Notice that there are two definitions for b/0; however that definition in the first file is visible only in the first file (since it is not exported, and the first file is a module). Thus the b/0 in the body of "a1 :- b." refers to the code in the first file.

The b/0 in the second file is exported. The b/0 in the clause "a2 :- b/0." refers to the definition in the second file.

The third file is also a module which uses a1/0 and a2/0. It also uses b/0. Which b/0 does it use? b/0 in the first file is not exported and so it cannot be the definition from file 1, whereas the definition of b/0 in the second file is visible (being exported) and so it is this definition used in the third file.

Notice that the predicate write/1 is not explicitly declared to be imported it is a globally known as a built-in predicate.

Loadtime Errors

Loading a compiled module with a hidden predicate which is not defined inside the module (or a linked file containing a module with this property) is a load time error.

When the loader (the portion of the runtime system responsible for loading compiled code) detects such a predicate it will print out its Name/Arity and then abort:

In the case where the loaded file is a linked file the name of the missing predicate will be preceded by a number (which is the number, in link order, of the file which contains the offending predicate): In this example, foo/3 was used in the 4th file linked into the application.

If a stand-alone application has not defined the predicate main/0, or the predicate main/0 fails, then an error occurs and you are returned to the operating system with the message:

Copyright ©1987-2000 Amzi! inc. All Rights Reserved.