Slide 1

Slide 1 text

Optimizing With Static Hermes N E I L D H A R , M E T A 2

Slide 2

Slide 2 text

What is Static Hermes? • Optional sound type annotations to improve performance • Allow ahead of time compilation to compact bytecode or fast native binaries • Tight native integration through zero-cost calls to C functions 3

Slide 3

Slide 3 text

Benefits 4 • Improve performance of existing Flow/TypeScript with minimal changes • Avoid needing to switch from JS to C++ for native interop

Slide 4

Slide 4 text

Are we Static Hermes yet? • Fully open source, in the static_h branch of the facebook/hermes repo • Not ready for general use • Many typed features implemented (e.g. unions, classes, generics) • Major improvements to untyped code spec compliance and performance in the works 5

Slide 5

Slide 5 text

6 1300 ms 120 ms ???

Slide 6

Slide 6 text

Compiler Pipeline 7 Parser Type checker Optimizer Hermes Bytecode Native code

Slide 7

Slide 7 text

Sound Types • Type annotations in Flow and TypeScript are unsound, and do not guarantee that types are correct at runtime • Static Hermes modifies some JS semantics to allow efficient sound typing • New soundly typed code can co-exist and interop with unmodified code 8

Slide 8

Slide 8 text

Unsound Types Example function addXY(x: number, y: number): number { return x + y; } let a: number[] = [1, 2, 3]; addXY(a[10], a[11]); 9

Slide 9

Slide 9 text

Unsound Types Example function addXY(x: number, y: number): number { return x + y; } let a: number[] = [1, 2, 3]; // Adding undefined + undefined! addXY(a[10], a[11]); 1 0

Slide 10

Slide 10 text

Sound Type Enforcement function addXY(x: number, y: number): number { return x + y; } let a: number[] = [1, 2, 3]; // RangeError thrown at runtime! addXY(a[10], a[11]); 1 1

Slide 11

Slide 11 text

Class Fields 1 2 class Vector { constructor(x, y, z) { this.x = x; this.y = y; this.z = z; } } class Vector { x: number; y: number; z: number; constructor(x: number, y: number, z: number) { this.x = x; this.y = y; this.z = z; } } Untyped Typed print(myVec.z);

Slide 12

Slide 12 text

Reading myVec.z 1. Check that myVec is an object 2. Get the cache entry for the read 3. Compare the object with the cache 4. On match, load from cached offset 5. On fail, full dictionary lookup 1. Load from offset 2 1 3 Untyped Typed

Slide 13

Slide 13 text

Array Access 1 4 function getIdx( myArr: number[], i: number ): number { return myArr[i]; } Untyped Typed function getIdx( myArr, i ) { return myArr[i]; }

Slide 14

Slide 14 text

Reading myArr[i] 1. Check that myArr is an object 2. Check if i is index-like 3. If it is, attempt to do an indexed load 4. Check if the value is available 5. If any checks fail, full dictionary lookup 1. Check that i is an integer within bounds 2. Load from the array at i 1 5 Untyped Typed

Slide 15

Slide 15 text

myVec.z + myArr[i] 1. Slow property load from myVec 2. Slow array load from myArr 3. Check if both are numbers 4. If they are, add them 5. If not, fall through to general case (strings, objects, BigInt, etc) 1. Fast load from myVec 2. Fast load from myArr 3. Add the numbers 1 6 Untyped Typed

Slide 16

Slide 16 text

1 7 1300 ms 120 ms Types 350 ms ???

Slide 17

Slide 17 text

Compiler Pipeline 1 8 Parser Type checker Optimizer Hermes Bytecode Native code

Slide 18

Slide 18 text

Inlining 1 9 1 9 “He will win who knows when to inline and when not to inline.” soft e

Slide 19

Slide 19 text

Motivation • Code tends to have a lot of small functions • Calls have significant overhead • Most optimizations cannot operate across calls 2 0

Slide 20

Slide 20 text

Inlining Example 2 1 function normal(pos: Vector): Vector { return Vector_norm(Vector_minus(pos, this.center)); } function Vector_minus(v1: Vector, v2: Vector): Vector { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } function Vector_norm(v: Vector): Vector { var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return new Vector(div * v.x, div * v.y, div * v.z); }

Slide 21

Slide 21 text

Inlining Example 2 2 function normal(pos: Vector): Vector { var v = new Vector( pos.x – this.center.x, pos.y – this.center.y, pos.z – this.center.z ); var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return new Vector(div * v.x, div * v.y, div * v.z); } *Artist’s impression Vector_minus Vector_norm

Slide 22

Slide 22 text

Inlining Example 2 3 function normal(pos: Vector): Vector { var v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; } *Artist’s impression

Slide 23

Slide 23 text

Call Analysis function foo(){ let op = (a, b) => a + b; function bar(a, b){ return op(a, b); } return bar; } 2 4

Slide 24

Slide 24 text

Call Analysis function foo(){ let op = Math.random() > 0.5 ? (a, b) => a + b : (a, b) => a / b; function bar(a, b){ return op(a, b); } return bar; } 2 5

Slide 25

Slide 25 text

Inlining Methods 2 6 class Base { getID(): number { return 0; } }; function getIDPlusOne(base: Base): number { return base.getID() + 1; } class Derived extends Base { getID(): number { return 42; } };

Slide 26

Slide 26 text

Inlining Methods 2 7 class Base { getID(): number { return 0; } }; function getIDPlusOne(base: Base): number { return base.getID() + 1; } class Derived extends Base { getID(): number { return 42; } };

Slide 27

Slide 27 text

Inlining Methods 2 8 class Base { getID(): number { return 0; } }; function getIDPlusOne(derived: Derived): number { return derived.getID() + 1; } class Derived extends Base { getID(): number { return 42; } };

Slide 28

Slide 28 text

Inlining Methods 2 9 class Base { getID(): number { return 0; } }; function getIDPlusOne(derived: Derived): number { return 43; } class Derived extends Base { getID(): number { return 42; } };

Slide 29

Slide 29 text

Types Enable Inlining 3 0 class Base { getID() { return 0; } }; function getIDPlusOne(derived) { return derived.getID() + 1; } class Derived extends Base { getID() { return 42; } };

Slide 30

Slide 30 text

Inlining Recap • Eliminate calls by copying a function’s contents to where it is called • Depends on the compiler’s ability to statically identify what function is being called, types make this much easier • Enables many other optimizations 3 1

Slide 31

Slide 31 text

3 2 1300 ms 120 ms Types 350 ms Inlining 210 ms ???

Slide 32

Slide 32 text

Object Elision 3 3

Slide 33

Slide 33 text

Motivation • JavaScript tends to create many small objects • Object allocations are expensive • Analysing the values of object fields is difficult 3 4

Slide 34

Slide 34 text

Object Elision Example 3 5 function normal(pos: Vector): Vector { var v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; }

Slide 35

Slide 35 text

Object Elision Example 3 6 function normal(pos: Vector): Vector { var v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; }

Slide 36

Slide 36 text

Object Elision Example 3 7 function normal(pos: Vector): Vector { var vx = pos.x – this.center.x; var vy = pos.y – this.center.y; var vz = pos.z – this.center.z; var mag = Math.sqrt(vx * vx + vy * vy + vz * vz); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * vx, y: div * vy, z: div * vz}; }

Slide 37

Slide 37 text

Elision Inlining class Foo { bar: () => number; constructor(bar: () => number) { this.bar = bar; } }; 3 8 function myFun(){ let fbar = () => 42; return fbar(); } function myFun(){ let f = new Foo(() => 42); let fbar = f.bar; return fbar(); } function myFun(){ return 42; } function myFun(){ let f = { bar: () => 42 }; let fbar = f.bar; return fbar(); } Inlining Object elision Inlining

Slide 38

Slide 38 text

3 9 1300 ms 120 ms Types 350 ms Inlining 210 ms Object elision

Slide 39

Slide 39 text

Recap • Static Hermes enforces soundness of declared types • We use type information to speed up basic operations • Types unlock additional ahead-of-time optimizations like inlining and object elision • These dramatically improve the performance of typed code 4 0

Slide 40

Slide 40 text

Thank You! • For questions or suggestions, reach out to us on GitHub! o https://github.com/facebook/hermes/discussions/categories/static-hermes • For an introduction to Static Hermes, watch Tzvetan's talk at RN EU 2023 o https://youtu.be/q-xKYA0EO-c • For the raytracer benchmark, check out the static_h branch on GitHub o https://github.com/facebook/hermes/tree/static_h/benchmarks/raytracer 4 1