Chapter 24  Compiler plugins

1  Overview

Starting from OCaml 4.03, it is possible to extend the native and bytecode compilers with plugins using the -plugin command line option of both tools. This possibility is also available for ocamldep for OCaml version ulterior to 4.05. Beware however that plugins are an advanced feature of which the design is still in flux and breaking changes may happen in the future. Plugins features are based on the compiler library API. In complement, new hooks have been added to the compiler to increase its flexibility.

In particular, hooks are available in the Pparse module to transform the parsed abstract syntax tree, providing similar functionality to extension point based preprocessors. Other hooks are available to analyze the typed tree in the Typemod module after the type-checking phase of the compiler. Since the typed tree relies on numerous invariants that play a vital part in ulterior phases of the compiler, it is not possible however to transform the typed tree. Similarly, the intermediary lambda representation can be modified by using the hooks provided in the Simplif module . A plugin can also add new options to a tool through the Clflags.add_arguments function (see Clflags module ).

Plugins are dynamically loaded and need to be compiled in the same mode (i.e. native or bytecode) that the tool they extend.

2  Basic example

As an illustration, we shall build a simple Hello world plugin that adds a simple statement print_endline "Hello from:$sourcefile" to a compiled file.

The simplest way to implement this feature is to modify the abstract syntax tree. We will therefore add an hooks to the Pparse.ImplementationHooks. Since the proposed modification is very basic, we could implement the hook directly. However, for the sake of this illustration, we use the Ast_mapper structure that provides a better path to build more interesting plugins.

The first step is to build the AST fragment corresponding to the evaluation of print_endline:

  let print_endline name =
    let open Ast_helper in
    let print_endline = Exp.ident
    @@ Location.mknoloc @@Longident.Lident "print_endline" in
    let hello = Exp.constant @@ Const.string @@ "Hello from: " ^ name in
    Str.eval @@ Exp.apply print_endline [Asttypes.Nolabel, hello]

Then, we can construct an ast mapper that adds this fragment to the parsed ast tree.

let add_hello name (mapper:Ast_mapper.mapper) structure =
  let default = Ast_mapper.default_mapper in
  (print_endline name) :: (default.structure default structure)

let ast_mapper name =
  { Ast_mapper.default_mapper with structure = add_hello name }

Once this AST mapper is constructed, we need to convert it to a hook and adds this hook to the Pparse.ImplementationsHooks.

let transform hook_info structure =
        let astm = ast_mapper hook_info.Misc.sourcefile in
        astm.structure astm structure

let () = Pparse.ImplementationHooks.add_hook "Hello world hook" transform

The resulting simplistic plugin can then be compiled with

$ ocamlopt -I +compiler-libs -shared plugin.ml -o plugin.cmxs

Compiling other files with this plugin enabled is then as simple as

$ ocamlopt -plugin plugin.cmxs test.ml -o test