Slide 1

Slide 1 text

Integrating Static Optimization and Dynamic Nature in JavaScript The 24th ACM SIGPLAN International Conference on Generative Programming: Concepts & Experiences (GPCE 2025) Tadashi Saito1 Hideya Iwasaki2 2025-07-03 1The University of Electro-Communications, Japan 2Meiji University, Japan

Slide 2

Slide 2 text

Background: Evolution of JavaScript 1 , class Point { 2 , 3 , 4 , constructor(x, y) { 5 , this.x = x; 6 , this.y = y; 7 , } 8 , manhattan() { 9 , return this.x + this.y; 10 , } 11 , } 12 , let p = new Point(12, 34); 13 , p.manhattan(); // => 46 JavaScript (JS) and its specification, ECMAScript (ES), are being actively updated: • ES2015 class Dedicated class definition syntax that utilizes the ‌ class ‌ keyword • Class field declaration (since ES2022) Predefine properties that always exist in the declared order just after ‌ new ‌ 1 / 23

Slide 3

Slide 3 text

Background: Evolution of JavaScript 1 , class Point { 2 , 3 , 4 , constructor(x, y) { 5 , this.x = x; 6 , this.y = y; 7 , } 8 , manhattan() { 9 , return this.x + this.y; 10 , } 11 , } 12 , let p = new Point(12, 34); 13 , p.manhattan(); // => 46 JavaScript (JS) and its specification, ECMAScript (ES), are being actively updated: • ES2015 class Dedicated class definition syntax that utilizes the ‌ class ‌ keyword • Class field declaration (since ES2022) Predefine properties that always exist in the declared order just after ‌ new ‌ 1 / 23

Slide 4

Slide 4 text

Background: Evolution of JavaScript 1 , class Point { 2 , x; 3 , y; 4 , constructor(x, y) { 5 , this.x = x; 6 , this.y = y; 7 , } 8 , manhattan() { 9 , return this.x + this.y; 10 , } 11 , } 12 , let p = new Point(12, 34); 13 , p.manhattan(); // => 46 JavaScript (JS) and its specification, ECMAScript (ES), are being actively updated: • ES2015 class Dedicated class definition syntax that utilizes the ‌ class ‌ keyword • Class field declaration (since ES2022) Predefine properties that always exist in the declared order just after ‌ new ‌ 1 / 23

Slide 5

Slide 5 text

Background: Evolution of JavaScript 1 , class Point { 2 , x; 3 , y; 4 , constructor(x, y) { 5 , this.x = x; 6 , this.y = y; 7 , } 8 , manhattan() { 9 , return this.x + this.y; 10 , } 11 , } 12 , let p = new Point(12, 34); 13 , p.manhattan(); // => 46 JavaScript (JS) and its specification, ECMAScript (ES), are being actively updated: • ES2015 class Dedicated class definition syntax that utilizes the ‌ class ‌ keyword • Class field declaration (since ES2022) Predefine properties that always exist in the declared order just after ‌ new ‌ Question: Can we apply well-known optimization techniques that are common in static class-based languages? 1 / 23

Slide 6

Slide 6 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? 2 / 23

Slide 7

Slide 7 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? A: Yes, but not so obvious. 2 / 23

Slide 8

Slide 8 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? A: Yes, but not so obvious. JavaScript is still a dynamic and prototype-based language even with the newer ‌ class ‌ syntax; e.g., 2 / 23

Slide 9

Slide 9 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? A: Yes, but not so obvious. JavaScript is still a dynamic and prototype-based language even with the newer ‌ class ‌ syntax; e.g., • Methods can be called with arbitrary receivers 2 / 23

Slide 10

Slide 10 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? A: Yes, but not so obvious. JavaScript is still a dynamic and prototype-based language even with the newer ‌ class ‌ syntax; e.g., • Methods can be called with arbitrary receivers • Declared property can be deleted 2 / 23

Slide 11

Slide 11 text

Background: Dynamic Nature of JavaScript Q: Can we apply well-known optimization techniques that are common in static class-based languages? A: Yes, but not so obvious. JavaScript is still a dynamic and prototype-based language even with the newer ‌ class ‌ syntax; e.g., • Methods can be called with arbitrary receivers • Declared property can be deleted • Parent class in inheritance is decided at runtime 2 / 23

Slide 12

Slide 12 text

Our Contribution • Point out three difficulties in applying optimization • Propose implementation-independent solutions • Implement in a practical engine QuickJS • Confirmed effectiveness without significant overhead through popular benchmarks 3 / 23

Slide 13

Slide 13 text

Our Contribution • Point out three difficulties in applying optimization • Propose implementation-independent solutions • Implement in a practical engine QuickJS • Confirmed effectiveness without significant overhead through popular benchmarks 3 / 23

Slide 14

Slide 14 text

Our Contribution • Point out three difficulties in applying optimization • Propose implementation-independent solutions • Implement in a practical engine QuickJS • Confirmed effectiveness without significant overhead through popular benchmarks 3 / 23

Slide 15

Slide 15 text

Our Contribution • Point out three difficulties in applying optimization • Propose implementation-independent solutions • Implement in a practical engine QuickJS • Confirmed effectiveness without significant overhead through popular benchmarks 3 / 23

Slide 16

Slide 16 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 1 (Empty) Objects o _ HC 4 / 23

Slide 17

Slide 17 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) Add x Objects o HC 0 12 Offsets 4 / 23

Slide 18

Slide 18 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 Offsets 4 / 23

Slide 19

Slide 19 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC Offsets 4 / 23

Slide 20

Slide 20 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC 0 56 Offsets 4 / 23

Slide 21

Slide 21 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC 0 56 1 78 Offsets 4 / 23

Slide 22

Slide 22 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC 0 56 1 78 Offsets Strength: Better space efficiency with table sharing 4 / 23

Slide 23

Slide 23 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC 0 56 1 78 Offsets Strength: Better space efficiency with table sharing Weakness: 1. Require a slow table lookup per access (e.g., hash tables) 4 / 23

Slide 24

Slide 24 text

Preliminary: Hidden Class (HC) • Common technique of property implementation in JavaScript • Table from name to offset extracted from objects 1 let o = {}; 2 o.x = 12; 3 o.y = 34; 4 let p = {}; 5 p.x = 56; 6 p.y = 78; Hidden Classes HC 2 x 0 HC 1 (Empty) HC 3 x 0 y 1 Add x Add y Objects o HC 0 12 1 34 p HC 0 56 1 78 Offsets Strength: Better space efficiency with table sharing Weakness: 1. Require a slow table lookup per access (e.g., hash tables) 2. Trigger HC transition: creation of new HC and update of a reference per property addition 4 / 23

Slide 25

Slide 25 text

Optimization 1: Method Specialization 1. Statically analyze and decide offsets for the properties in the class field declaration 1 , class Point { 2 , x; // Fixed offset 0 3 , y; // and 1 4 , manhattan() { 5 , return this.x // Always access at offset 0 6 , + this.y; // and 1 7 , } 8 , } 5 / 23

Slide 26

Slide 26 text

Optimization 1: Method Specialization 1. Statically analyze and decide offsets for the properties in the class field declaration 2. Compile every property access into an instruction using the fixed offset 1 , class Point { 2 , x; // Fixed offset 0 3 , y; // and 1 4 , manhattan() { 5 , return this.x // Always access at offset 0 6 , + this.y; // and 1 7 , } 8 , } 5 / 23

Slide 27

Slide 27 text

Optimization 1: Method Specialization 1. Statically analyze and decide offsets for the properties in the class field declaration 2. Compile every property access into an instruction using the fixed offset = Direct access instruction ⇆ ⇆ ⇆ Normal access instruction 1 push_this 2 get_this_field 0 3 return 1 push_this 2 get_field x 3 return 5 / 23

Slide 28

Slide 28 text

Optimization 1: Method Specialization 1. Statically analyze and decide offsets for the properties in the class field declaration 2. Compile every property access into an instruction using the fixed offset = Direct access instruction ⇆ ⇆ ⇆ Normal access instruction 1 push_this 2 get_this_field 0 3 return 1 push_this 2 get_field x 3 return Speeds up since no table lookup needed 5 / 23

Slide 29

Slide 29 text

Optimization 2: Prior-Built Hidden Class (PBHC) 1. Statically collect property names in the class field declarations 2. Create an HC from the names in advance of ‌ new ‌ 1 , class Point { 2 , x; 3 , y; 4 , // ... 5 , } 6 , 7 , Hidden Classes Point PBHC x 0 y 1 Objects 6 / 23

Slide 30

Slide 30 text

Optimization 2: Prior-Built Hidden Class (PBHC) 1. Statically collect property names in the class field declarations 2. Create an HC from the names in advance of ‌ new ‌ = PBHC 1 , class Point { 2 , x; 3 , y; 4 , // ... 5 , } 6 , 7 , Hidden Classes Point PBHC x 0 y 1 Objects 6 / 23

Slide 31

Slide 31 text

Optimization 2: Prior-Built Hidden Class (PBHC) 1. Statically collect property names in the class field declarations 2. Create an HC from the names in advance of ‌ new ‌ = PBHC 3. Refer the PBHC at once when ‌ new ‌ triggered 1 , class Point { 2 , x; 3 , y; 4 , // ... 5 , } 6 , let o = new Point(1, 2); 7 , let p = new Point(3, 4); Hidden Classes Point PBHC x 0 y 1 Objects o HC 0 1 p HC 0 1 6 / 23

Slide 32

Slide 32 text

Optimization 2: Prior-Built Hidden Class (PBHC) 1. Statically collect property names in the class field declarations 2. Create an HC from the names in advance of ‌ new ‌ = PBHC 3. Refer the PBHC at once when ‌ new ‌ triggered Speeds up and reduces memory since no HC transition needed 1 , class Point { 2 , x; 3 , y; 4 , // ... 5 , } 6 , let o = new Point(1, 2); 7 , let p = new Point(3, 4); Hidden Classes Point PBHC x 0 y 1 Objects o HC 0 1 p HC 0 1 6 / 23

Slide 33

Slide 33 text

Problem 1: Method Takeout A specialized method can be taken-out =⇒ Its receiver ‌ this ‌ can be an unexpected object 1 let p = new Point(12, 34); 2 let q = {}; // Create an empty object q 3 q.f = p.manhattan; // Takeout manhattan as a property f in q 4 q.f(); // Call manhattan with the receiver q The object q has no properties; property access may cause invalid memory access if ‌ this.x ‌ performs direct access to offset 0 7 / 23

Slide 34

Slide 34 text

Problem 2: Property Deletion A predefined property can be deleted =⇒ Its offset becomes invalid 1 delete p.x; // Delete the declared property x 2 p.manhattan(); // The x no longer exists A direct access to the offset 0 may cause invalid memory access like Problem 1 if the ‌ delete ‌ triggered memory compaction 8 / 23

Slide 35

Slide 35 text

Problem 3: Dynamic Inheritance A parent class is determined at runtime. 1 class Empty {} // An empty class without property 2 function f() { // Returns either two classes 3 return (...) ? Empty : Point; 4 } 5 // The parent of A is settled at runtime with .. 6 class A extends f() { // .. the evaluation of f() 7 z; // is at offset 0 when Empty, or 2 when Point 8 getZ() { 9 return this.z; // We cannot decide the offset statically 10 } 11 } The child PBHC cannot be built statically because it depends on the parent. 9 / 23

Slide 36

Slide 36 text

Solution 1: To Method Takeout Despecialization: Dynamic transformation for removing specialization Every direct access instruction is replaced by its corresponding normal access instruction. 1 push_this 2 get_this_field 0 3 return ˰ 1 push_this 2 get_field x 3 return 10 / 23

Slide 37

Slide 37 text

Solution 2: To Property Deletion Two dynamic solutions: 1. Mark a property as “deleted” and preserve offsets 2. Despecialize all of direct access instructions We choose the first in our implementation with disabling memory compaction because properties are rarely deleted. [Richards et al., PLDI 2010] 11 / 23

Slide 38

Slide 38 text

Solution 3: To Dynamic Inheritance Dynamic rewrite: A class’s parent and its PBHC are determined when the class initialized 1. Build tentative PBHC for each class independent of inheritance 2. Merge parent PBHC into the front of child PBHC 3. Rewrite all offsets in specialized methods Note: This rewrite cannot be bottleneck because the class initialization is done only once per class 12 / 23

Slide 39

Slide 39 text

Implementation We implemented described optimizations and solutions to QuickJS 2024-01-13, the latest version of a small JavaScript engine. • It’s simple, stack-based, and bytecode VM interpreter • Approx. 1,800 LOC modified out of 85,000 LOC in C language • Added bytecode instructions for direct access QJSo: Original implementation QJSn: Our new implementation 13 / 23

Slide 40

Slide 40 text

Implementation: Existing Bytecode Instructions Basic 3 instructions of property access: • get_field prop • get_field2 prop • put_field prop Get/set a property value named prop with table lookup 14 / 23

Slide 41

Slide 41 text

Implementation: Custom Instructions for Direct Access Custom 3 instructions for specialized property access: • get_this_field offset • get_this_field2 offset • put_this_field offset Get/set a property value with an integer offset without table lookup; the offset can be obtained statically using table lookup and prop with PBHCs 15 / 23

Slide 42

Slide 42 text

Implementation: Despecialization Despecialization is performed in the existing instructions by default to preserve existing behavior and semantics • get_field prop • get_field2 prop Replace every direct access instruction to a corresponding normal access instruction if the obtained value is a specialized method 16 / 23

Slide 43

Slide 43 text

Evaluation We implemented the optimizations and the solutions which we proposed, then evaluated it on Debian on x64 CPU. Environment: CPU: Intel Core i7-3770, 2.30 GHz RAM: 8 GB OS: Debian GNU/Linux 12.10 C Compiler: GCC 12.2.0 (Debian 12.2.0-14) 17 / 23

Slide 44

Slide 44 text

Evaluation: Benchmarks We collected basic data, execution time, and space consumption by running multiple programs. • Selected 13 programs suitable for ‌ class ‌ from existing benchmarks • ARES-6, JetStream, Octane, SunSpider • Transformed into ‌ class ‌ syntax with class field declaration which our technique requires while keeping the same meaning 18 / 23

Slide 45

Slide 45 text

Benchmarks: Basic Data We collected data as the basis of our discussion and discovered several facts. • Direct access instructions accounted for many or most of the execution Program Direct access (%) get put Air 26.7 51.5 Babylon 35.9 39.7 ML 19.7 99.9 async-fs 16.7 99.9 Deltablue 42.5 26.1 EarleyBoyer 0.0 96.3 GB Emulator 66.5 90.8 Raytrace 45.2 81.1 Richards 66.6 84.2 Splay 6.0 15.4 3d-raytrace 66.5 100.0 binary-trees 74.9 100.0 nbody 8.7 0.0 19 / 23

Slide 46

Slide 46 text

Benchmarks: Basic Data We collected data as the basis of our discussion and discovered several facts. • Direct access instructions accounted for many or most of the execution • PBHC reduced HC transition and eliminated it almost entirely in some cases Program No. of HC transitions QJSo QJSn Air 96,747 59,889 Babylon 190,670 23,502 ML 917,168 708,803 async-fs 159,208 158,445 Deltablue 103,819 18,231 EarleyBoyer 10,791,113 172,780 GB Emulator 23,689 18,103 Raytrace 4,884,189 1,889 Richards 3,549 1,589 Splay 11,281,279 10,782,151 3d-raytrace 535,705 2,715 binary-trees 1,263,385 1,225 nbody 2,665 1,225 19 / 23

Slide 47

Slide 47 text

Benchmarks: Execution Time The ratio of the execution time QJSn/QJSo, lower ⇓ is better • 87.0 % on average, 60.1 % at the best case of binary-trees • 100.2 % at the worst cases, almost within the margin of errors 0% 20% 40% 60% 80% 100% 120% Average Air Babylon M L async-fs Deltablue EarleyBoyer GB Emulator Raytrace Richards Splay 3d-raytrace binary-trees nbody Normalized execution time 87.0 97.0 88.5 100.2 99.8 88.5 82.7 94.9 64.8 86.4 98.8 82.3 60.1 100.2 20 / 23

Slide 48

Slide 48 text

Benchmarks: Space Overhead The ratio of space consumption QJSn/QJSo, lower is better • The overhead was limited to around ≈+1 % in most cases, +2.1 % on average Program name Ratio (%) Air 79.1 Babylon 100.9 ML 117.0 async-fs 105.4 Deltablue 99.4 EarleyBoyer 100.4 GB Emulator 102.0 Raytrace 101.3 Richards 100.0 Splay 102.5 3d-raytrace 117.5 binary-trees 101.2 nbody 106.1 21 / 23

Slide 49

Slide 49 text

Benchmarks: Space Overhead The ratio of space consumption QJSn/QJSo, lower is better • The overhead was limited to around ≈+1 % in most cases, +2.1 % on average • Decreased -20.9 % on Air, also Deltablue decreased a little • Our technique to reduce internal objects, including PBHC, may contributed Program name Ratio (%) Air 79.1 Babylon 100.9 ML 117.0 async-fs 105.4 Deltablue 99.4 EarleyBoyer 100.4 GB Emulator 102.0 Raytrace 101.3 Richards 100.0 Splay 102.5 3d-raytrace 117.5 binary-trees 101.2 nbody 106.1 21 / 23

Slide 50

Slide 50 text

Benchmarks: Space Overhead The ratio of space consumption QJSn/QJSo, lower is better • The overhead was limited to around ≈+1 % in most cases, +2.1 % on average • Decreased -20.9 % on Air, also Deltablue decreased a little • Our technique to reduce internal objects, including PBHC, may contributed • Method takeout may had an impact on the increase in 3d-raytrace and ML Program name Ratio (%) Air 79.1 Babylon 100.9 ML 117.0 async-fs 105.4 Deltablue 99.4 EarleyBoyer 100.4 GB Emulator 102.0 Raytrace 101.3 Richards 100.0 Splay 102.5 3d-raytrace 117.5 binary-trees 101.2 nbody 106.1 21 / 23

Slide 51

Slide 51 text

Related Work Sealed Classes [Serrano, 2022] • Optimize property access limiting dynamic features of a class with annotation • Ours: No limitation or modification required for user programs Inline Caching [Deutsch et al., 1984] • Cache an HC with the offset from the result of table lookup • Ours: A cache miss never happens and table lookup never performed while method specialization is effective • Our specialization and inline caching can co-exist Deoptimization [Hölzle et al., 1992] • Remove optimization for debugging then drop the optimized code • Ours: Codes of before/after despecialization coexist and are used selectively 22 / 23

Slide 52

Slide 52 text

Conclusion • Applied two optimizations from well-known techniques • Pointed out multiple problems which originate in JavaScript dynamicity • Proposed multiple implementation-independent solutions Clear performance improvements observed in our implementation of QuickJS: • Execution time decreased to 87.0 % in average, 60.1 % at the best • Only few space overheads observed 23 / 23