Chapter 3 The JoCaml language and system
3.1 Jocaml Tools
Most jocaml tools are Objective-Caml tools with an initial
``j''. For example, ocamlc is called jocamlc, ocaml
is called jocaml, ocamlrun is called jocamlrun, and
so on. There is no compatibility between Objective-Caml compiled files
and Jocaml compiled files, even with a same source file.
In this section, we first describe new options for old Objective-Caml
tools, and then new tools designed for Join programmers.
3.1.1 jocamlc
New options:
-
-join
Link the executable with a ``thread-safe'' version of the standard
library, needed for join programs.
- -nodyncheck
Modules containing location definitions are dynamically linked
during execution. Modules and primitives dependence are resolved
during the first execution of each dependencies. However, by default,
these dependencies are also checked by the compiler before linking.
This option prevent the compiler from checking these dependencies.
This is useful if you create a location with a dependence to a
module or primitive which is not immediately linked, but which will
be available where the location will migrate.
- -make_vm
This option enables you to create new runtime (as jocamlrun)
to execute bytecode programs using non standard C and bytecode
libraries, without using the -custom option. Executables for these
new runtimes will only contain their own bytecode, while finding
libraries bytecode and external primitives in the new runtime. The
name of the runtime is specified with the -o runtime
option. The compiler creates two files, runtime and runtime.cmc. runtime is the runtime executable and
runtime.cmc is used by the compiler to create programs
executing on runtime.
- -use_vm absolute-path/runtime
This option is used to create programs executing on runtime.
The associated file runtime.cmc must be available in the
compiler search path (the standard directory, the current one and
those specified with the -I option). The created program will
always search its runtime at the absolute position fixed by the path
specified during the compilation. However, the runtime may not be at
this position during the compilation.
- -cca cclib-option
Creates a bytecode library (same as the -a option), and
specifies an argument which will be passed to the C-linker when
linking a program with this library. For example, the graphics
library is created with:
jocamlc -cca -lX11 -lgraphics -o graphics.cma
graphics.cmo
- -noautolink
This option prevents the compiler from using arguments given with
the -cca option. It is most useful when, for some reasons,
these arguments must be temporary overridden.
- -l library
Equivalent to ``library.cma''
3.1.2 jocamlopt
Mixing native code and bytecode
As an experimental feature, the Jocaml system enables you to mix
native code and bytecode in a same executable. This feature is useful
since join constructs are only available using bytecode, whereas good
performances are achieved using native code (As an example, see
mandel.opt in the distribution).
In a mixed runtime, native code program is started first. At the end of the
native code, the bytecode program is executed. The bytecode may call
native functions through dedicated ``call-back'' modules. These modules
are Objective-Caml source files with a ``.mlx'' suffix, which, when
compiled with jocamlopt, generate two object files, one in
native code, with real functions, and one in bytecode, which functions
are only wrappers to the native code functions.
New options:
-
-join
Same as for jocamlc.
- -make_vm
Same as for jocamlc. However, the runtime may
also contain native code.
- -byte source
ml Compile file source.ml in bytecode instead of native code.
- -cca cclib-option
Same as for jocamlc.
- -l library
Equivalent to ``library.cmxa''
- -lbyte library
Equivalent to ``library.cma''
3.1.3 jocrun, joc, jocl and joctop
The jocaml system includes three runtimes: jocamlrun, jocrun and jogrun. jocrun is created using the -make_vm option, and contains the thread-safe standard library,
the Unix library, the bytecode thread library and the join
library. Thus, it can be used to execute most standard Join
programs. To compile programs using this runtime, you can use the
command jocamlc -use_vm jocaml-bin-directory/jocrun other-args, or use the dedicated program joc: joc other-args.
If you need to link your program with other libraries which are not
contained in the jocrun runtime, you can use the command jocamlc -join -l unix -l join other-args, or the dedicated
program jocl: jocl other-args.
Both joc and jocl programs add a -linkall option to
include all modules in the program created. This is safer, since join
programs may receive locations from other programs which have more
dependencies than the original program.
The jocaml system also includes two toplevels: jocaml and joctop. jocaml is the Objective-Caml toplevel, without join
constructs, whereas joctop is the Objective-Caml toplevel
compiled on the jocrun runtime, and thus extended with join
constructs (join definitions and locations).
3.1.4 jocns, jocclient
jocns is a name server used by the Ns module. It can be
started directly. It is not started automatically by a program calling
Ns.register or Ns.lookup. The default port is 20001, but can be
modified with the JNSPORT environment variable. The JNSNAME
environment variable can be used to specify the name server machine
if it is not the localhost. The user name is appended to the resource
name that is registered or looked up. If no user name is specified
(for instance in the environment variable USER), the default
user name pub is used. To change the user name in a jocaml
program, simply set the Ns.user string reference:
Ns.user := "toto"
jocclient is a generic client for JoCaml applications: It
queries the default name server (jocns) for a generic channel
name ``newclient'' of type Join.location * string * string list
-> unit, and applies this channel to its location, the user login
name, and its list of arguments. The Genserver module enables
programmers to easily create servers for generic clients. The name
server host and port can be set either by environment variables
(JNSNAME and JNSPORT) or by options (-ns_host and -ns_port). The queried name can be modified by the -ns_name
option and the user name used by -ns_user.
3.1.5 jogrun, jogc and jogclient
jogrun is a specialized runtime including the Join library and
the Graphics library. jogc is the equivalent of joc for
building programs using this runtime, and jogclient is a generic
client built on this runtime (thus, this client must be used to
receive locations needing the Graphics library). It can be used for most
examples of the distribution (Tron, Bomberman, Bataille, Pong, Mandel,...).
3.2 Syntax and Examples
Expressions |
declaration |
::= |
Ocaml-declaration |
|
|
let def automata-definition |
|
|
let loc join-locations |
automata-definition |
::= |
automaton [and automata-definition ] |
automaton |
::= |
join-pattern = process [or automaton ] |
join-pattern |
::= |
channel-decl [ | join-pattern ] |
channel-decl |
::= |
synchronous-name arguments |
|
|
asynchronous-name arguments |
synchronous-name |
::= |
Ocaml-lower-ident |
asynchronous-name |
::= |
Ocaml-lower-lident ! |
arguments |
::= |
Ocaml-pattern |
process |
::= |
|
|
|
final-process |
|
|
final-process | process |
|
|
declaration in process |
|
|
if expression then final-process |
|
|
expression ; process |
final-process |
::= |
reply [expression] [to Ocaml-lower-lident] |
|
|
asynchronous-send |
|
|
if expression then final-process else final-process |
|
|
match expression with |
|
|
Ocaml-pattern -> final-process |
|
|
[ | Ocaml-pattern -> final-process
[...]] |
|
|
loc join-locations |
|
|
{ process } |
expression |
::= |
Ocaml-expression |
|
|
spawn { process } |
asynchronous-send |
::= |
expression expression |
join-locations |
::= |
location-definition [and join-locations] |
location-definition |
::= |
location-name[def automata-definition] |
|
|
do final-process |
Types: |
channel-type |
::= |
function-type |
|
|
asynchronous-channel |
asynchronous-channel |
::= |
<< core-type >> |
3.2.1 Join definitions
Join channels are defined with the let def construct.
For example, the definition of a memory cell flows (this lines have
been written in the joctop program):
# let def new_cell s =
let def get () | state! s = reply s | state s
or set s | state! _ = state s | reply ()
in
state s | reply (get,set)
;;
val new_cell : 'a -> (unit -> 'a) * ('a -> unit) = chan
In this definition, we create a synchronous name new_cell,
with one parameter. When this name is called, it creates three names,
two synchronous (get and set) and one asynchronous (note
the ! notation) (state). Notice that all names are lowercase standard Objective-Caml
identifiers. Multiple join patterns in the same definition are
introduced with the or keyword. Continuations for reply
constructs are not specified, since there is only one synchronous name
per join pattern in this example. With join patterns containing
several synchronous names, continuations should have been specified
with the reply value to name construct.
The type of new_cell is a polymorphic synchronous channel,
returning a tuple of two synchronous channels. Asynchronous calls use
the functional application construct (state s), but only inside
a process construct. Indeed, state s; would raise a type error,
since state is an asynchronous channel, used in a functional
application inside an expression (the context before a semi colon (;)
is always an expression, and not a process). In the same way, reply constructs and parallel compositions (|) can only appear
in processes. However, the spawn { process }
construct can be used to fork a process from within an expression.
# let (get_a,set_a) = new_cell "hello";;
val get_a : unit -> string = <fun>
val set_a : string -> unit = <fun>
Finally:
# let _ =
print_string (get_a());
print_char ' ';
set_a "world";
print_string (get_a());
print_newline ()
;;
hello world
- : unit = ()
Other more complicated examples can be found in the examples/
directory of the distribution.
3.2.2 Location definitions and migration
Here is the definition of a location mobile, with two included
join definitions, one with one synchronous name go, and another
close to the previous memory cell:
# let loc mobile def
goto location = go location; set_position location; reply
and set_position location | position! _ = position location | reply
or get_position () | position! location = position location | reply location
do { position here }
;;
val mobile : Join.location = <abstr>
val goto : Join.location -> unit = chan
val position : <<Join.location>> = chan
val set_position : Join.location -> unit = chan
val get_position : unit -> Join.location = chan
First, we can notice that the type of locations is Join.location, ie location from the Join module. The
Join module packages basic primitives for the join-calculus
language. Here, we also use the Join.go function, which is used
for location migration, and Join.here, which is the predefined
toplevel location of the program.
Second, the join-calculus init process end construct
is replaced by the struct location-items end
construct. This construct enables you to define local values inside
the location, which are not available from outside the location.
During location migration, join automata (definitions) and
Objective-Caml values behave differently. Join automata are always
unique. Consequently, when they migrate with their location to another
runtime, local pointers to these automata from the old runtime are
replaced by remote pointers to the new runtime. On the contrary,
Objective-Caml values are always copied. For Objective-caml primitives
and functions, this means that next call to one of them will yield to
an attempt to find the function code or the primitive C code locally
in the new runtime. This will success if the module where the function
was defined is contained in the new runtime, or if the module code has
been copied during the migration (indeed, the smallest unit of
bytecode is the module, thus, all the module code migrate when the
location code defined in this module migrate). This will abort and an
Reloc.ModuleNotAvailable exception will be raised if the
function code is not present. However, since the default runtime
contains the standard library, the Unix module, the Thread
module and the Join module, most needed functions are always
available, and other locations dependencies should be defined in the
same module as the locations.
3.2.3 Name server
The join library contains a Ns module used to make requests to
the jocns name server. This module contains two functions:
val lookup : string -> 'a metatype -> 'a
val register : string -> 'b -> 'b metatype -> unit
val user : string ref
Ns.lookup is used to lookup the value associated with a
particular name in the name server, whereas Ns.register is used
to associate a value to a name in the name server. Ns.user is
appended to every name for a lookup or a register, and is equal by
default either to the environment variable USER, or to pub
if this variable is not set. Since it is a reference, its value can be
modified to access a particular resource registered by another user.
If no name server is running during these requests, Ns.lookup
and Ns.register raise an exception. The name server address is
hostname:20001 by default, and can be overridden with JNSNAME and JNSPORT environment variables.
Ns.lookup and Ns.register require an extra argument of
type 'a metatype which is used to check the types of the requested
value with the real type of the value in the name server. This extra
argument is set with the vartype keyword, and a type constraint
on the 'a metatype parameter type.
For example,
# Ns.register "mobile" mobile (vartype:Join.location metatype);;
will register the previously defined location mobile with the
name mobile and the type Join.location.
3.2.4 Distributed Garbage collection
A distributed garbage collector is implemented to collect unreachable
objects. It is based on the SSPC garbage collector. This garbage
collector can only collect acyclic distributed garbage. Distributed
garbage collection is triggered by major garbage collections.
To suppress distributed garbage collection, the environment variable
JNODGC can be set before starting the runtime. To avoid
distributed garbage collections only during critical periods, use the
two functions Join.dgc_stop and Join.dgc_restart ().
3.2.5 Failures
The Join module contains two functions to handle failures:
val halt : unit -> 'a
val fail : location -> unit
Join.halt is used to halt a location. Currently, it does not
work for the toplevel location. Join.fail is used to detect
location failures. Indeed, a call to Join.fail only returns when
the location in argument has failed. Currently, only locations halted
with Join.halt are detected. Moreover, this detection is
immediate for locations in the same runtime, and lazily propagated by
the distributed garbage collector for different runtimes. As a
consequence, only active runtimes may detect location failures.
3.2.6 Distributed objects
The Objective-Caml objects system has been extended with distributed
objects: Objective-Caml objects are also locations. Thus, they can
migrate using the Join.go function with all automata, locations
and objects they contain. More precisely, any object or location that
migrated to this object will be carried along, as well as any channel
definition, location, or object created inside the migrating object.
The function Join.goo can be used to migrate a location into an
object, or an object into another object.
Moreover, all methods calls are implemented as a new process started
in the object (as location). Thus, running method threads migrate with
the object.
3.2.7 Exceptions
Exceptions raised in automata processes are propagated back to all
synchronous names which will not receive a reply owing to the raised
exception:
#let def a() | b() | c() = reply to a |
raise Not_found; { reply to b | reply to c}
;;
val c : unit -> unit = chan
val b : unit -> unit = chan
val a : unit -> unit = chan
In this example, the call to a will receive a reply, whereas the
calls to b and c will raise the Not_found exception.
3.2.8 Join module
Most join-calculus primitives are available from the Join
module:
type location
type space_id
val new_port : int -> unit
val server : unit -> unit
val here : location
val go : location -> unit
val goo : < > -> unit
val halt : unit -> 't
val fail : location -> unit
val getRemoteService : space_id -> string -> 'n metatype -> 'n
val getLocalService : string -> 'o metatype -> 'o
val setLocalService : string -> 'p -> 'p metatype -> unit
val getSpaceService : Unix.inet_addr -> int -> string -> 'q metatype -> 'q
val dgc_stop : unit -> unit
val dgc_restart : unit -> unit