eir

Erlang Compiler Infrastructure Project

#CodeBEAMSTO

Hans Elias B. Josephsen, @hansihe

#CodeBEAMSTO

+

Elixir on the web?

#CodeBEAMSTO

Parallelism & Concurrency

Fault tolerance

Hot-code reloading

#CodeBEAMSTO

Download size

Startup time

#CodeBEAMSTO

#CodeBEAMSTO

#CodeBEAMSTO

eir

Erlang Compiler Infrastructure Project

#CodeBEAMSTO

Eir: SSA-based IR for BEAM languages, designed for compatibility with LLVM

Technical subtitle:

#CodeBEAMSTO

if a: do b
0xdeadbeef
Intermediate Representation

#CodeBEAMSTO

ENTRY:
if a: goto BB1
goto BB2
BB1:
...
BB2:
...

#CodeBEAMSTO

= "static single assignment"
= 5

= 2

#CodeBEAMSTO

ENTRY:
%1 = 5
if a: goto BB1()
goto BB2()
BB1():
%2 = %1 + 5
goto BB3(%2)
BB2():
%3 = %1 + 10
goto BB3(%3)
BB3(%4):
return %4

#CodeBEAMSTO

LLVM

The LLVM compiler infrastructure project is a "collection of modular and reusable compiler and toolchain technologies"[3] used to develop compiler front ends and back ends.

 

[...] designed for compile-time, link-time, run-time, and "idle-time" optimization of programs written in arbitrary programming languages.

#CodeBEAMSTO

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

Eir Project

#CodeBEAMSTO

def my_fun(:hi), do: :hello
def my_fun(:bye), do: Goodbye.run() + 2

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

'my_fun'/1 =
    %% Line 43
    fun (_0) ->
	case _0 of
	  <'hi'> when 'true' ->
	      'hello'
	  %% Line 44
	  <'bye'> when 'true' ->
	      let <_1> =
		  call 'Elixir.Goodbye':'run'
		      ()
	      in  call 'erlang':'+'
		      (_1, 2)
	  ( <_2> when 'true' ->
		( primop 'match_fail'
		      ({'function_clause',_2})
		  -| [{'function_name',{'my_fun',1}}] )
	    -| ['compiler_generated'] )
	end

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

    my_fun/1 {
        %5 = [];
        %7 = a"true";
        %9 = a"hello";
        %11 = a"true";
        %13 = a"Elixir.Goodbye";
        %14 = a"run";
        %17 = a"erlang";
        %18 = a"+";
        %19 = 2;
        %24 = a"true";
        %26 = a"function_clause";
        %28 = a"error";
        %29 = a"internal_err_data";

        B0(%0):
            %4 = case_start on: %0, values: [] {
                clause assigns: [] {
                    pattern a"hi";
                };
                clause assigns: [] {
                    pattern a"bye";
                };
                clause assigns: [A0] {
                    pattern A0 = (_);
                };
            } branch B3();

        B5:
            jump B1(%5);

        B8:
            %22 = case_values %4;
            %25 = pack_value_list;
            if_truthy %24 else B11(%25);
            case_guard_ok %4;
            %27 = make_tuple [%26, %22];
            %30 = make_tuple [%28, %27, %29];
            jump B1(%30);

        B12:
            %31 = pack_value_list;
            jump B4(%31);

        B11(%23):
            case_guard_fail %4 branch B3();

        B7:
            case_values %4;
            %12 = pack_value_list;
            if_truthy %11 else B10(%12);
            case_guard_ok %4;
            %15, %16 = call %13:%14/0() except B1(%16);
            %20, %21 = call %17:%18/2(%15, %19) except B1(%21);
            jump B4(%20);

        B10(%10):
            case_guard_fail %4 branch B3();

        B6:
            case_values %4;
            %8 = pack_value_list;
            if_truthy %7 else B9(%8);
            case_guard_ok %4;
            jump B4(%9);

        B9(%6):
            case_guard_fail %4 branch B3();

        B4(%3):
            jump B2(%3);

        B3:
            case_body %4 branch B5(), B6(), B7(), B8();

        B1(%1):
            return_throw %1;

        B2(%2):
            return_ok %2;

    }

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

my_fun/1 {
    %5 = [];
    %7 = a"true";
    %9 = a"hello";
    %11 = a"true";
    %13 = a"Elixir.Goodbye";
    %14 = a"run";
    %17 = a"erlang";
    %18 = a"+";
    %19 = 2;
    %24 = a"true";
    %26 = a"function_clause";
    %28 = a"error";
    %29 = a"internal_err_data";

    B0(%0):
        %4 = case_start on: %0, values: [] {
            clause assigns: [] {
                pattern a"hi";
            };
            clause assigns: [] {
                pattern a"bye";
            };
            clause assigns: [A0] {
                pattern A0 = (_);
            };
        } branch B3();

    B5:
        jump B1(%5);

    B8:
        %22 = case_values %4;
        %25 = pack_value_list;
        if_truthy %24 else B11(%25);
        case_guard_ok %4;
        %27 = make_tuple [%26, %22];
        %30 = make_tuple [%28, %27, %29];
        jump B1(%30);

    B12:
        %31 = pack_value_list;
        jump B4(%31);

    B11(%23):
        case_guard_fail %4 branch B3();

    B7:
        case_values %4;
        %12 = pack_value_list;
        if_truthy %11 else B10(%12);
        case_guard_ok %4;
        %15, %16 = call %13:%14/0() except B1(%16);
        %20, %21 = call %17:%18/2(%15, %19) except B1(%21);
        jump B4(%20);

    B10(%10):
        case_guard_fail %4 branch B3();

    B6:
        case_values %4;
        %8 = pack_value_list;
        if_truthy %7 else B9(%8);
        case_guard_ok %4;
        jump B4(%9);

    B9(%6):
        case_guard_fail %4 branch B3();

    B4(%3):
        jump B2(%3);

    B3:
        case_body %4 branch B5(), B6(), B7(), B8();

    B1(%1):
        return_throw %1;

    B2(%2):
        return_ok %2;

}

#CodeBEAMSTO

my_fun/1 {
    %9 = a"hello";
    %13 = a"Elixir.Goodbye";
    %14 = a"run";
    %17 = a"erlang";
    %18 = a"+";
    %19 = 2;
    %26 = a"function_clause";
    %28 = a"error";
    %29 = a"internal_err_data";
    %32 = a"bye";
    %33 = a"hi";

    B0(%0):
        compare equal [%0, %32] branch B22();
        %15, %16 = call %13:%14/0() except B1(%16);
        call tail %17:%18/2(%15, %19);

    B22:
        compare equal [%0, %33] branch B23();
        return_ok %9;

    B23:
        %27 = make_tuple [%26, %0];
        %30 = make_tuple [%28, %27, %29];
        jump B1(%30);

    B1(%1):
        return_throw %1;

}

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

An aside:

Tail calls

Stack top

fun1

fun2

def fun1(a, state) do
  b = fun2(a)
  fun1(b, state)
end
def fun1(a, state) do
  b = fun2(a)
  fun1(b, state)
end

a, state

a, ...

b, a, state

fun1

a, state

def fun1(a, state) do
  b = fun2(a)
  fun1(b, state)
end

fun1

a, state

#CodeBEAMSTO

WebAssembly

doesn't like tail calls

:(

Solution?

Make all calls tail-calls!

#CodeBEAMSTO

my_fun/1 {
    %3 = a"bye";
    %5 = a"hi";
    %10 = a"Elixir.Goodbye";
    %11 = a"run";
    %15 = a"hello";
    %19 = a"function_clause";
    %21 = a"error";
    %22 = a"internal_err_data";

    B0(%0, %1, %2):
        compare equal [%2, %3] branch B1();
        %6 = pack_env E24 [%0, %1];
        %7 = bind_closure Elixir.NiffyTest.NifTest:my_fun@24.0/1 with %6;
        %8 = pack_env E25 [%0, %1];
        %9 = bind_closure Elixir.NiffyTest.NifTest:my_fun@25.0/1 with %8;
        call tail %10:%11/0(%7, %9);

    B1:
        compare equal [%2, %5] branch B2();
        apply cont %0(%15);

    B2:
        %18 = make_tuple [%19, %2];
        %20 = make_tuple [%21, %18, %22];
        jump B3(%20);

    B3(%23):
        apply cont %1(%23);

}

my_fun@24.0/1 {
    %4 = 2;
    %5 = a"erlang";
    %6 = a"+";

    B0(%0, %1):
        %2, %3 = unpack_env %0;
        call tail %5:%6/2(%2, %3, %1, %4);

}

my_fun@25.0/1 {

    B0(%0, %1):
        %2, %3 = unpack_env %0;
        apply cont %3(%1);

}

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

define void @GNIF7_testing7_my__fun1_n_n(%whirl_process_env*, i64, i64, i64, i64) {
entry:
  br label %ebb0

ebb0:                                             ; preds = %entry
  %value0 = phi i64 [ %2, %entry ]
  %value1 = phi i64 [ %3, %entry ]
  %value2 = phi i64 [ %4, %entry ]
  %5 = load i64, i64* @whirlc_module_testing_atom_hi
  %6 = call i1 @whirlrt_term_eq(%whirl_process_env* %0, i64 %value2, i64 %5)
  br i1 %6, label %compare_eq_ok, label %ebb1

ebb1:                                             ; preds = %ebb0
  %7 = load i64, i64* @whirlc_module_testing_atom_bye
  %8 = call i1 @whirlrt_term_eq(%whirl_process_env* %0, i64 %value2, i64 %7)
  br i1 %8, label %compare_eq_ok1, label %ebb2

ebb2:                                             ; preds = %ebb1
  %9 = alloca i64, i32 0
  %10 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 0, i64* %9)
  %11 = load i64, i64* @whirlc_module_testing_atom_function_clause
  %12 = alloca i64, i32 2
  %13 = getelementptr i64, i64* %12, i64 0
  store i64 %11, i64* %13
  %14 = getelementptr i64, i64* %12, i64 1
  store i64 %value2, i64* %14
  %15 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 2, i64* %12)
  %16 = load i64, i64* @whirlc_module_testing_atom_error
  %17 = load i64, i64* @whirlc_module_testing_atom_internal_err_data
  %18 = alloca i64, i32 3
  %19 = getelementptr i64, i64* %18, i64 0
  store i64 %16, i64* %19
  %20 = getelementptr i64, i64* %18, i64 1
  store i64 %15, i64* %20
  %21 = getelementptr i64, i64* %18, i64 2
  store i64 %17, i64* %21
  %22 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 3, i64* %18)
  br label %ebb3

ebb3:                                             ; preds = %ebb2
  %value23 = phi i64 [ %22, %ebb2 ]
  call void @whirlrt_call_cont(%whirl_process_env* %0, i64 %value1, i64 %value23)
  unreachable

compare_eq_ok:                                    ; preds = %ebb0
  %23 = alloca i64, i32 0
  %24 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 0, i64* %23)
  %25 = load i64, i64* @whirlc_module_testing_atom_hello
  call void @whirlrt_call_cont(%whirl_process_env* %0, i64 %value0, i64 %25)
  unreachable

compare_eq_ok1:                                   ; preds = %ebb1
  %26 = alloca i64, i32 0
  %27 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 0, i64* %26)
  %28 = alloca i64, i32 2
  %29 = getelementptr i64, i64* %28, i64 0
  store i64 %value0, i64* %29
  %30 = getelementptr i64, i64* %28, i64 1
  store i64 %value1, i64* %30
  %31 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 2, i64* %28)
  call void @whirlrt_temp_hacky_transmute_tup_to_fun_env(%whirl_process_env* %0, i64 %31, i8* bitcast (void (%whirl_process_env*, i64, i64)* @GNIF7_testing7_my__fun1_lambda_env42_0 to i8*))
  %32 = alloca i64, i32 2
  %33 = getelementptr i64, i64* %32, i64 0
  store i64 %value0, i64* %33
  %34 = getelementptr i64, i64* %32, i64 1
  store i64 %value1, i64* %34
  %35 = call i64 @whirlrt_term_make_tuple(%whirl_process_env* %0, i32 2, i64* %32)
  call void @whirlrt_temp_hacky_transmute_tup_to_fun_env(%whirl_process_env* %0, i64 %35, i8* bitcast (void (%whirl_process_env*, i64, i64)* @GNIF7_testing7_my__fun1_lambda_env43_0 to i8*))
  %36 = load i64, i64* @whirlc_module_testing_atom_Elixir.Goodbye
  %37 = load i64, i64* @whirlc_module_testing_atom_run
  %38 = call i64 @whirlrt_term_make_fun(%whirl_process_env* %0, i8* bitcast (void (%whirl_process_env*, i64, i64, i64)* @GNIF14_Elixir.Goodbye3_run0_n_n to i8*))
  call void @GNIF14_Elixir.Goodbye3_run0_n_n(%whirl_process_env* %0, i64 %38, i64 %31, i64 %35)
  unreachable
}

declare void @GNIF14_Elixir.Goodbye3_run0_n_n(%whirl_process_env*, i64, i64, i64)

define void @GNIF7_testing7_my__fun1_lambda_env43_0(%whirl_process_env*, i64, i64) {
entry:
  br label %ebb0

ebb0:                                             ; preds = %entry
  %value0 = phi i64 [ %1, %entry ]
  %value1 = phi i64 [ %2, %entry ]
  %3 = alloca i64, i32 2
  call void @whirlrt_term_unpack_closure_env(%whirl_process_env* %0, i64 %value0, i32 2, i64* %3)
  %4 = getelementptr i64, i64* %3, i32 0
  %5 = load i64, i64* %4
  %6 = getelementptr i64, i64* %3, i32 1
  %7 = load i64, i64* %6
  call void @whirlrt_call_cont(%whirl_process_env* %0, i64 %7, i64 %value1)
  unreachable
}

define void @GNIF7_testing7_my__fun1_lambda_env42_0(%whirl_process_env*, i64, i64) {
entry:
  br label %ebb0

ebb0:                                             ; preds = %entry
  %value0 = phi i64 [ %1, %entry ]
  %value1 = phi i64 [ %2, %entry ]
  %3 = alloca i64, i32 2
  call void @whirlrt_term_unpack_closure_env(%whirl_process_env* %0, i64 %value0, i32 2, i64* %3)
  %4 = getelementptr i64, i64* %3, i32 0
  %5 = load i64, i64* %4
  %6 = getelementptr i64, i64* %3, i32 1
  %7 = load i64, i64* %6
  %8 = load i64, i64* @whirlc_module_testing_atom_erlang
  %9 = load i64, i64* @"whirlc_module_testing_atom_+"
  %10 = call i64 @whirlrt_term_make_smallint(%whirl_process_env* %0, i64 2)
  %11 = call i64 @whirlrt_term_make_fun(%whirl_process_env* %0, i8* bitcast (void (%whirl_process_env*, i64, i64, i64, i64, i64)* @GNIF6_erlang2__p2_n_n to i8*))
  call void @GNIF6_erlang2__p2_n_n(%whirl_process_env* %0, i64 %11, i64 %5, i64 %7, i64 %value1, i64 %10)
  unreachable
}

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

Introducing Whirl

WebAssembly runtime for BEAM languages

#CodeBEAMSTO

Runtime

Compiled Erlang

LLVM

#CodeBEAMSTO

Demo

#CodeBEAMSTO

Let's step back

#CodeBEAMSTO

Erlang/Elixir

CORE Erlang

High level Eir

Eir

CSP Eir

LLVM IR

WebAssembly

#CodeBEAMSTO

Niffy

defmodule NiffyTest.NifTest do
  use Niffy

  # The following function will be compiled 
  # to native code and loaded as a NIF.

  @niffy true
  def woohoo(a) do
    case a do
      1 -> :woo
      2 -> 1
      _ -> a + 2
    end
  end

end

#CodeBEAMSTO

#CodeBEAMSTO

All open source!

#CodeBEAMSTO

Hans Elias B. Josephsen

@hansihe

The Eir Project

By Hans Elias Bukholm Josephsen