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

Integrating Static Optimization and Dynamic Nat...

Integrating Static Optimization and Dynamic Nature in JavaScript (GPCE 2025)

Presented at ACM SIGPLAN GPCE 2025 in Bergen, Norway on 3rd July.

• Authors: Tadashi Saito, Hideya Iwasaki
Presentation Detail
Paper (DOI)

Avatar for Tadashi Saito

Tadashi Saito

July 03, 2025
Tweet

More Decks by Tadashi Saito

Other Decks in Research

Transcript

  1. 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 <[email protected]> 2Meiji University, Japan
  2. 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
  3. 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
  4. 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
  5. 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
  6. Background: Dynamic Nature of JavaScript Q: Can we apply well-known

    optimization techniques that are common in static class-based languages? 2 / 23
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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