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

Annotating Deeply Embedded Languages

Annotating Deeply Embedded Languages

Presented at the Haskell Implementers Workshop 2022: https://icfp22.sigplan.org/home/hiw-2022#About
Video: TBD

Haskell's strong static typing and the ease at which the programmer can create and manipulate algebraic data types make it a good choice for implementing (embedded) programming languages. However a common problem with deeply embedded languages is that there is no strong connection between the source program the user writes; the abstract syntax tree that is generated for that embedded program; and the object code that is eventually generated, complied, and executed for that program. Once the embedded language moves beyond the prototype phase, this disconnect makes it almost impossible for a user to debug their program or determine where the execution time is being spent.

In this talk we discuss recent work on annotating the abstract syntax tree of the deeply embedded languages Accelerate. We present our approach for automatically gathering source location information for embedded expressions using a novel implicit-parameter based approach. Unlike the existing HasCallStack mechanism provided by GHC, our approach statically ensures that the required source location information is available when needed. Annotating the abstract syntax tree of the program with this information, it is now possible to map the embedded program back to the original source code. We demonstrate our current progress on using this information to improve compiler diagnostics, as well as enable profiler and debugger integration for embedded programs.

Trevor L. McDonell

September 11, 2022
Tweet

More Decks by Trevor L. McDonell

Other Decks in Research

Transcript

  1. Annotating Deeply Embedded Languages
    Robbert van der Helm
    Trevor L. McDonell
    Gabriele Keller

    View Slide

  2. Embedded Languages
    A language written inside of another language


    May be domain speci
    fi
    c


    That other language is called the host language
    2

    View Slide

  3. Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .

    View Slide

  4. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .

    View Slide

  5. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .
    eval
    : :
    Exp Int
    - >
    Int


    eval (Lit i) = i


    eval (Succ x) = 1 + eval x


    . . .

    View Slide

  6. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .
    eval
    : :
    Exp Int
    - >
    Int


    eval (Lit i) = i


    eval (Succ x) = 1 + eval x


    . . .

    View Slide

  7. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .
    eval
    : :
    Exp Int
    - >
    Int


    eval (Lit i) = i


    eval (Succ x) = 1 + eval x


    . . .

    View Slide

  8. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .
    eval
    : :
    Exp Int
    - >
    Int


    eval (Lit i) = i


    eval (Succ x) = 1 + eval x


    . . .
    academics

    View Slide

  9. ans
    : :
    Exp Int


    ans = Succ (Lit 41)
    Deeply Embedded Languages
    3
    https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gadt.html
    data Exp a where


    Lit
    : :
    Int
    - >
    Exp Int


    Succ
    : :
    Exp Int
    - >
    Exp Int


    . . .
    eval
    : :
    Exp Int
    - >
    Int


    eval (Lit i) = i


    eval (Succ x) = 1 + eval x


    . . .
    academics
    me

    View Slide

  10. Deeply Embedded Languages
    4

    View Slide

  11. Deeply Embedded Languages
    Advantages:
    Integrates with the host language
    No separate parsing step
    4

    View Slide

  12. Deeply Embedded Languages
    Advantages:
    Integrates with the host language
    No separate parsing step
    Disadvantages:
    No separate parsing step
    4

    View Slide

  13. Deeply Embedded Languages
    Advantages:
    Integrates with the host language
    No separate parsing step
    Disadvantages:
    No separate parsing step
    Deep embeddings lack context information
    4

    View Slide

  14. The Problem
    • Let’s write an embedded program!
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  15. The Problem
    • Let’s write an embedded program!
    1. Write some code
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  16. The Problem
    • Let’s write an embedded program!
    1. Write some code
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  17. The Problem
    • Let’s write an embedded program!
    1. Write some code
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  18. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  19. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  20. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  21. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate

    View Slide

  22. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  23. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  24. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  25. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  26. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  27. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  28. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  29. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  30. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  31. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  32. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍

    View Slide

  33. The Problem
    • Let’s write an embedded program!
    1. Write some code
    2. Run it!
    3. …
    4. Pro
    fi
    t?
    5
    https://github.com/tmcdonell/lulesh-accelerate
    👍
    🤔

    View Slide

  34. The Problem
    There is a disconnect between:


    the embedded program the user writes;


    the abstract syntax tree generated for that program;


    and the optimised code that is eventually executed
    6

    View Slide

  35. Objective
    1. Recover context in deeply embedded programs


    2. Annotate the embedded program with that information


    3. Find other uses for the annotation system
    7

    View Slide

  36. The Idea
    The program is described by some abstract syntax tree


    This AST is built via smart constructors


    These smart constructors should generate and store the necessary annotations
    8

    View Slide

  37. The Idea
    These smart constructors can be encountered by:


    Regular functions


    Type class methods


    Pattern synonymns
    9
    Embedded Pattern Matching, McDonell T.L., Meredith, J.D, and Keller G.

    View Slide

  38. The Idea
    These smart constructors can be encountered by:


    Regular functions


    Type class methods


    Pattern synonymns
    9
    Embedded Pattern Matching, McDonell T.L., Meredith, J.D, and Keller G.
    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit

    View Slide

  39. The Idea
    These smart constructors can be encountered by:


    Regular functions


    Type class methods


    Pattern synonymns
    9
    Embedded Pattern Matching, McDonell T.L., Meredith, J.D, and Keller G.
    instance Num (Exp a) where


    (+) = PrimApp PrimAdd
    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit

    View Slide

  40. The Idea
    These smart constructors can be encountered by:


    Regular functions


    Type class methods


    Pattern synonymns
    9
    Embedded Pattern Matching, McDonell T.L., Meredith, J.D, and Keller G.
    instance Num (Exp a) where


    (+) = PrimApp PrimAdd
    pattern Maybe_
    : :
    Exp a
    - >
    Exp (Maybe a)
    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit

    View Slide

  41. Annotations
    Store metadata for an AST node


    Should be easily extensible


    Adding them shouldn’t change the user-facing language
    10

    View Slide

  42. Storing Annotations
    11
    Trees that Grow, Njjd S. and Peyton-Jones S.
    data Ann = Ann {
    . . .
    }


    data Exp a where


    Lit
    : :
    Ann
    - >

    Succ
    : :
    Ann
    - >

    . . .

    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit mkAnn


    mkAnn
    : : . . . = >
    Ann


    mkAnn = Ann {
    . . .
    }
    Int
    - >
    Exp Int


    Exp Int
    - >
    Exp Int

    View Slide

  43. Storing Annotations
    11
    Trees that Grow, Njjd S. and Peyton-Jones S.
    data Ann = Ann {
    . . .
    }


    data Exp a where


    Lit
    : :
    Ann
    - >

    Succ
    : :
    Ann
    - >

    . . .

    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit mkAnn


    mkAnn
    : : . . . = >
    Ann


    mkAnn = Ann {
    . . .
    }
    Int
    - >
    Exp Int


    Exp Int
    - >
    Exp Int

    View Slide

  44. Storing Annotations
    11
    Trees that Grow, Njjd S. and Peyton-Jones S.
    data Ann = Ann {
    . . .
    }


    data Exp a where


    Lit
    : :
    Ann
    - >

    Succ
    : :
    Ann
    - >

    . . .

    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit mkAnn


    mkAnn
    : : . . . = >
    Ann


    mkAnn = Ann {
    . . .
    }
    Int
    - >
    Exp Int


    Exp Int
    - >
    Exp Int

    View Slide

  45. Storing Annotations
    11
    Trees that Grow, Njjd S. and Peyton-Jones S.
    data Ann = Ann {
    . . .
    }


    data Exp a where


    Lit
    : :
    Ann
    - >

    Succ
    : :
    Ann
    - >

    . . .

    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit mkAnn


    mkAnn
    : : . . . = >
    Ann


    mkAnn = Ann {
    . . .
    }
    Int
    - >
    Exp Int


    Exp Int
    - >
    Exp Int

    View Slide

  46. Storing Annotations
    11
    Trees that Grow, Njjd S. and Peyton-Jones S.
    data Ann = Ann {
    . . .
    }


    data Exp a where


    Lit
    : :
    Ann
    - >

    Succ
    : :
    Ann
    - >

    . . .

    constant
    : :
    Int
    - >
    Exp Int


    constant = Lit mkAnn


    mkAnn
    : : . . . = >
    Ann


    mkAnn = Ann {
    . . .
    }
    Int
    - >
    Exp Int


    Exp Int
    - >
    Exp Int

    View Slide

  47. Source Locations
    Associate AST fragments back to their original source location


    Use that for diagnostics, pro
    fi
    ling, debugging…
    12

    View Slide

  48. Source Locations
    1. GHC Call Stacks: GHC.Stack


    2. RTS Execution Stacks: GHC.ExecutionStack
    13

    View Slide

  49. Source Locations
    1. GHC Call Stacks: GHC.Stack


    2. RTS Execution Stacks: GHC.ExecutionStack
    13
    • Created at compile time
    • Functions require a HasCallStack constraint

    View Slide

  50. Source Locations
    1. GHC Call Stacks: GHC.Stack


    2. RTS Execution Stacks: GHC.ExecutionStack
    13
    • Runtime backtraces!
    • No changes to user code required!
    • Created at compile time
    • Functions require a HasCallStack constraint

    View Slide

  51. Source Locations
    1. GHC Call Stacks: GHC.Stack


    2. RTS Execution Stacks: GHC.ExecutionStack
    13
    • Runtime backtraces!
    • No changes to user code required!
    • Currently unusable 🙁
    • Created at compile time
    • Functions require a HasCallStack constraint

    View Slide

  52. Source Locations
    Three scenarios:


    Regular functions: GHC Call Stacks


    (Existing) Type class methods: RTS Execution Stacks


    Pattern synonyms: GHC Call Stacks (plus some trickery)


    14
    https://gitlab.haskell.org/ghc/ghc/-/issues/19289

    View Slide

  53. HasCallStack
    15
    printError
    : :
    HasCallStack
    = >
    String
    - >
    IO ()


    printError msg = putStrLn msg
    > >
    print callStack

    View Slide

  54. HasCallStack
    15
    printError
    : :
    HasCallStack
    = >
    String
    - >
    IO ()


    printError msg = putStrLn msg
    > >
    print callStack
    printError
    : :
    (?callStack
    : :
    CallStack)
    = >
    String
    - >
    IO ()


    printError msg = putStrLn msg
    > >
    print ?callStack
    desugars to….

    View Slide

  55. HasCallStack
    16
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint!


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()
    - -
    only prints ‘bar’


    bar = print callStack

    View Slide

  56. HasCallStack
    16
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint!


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()
    - -
    only prints ‘bar’


    bar = print callStack

    View Slide

  57. HasCallStack
    16
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint!


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()
    - -
    only prints ‘bar’


    bar = print callStack

    View Slide

  58. SourceMapped
    17
    data OpaqueType = NotExported


    type SourceMapped =


    ( ?requiresSourceMapping
    : :
    OpaqueType, HasCallStack )


    - -
    Throws an error if the caller did not have the HasCallStack constraint


    sourceMap
    : :
    HasCallStack
    = >
    (SourceMapped
    = >
    a)
    - >
    a


    sourceMap k =
    . . .

    View Slide

  59. SourceMapped
    17
    data OpaqueType = NotExported


    type SourceMapped =


    ( ?requiresSourceMapping
    : :
    OpaqueType, HasCallStack )


    - -
    Throws an error if the caller did not have the HasCallStack constraint


    sourceMap
    : :
    HasCallStack
    = >
    (SourceMapped
    = >
    a)
    - >
    a


    sourceMap k =
    . . .
    The only way to satisfy
    the SourceMapped constraint

    View Slide

  60. SourceMapped
    18
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()


    bar = print callStack


    qux
    : :
    HasCallStack
    = >

    qux = sourceMap bar
    - -
    Runtime error: no HasCallStack
    IO ()

    View Slide

  61. SourceMapped
    18
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()


    bar = print callStack


    qux
    : :
    HasCallStack
    = >

    qux = sourceMap bar
    SourceMapped
    - -
    Runtime error: no HasCallStack
    IO ()

    View Slide

  62. SourceMapped
    18
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()


    bar = print callStack


    qux
    : :
    HasCallStack
    = >

    qux = sourceMap bar
    SourceMapped
    - -
    Compilation error: unbound implicit parameter
    - -
    Runtime error: no HasCallStack
    IO ()

    View Slide

  63. SourceMapped
    18
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()


    bar = print callStack


    qux
    : :
    HasCallStack
    = >

    qux = sourceMap bar
    SourceMapped
    - -
    Compilation error: unbound implicit parameter
    - -
    Runtime error: no HasCallStack
    IO ()

    View Slide

  64. SourceMapped
    18
    main
    : :
    HasCallStack
    = >
    IO ()


    main = foo


    foo
    : :
    IO ()
    - -
    silent error: no HasCallStack constraint


    foo = bar


    bar
    : :
    HasCallStack
    = >
    IO ()


    bar = print callStack


    qux
    : :
    HasCallStack
    = >

    qux = sourceMap bar
    SourceMapped
    - -
    Compilation error: unbound implicit parameter
    IO ()
    - -
    Works!

    View Slide

  65. Putting it together
    19
    data Ann = Ann { locations
    : :
    HashSet CallStack,
    . . .
    }


    mkAnn
    : :
    SourceMapped
    = >
    Ann


    mkAnn = Ann { locations = capture ?callStack }


    where


    capture =
    . . .

    constant
    : :
    HasCallStack
    = >
    Int
    - >
    Exp Int


    constant = sourceMap $ Lit mkAnn

    View Slide

  66. Putting it together
    19
    data Ann = Ann { locations
    : :
    HashSet CallStack,
    . . .
    }


    mkAnn
    : :
    SourceMapped
    = >
    Ann


    mkAnn = Ann { locations = capture ?callStack }


    where


    capture =
    . . .

    constant
    : :
    HasCallStack
    = >
    Int
    - >
    Exp Int


    constant = sourceMap $ Lit mkAnn
    • inlining
    • loop unrolling
    • [no] fast math
    • …

    View Slide

  67. Case study: Accelerate
    • Deeply embedded language for data-parallel array computations

    - Multiple backends (CPU, GPU, …)
    - Multiple expression types (collective array and scalar expression)
    - Multiple AST types (surface language in HOAS, internal language is
    fi
    rst-order)
    20

    View Slide

  68. Accelerate: Sharing recovery
    • Finds shared parts of the program based on stable names; moves those parts to explicit binders

    • NEW:

    - Keep track of annotation state during sharing recovery
    - Enable terms to be inlining by ignoring sharing
    21

    View Slide

  69. Accelerate: Array fusion
    • Producer/consumer fusion rewrites the program to combine operations
    22

    View Slide

  70. Accelerate: Array fusion
    • Producer/consumer fusion rewrites the program to combine operations
    22
    map f . map g

    View Slide

  71. Accelerate: Array fusion
    • Producer/consumer fusion rewrites the program to combine operations
    22
    map f . map g
    map (f . g)
    rewries into…

    View Slide

  72. Accelerate: Array fusion
    • Producer/consumer fusion rewrites the program to combine operations
    • NEW:

    - Multiple AST nodes may be merged; new annotation contains the union of the call stack sets
    - Source locations may be disjoint
    - Optimisation
    fl
    ags might differ…
    22
    map f . map g
    map (f . g)
    rewries into…

    View Slide

  73. DEMO
    23

    View Slide

  74. Pro
    fi
    ling LULESH
    • One kernel accounts for 66% of the runtime

    - A good candidate to experiment with loop unrolling…





    24
    Ryzen 9 5900x: 8.66 s
    - >
    9.6 s
    RTX 2080 Super: 3.13 s
    - >
    3.07 ± 0.04 s

    View Slide

  75. Pro
    fi
    ling LULESH
    • One kernel accounts for 66% of the runtime

    - A good candidate to experiment with loop unrolling…





    24
    Ryzen 9 5900x: 8.66 s
    - >
    9.6 s
    RTX 2080 Super: 3.13 s
    - >
    3.07 ± 0.04 s

    View Slide

  76. Pro
    fi
    ling LULESH
    • One kernel accounts for 66% of the runtime

    - A good candidate to experiment with loop unrolling…





    24
    Ryzen 9 5900x: 8.66 s
    - >
    9.6 s
    RTX 2080 Super: 3.13 s
    - >
    3.07 ± 0.04 s
    • 14x higher L1 inst. cache miss rate
    • 7x higher LL inst. cache miss rate

    View Slide

  77. Summary
    New annotation system opens the door to a better developer experience


    Future work:


    1. Expression-level debugging and pro
    fi
    ling


    2. Granular loop optimisations


    3. …?


    4. Merge it
    25

    View Slide

  78. AccelerateHS.org
    https://github.com/AccelerateHS/
    Robbert van der Helm Trevor L. McDonell Gabriele Keller

    View Slide