Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dr. Futamura's Projection Machine: From Interpreters to Compilers through a Marvelous Device

Dr. Futamura's Projection Machine: From Interpreters to Compilers through a Marvelous Device

Partial evaluation is a fascinating technique to specialize programs that has a number of applications; it dates back at least to the 1970s, but it has even deeper roots in computational theory. Recently, the buzz around the term has increased, as the GraalVM project has become mainstream in the industry; in fact, one ingredient to the “secret sauce” of this polyglot platform is partial evaluation: Futamura projections make it possible to derive optimized compilers from a high-level interpreter definition. In this talk, we will learn more about the theory behind GraalVM’s compiler: the three Futamura projections. Come and see how deep the rabbit hole goes!

Edoardo Vacchi

September 03, 2021
Tweet

More Decks by Edoardo Vacchi

Other Decks in Programming

Transcript

  1. Copyright © 2018, Oracle and/or its affiliates. All rights reserved. | Confidential – Oracle Internal/Restricted/Highly Restricted !5
    standalone
    Automatic transformation of interpreters to compilers
    Engine integration native and managed
    https://gotober.com/2018/sessions/650/graalvm-run-programs-faster-anywhere

    View full-size slide

  2. Copyright © 2018, Oracle and/or its affiliates. All rights reserved. | !9
    https://gotober.com/2018/sessions/650/graalvm-run-programs-faster-anywhere

    View full-size slide

  3. The performance of many dynamic language implementations suffers
    from high allocation rates and runtime type checks. This makes
    dynamic languages less applicable to purely algorithmic problems,
    despite their growing popularity. In this paper we present a simple
    compiler optimization based on online partial evaluation to remove
    object allocations and runtime type checks in the context of a tracing
    JIT. We evaluate the optimization using a Python VM and find that it
    gives good results for all our (real-life) benchmarks.

    View full-size slide

  4. The performance of many dynamic language implementations suffers
    from high allocation rates and runtime type checks. This makes
    dynamic languages less applicable to purely algorithmic problems,
    despite their growing popularity. In this paper we present a simple
    compiler optimization based on online partial evaluation to remove
    object allocations and runtime type checks in the context of a tracing
    JIT. We evaluate the optimization using a Python VM and find that it
    gives good results for all our (real-life) benchmarks.

    View full-size slide

  5. Most high-performance dynamic language virtual machines duplicate
    language semantics in the interpreter, compiler, and runtime system.
    This violates the principle to not repeat yourself. In contrast, we define
    languages solely by writing an interpreter. The interpreter performs
    specializations, e.g., augments the interpreted program with type
    information and profiling information. Compiled code is derived
    automatically using partial evaluation while incorporating these
    specializations. This makes partial evaluation practical in the context
    of dynamic languages: it reduces the size of the compiled code while
    still compiling all parts of an operation that are relevant for a
    particular program. When a speculation fails, execution transfers back
    to the interpreter, the program re-specializes in the interpreter, and
    later partial evaluation again transforms the new state of the
    interpreter to compiled code.

    View full-size slide

  6. Most high-performance dynamic language virtual machines duplicate
    language semantics in the interpreter, compiler, and runtime system.
    This violates the principle to not repeat yourself. In contrast, we define
    languages solely by writing an interpreter. The interpreter performs
    specializations, e.g., augments the interpreted program with type
    information and profiling information. Compiled code is derived
    automatically using partial evaluation while incorporating these
    specializations. This makes partial evaluation practical in the context
    of dynamic languages: it reduces the size of the compiled code while
    still compiling all parts of an operation that are relevant for a
    particular program. When a speculation fails, execution transfers back
    to the interpreter, the program re-specializes in the interpreter, and
    later partial evaluation again transforms the new state of the
    interpreter to compiled code.

    View full-size slide

  7. We implement the language semantics only once in a simple form: as a
    language interpreter written in a managed high-level host language.
    Optimized compiled code is derived from the interpreter using partial
    evaluation. This approach and its obvious benefits were described in
    1971 by Y. Futamura, and is known as the first Futamura projection.
    To the best of our knowledge no prior high-performance language
    implementation used this approach.

    View full-size slide

  8. We implement the language semantics only once in a simple form: as a
    language interpreter written in a managed high-level host language.
    Optimized compiled code is derived from the interpreter using partial
    evaluation. This approach and its obvious benefits were described in
    1971 by Y. Futamura, and is known as the first Futamura projection.
    To the best of our knowledge no prior high-performance language
    implementation used this approach.

    View full-size slide

  9. We implement the language semantics only once in a simple form: as a
    language interpreter written in a managed high-level host language.
    Optimized compiled code is derived from the interpreter using partial
    evaluation. This approach and its obvious benefits were described in
    1971 by Y. Futamura, and is known as the first Futamura projection.
    To the best of our knowledge no prior high-performance language
    implementation used this approach.

    View full-size slide

  10. https://codon.com/compilers-for-free

    View full-size slide

  11. Programs and Programming Languages

    View full-size slide

  12. Programs
    • We call a program a sequence
    of instructions that can be
    executed by a machine.
    • The machine may be a virtual
    machine or a physical machine
    • In the following, when we say
    that a program is evaluated,
    we assume that there exists
    some machine that is able to
    execute these instructions.

    View full-size slide

  13. Program Evaluation
    • Consider a program P, with input data D;
    • when we evaluate P over D it produces some output result R.
    D R
    P

    View full-size slide

  14. f(k, u) = k + u
    3
    4
    7
    k + u

    View full-size slide

  15. Interpreters
    • An interpreter I is a program
    • it evaluates some other given program P over some given data D,
    and it produces the output result R.
    P
    D
    R
    I
    • We denote this with I(P, D)

    View full-size slide

  16. f(k, u) = k + u
    Instructions
    add x y
    sub x y
    mul x y
    ...
    write(D)
    while(has-more-instructions(P)):
    instr ← fetch-next-instruction(P)
    switch(op(instr)):
    case ’add’:
    x ← read()
    y ← read()
    result ← x + y
    write(result)
    case . . .

    View full-size slide

  17. Compilers
    • Let be P a program that evaluates to R when given D;
    • A compiler C translates a source program P into an object
    program C(P) that evaluated over an input D still produces R
    P C C(P)
    C(P)
    D R
    • We denote this with C(P)(D)

    View full-size slide

  18. f(k, u) = k + u
    sum:
    lea eax, [rdi+rsi]
    ret

    View full-size slide

  19. $ cat example.ml
    print_string "Hello world!\n"
    $ ocaml example.ml
    Hello world!
    $ ocamlc example.ml
    $ ./a.out
    Hello world!

    View full-size slide

  20. C(P)(D) = I(P, D)

    View full-size slide

  21. Partial Evaluation

    View full-size slide

  22. Partial Evaluation (intuition)
    Let us have a computation f of two parameters k, u
    f(k, u)
    • Now suppose that f is often called with k = 5;
    • f5(u) := “f by substituting 5 for k and doing all possible
    computation based upon value 5”
    • Partial evaluation is the process of transforming f(5, u) into f5(u)

    View full-size slide

  23. This is Currying! I Know This!
    • Not exactly! In functional
    programming currying or
    partial applicationa is
    f5(u) := f(5, u)
    let f =
    (k, u) =>
    k * (k * (k+1) + u+1) + u*u;
    let f5 = (u) => f(5, u);
    • In a functional programming
    language this usually does not
    change the program that
    implements f
    a
    Although, strictly speaking they are not synonyms, see
    https://en.wikipedia.org/wiki/Currying

    View full-size slide

  24. Simplification
    let f = (k, u) => k * (k * (k+1) + u + 1) + u * u;
    by fixing k = 5 and simplifying:
    let f5 = (u) => 5 * (31 + u) + u * u;

    View full-size slide

  25. Rewriting
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    function pow5(n) {
    return pow(n, 5);
    }

    View full-size slide

  26. Rewriting
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    function pow5(n) {
    return n * pow(n, 4);
    }

    View full-size slide

  27. Rewriting
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    function pow5(n) {
    return n * n * pow(n, 3);
    }

    View full-size slide

  28. Rewriting
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    function pow5(n) {
    return n * n * n * n * n;
    }

    View full-size slide

  29. Rewriting
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    function pow5(n) {
    return n * n * n * n * n;
    }
    In compilers this
    is sometimes
    called inlining

    View full-size slide

  30. Rewriting and Simplification
    • Rewriting is similar to macro expansion and procedure
    integration (β-reduction, inlining) in the optimization technique of
    a compiler.
    • Often combined with simplification (constant folding)

    View full-size slide

  31. Projection
    Projection
    The following equation holds for fk and f
    fk(u) = f(k, u) (1)
    we call fk a projection of f at k

    View full-size slide

  32. Partial Evaluator
    A partial computation procedure may be a computer program α called
    a projection machine, partial computer or partial evaluator.
    α(f, k) = fk (2)

    View full-size slide

  33. Partial Evaluator
    k
    u
    f(k, u)
    f

    View full-size slide

  34. Partial Evaluator
    k
    u
    f(k, u)
    f

    View full-size slide

  35. Partial Evaluator
    k
    u
    f(k, u)
    f
    fk
    α

    View full-size slide

  36. Partial Evaluator
    function pow(n, k) {
    if (k <= 0) {
    return 1;
    } else {
    return n * pow(n, k-1);
    }
    }
    let pow5 = alpha(pow, {k:5});
    // (n) => n * n * n * n * n;

    View full-size slide

  37. Examples
    The paper presents:
    • Automatic theorem proving
    • Pattern matching
    • Syntax analyzer
    • Automatically generating a compiler

    View full-size slide

  38. Examples
    The paper presents:
    • Automatic theorem proving
    • Pattern matching
    • Syntax analyzer
    • Automatically generating a compiler

    View full-size slide

  39. Interpreters and Compilers (reprise)
    • An interpreter is a program
    • This program takes another program and the data as input
    • It evaluates the program on the input and returns the result
    I(P, D)
    • A compiler is a program
    • This program takes a source program and returns an object
    program
    • The object program processes the input and returns the result
    C(P)(D)

    View full-size slide

  40. Partial Evaluation of an Interpreter
    P
    D
    R
    I

    View full-size slide

  41. Partial Evaluation of an Interpreter
    P
    D
    R
    I

    View full-size slide

  42. Partial Evaluation of an Interpreter
    P
    D
    R
    I
    IP
    α

    View full-size slide

  43. First Equation of Partial Computation (First Projection)
    D R
    IP
    • That is, by feeding D into IP, you get R;
    • in other words, IP is an object program.
    I(P, D) = C(P)(D)
    α(I, P) = IP
    IP = C(P) (4)

    View full-size slide

  44. f(k, u) = k + u (add x y)
    write(D)
    while(has-more-instructions(P)):
    instr ← fetch-next(P)
    switch(op(instr)):
    case ’add’:
    x ← read()
    y ← read()
    result ← x + y
    write(result)
    case . . .

    View full-size slide

  45. f(k, u) = k + u (add x y)
    write(D)
    while(has-more-instructions(P)):
    instr ← fetch-next(P)
    switch(op(instr)):
    case ’add’:
    x ← read()
    y ← read()
    result ← x + y
    write(result)
    case . . .
    ...but this interpreter executes on a
    machine!

    View full-size slide

  46. sum:
    lea eax, [rdi+rsi]
    ret

    View full-size slide

  47. Partial Evaluation of an Interpreter
    P
    D
    R
    I
    IP
    α

    View full-size slide

  48. Partial Evaluation of an Interpreter
    I
    P
    IP
    α

    View full-size slide

  49. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    I
    P
    IP
    α

    View full-size slide

  50. Partial Evaluation of an Interpreter
    I
    P
    IP
    α
    αI
    α

    View full-size slide

  51. Second Equation of Partial Computation (Second Projection)
    P IP
    αI
    αI(P) = IP (5)
    • but IP, evaluated on D gives R

    View full-size slide

  52. Second Equation of Partial Computation (Second Projection)
    P C(P)
    αI
    αI(P) = IP (5)
    • but IP, evaluated on D gives R
    • then IP is an object program (P = C(P))

    View full-size slide

  53. Second Equation of Partial Computation (Second Projection)
    P C(P)
    αI
    αI(P) = IP (5)
    • but IP, evaluated on D gives R
    • then IP is an object program (P = C(P))
    • αI transforms a source program P to IP (i.e., C(P))

    View full-size slide

  54. Second Equation of Partial Computation (Second Projection)
    P C(P)
    C
    αI(P) = IP (5)
    • but IP, evaluated on D gives R
    • then IP is an object program (P = C(P))
    • αI transforms a source program P to IP (i.e., C(P))
    • then αI is a compiler

    View full-size slide

  55. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    I
    P
    IP
    α
    αI = C
    α

    View full-size slide

  56. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α

    View full-size slide

  57. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α

    View full-size slide

  58. Partial Evaluation of the
    Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α
    αα
    α

    View full-size slide

  59. Third Equation of Partial Computation (Third Projection)
    I αI = C
    αα
    αα(I) = αI (6)
    • αα is a program that given I, returns αI = C
    • αI transforms a source program to an object program
    • αI is a compiler
    • αα is a compiler-compiler (a compiler generator) which generates
    a compiler αI from an interpreter I

    View full-size slide

  60. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α

    View full-size slide

  61. Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α

    View full-size slide

  62. Partial Evaluation of the
    Partial Evaluation of the
    Partial Evaluation of an Interpreter
    α
    I
    αI = C
    α
    αα
    α

    View full-size slide

  63. Projection of α at α
    α
    α
    αα
    α

    View full-size slide

  64. Projection of α at α
    α
    α
    αα
    α

    View full-size slide

  65. Projection of α at α
    α
    α
    αα
    α
    αα
    α

    View full-size slide

  66. Fourth Equation of Partial Computation
    αα(α) = αα
    α αα
    αα
    • αα(I) = αI = C is a compiler for the language interpreter I; thus:
    αα(I) = αI = C
    αα(I)(P) = IP = C(P)
    • I is an interpreter
    • but at the beginning we said it could be any program
    • so, what is αα?

    View full-size slide

  67. What is αα
    ?
    αα(I) = αI = C
    αα(α) = αα = C(α)
    • αα is a “compiler” for the “language α” !
    • In other words, by finding αα we can generate fk for any f, k !
    αα(f)(k) = fk (Fourth Equation)
    • That is, αα is a partial evaluation compiler (or generator).
    • However, the author notes, at the time of writing, there is no
    way to produce αα from α(α, α) for practical α’s.

    View full-size slide

  68. We implement the language semantics only once in a simple form: as a
    language interpreter written in a managed high-level host language.
    Optimized compiled code is derived from the interpreter using partial
    evaluation. This approach and its obvious benefits were described in
    1971 by Y. Futamura, and is known as the first Futamura projection.
    To the best of our knowledge no prior high- performance language
    implementation used this approach.

    View full-size slide

  69. We implement the language semantics only once in a simple form: as a
    language interpreter written in a managed high-level host language.
    Optimized compiled code is derived from the interpreter using partial
    evaluation. This approach and its obvious benefits were described in
    1971 by Y. Futamura, and is known as the first Futamura projection.
    To the best of our knowledge no prior high- performance language
    implementation used this approach.

    View full-size slide

  70. We believe that a simple partial evaluation of a dynamic language
    interpreter cannot lead to high-performance compiled code: if the
    complete semantics for a language operation are included during
    partial evaluation, the size of the compiled code explodes; if language
    operations are not included during partial evaluation and remain
    runtime calls, performance is mediocre. To overcome these inherent
    problems, we write the interpreter in a style that anticipates and
    embraces partial evaluation. The interpreter specializes the executed
    instructions, e.g., collects type information and profiling information.
    The compiler speculates that the interpreter state is stable and creates
    highly optimized and compact machine code. If a speculation turns out
    to be wrong, i.e., was too optimistic, execution transfers back to the
    interpreter. The interpreter updates the information, so that the next
    partial evaluation is less speculative.

    View full-size slide

  71. We believe that a simple partial evaluation of a dynamic language
    interpreter cannot lead to high-performance compiled code: if the
    complete semantics for a language operation are included during
    partial evaluation, the size of the compiled code explodes; if language
    operations are not included during partial evaluation and remain
    runtime calls, performance is mediocre. To overcome these inherent
    problems, we write the interpreter in a style that anticipates and
    embraces partial evaluation. The interpreter specializes the executed
    instructions, e.g., collects type information and profiling information.
    The compiler speculates that the interpreter state is stable and creates
    highly optimized and compact machine code. If a speculation turns out
    to be wrong, i.e., was too optimistic, execution transfers back to the
    interpreter. The interpreter updates the information, so that the next
    partial evaluation is less speculative.

    View full-size slide

  72. https://twitter.com/larsr h/status/1227956746104266753

    View full-size slide

  73. References
    • W¨
    urthinger et al. 2017, Practical Partial Evaluation for
    High-Performance Dynamic Languages, PLDI’17
    • ˇ
    Selajev 2018, GraalVM: Run Programs Faster Anywhere, GOTO
    Berlin 2018
    • Bolz et al. 2011, Allocation Removal by Partial Evaluation in a
    Tracing JIT, PEPM’11
    • Stuart 2013, Compilers for Free, RubyConf 2013
    • Cook and L¨
    ammel 2011, Tutorial on Online Partial Evaluation,
    EPTCS’11

    View full-size slide