Previous Contents Next

Chapter 7   The compiler (jcc), standard usage

This chapter describes the standard usage of the join-calculus compiler jcc, which compiles join-calculus source files to bytecode object files and link these object files to produce standalone bytecode executable files. These executable files are then run by the bytecode interpreter jcrun.

7.1   Overview of the compiler

The jcc command has a command-line interface similar to the one of most C compilers. It accepts several types of arguments:

The output of the linking phase is a file containing compiled bytecode that can be executed by the join-calculus bytecode interpreter: the command named jcrun. If a.out is the name of the file produced by the linking phase, the command
jcrun a.out arg1 arg2 ...argn
executes the compiled code contained in a.out, passing it as arguments the character strings arg1 to argn. (See chapter 10 for more details.)

On most Unix systems, the file produced by the linking phase can be run directly, as in:
./a.out arg1 arg2 ...argn
The produced file has the executable bit set, and it manages to launch the bytecode interpreter by itself.

7.2   Options

The following command-line options are recognized by jcc.

-c
Compile only. Suppress the linking phase of the compilation. Source code files are turned into compiled files, but no executable file is produced. This option is useful to compile modules separately.

-i
Cause the compiler to print all defined names (with their inferred types) when compiling an implementation (.j file). This can be useful to check the types inferred by the compiler. Also, since the output follows the syntax of interfaces, it can help in writing an explicit interface (.ji file) for a file: just redirect the standard output of the compiler to a .ji file, and edit that file to remove all declarations of UN-exported names.

-I directory
Add the given directory to the list of directories searched for compiled interface files (.jio) and compiled object code files (.jo). By default, the current directory is searched first, then the standard library directory. Directories added with -I are searched after the current directory, in the order in which they were given on the command line, but before the standard library directory.

-o exec-file
Specify the name of the output file produced by jcc. When linking occurs, the default output name is a.out, in keeping with the Unix tradition. If linking does not take place (see the -c option) and that one file x.j is given, the default output name is x.jo.

-v
Cause the compiler to print various messages on what it is doing. This flag can be given twice to make the compiler very talkative. This is useful for debugging your programs, your installation or the compiler itself.

-comment
Include comment in produced code. This is useful for debugging your programs (see the -t trace option in the description of jcrun in chapter 10).

-noanalyze
Suppress automata optimization.

-noschedule
Suppress scheduling optimizations.

-noopt
Suppress all optimizations.
The last three options are merely here for compiler development purposes. Enabling them may produce incorrect executables.

7.3   Modules and the file system

This short section is intended to clarify the relationship between the names of the modules corresponding to compilation units and the names of the files that contain their compiled interface and compiled implementation.

The compiler always derives the module name by taking the base name of the source file (.j or .ji file). That is, it strips the leading directory name, if any, as well as the .j or .ji suffix. For instance, compiling the file mylib/misc.j provides an implementation for the module named misc. Other compilation units may refer to components defined in mylib/misc.j under the names misc.name; they can also do open misc, then use unqualified names name.

The .jio and .jo files produced by the compiler have the same base name as the source file. Hence, the compiled files always have their base name equal to the name of the module they describe (for .jio files) or implement (for .jo files).

When the compiler encounters a reference to a free module identifier mod, it looks in the search path for a file mod.jio and loads the compiled interface contained in that file. As a consequence, renaming .jio files is not advised: the name of a .jio file must always correspond to the name of the compilation unit it implements. It is admissible to move them to another directory, if their base name is preserved, and the correct -I options are given to the compiler.

Compiled bytecode files (.jo files), on the other hand, can be freely renamed once created. That's because the linker never attempts to find by itself the .jo file that implements a module with a given name: it relies instead on the user providing the list of .jo files by hand.

7.4   Common errors

This section describes and explains the most frequently encountered error messages. Apart from lexing and syntax errors you are more likely to encounter the following errors:
cannot find file filename
The named file could not be found in the current directory, nor in the directories of the search path. The filename is either a compiled interface file (.jio file), or a compiled bytecode file (.jo file). If filename has the format mod.jio, this means you are trying to compile a file that references identifiers from module mod, but you have not yet compiled an interface for module mod. Fix: compile mod.ji or mod.j first, to create the compiled interface mod.jio.

If filename has the format mod.jo, this means you are trying to link a bytecode object file that does not exist yet. Fix: compile mod.j first.

If your program spans several directories, this error can also appear because you haven't specified the directories to look into. Fix: add the correct -I options to the command line.

Finally, the error may steem form a misspelling in a module name. That is, you meant mod.id but wrote mod'.id, where mod' is a non-existent module.

this expression has type t1, but is used with type t2
This is by far the most common type error in programs. Type t1 is the type inferred for the expression (the part of the program that is displayed in the error message), by looking at the expression itself. Type t2 is the type expected by the context of the expression; it is deduced by looking at how the value of this expression is used in the rest of the program. If the two types t1 and t2 are not compatible, then the error above is produced.

name name has no continuation
This error is flagged by the typechecker when Name name is not a synchronous name and that a reply ... to name construct exist in the program.

Cannot determine continuation
This may happen using the abbreviated reply ... construct. The compiler is able to change it into the full reply ... to \var{name} construct, provided name is the only name defined on the left hand-side of a join-definition. That is, let f(x) = reply ... works, whereas let f(x) | g(y) = reply ... will fail and raise the ``Cannot determine continuation'' error.

name: t1 is not an instance of t2
On the one hand, the name name is defined in an implementation (a .j file) and the compiler inferred type t2 for it; on the other hand name is made public in the corresponding interface (a .ji file) with type t1. The error occurs when t1 is not a valid instance (i.e., a more specific type) of t2.

required name: name is not provided
This error occurs when an interface exports name and that the corresponding compilation unit does not define name.

type of exported name is not closed: t
This error occurs when name is defined in a .j file with no .ji associated file. Hence, name is automatically exported from the module. Type variables ('a, 'b, ...) in a type t can be in either of two states: generalized (which means that the type t is valid for all possible instantiations of the variables) and not generalized (which means that the type t is valid only for one instantiation of the variables. In a let binding let name1(x) | name2(y) = proc, the type-checker tries to generalize as many type variables as it can. Generalizing type variables that appear in the types of several co-defined names (name1 and name2 here) would lead to runtime error, when these names are used inconsistently. Note that non-generalized type variable are prefixed by an underscore in compiler output (they yield: '_a, '_b,...). Another way to introduce non-generalized type variable is using the let name = expr construct, where type variable are never generalized.

Non-generalized type variables in a type cause no difficulties inside compilation unit (the contents of a .j file) but they cannot be allowed inside interfaces (.ji or .jio file), because they could be used inconsistently later. Therefore, the compiler flags an error when a compilation unit defines and exports a name name whose type contains non-generalized type variables. There are three ways to fix this error:
name : t1 is not a closed instance of t2
This error is similar to the previous one. On the one hand, the name name is defined in an implementation (a .j file) and the compiler inferred type t2 for it; on the other hand name is made public in the corresponding interface (a .ji file) with type t1. The error occurs when t1 is not specific enough to instantiate the non-generalized variables of t2.

undefined global mod.name
This error appears when trying to link an incomplete or incorrectly ordered set of files. Either you have forgotten to provide an implementation for the compilation unit named mod on the command line (typically, the file named mod.jo) Fix: add the missing .j or .jo file to the command line. Or, you have provided an implementation for the module named mod, but it comes too late on the command line: the implementation of mod must come before all bytecode object files that reference mod. Fix: change the order of .j and .jo files on the command line. Note that mutually recursive modules are not possible. The reason for this is that modules not only define name but also include code to be executed to initialize these names. This initialization code must be executed in some order that guarantees that names are initialized before they are used

unbound var: name
This error occurs when a program (a .j file) refers to a name and when the compiler is unable to find a definition for that name. When name is a qualified name mod.name, the compiler has found a mod.jio file but this file contains no declaration for name. Usually, name is misspelled or the mod.jio file that is read is not the expected one.

unbound type constr: name
This error occurs when an interface (a .ji file) refers to a type constructor name (sys.int, ml.hashtbl are type constructors) and when the compiler is unable to find a definition for that name.

this is an external primitive: mod.name
Primitives such as ``halt'' or externals such as ``print_string'' are not real join-calculus port names. They can be applied to their arguments (as in print_string("coucou")) but cannot be passed as arguments or given back as results (as in reply print_string to ...). This error occurs in the latter situation, when a primitive or external is used as a first-class name. Fix: introduce a new definition, for instance the following program does not compile and an error this is not a port name: sys.print_string is flagged:
let call(p,x) = p(x) ; reply
;;

do call(print_string,"coucou")
;;
Whereas this one does compile:
let call(p,x) = p(x) ; reply
;;

let my_print_string(s) = print_string(s) ; reply
;;

do call(my_print_string,"coucou")
;;
continuation outside of its scope: name
Synchronous names carry an implicit extra continuation argument. The name name is determined synchronous when its definition includes reply ... to name constructs, which are translated by the compiler into invocations on the implicit continuation of name. For efficiency reasons, continuation are treated specially. In particular, continuations cannot occur free in a definition, as the continuation of f in the following example, where an error continuation outside of its scope: f is flagged:
let f(x) =
  let g(y) = reply x+y to f in
  f(x+1)
;;
However, the error may also stem from a misspelling in a continuation name.

Threading: double answer on the continuation of name
To enable compilation of synchronous calls as function calls as well as a simple threading policy, continuation usage (i.e. reply ... to ... constructs) is severely restricted. This error is flagged when two different threads both contain an invocation on the continuation of name, as in reply 1 to name | reply 2 to name.

Threading: no answer on the continuation of name
This error is flagged when one branch of a thread contains an invocation on the continuation of name, whereas another does not, as in if b then {reply to name} else {}.

Threading: reply .. to .. cannot be on the left hand-side of | here
This error is flagged when the compiler begs for user's help in its attempt to determine the so-called ``principal thread''. Informally, any guarded process that include at least one reply ... to ... construct needs a principal thread to run. More specificaly, such a guarded process is not forked, instead it executes on one of the synchronous callers thread (the principal thread). The compiler is not very clever and aborts when it fails to discover a principal thread.
All other errors are bugs, either in the compiler or documentation. Please report them.
Previous Contents Next