Chapter 22 Compiler plugins

22.1 Overview

Starting from OCaml 4.03, it is possible to extend the native and bytecode compilerswith 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 designis still in flux and breaking changes may happen in the future. Plugins featuresare based on the compiler library API. In complement, new hooks have been added tothe compiler to increase its flexibility.

In particular, hooks are available in thePparse moduleto transform the parsed abstract syntax tree, providing similar functionalityto extension point based preprocessors.Other hooks are available to analyze the typed tree in theTypemod moduleafter the type-checking phase of the compiler. Since the typed tree relieson numerous invariants that play a vital part in ulterior phases of thecompiler, it is not possible however to transform the typed tree.Similarly, the intermediary lambda representation can be modified by using thehooks provided in theSimplif module.A plugin can also add new options to a tool through theClflags.add_arguments function (seeClflags module).

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

22.2 Basic example

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

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

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

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

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

  1. let add_hello name (mapper:Ast_mapper.mapper) structure =
  2. let default = Ast_mapper.default_mapper in
  3. (print_endline name) :: (default.structure default structure)
  4.  
  5. let ast_mapper name =
  6. { 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 thishook to the Pparse.ImplementationsHooks.

  1. let transform hook_info structure =
  2. let astm = ast_mapper hook_info.Misc.sourcefile in
  3. astm.structure astm structure
  4.  
  5. let () = Pparse.ImplementationHooks.add_hook "Hello world hook" transform

The resulting simplistic plugin can then be compiled with

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

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

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