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

Designing with Static Capabilities and Effects

Designing with Static Capabilities and Effects

Presentation at the Workshop on Object Capability Languages, Systems and Applications (OCAP) 2018 in Boston, MA.

Colin S Gordon

November 06, 2018
Tweet

More Decks by Colin S Gordon

Other Decks in Research

Transcript

  1. Capabilities and Effects • Capabilities exist to restrict program effects

    • Expression e can only do what it has capabilities for • If e is given no capability to do X, it cannot do X! • Can be done statically, or dynamically • Statically, object capabilities correspond to reference capabilities. • An alternative static approach to restricting effects is an effect system. • Both are useful for the same purpose – so why do we have both?
  2. Trade-Offs • If you’ve worked with these systems, you already

    know them! • But nobody talks about them • We’ll talk about trade-offs between specific systems for the same problem • Outsiders / newcomers have to learn it by trial and error • No established language to talk about these things • Let’s talk
  3. What Will We Talk About? • My experience, which seems

    generalizable • I’ve worked on reference capability systems and effect systems, sometimes separate, sometimes together! • This describes most of the research I’ve done • Drawbacks of reference capabilities, and partial work-arounds • Drawbacks of effect systems • How these drawbacks affected the design of three systems: • M# (OOPSLA 2012), JavaUI (ECOOP 2013), SJS (OOPSLA 2016)
  4. Constraining Effects with Capabilities • Reference immutability uses reference capabilities

    to restrict mutation • Can be used for data race freedom • E.g., M# (OOPSLA 2012), a precursor to Pony (AGERE 2015) • M# was the primary programming language for Midori, an attempt to productize the Singularity OS
  5. Exploiting Capabilities Г, ∆ Г ⊢ ⊣ Г′ ∆ ⊢

    ⊣ ∆′ Г, ∆ ⊢ || ⊣ Г′, ∆′ 𝐼𝐼𝐼𝐼𝐼𝐼𝐼𝐼𝐼𝐼 Г, Г′ Г ⊢ ⊣ Г′, : 𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟𝑟 Г′ ⊢ ⊣ Г′, : 𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖𝑖
  6. Using Recovery readable T RandomChoice(readable T a, readable T b)

    { ... } ... {x,y:immutable T} z = RandomChoice(x,y); {x,y:immutable T,z:readable T} {x,y,z:immutable T}
  7. Use vs. Mention {x:readable T,y:writable T,z:writable T} /* concurrent work

    with x */ y = z; /* more concurrent work with x */ {x:readable T,y:writable T,z:writable T} This cannot be parallelized! But y and z aren’t used in unsafe ways. I call this the Use-Mention Distinction after a similar concept from analytic philosophy.
  8. Effects Distinguish Use and Mention • Consider a hypothetical effect

    system, NoHeapAccess ≤ HeapAccess • Г, : , : ⊢ = ∶ 𝑢𝑢𝑢𝑢𝑢𝑢 | • This super-simple effect system could be used to parallelize “x=y” The simplicity of capabilities comes from conflating use and mention: Can’t use what you can’t even mention
  9. Working Around Use-Mention • This is a problem, but clearly

    not fatal • M# used to write most of Midori (several million LOC) • Pony is used in industry • Arguably Rust has some flavor of this • Framing / Weakening to the rescue! • Refactor the x=y elsewhere, frame away x and y and parallelize • But this only works for some problems
  10. Safe Concurrency in UIs • GUI frameworks use a distinguished

    UI thread • Background threads are used for expensive work • All UI updates *must* occur on the UI thread • Interesting reasons; see ECOOP’13 paper for discussion • Don’t do it? Exceptions, crashes, corruptions, and more. Background Thread UI Thread
  11. Statically Safe UI Concurrency • Keeping UI actions on the

    UI thread requires identifying UI elements statically • References to UI elements == reference capabilities • Idea: Don’t let background threads hold refs to UI objects! • Sound! • Also useless for existing frameworks…
  12. Sending Results from BG to UI final @UI JLabel label

    = ...; ...new Thread() { public void run() { // do really slow computation Display.asyncExec(new @UI Runnable() { public void run() { label.setText("Complete!"); } }); } }... Local on UI thread Captured as member of BG thread Captured again Used on UI thread Must distinguish use and mention! UI  background  UI data flow So we’ll just always use effects, right?
  13. Thought Experiment: Reference Immutability as an Effect System • Effects

    need to indicate variables used transitively for writing • With typical assumptions: Γ ⊢ x. f := e : U |{wr(x)} • What about let x = e1 in e2? • If e2’s effect mentions x, how do we rename? • If e1 ≡ y.f.g, we need to work back towards y • But if e2 reassigns y, or y.f, or y.f.g… we need flow-sensitive points-to analysis • Problem: Naming in the presence of mutation is hard! • Think this gets ugly? I tried to work it out for all of M#. • Think *that* sounds ugly? Wait till you see what else I tried!
  14. What Went Wrong? • Why does an effect system version

    of reference immutability get so complicated so quickly? 1. Naming things is hard (+ mutation + aliasing…) 2. The effect system has to work out on its own how variables are used • So why is the capability version simpler? • Type contexts enforce invariants • If x and y have the same type, they are treated the same • Adding capabilities to the types means the capabilities conflate what types already do – resulting in less friction for developers or designers But is it only this “kind” of system that breaks this way?
  15. Compiling JavaScript is Hard, Reason 1 of • JavaScript can

    dynamically add and remove object fields • So JavaScript breaks a fundamental assumption for optimization • This is one of many reasons JITs are used for JavaScript: • Generate code specialized to the layout of one set of objects • If the layout changes, throw away and recompile at runtime • Great performance! • Terrible memory usage! Goal: Using types, remove this pathology, and compile field accesses statically & efficiently
  16. Violating Fixed Object Layout var f = new F(); //

    f : { x:num, count : ()-->num, inc : ()-->() } f.inc(); // modifies x // F.prototype : { count : ()-->num, inc : ()-->() } F.prototype.inc() // ADDS x Calls method writing this.x on object without x!
  17. Controlling Writes, Take 1: Effects • Effects for which receiver

    fields are modified {x:num, get : ()-->num, inc : ()--[x]-->() } • Good: Fixes previous slide! • F.prototype.inc() invokes on a method w/o field x • Bad: receivers can escape methods, receivers insufficient … function() { foo(this); }
  18. Controlling Writes, Take 2: Better Effects • Effects track which

    fields are changed in *each* object {x:num, get : ()-->num, inc : ()--[this.x]-->() } • Good: Fixes original and examples with receiver escape! • Bad: need points-to information… solvable • Worse: rep exposure causes abstraction issues. No common supertype of f & g can call inc: f:{x:num, get : ()-->num, inc : ()--[this.x]-->() } g:{y:num, get : ()-->num, inc : ()--[this.y]-->() }
  19. Controlling Writes, Take 3: Existentials & Rows • First existentially

    quantify modified field { x:num, …, inc : ∃x.()--[this.x]-->() } • Then account for variable # of fields: { x:num, …, inc : ∃X.()--[wr(this.X)]-->() } • Good: The two inc signatures have a common abstraction! • Bad: Row variable disconnected from field presence… Still can’t call inc!
  20. Controlling Writes, Take 4: Existentials, Rows, and Bounds • Bounded

    existentially quantified row variables! • Good: Solves previous problem • Bad: As ugly as it sounds • Worse: Parallel problem for field reads…
  21. …Mistakes Were Made • Clearly complicated. • But is it

    inappropriately complicated? • Context required type inference • Type inference with records requires row variables • (Mitchell Wand invented rows for this purpose – LICS’87) • Type inference with records and subtyping requires row constraints • Lots of work by Cardelli, Smith, Trifinov, Pottier, and others in the 1990s • Hard to tell at the time – and the impl. still uses these! • In hindsight: Clearly too complex, inference would have had TERRIBLE error messages
  22. Key: Global Invariants Through Local Capabilities • Key concepts when

    splitting reference capability A into B and C: • B and C together permit at most what A allowed • B (resp. C) assumes interference as assumed by A, plus C (resp. B) • Used to enforce invariants (immutability or more) in: • Rely-Guarantee references (PLDI’13, TOPLAS’17) • Rely-Guarantee protocols (ECOOP’14, ECOOP’16) • All reference immutability systems (M#, Pony, Javari, OIGJ…) • Kappa (ECOOP’17) • … Compatibility is an underappreciated aspect of capabilities. We solved the fixed layout problem with compatible concrete & abstracted capabilities
  23. Wrapping Up • Static capabilities and effects both have their

    place • Capabilities permit (subjectively) simpler systems for controlling effects, but can’t distinguish use and mention • Effects are more powerful and distinguish use and mention, but data- specific effects can quickly get out hand • Effects can be a useful intermediate stage of designing a capabilities system • Using capabilities to enforce global invariants is underappreciated