Dynamic Languages need Dynamic Compilers Maxime Chevalier-Boisvert - - PowerPoint PPT Presentation
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:
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
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
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
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
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
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
8
9
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
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
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
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
15
A C B D false true
16
is_int32(n)? C B D false true
17
if (is_int32(n)) { // B ... } else { // C ... } // D ...
18
is_int32(n)? C B D false true
19
is_int32(n)? C B D false true n is int32
20
is_int32(n)? C B D false true n is not int32 n is int32
21
is_int32(n)? C B D false true n is not int32 n is int32 n is ???
22
is_int32(n)? C B D' false true n is not int32 n is int32 D'' n is int32 n is not int32
23
var v = 4294967296; for (var i = 0; i < 600000; i++) v = v & i; // From the SunSpider bitwise-and benchmark
24
var v = 4294967296; for (var i = 0; less_than(i,600000); i = add(i,1)) v = bitwise_and(v,i);
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)); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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)); }
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 }
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 }
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
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?
44
Results!
45
46
47
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
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
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
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
52
foo(v) { if (typeof v === 'number') return v; if (typeof v === 'string') return v.length; throw TypeError('invalid input'); }
53
foo(v) { if (typeof v === 'number') return v; if (typeof v === 'string') return v.length; throw TypeError('invalid input'); }
54
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…) true false false true
55
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
56
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
57
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
58
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
59
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
60
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
61
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
62
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
63
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
64
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
65
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
66
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
67
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(3) print(x)
68
typeof v === 'number' foo(v) return v typeof v === 'string' return v.length
throw TypeError(…)
x = foo(i) print(x)
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
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
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
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
73
github.com/maximecb/Higgs #higgsjs on freenode IRC pointersgonewild.wordpress.com maximechevalierb@gmail.com Love2Code on twitter
74
Special thanks to:
- Prof. Marc Feeley