Dynamic Languages need Dynamic Compilers Maxime Chevalier-Boisvert - - PowerPoint PPT Presentation

dynamic languages need dynamic compilers
SMART_READER_LITE
LIVE PREVIEW

Dynamic Languages need Dynamic Compilers Maxime Chevalier-Boisvert - - PowerPoint PPT Presentation

Dynamic Languages need Dynamic Compilers Maxime Chevalier-Boisvert mloc.js 2014 Introduction PhD candidate at the Universit de Montral, prof. Marc Feeley Research: compilers, optimizing dynamic languages, JS This talk is about:


slide-1
SLIDE 1

Dynamic Languages need Dynamic Compilers

Maxime Chevalier-Boisvert mloc.js 2014

slide-2
SLIDE 2

2

Introduction

  • PhD candidate at the Université de Montréal, prof. Marc Feeley
  • Research: compilers, optimizing dynamic languages, JS
  • This talk is about:

– Dynamic languages – JIT compilation & optimization – The Higgs JS compiler – Basic block versioning – Lazy incremental compilation

slide-3
SLIDE 3

3

Dynamic languages

  • JS is not an "interpreted language"

– Originally ran in an interpreter, back in 1995 – Today, all browsers have JIT compilers

  • Family of languages sharing “dynamic” characteristics

– e.g.: JS, Python, Ruby, PHP, Perl, MATLAB, LISPs

  • Huge gain in popularity with web development

– Rapid prototyping, short development cycles

slide-4
SLIDE 4

4

Defining dynamic languages

  • Dynamic typing

– Variables can change type over time – Types associated with values – No type annotations

  • Late binding

– Symbols resolved dynamically (e.g.: globals) – Dynamic loading of code (eval, load)

  • Dynamic growth of objects

– Dynamic arrays – Objects as dictionaries/maps

slide-5
SLIDE 5

5

"I think of a programming language as a tool to convert a programmer's mental images into precise operations that a machine can perform. The main idea is to match the user's intuition as well as possible. […]" – Donald Knuth

slide-6
SLIDE 6

6

Higher-level thinking

  • Reduce the cognitive burden on programmers
  • Hope to make you more productive

– Write less code for the same result – Each line of code conveys more meaning

  • Leave more details implicit, assumed

– Invisible work happen behind the scenes – This invisible work carries extra overhead

slide-7
SLIDE 7

7

Dynamic type tests

  • Operators change meaning based on types

– Different operations for different inputs

  • For example, the + operator could mean:

– Integer addition – Floating-point addition – String concatenation – Array appending

  • Inside each operator hide many type tests

– Functions with cascades of if statements testing types

slide-8
SLIDE 8

8

slide-9
SLIDE 9

9

slide-10
SLIDE 10
slide-11
SLIDE 11

11

JIT compilation

  • Key advantages over static compilers

– Observe, monitor programs as they run – Modify code at run time

  • In theory, JITs can produce faster code

– Code adapted to program's behavior – Don't account for unexecuted code

  • Opinion: modern JITs still overly static

– Compiler books are all about AOT compilers

slide-12
SLIDE 12

12

Higgs

  • JavaScript JIT compiler for x86-64

– Open source, mostly written in D – Self-hosted runtime library, in JS – Relatively small codebase (~ 50 KLOC)

  • Support for most of ECMAScript 5

– No with statement, no getters/setters – Extras: Foreign Function Interface (FFI)

  • Experimental platform

– Try new ideas, uncharted territory – Basic block versioning, lazy incremental compilation

slide-13
SLIDE 13

13

Self-hosting

  • Runtime and standard library are self-hosted
  • JS primitives (e.g.: + operator) written in extended JS

– Exposes low-level operations (e.g.: machine integer ops)

  • Primitives compiled & inlined like any other JS code

– Avoids opaque calls into C or D code

  • Easy to change & extend runtime
  • Inlining critical for performance
slide-14
SLIDE 14

Basic block versioning

  • Similarities with tracing, procedure cloning
  • As you compile code, accumulate facts

– e.g.: type information – Branch tests add type & shape information

  • Specialize based on accumulated facts

– Type information: type tags, variables, objects – Potentially: register allocation state

  • May compile multiple versions of code
slide-15
SLIDE 15

15

A C B D false true

slide-16
SLIDE 16

16

is_int32(n)? C B D false true

slide-17
SLIDE 17

17

if (is_int32(n)) { // B ... } else { // C ... } // D ...

slide-18
SLIDE 18

18

is_int32(n)? C B D false true

slide-19
SLIDE 19

19

is_int32(n)? C B D false true n is int32

slide-20
SLIDE 20

20

is_int32(n)? C B D false true n is not int32 n is int32

slide-21
SLIDE 21

21

is_int32(n)? C B D false true n is not int32 n is int32 n is ???

slide-22
SLIDE 22

22

is_int32(n)? C B D' false true n is not int32 n is int32 D'' n is int32 n is not int32

slide-23
SLIDE 23

23

var v = 4294967296; for (var i = 0; i < 600000; i++) v = v & i; // From the SunSpider bitwise-and benchmark

slide-24
SLIDE 24

24

var v = 4294967296; for (var i = 0; less_than(i,600000); i = add(i,1)) v = bitwise_and(v,i);

slide-25
SLIDE 25

25

var v = 4294967296; for (var i = 0; less_than(i,600000); i = add(i,1)) v = bitwise_and(v,i); function bitwise_and(x,y) { if (is_int32(x) && is_int32(y)) return bitwise_and_int32(x,y); // Fast path return bitwise_and_int32(toInt32(x), toInt32(y)); }

slide-26
SLIDE 26

26

var v = 4294967296; for (var i = 0; less_than(i,600000); i = add(i,1)) v = bitwise_and(v,i); function bitwise_and(x,y) { if (is_int32(x) && is_int32(y)) return bitwise_and_int32(x,y); // Fast path return bitwise_and_int32(toInt32(x), toInt32(y)); } function add(x,y) { if (is_int32(x) && is_int32(y)) { var r = add_int32(x,y); // Fast path if (cpu_overflow_flag) r = add_double(toDouble(x), toDouble(y)); return r; } return add_general(x,y); }

slide-27
SLIDE 27

27

var v = 4294967296; for (var i = 0; less_than(i,600000); i = add(i,1)) v = bitwise_and(v,i); function bitwise_and(x,y) { if (is_int32(x) && is_int32(y)) return bitwise_and_int32(x,y); // Fast path return bitwise_and_int32(toInt32(x), toInt32(y)); } function add(x,y) { if (is_int32(x) && is_int32(y)) { var r = add_int32(x,y); // Fast path if (cpu_overflow_flag) r = add_double(toDouble(x), toDouble(y)); return r; } return add_general(x,y); }

slide-28
SLIDE 28

28

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (is_int32(i) && is_int32(600000)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i) && is_int32(1)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-29
SLIDE 29

29

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (is_int32(i) && is_int32(600000)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i) && is_int32(1)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-30
SLIDE 30

30

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (is_int32(i)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-31
SLIDE 31

31

var v = 4294967296; var i = 0; // when we enter the loop, i is int32 for (;;) { // if (i >= 600000) break; if (is_int32(i)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-32
SLIDE 32

32

var v = 4294967296; var i = 0; for (;;) { // assume i is int32 when entering loop // if (i >= 600000) break; if (is_int32(i)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-33
SLIDE 33

33

var v = 4294967296; var i = 0; for (;;) { // assume i is int32 when entering loop // if (i >= 600000) break; if (is_int32(i)) if (greater_eq_int32(i, 600000)) break; else if (greater_eq_general(i, 600000) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-34
SLIDE 34

34

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-35
SLIDE 35

35

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-36
SLIDE 36

36

var v = 4294967296; var i = 0; for (;;) { // assume both i and v are int32 // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i if (is_int32(v) && is_int32(i)) v = bitwise_and_int32(v,i); else v = bitwise_and_int32(toInt32(v), toInt32(i)); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-37
SLIDE 37

37

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i v = bitwise_and_int32(v,i); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-38
SLIDE 38

38

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i v = bitwise_and_int32(v,i); // i = i + 1 if (is_int32(i)) { i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); } else i = add_general(i,1); }

slide-39
SLIDE 39

39

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i v = bitwise_and_int32(v,i); // v remains int32 after this // i = i + 1 i = add_int32(i,1); if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); }

slide-40
SLIDE 40

40

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i v = bitwise_and_int32(v,i); // i = i + 1 i = add_int32(i,1); // the add could overflow! if (cpu_overflow_flag) i = add_double(toDouble(i), toDouble(1)); // i becomes double }

slide-41
SLIDE 41

41

var v = 4294967296; var i = 0; for (;;) { // if (i >= 600000) break; if (greater_eq_int32(i, 600000)) break; // v = v & i v = bitwise_and_int32(v,i); // i = i + 1 i = add_int32(i,1); if (cpu_overflow_flag) { i = add_double(toDouble(i), toDouble(1)); // Jump to a loop version where i is a double NEW_LOOP_VERSION = gen_new_version({'i':'double'}); goto NEW_LOOP_VERSION; } // If we make it here, i is still int32 }

slide-42
SLIDE 42

42

A “multi-world” approach

  • Traditional type analysis

– Fixed-point on types – Types found must agree with all inputs – Pessimistic, conservative answer

  • Basic block versioning

– Multiple solutions possible for each block – Don't necessarily have to sacrifice – Fixed-point on versioning of blocks

slide-43
SLIDE 43

43

Research questions

  • How much code blowup can we expect?
  • What can we do to reduce code blowup?
  • What performance gains can we expect?
  • What kind of info should we version with?

– Constant propagation – Granularity of type info used – How much is too much?

  • What's the effect on compilation time?
slide-44
SLIDE 44

44

Results!

slide-45
SLIDE 45

45

slide-46
SLIDE 46

46

slide-47
SLIDE 47

47

slide-48
SLIDE 48

48

Unseen benefits

  • Automatically hoists redundant type tests in loops
  • Unfolds the first few loop iterations when beneficial
  • Multiple separate optimized code paths
  • Can be controlled to converge as fast as desired

– Can place hard upper limit on compilation time – Not worried about time/code size blowup

slide-49
SLIDE 49

49

Higgs 1.0 : weaknesses

  • Some versions compiled but never executed!

– Method-based JIT: not enough information – Can't know which paths will/won't be executed

  • Self-hosting, must inline primitives for performance

– Primitives are relatively large functions – Inlining large functions is slow, inefficient – On short benchmarks, inlining doesn't pay for itself

slide-50
SLIDE 50

50

Method-based compilation

  • Most JIT compilers

– V8, SpiderMonkey, JSC, Java VMs – Functions as a compilation unit

  • Compile entire methods/functions at a time

– Often done lazily, on first call

  • Weaknesses

– Info available at compilation time may be limited – If changing compiled code, recompile whole method – Inlining requires recompilation

slide-51
SLIDE 51

51

Lazy incremental compilation

  • At the intersection of method-based and trace compilation
  • Smaller compilation unit: basic-blocks

– Small chunk of code with no control-flow inside – e.g.: then clause of if statement, body of a loop

  • Interleaves compilation and execution

– Compiles blocks lazily: when they are first executed

  • Avoid compiling entire methods, invalidating entire methods

– e.g.: never executed error handling is never compiled

  • Meshes very well with basic block versioning
slide-52
SLIDE 52

52

foo(v) { if (typeof v === 'number') return v; if (typeof v === 'string') return v.length; throw TypeError('invalid input'); }

slide-53
SLIDE 53

53

foo(v) { if (typeof v === 'number') return v; if (typeof v === 'string') return v.length; throw TypeError('invalid input'); }

slide-54
SLIDE 54

54

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…) true false false true

slide-55
SLIDE 55

55

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

slide-56
SLIDE 56

56

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-57
SLIDE 57

57

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-58
SLIDE 58

58

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-59
SLIDE 59

59

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-60
SLIDE 60

60

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-61
SLIDE 61

61

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-62
SLIDE 62

62

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-63
SLIDE 63

63

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-64
SLIDE 64

64

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-65
SLIDE 65

65

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-66
SLIDE 66

66

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-67
SLIDE 67

67

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(3) print(x)

slide-68
SLIDE 68

68

typeof v === 'number' foo(v) return v typeof v === 'string' return v.length

throw TypeError(…)

x = foo(i) print(x)

slide-69
SLIDE 69

69

On inlining

  • With self-hosting, primitives in JS, inlining is crucial

– Each JS operator is a costly function call

  • Inlining is expensive

– Copying, transforming IR nodes – Recompiling entire methods to inline callees

  • Inlining is highly speculative, cost vs benefit

– High cost of recompiling/replacing caller function – Eliminate call overhead, inlined code may be faster

  • Many callees never fully executed, only parts

– Inline a huge function, only use a small part of it

slide-70
SLIDE 70

70

Incremental inlining

  • Incremental compilation allows recompiling basic blocks
  • Idea: inline by recompiling call sites

– Incrementally compile callee as if part of caller – Patch call site, jump to the inlined function

  • Advantages:

– Avoid throwing away compiled code – Do away with on-stack replacement – Inline only what is executed in the callee

slide-71
SLIDE 71

71

Current status

  • Higgs: available on GitHub

– Tested on ~30 JS benchmarks – Bug reports & pull requests welcome

  • Basic block versioning: working & tested

– Results to be published soon

  • Lazy incremental compilation: in latest Higgs
  • Incremental inlining: work in progress
slide-72
SLIDE 72

72

How can Higgs help you?

  • Learn about JIT compilers

– Small codebase, highly commented, helpful people

  • Borrow pieces for your own project

– Parser, AST, IR, x86 codegen, runtime, stdlib

  • Test out new ideas

– Tinker with it, new primitivies, extend the language

  • Things you can't do in other JavaScript VMs

– C FFI: malloc/free, fork, stdio, etc. – Low-level machine instructions

slide-73
SLIDE 73

73

github.com/maximecb/Higgs #higgsjs on freenode IRC pointersgonewild.wordpress.com maximechevalierb@gmail.com Love2Code on twitter

slide-74
SLIDE 74

74

Special thanks to:

  • Prof. Marc Feeley

Tom Brasington @t3brz Luke Wagner (blog.mozilla.org/luke) Zimbabao Borbaki @zimbabao Óscar Toledo @nanochess