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:
-
Arguments ending in .ji are taken to be source files for
compilation unit interfaces. Interfaces specify the names exported by
compilation units: they declare value names with their types and
declare abstract data types. From the
file x.ji, the jcc compiler produces a compiled interface
in the file x.jio.
-
Arguments ending in .j are taken to be source files for compilation
unit implementations. Implementations provide definitions for the
names exported by the unit, and also contain expressions to be
evaluated for their side-effects. From the file x.j, the jcc
compiler produces compiled object bytecode in the file x.jo.
If the interface file x.ji exists, the implementation
x.j is checked against the corresponding compiled interface
x.jio, which is assumed to exist. If no interface
x.ji is provided, the compilation of x.j produces a
compiled interface file x.jio in addition to the compiled
object code file x.jo. The file x.jio produced
corresponds to an interface that exports everything that is defined in
the implementation x.j.
-
Arguments ending in .jo are taken to be compiled object bytecode. These
files are linked together, along with the object files obtained
by compiling .j arguments (if any), and the join-calculus standard
library, to produce a standalone executable program. The order in
which .jo and .j arguments are presented on the command line is
relevant: compilation units are initialized in that order at
run-time, and it is a link-time error to use a component of a unit
before having initialized it. Hence, a given x.jo file must come
before all .jo files that refer to the unit x.
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:
-
Add a .ji file to give a monomorphic
type (without type variables) to name. For instance, assuming
you need a reference cell for holding integers, instead of
writing only the irefcell.j file
let get() | state(s) = state(s) | reply s to get
and set(s) | state(t) = state(s) | reply to set
(*
jcc -i output :
val get : <> -> <'_a>
val set : <'_a> -> <>
val state : <'_a>
*)
write a irefcell.ji file:
val get : <> -> <int>
val set : <int> -> <>
val state : <int>
- If the faulty names do not need to be exported, then add a .ji
file that does not export them.
- If you really need names with polymorphic types,
then their definitions should be changed. The most simple way to do this
is to encapsulate the faulty names together in a local definition.
For instance, to make a polymorphic reference cell, write the
following refcell.j file:
let ref(s0) =
let get() | state(s) = state(s) | reply s to get
and set(s) | state(t) = state(s) | reply to set in
state(s0) | reply get,set to refcell
;;
Then both names get and set are packed together as a result of an
invocation of refcell and their common type variable is generalized
when the type of refcell is generalized.
# jcc -i refcell.j -c
val ref : <'a> -> <<> -> <'a> * <'a> -> <>>
To use get and set from another module, you will have to create
fresh reference cells, thereby creating two fresh instances of
get and set:
let gi,si = refcell.ref(0)
;;
let gs,ss = refcell.ref("")
;;
The jcc -i output for compiling this file is:
val gi : <> -> <int>
val si : <int> -> <>
val gs : <> -> <string>
val ss : <string> -> <>
- 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