Skip to content

Aggregation pass

Rémi requested to merge clean_up_morphs into dev

Problem we are trying to solve

Two dependant transformation are currently battling in the compiler: morphisation (module transformed to records) and monomorphisation (specialisation of polymorphic functions).
Those transformation are defined as "flying processes" meaning that they are not defined as self-passes but rather as routines that the caller (here, the build system) need to call in a specific order - keep in mind that monomorphization of functions requires actual application that might hide in a different module than the definition ... -

Solution

New stage

This MR defines a new stage: 6-ast_aggregated. This stage is similar to 5-ast_typed but without module constructs nor declarations. Two new passes are added:

  1. Aggregation: ast_typed to ast_aggregated.
  2. Self aggregated: ast_aggregated to ast_aggregated.

Aggregation

we do a "flattening" of modules, i.e. we eliminate all accessors, module constructors, etc. this happens in src/passes/12-aggregation/aggregation.ml

Declaration aggregation

The program representation at this point is a little bit weird.. It consists of a chain of "let-ins" definitions ending with a "hole". This hole represents the result of the last binding of the chain: type 'a program = 'a -> expression -> Ast_typed.expression -> expression
e.g. file

let a = 1
let b = 2

will be aggregated using Aggregation.compile_program : Ast_typed.module_fully_typed -> Ast_typed.expression Ast_aggregated.program:

let a = 1 in
let b = 2 in
<hole>

To evaluate an arbitrary expression in this context we call Aggregation.compile_expression_in_context : Ast_typed.expression -> Ast_typed.expression Ast_aggregated.program -> Ast_aggregated.expression. This would compile the given expression in the context of the program and produce a "flat" expression

Module flattening

Modules's declarations are "lifted at top-level" (?), module accesses are transformed into simple variables.

module A = struct
  let a = 1
end

module B = struct
  module A = A
  let b = 1
end

let x = B.A.a

|->

let #A#a#193 = 1[@inline] in
let #B#b#194 = 1[@inline] in
let x = #A#a#193 in
<hole>

Self aggregated

We apply monomorphisation and provide the tools for checking that an expression is "object" ligo (these were previously in self_ast_typed). Happens in src/passes/13-self_ast_aggregated/monomorphisation.ml

Side effects

  • the REPL's state is simplified (at the ast_typed level)
  • the interpreter is simplified by adapting it to work at the level of ast_aggregated (no module support needed).
  • in self_mini_c, one condition for beta reduction has been modified:
(** This case shows up in the compilation of modules:
      (let x = e1 in e2)@e3 ↦ let x = e1 in e2@e3  (only if e2 and e3 are pure??) *)
  | E_application ({ content = E_let_in (e1, inline, ((x, a), e2)); _ }, e3) ->
    if is_pure e2 || is_pure e3
    then
      let x' = Location.wrap (Var.fresh_like (Location.unwrap x)) in
      let e2 = Subst.replace e2 x x' in
      changed := true;
      {e with content = E_let_in (e1, inline, ((x', a), {e with content = E_application (e2, e3)}))}
    else e

this makes compilation faster since we don't have to inline every module declarations

Perfs

on a big contract using many modules ; most of the time passed in self-mini-c optimizations

ligo 31.0 after this MR
size 38029 bytes 34311 bytes
time to compile 4.8 sec 3.5 sec
  • has a changelog entry

Related issues

fix #1309 (closed) : after !1431 (9ca6eb00) , we don't need to inline declaration anymore

Edited by Rémi

Merge request reports