Showing posts with label ocaml. Show all posts
Showing posts with label ocaml. Show all posts

2013/06/30

Structuring the compiler

Simple is difficult

In the implementation of the L compiler, the focus was put on the readability and simplicity of the source code. As often in computer science, it is difficult to make things simple: I had a complete, working prototype, with parser, typing, cps transformation and compilation to LLVM when I started this blog; I spent all this time cleaning, documenting, and especially simplifying the compiler, publishing the progress along the way.

I believe that the result is worth the work: a simpler compiler is easier to extend, both for myself and future contributors. The challenge will be to keep things easy as more features are added; this is possible only if we wait for things to be well structured before adding the non-essential features1; and if we do not hesitate to perform a local redesign occasionally.

Implementation of the toplevel of the compiler

The toplevel of the compiler, that link all the passes together, is a good example of the results of such a redesign. A design goal of this toplevel is to make each pass communicate with a minimal interface. I had written a first attempt, using a record updated functionally, with each field containing the internal state of one pass. The toplevel consisted in a giant function with nested loops – one loop per pass. This structure was inelegant for a number of reasons:

  • Even if the type of the state could be made abstract, the abstract type was still part of the interface. Moreover, the initial value for this type also had to be provided by the passes.
  • Updating the environment functionally introduced a lot of boilerplate code; even if imperative updates might have helped a little.
  • There was also some boilerplate code to handle the case where a single input could return multiple output (e.g. a datatype declaration in ML defines both a type and some constructors).
  • The nested loop pattern was not really readable.

After a while, I came up with a much simpler design using streams of definitions. The code for a toplevel is then dead-simple:

module Stream = Extensions.Stream

let process_file file =
   let parsetree_stream = Parser.make_stream file in
   let ast_stream = Astfromsexp.from_stream parsetree_stream in
   let cps_stream = Cpstransform.from_stream ast_stream in
   let converted_stream =
     Stream.iter_and_copy Cpsconvertclosures.in_definition cps_stream in
   let llvm_stream = Cpsllvm.from_stream converted_stream in
   Stream.iter (fun elt → Llvmexec.exec_nodef () elt) llvm_stream

The actual implementation also interposes optional printer to see the contents of the stream, which is very useful when debugging the compiler.

The stream interface make it easy to add new passes, completely remove the state from the interface, factorize the pattern when a pass can produce several elements at once, retain the minimal memory consumption of the nested loop design, and can be easily updated to a parallel pipelining implementation.

Interface to the passes and modular implementation

Interface of each pass

The interface of each pass is made minimal. Each pass only declares:

  • The type of the elements in the stream (e.g. tokens, typed AST definitions, CPS definitions…). The type is not abstract: it is used by the following pass to convert them to its own representation (e.g. the CPS pass use the AST type to perform CPS transformation, the LLVM pass use the CPS type to compile to LLVM).
  • A function mapping an input stream to an output stream.
  • Possibly, a function to print elements in the stream, used for debugging the compiler.

And that is all. The interface of each pass clear is minimal, which allows to study or change it independently.

Note: this way of structuring things imply that transformation from one pass to the next is done in the second pass. (This implies that the second pass depends on the first one).

An alternative is to expose functions to build elements in the second pass, and move the transformation code from the beginning of the second pass to the end of the first pass. In this case, the first pass depends on the second pass. One advantage is that the first pass does not have to expose the type of its internal format. I am considering using this structure for the parser, where a "parsetree" is produced, but whose type is not very interesting outside of the parsing phase; while the building functions for the AST are more interesting.

Modules inside each pass

Inside each pass, the code is structured around a hierarchy of modules and interfaces. The idea is that each interface progressively hides more details about the implementation of the pass, until the top where we get the minimal interface presented earlier. Intermediate levels show up when we manage to group a complex piece of code into a simple interface. For instance in the CPS pass, there are four intermediary modules:

  • CPStransform, performing AST to CPS transformation. Its implementation consist of 4 sub-modules, that include the compilation of pattern matching; its interface only consists of a single function.
  • CPSconvertclosure is a slightly less complex algorithm; its interface also consists in a single function.
  • CPSshrink will perform shrinking reductions and optimizations. I have not yet implemented the cleaned version of this module.
  • CPSbase provides general functions for creating and updating CPS terms; the interface of this module is more complex, but hides many details about the internal CPS data structure (which is quite complex), and prevents direct writes to this structure.

Structuring the code around hierarchical, modular interfaces and implementations thus allow intermediate levels of abstractions; when completing a task one is provided with just the needed information, the right degree of abstraction; this helps to focus. The hierarchy also decrease, and thus simplifies, the size of the interfaces. OCaml was perfect for this job, but C, to a lesser extent, also allows this kind of modular programming.

Comparison with object orientation

Note that this design is not at all object-oriented. I think that when providing a new abstract type, packing it with all the methods allowing to access or update objects of this type is an excellent idea. However by using only object-oriented interfaces, the tendency is to to present all methods of an object in a flat manner, and implement all of them in the same class and file. This is problematic when there are many methods (which should be grouped by themes), or that some methods are lengthy algorithms (the algorithm should be set in a separate file). Modules allow such grouping and separation of methods, while creating a new usual object-oriented classes would not work (because we operate on an object defined in another class). Of course there are object-oriented work-arounds; but I think that modules and interfaces with abstract types solve the problem more elegantly (while still allowing "type + methods"-style interfaces). I hope that the code for the L compiler, written in OCaml, shows my point.

Footnotes:

1 I have a huge and ever-growing list of extensions I would like to experiment or add to the L language

2012/12/20

Using OCaml packages with ocamlbuild: a recipe

Using OCaml packages with ocamlbuild: a recipe

Using OCaml packages with ocamlbuild can raise different error messages that are difficult to trace; there is a documentation ( http://brion.inria.fr/gallium/index.php/Ocamlbuild_and_module_packs ) that helps, but does not provide a step-by-step guide with common pitfalls, so here is one.

Compilation of bytecode files

First create a project with the following files:

File: plu/bla.ml

let bla = 3;;

File: plu/bli.ml

let bli = Bla.bla;;

File: plu.mlpack

plu/Bla
plu/Bli

File: use.ml

Plu.Bli.bli

Note: all the files do not have to be in a directory named plu/; this is just my convention.

Try to compile:

ocamlbuild use.byte
/usr/bin/ocamlc -c -o use.cmo use.ml
+ /usr/bin/ocamlc -c -o use.cmo use.ml
File "use.ml", line 1, characters 0-11:
Error: Unbound module Plu
Command exited with code 2.

Compilation will not work, probably because by default Ocamlbuild does not traverse directories. It will work if you use the -r option to ocamlbuild, or create a (even empty) _tags file at the root of the project, that will allow ocamlbuild to traverse the directories:

ocamlbuild use.byte
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlc plu.cmo use.cmo -o use.byte

Notice that ocamlbuild has added -I plu when compiling modules of the Plu package; so for instance, Bli can access Bla.

This odd behaviour can be the cause of many headaches, so I prefer to document it here (and I filed a bug report about this issue here).

Also note that if you make a mistake in the plu.mlpack file, for instance an error in the name of the directory or in that of the module, ocamlbuild will fail without much warning. It will fail with a compile error, as before; it may also fail at the linking step, if you make a mistake in the plu.mlpack file, but you had successfully compiled a previous version. This problem can also be painful to track, so I hope this help someone.

ocamlbuild use.byte
/usr/bin/ocamlc use.cmo -o use.byte
+ /usr/bin/ocamlc use.cmo -o use.byte
File "_none_", line 1, characters 0-1:
Error: Error while linking use.cmo:
Reference to undefined global `Plu'
Command exited with code 2.

Adding native compilation

You cannot pack a set of .cmx files (resulting from compilation of a file) if you did not specified that the .cmx can be packed when you compile the file. Failure to do that result in an error like this:

ocamlbuild use.native
/usr/bin/ocamldep -modules use.ml > use.ml.depends
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlopt -c -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -I plu -o plu/bli.cmx plu/bli.ml
touch plu.mli  ; if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ; then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
+ touch plu.mli  ; if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ; then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
File "plu.cmx", line 1, characters 0-1:
Error: File plu/bla.cmx
was not compiled with the `-for-pack Plu' option
Command exited with code 1.

The lines:

/usr/bin/ocamlopt -c -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -I plu -o plu/bli.cmx plu/bli.ml

Need to be compiled with the -for-pack Plu option. This is achieved by a simple change in the _tags file: just fill it with

<plu/*.cmx>: for-pack(Plu)

And now everything works:

ocamlbuild use.native
/usr/bin/ocamldep -modules use.ml > use.ml.depends
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlopt -c -for-pack Plu -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -for-pack Plu -I plu -o plu/bli.cmx plu/bli.ml
touch plu.mli  ; if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ; then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
/usr/bin/ocamlopt -c -o use.cmx use.ml
/usr/bin/ocamlopt plu.cmx use.cmx -o use.native

I will update this post if/when I find more weird error messages using packages with ocamlbuild; or if you find such error, just tell me!

Update: linking problem when refering to modules outside of the package

I discovered a new problem with ocamlbuild. The problems occur when you put code in a library which is included, and referenced only inside of a package; in that case ocamlbuild forgets to link with the library. The problem is well described in this mail. The author of that mail also provides a ocamlbuild plugin that works around the problem.

This seems to be a classical bug, and there is a pending bug report for it here.

2012/08/10

Incorporating C code in an Ocaml project using Ocamlbuild

I am currently developping the code generation phase of my L programming language, using LLVM. LLVM provides Ocaml bindings for building LLVM code and executing them, but there are no easy way to inspect the result of execution when complex data structures are produced.

It turned out that writing these C stubs was not hard at all; and the OCaml documentation for doing that is quite good. The most difficult part was fighting Ocamlbuild to incorporate these C stubs into the project.

A simple example project

To get started, here is a sample example project with two OCaml file and one C file.

  • File toto_c.c:
#include <stdio.h>
void toto(void)
{
  printf("Hello from C\n");
}
  • File toto.ml:
external toto_a: unit -> unit = "toto";;

let toto_b () = toto_a(); toto_a();;

This file provides the "ocaml part" of the "toto" library: the additional definition toto_b is implemented in ml, not in C.

In simpler implementations with no OCaml definition of the library, or to provide a separation, the external declarations could be put in a standalone .mli file (e.g. toto.mli).

  • File main.ml:
Test.toto_a();;
Test.toto_b();;

This simple application can be simply compiled using a Makefile/shell script as follows:

ocamlc -c -o toto.cmo toto.ml
ocamlc -c -o main.cmo main.ml
gcc -c toto_c.c # or ocamlc -c toto_c.c
ocamlc -custom toto_c.o toto.cmo main.cmo -o main.byte 

Notes:

  • The -custom flag allows to build a custom runtime, i.e. to embed an OCaml interpreterin the test.byte file, linked with toto.o.
  • Here I compiled the c file using gcc, but it may also be built using ocamlc (the benefit being that ocamlc passes the correct include path options to gcc).

Using ocamlbuild flags

A very simple solution can be obtained by passing flags to ocamlbuild:

ocamlbuild toto_c.o
ocamlbuild main.byte -lflags -custom,toto_c.o

This solution could be used, for instance, in a solution that would use a "Makefile" driver together with ocamlbuild.

My current "pure ocamlbuild" solution

You will not be able to compile this with Ocamlbuild alone without a custom plugin, which is OCaml code (with regular syntax) to put in a myocamlbuild.ml file, that is automatically compiled by ocamlbuild.

Note: the user manual of ocamlbuild does not explain how to write a plugin, but the wiki http://brion.inria.fr/gallium/index.php/Ocamlbuild has several pages that help.

After trying complicated alternatives, the solution I found consisted in writing a simple plugin adding a "linkdep" parameterized tag. When the operation is a link, this tag:

  • adds the file to the dependency list (so that it is also built)
  • adds it to the list of files to be linked (for some reason, when the "link" tag is active, i.e. the command is a link command, then Ocamlbuild add the files in the dependency list to list of input files to be linked).

The ocamlbuild plugin is rather small: just put the following in the myocamlbuild.ml file.

open Ocamlbuild_plugin;;

dispatch (function
  | After_rules ->
    pdep ["link"] "linkdep" (fun param -> [param])
  | _ -> ())

What this plugin does is "register that whenever the link and linkdep plugins are set, add the file passed as a parameter to the linkdep tag to the list of dependencies".

Then the _tags file only has to contain this:

<*.byte>: linkdep(toto_c.o), custom
<*.native>: linkdep(toto_c.o)

And the whole application can be simply built with:

ocamlbuild main.byte
ocamlbuild main.native

When the main.byte or main.native files are built (i.e. linked), the toto_c.o file is added as a dependency (and to the list of files that are linked). Additionally, the custom tag add the -custom option to the OCaml linker.

Linking with a .a library

If there is a need to link with several C object files, the solution also allows to use a library as follow:

  • Create a file name libname.clib containing the list of object files: for instance my libtoto.clib contains toto_c.o
  • Change the tag file to require libtoto.a instead of toto_c.o: the _tags file become:
<*.byte>: linkdep(libtoto.a), custom
<*.native>: linkdep(libtoto.a)

And that's all. Ocamlbuild knows how to build .a files from .clib files, and will add the contents of the .clib file as dependencies from the .a file.

Note that it is important that the library file begins by "lib", or the ocamlbuild rules will not work. The command ocamlbuild -documentation provides the list of rules.

Possible enhancements

There are several possible enhancements that could be brought:

  • The scheme presented here is sufficent for simple C stubs, but compiling more complex c files would require to change CFLAGS of the C compiler (in particular the include directories), and add new libraries to the lflags. http://mancoosi.org/~abate/ocamlbuild-stubs-and-dynamic-libraries and http://brion.inria.fr/gallium/index.php/Ocamlbuild_example_with_C_stubs deal with this problem by changing the plugin to add an "includename" flag. I think this idea could be generalized to add a parametrized include tag.
  • I think it is a pity that one has to tag the final executable with the linkdep. It would have been cleaner to tag the ml files that depended on them, such that linking the final executable is independent on whether one of the module uses a C stub or not. OCaml provide a mean to record the list of C library needed for an ocaml library (.cma), but not with individual (.cmo) objects, so there is no simple way to do that.

Conclusion

After spending some time fighting with ocamlbuild, I managed to have a working, but less-than-satisfactory result: library added to all targets for instance. And I remember that I had a similar fight to handle building custom Camlp4 extensions.

I usually like using Ocamlbuild, that allows building medium-sized ocaml projects with no or minimum configuration; especially with the recent support for findlib packages. But I am not a fan of having to write OCaml code to build my project properly. I think that Ocamlbuild could get better if more default parametrized tags would be provided, such as for adding dependencies, compile flags, and so on. But maybe the _tags file format is not sufficient for that.

So maybe I should keep the dual make/ocamlbuild solution. It may be the simplest to maintain, as it does not require a knowledge of the internals of ocamlbuild that I am likely to forget.

Maybe I should also try using OMake instead, that would allow a unifying solution. And Ocamlbuild will not be the adequate tool for building L projects anyway, when I will start bootstrapping the compiler. But OMake seems not to be maintained anymore…