1 of 18

COMPILER PLUGINS

Aleksandr Kuzmenko. HaxeUP Linz 2019

2 of 18

COMPILATION

Source

code

Abstract Syntax Tree

Typing

Optimizing

Generating

Parsing

Typed AST

Typed AST

Target code/binary

Macros

AST

3 of 18

MACROS

  • AST (without type info)
    • analyze
    • transform
    • generate

  • Typed AST
    • analyze

4 of 18

MACROS

  • Performance
    • have to be compiled on each compiler run
    • slower than the rest of the compiler
    • have to encode/decode AST
  • Behavior
    • attempt to type AST may explode compilation
    • no way to modify typed AST
    • have to reimplement compiler in some cases

5 of 18

PLUGINS

6 of 18

PLUGINS

  • AST (without type info)
    • analyze
    • transform
    • generate

  • Typed AST
    • analyze
    • transform
    • generate

7 of 18

PLUGINS

  • Performance
    • no need to compile on each compiler run
    • As performant as the rest of the compiler
    • no need to encode/decode AST �(most of the time)

8 of 18

PLUGINS

  • Behavior
    • can check, which types are already loaded
    • modify typed AST
    • access to a lot of built-in compiler functions

9 of 18

COMPILATION

Source

code

Abstract Syntax Tree

Typing

Optimizing

Generating

Parsing

Typed AST

Typed AST

Target code/binary

Plugin

AST

Plugin

TAST

Plugin

TAST

10 of 18

PLUGINS, But...

  • Written in OCaml
    • but it’s safe!
  • No reification - manually build AST using enum constructors
  • No type checking for manually assembled TAST
  • Need development environment for the compiler
  • Need compiler built in the same environment

11 of 18

PLUGINS, But...

  • Need compiler built in the same environment
    • same libraries versions
    • same OCaml version
    • same commit of Haxe repository
    • maybe something else should be the same

12 of 18

PLUGINS, But...

No sense to release prebuilt plugin binaries

on haxelib

(but there is hope to figure it out)

13 of 18

BLAZING FAST

Iterate all fields of all classes in compiler’s unit test suite

Macro: 50-60ms

Plugin: 0-1ms

Context.onAfterTyping(types -> {

for(type in types) {

switch type {

case TClassDecl(_.get() => c):

for(field in c.fields.get()) {

fieldName = field.name;

}

for(field in c.statics.get()) {

fieldName = field.name;

}

case _:

}

}

});

let field_name = ref "" in

let handle field = field_name := field.cf_name in

let compiler = (EvalContext.get_ctx()).curapi in

compiler.after_typing (fun haxe_types ->

List.iter

(fun hx_type ->

match hx_type with

| TClassDecl cls ->

List.iter handle cls.cl_ordered_statics;

List.iter handle cls.cl_ordered_fields

| _ -> ()

)

haxe_types

);

14 of 18

USE CASES

  • First step to learn OCaml and compiler internals :)
  • Improve compilation speed of a macro-heavy project
  • Custom target generator
  • Fast tools for code analysis
  • Project-specific optimizations
  • Sandbox for new compiler features

15 of 18

USE CASES

  • Project-specific optimizations

AST

Typed AST

Possible optimization by a plugin

matrices

m3 = m1 * m2;

m3.a11 = a11 * b11 + a12 * 0 + a13 * 0 + a14 * 0;

m3.a12 = a11 * 0 + a12 * f + a13 * 0 + a14 * 0;

m3.a13 = a11 * 0 + a12 * 0 + a13 * b33 + a14 * -1;

m3.a14 = a11 * 0 + a12 * 0 + a13 * b34 + a14 * 0;

m3.a21 = a21 * b11 + a22 * 0 + a23 * 0 + a24 * 0;

m3.a22 = a21 * 0 + a22 * f + a23 * 0 + a24 * 0;

m3.a23 = a21 * 0 + a22 * 0 + a23 * b33 + a24 * -1;

m3.a24 = a21 * 0 + a22 * 0 + a23 * b34 + a24 * 0;

m3.a31 = a31 * b11 + a32 * 0 + a33 * 0 + a34 * 0;

<...>

m3.a11 = a11 * b11;

m3.a12 = a12 * f;

m3.a13 = a13 * b33 + a14 * -1;

m3.a14 = a13 * b34;

m3.a21 = a21 * b11;

m3.a22 = a22 * f;

m3.a23 = a23 * b33 + a24 * -1;

m3.a24 = a23 * b34;

m3.a31 = a31 * b11;

<...>

16 of 18

USE CASES

  • Sandbox for new compiler features
    • null safety
      • already merged into compiler
    • coroutines
      • https://github.com/RealyUniqueName/Coro
      • (outdated)
      • (not for merging into compiler)

17 of 18

LET’S PLAY

18 of 18

HAXE IS GREAT

Questions?

alex@stablex.ru

twitter @RealyUniqueName