PPLサマースクール2021

JavaScript エンジンの実装技術
東京⼤学⼤学院情報理⼯学系研究科
鵜川 始陽

1

⾃⼰紹介
• 鵜川始陽（東京⼤学⼤学院情報理⼯学系研究科）••••

2005 京都⼤学
2008 電気通信⼤学
2014 ⾼知⼯科⼤学
2020 東京⼤学• 専⾨: ごみ集め

The garbage collection handbook(R.E.Jones他) の翻訳• 「ガベージコレクション
⾃動メモリ管理を構成する理論と実装」翔泳社• JavaScriptの研究• PPL 2010の招待公演を聞いて研究を始める• ⼩野寺⺠也 (IBM) 「 オブジェクト指向⾔語余話」• IoT向けJavaScriptインタプリタeJSを開発
https://github.com/tugawa/ejs-new.git

2JavaScript• Webブラウザ内で動くプログラミング⾔語• 時計，カレンダー• AJAX: Google Mapなど• ページ遷移せずにサーバからコンテンツを取得• Webフレームワーク: Ruby on Railsなど• クラウドアプリ: G suite, Office365など• リモートワークで需要急増• ブラウザ以外でも利⽤• サーバサイド: node.js• IoT: Espruino, JerryScript, eJS• 世界⼀使われている⾔語• ピークアウトしている？
3

JavaScriptの歴史
Allen Wirfs-Brock & Brendan Eich: JavaScript: The First 20 Years,
History of Programming Language (HOPL IV), 2021

• Netscape communications社が開発 1995/5• ECMAで標準化: ECMA-262
略称

edition

公開⽇

ES1

1st

1997/6

ES2

2nd

1998/8ES3

3rd

1999/12

ES4

4th

中断

ES5 (ES3.1)

5th

2009/12

ES5.1

5.1

2011/6

ES6 (ES20015)

6th

2015/6ES20167th

2016/6以降毎年更新この辺⾊々あったらしい4

glue⾔語から汎⽤⾔語に• 初期 (2000年ごろ)• C⾔語で書かれたのコンポーネントをつなぎ合わせるのりの役割• 性能はあまり気にならない• 現在• クラウドアプリを記述する⾔語• サーバサイド、デスクトップアプリ• 組込みシステム• 性能はすごく気になる
実⾏速度、実⾏に必要なメモリ
5コンパイルによる⾼速化• コンパイルすれば⾼速に実⾏できる？
• LLVMに最適化させる

.js

⼊⼒
.js

⼊⼒

インタプリタ

コンパイラ

バイナリ

出⼒

出⼒
実⾏時

6
実⾏時

静的コンパイルの例
while(i < a.length) {sum += a[i]; i += 1;}

質問：どんなバイナリにコンパイルすれば良い？

7

例の出典：Manuel Serrano: JavaScript AOT compilation, DLS ‘18, 2018

静的コンパイルの例
while(i < a.length) {sum += a[i]; i += 1;}

B:cmp %i, 0(%a) ; lengthjge E
add %sum, 4(%a, %i, 4) ; %a+%i*4+4
inc %ijmp B
length [0] [1] [2] [3] [4]E:a
5 3 1 7 10 58

例の出典：Manuel Serrano: JavaScript AOT compilation, DLS ‘18, 2018

静的コンパイルの例
while(i < a.length) {sum += a[i]; i += 1;}

B:cmp %i, 0(%a) ; lengthjge E
add %sum, 4(%a, %i, 4) ; %a+%i*4+4
inc %ijmp B
length [0] [1] [2] [3] [4]E:a
5 3 1 7 10 5
9

静的コンパイルの例
var i = “0”var a = {length: “011”,“0”: 0, “01”: 1, “011”: 2};
while(i < a.length) {sum += a[i]; i += 1;}

10

例の出典：Manuel Serrano: JavaScript AOT compilation, DLS ‘18, 2018

JavaScript⾔語の特徴
めちゃくちゃフレキシブル=> 実⾏時まで分からないことが多い• 動的型• オブジェクトは連想配列• オーバロードされた演算⼦+⾃動型変換• ユーザ定義は不可 (vs. ruby, C++)

11

動的型
• 値が型を持つ• 式や変数には型がない• 様々な型の値が格納される可能性がある
function f(a, b) {if (a === 0)
return b;
return a;}f(0, “abc”); // => “abc”
f(“abc”, 1); // => “abc”
f(1, “abc”); // => 1

JavaScript(ES5)の型
• 数値
• ⽂字列
• 真偽値
• undefined• オブジェクト
12

オブジェクトは連想配列• オブジェクト＝プロパティ名→値のマップ• プロパティには型がない（値に型がある）• 実⾏中にプロパティが増える• プロパティ名は任意の⽂字列• 実⾏時にプロパティ名を計算可能• []を使ったアクセス

var obj = {“x”: “abc”}
obj.x = 0;//{“x”:0}
obj.y = 1;//{“x”:0,“y”:1}
var n = “z”;
obj.[n + “1”] = 5; //{“x”:0,“y”:1,“z1”: 5}
13

メソッド
• メソッド = プロパティに格納された関数• thisは呼び出される時に決まる

function f() {return this.x;}
obj = {”x”: 1};
obj.getX = f; // obj = {“x”:1, “getX”:関数}
obj.getX();// => 114

演算⼦オーバーロード• 同じ演算⼦でもオペランドの型で動作が変わる• オペランドの型は⾃動で変換される
1 + 1;// => 2”1” + “1”; // => “11”
1 + “1”;// => “11”function add(a, b) {return a + b; // オペランドが即値でない}
add(1,2); // => 3
add(true, “abc”); // => “trueabc”
17インタプリタ
while(i < a.length) {sum += a[i]; i += 1;}eval(n) { //nは構⽂⽊の節点…
if(n->type == LESSTHAN){l = eval(n->lhs);
r = eval(n->rhs);
return op_lt(l, r);}}

LESSTHANi

GET_PROP

alengthAST(構⽂⽊)18

インタプリタ
while(i < a.length) {sum += a[i]; i += 1;}
op_lt(lhs, rhs) {if (type(lhs)==OBJ){lhs=lhs.valueOf()}eval(n){… if (type(rhs)==OBJ) {rhs=rhs.valueOf()}if (type(lhs)==STR&& type(rhs)==STR)
if(n->type== LESSTHAN){
strcmp(lhs, rhs) < 0;lhsreturn= eval(n->lhs);
if (type(lhs)!=NUM)
rhs= eval(n->rhs); lhs=to_num(lhs);
if (type(rhs)!=NUM)
rhs=to_num(rhs);
returnop_lessthan(lhs,rhs);} if (type(lhs)!=NUM || type(rhs)!=NUM)
return FALSE;}• 型によるディスパッチ
return lhs < rhs;
• 型変換}
※正確にはもっと複雑な処理が必要です19インタプリタ
while(i < a.length) {sum += a[i]; i += 1;}eval(n) {…
if(n->type == GETPROP){o = eval(n->obj);
p = eval(n->prop);
if (type(p) != STR)
p = to_str(p);// 連想配列の検索
return lookup(o, p);}}

LESSTHANi

GET_PROP

alength20

静的コンパイルの例
while(i < a.length) {sum += a[i]; i += 1;}
B: i = get_global(“i”);
len = lookup(a, “length”);
tmp = op_lt(i, len);if (tmp == FALSE) goto E;
if (type(i) != STR)
tmp = lookup(a, to_str(i));
elsetmp = lookup(a, i);sum = op_add(sum, tmp);
set_global(“i”, op_add(i, 1));
goto B;E:

21

静的コンパイルでは

簡単には性能が出ない• 重いところはコンパイルしても残る• フィールドアクセス• 実⾏時に計算されるフィールド名• 複雑な検索 (vs. C⾔語のオフセットアクセス)• 演算⼦• オペランドの値の型でディスパッチ
• 型の変換• インライン展開できない => 最適化が効かない• 呼び出すメソッドの実体は実⾏時に決まる（メソッドはプロパティの値）22静的コンパイルの研究

Manuel Serrano: JavaScript AOT compilation, DLS ‘18, 2018

楽観的な仮定：The most likely data structure a program will use is the one for which
the compiler is able to produce its best code.
（現実のプログラムではコンパイラに優しいデータ構造を扱うことが多い）

23

⼆つのバージョンにコンパイル
if (楽観的な仮定が成⽴)
B:cmp %i, 0(%a) ; lengthjge E
add %sum, 4(%a, %i, 4)
inc %i
jmp B
E:

else

; %a+%i*4+4

B: i = get_global(“i”);
len = lookup(a, “length”);
tmp = op_lt(i, len);if (tmp == FALSE) goto E;
if (type(i) != STR)
tmp = lookup(a, to_str(i));
elsetmp = lookup(a, i);sum = op_add(sum, tmp);
set_global(“i”, op_add(i, 1));
goto B;
E:
24

速い
0.1
V8⽐

m
az
e
pu
zz
le
qs
o
ric rt
ha
rd
s
sie
ve
sp
l
ta a y
gc
lou
d

f ib

ba
gu
e
ba
se
64
bo
ye
r
cr
cr yptoypt
cr o-ae
yp
to s
-m
de d5
lta
b
fa lue
nn
ku
ch

結果

• 最新のインタプリタ+JITには及ばない• ⽬標：インタプリタ(+JIT)の半分の性能• ベンチマークではV8の0.47倍〜12.45倍の実⾏時間
実⾏時間

100

10

遅い
1

25

JITコンパイル（実⾏時コンパイル）• 実⾏時にプロファイリングした情報を利⽤• 変数の型• オブジェクトの型• プロパティ名、プロパティの型• データ構造もそれに合わせて最適化• オブジェクト: 連想配列 -> 構造体• 主要なアイデアはコンパイルするかどうかとは
独⽴• hidden class, inline caching26

静的⾔語の構造体（C⾔語）• 定数オフセットアクセスにコンパイル• フィールドのオフセットは型とフィールド名から⼀意に決まるobj

struct S {int x, y;};…
S* obj;

+0(x)

1

+4(y)

5

;; obj->x = 1
mov 0(%obj), 1;; obj->y = 5
mov 4(%obj), 5
27

hidden class
オブジェクトに型をつける

28

JavaScriptのオブジェクト（再掲）• オブジェクト＝プロパティ名→値のマップ• プロパティには型がない（値に型がある）• 実⾏中にプロパティが増える• プロパティ名は任意の⽂字列• 実⾏時にプロパティ名を計算可能• []を使ったアクセス

var obj = {“x”: “abc”}
obj.x = 0;//{“x”:0}
obj.y = 1;//{“x”:0,“y”:1}
var n = “z”;
obj.[n + “1”] = 5; //{“x”:0,“y”:1,“z1”: 5}29

ハッシュ表による実装• 全部ハッシュ表のlookup• メソッド呼び出し• プロパティアクセス
• 配列の要素アクセス

var obj = {“x”: 1};
obj.y = 5;
obj.f =function(){…};

• 遅い
obj

f
x

1

y

5

関数
30

ハッシュ表による実装• 全部ハッシュ表のlookup• メソッド呼び出し
var obj = {“x”: 1};
obj.y = 5;
• プロパティアクセス
obj.f =function(){…};• 配列の要素アクセス• 遅い eval(n) {…
// obj.prop
if(n->type == GET_PROP){fobj
o = eval(n->obj);x1
p = eval(n->prop);
loc = hash_lookup(o, p);関数
y5return loc.value;}31}

メモリの無駄も多い• プロパティ名が重複する• 検索のための仕組みにメモリが使われる
for (i = 0; i < N; i++) {var obj = {“x”: i, “y”: i*i};array.push(obj);
}
x

1

x

0

y

1

y

0

x

3

y

9

32

JavaScriptの全⼒• 動的⾔語の柔軟性を全⼒で活⽤するプログラムにはこれが正解• 例）オブジェクトを辞書として使う
dict = {};function put(key, value) {dict[key] = value;}function get(key) {return dict[key];}33

実際のプログラムは希望が持てる• 似たオブジェクトは多く作られやすい• 同じところで作られるオブジェクトは同じプロパ
ティを持つことが多い• 同じコンストラクタで初期化されると同じプロパ
ティを持つ
for (i = 0; i < N; i++) {var obj = {“x”: i, “y”: i*i};array.push(obj);}
function Point(x, y) {this.x = x;this.y = y;};var p = new Point(1,2);35

hidden class

Chambers, C., Ungar, D. & Lee, E.: An Efficient Implementationof SELF a Dynamically-Typed Object-Oriented Language Basedon Prototypes, OOPSLA ‘89, 1989

• オブジェクトのデータとレイアウトを分離• hidden class (HC): レイアウトを表すメタオブジェクト• HCがオブジェクトの型• プロパティのアクセス
1. HCを検索してオフセットを得る
2. オブジェクト先頭アドレス+オフセットでアクセス
obj

obj

f
x

HC

1

関数
y

5

HC
x

+4

+4

1

y

+8

+8

5

f

+12

+12

関数
36

プロパティのアクセス
1. HCを検索してオフセットを得る• ここはハッシュ表のlookup

2. オブジェクト先頭アドレス+オフセットでアクセス
eval(n) {…
// obj.prop
if(n->type == GET_PROP){obj
o = eval(n->obj);HC
p = eval(n->prop);+41
loc = lookup(o->HC, p);+8
5
return *(o + loc.offset);
+12
}
}

HC

offset

x

+4

y

+8

f

+12

関数
37

hidden classの共有• 同じプロパティ集合を持つオブジェクトはHCを共有できるfor (i = 0; i < N; i++) {var obj = {“x”: i, “y”: i*i};array.push(obj);
}

HC

HC

HC

+4

0

+4

1

+4

2

+8

0

+8

1

+8

4

hidden class
x

+4

y

+8

38

プロパティの追加

var obj = {};obj.x = 1;
obj.y = 2;

• プロパティが増えるとHCが変わる• 共有している可能性があるのでHCはread only
hidden class 0
空

HC

39

プロパティの追加

var obj = {};obj.x = 1;
obj.y = 2;

• プロパティが増えるとHCが変わる• 共有している可能性があるのでHCはread only
hidden class 0

hidden class 1
x

空

+4

HC
+4

1
40

プロパティの追加

var obj = {};obj.x = 1;
obj.y = 2;

• プロパティが増えるとHCが変わる• 共有している可能性があるのでHCはread only
hidden class 0

hidden class 1 hidden class 2
x

空

+4

x

+4

y

+8

HC
+4

1

+8

2
41

hidden classを探す• プロパティ追加時に既に作られたHCがあればそれを共有したい

質問：どうやって次のHCを探す？
foo

+4

HC
+4

+4

+4

bar

+8

？

x

+4

y

+8

z

+12

x
空

1

foo

x

+4

x

+4

y

+8

a

+8

while(...) {var obj = {};obj.x = …;
obj.y = …;…

42

hidden class transition• HCの状態遷移図を構成する• 辺ラベル: 追加するプロパティ名• ⾏き先がなければ作る

hidden class tree
空

x

x

x

+4

y

y

+8

a

x

+4

a

+8

+4

while(...) {var obj = {};obj.x = …;
if (...)
obj.y = …;
else
obj.a = …;}
43

ここまででできたこと• オブジェクトに型を付ける• メモリを節約する

44

Inline caching
プロパティの検索結果をキャッシュする45

プロパティのアクセス（再掲）1. HCを検索してオフセットを得る• ここはハッシュ表のlookup←ここが遅い

2. オブジェクト先頭アドレス+オフセットでアクセス
eval(n) {…
// obj.prop
if(n->type == GET_PROP){obj
o = eval(n->obj);HC
p = eval(n->prop);+41
loc = lookup(o->HC, p);+8
5
return *(o + loc.offset);
+12
}
}

HC

offset

x

+4

y

+8

f

+12

関数
46

inline caching

Deutsch, L. P. & Schiffman, A. M. :Efficient Implementation of the Smalltalk-80 System.
POPL ‘84, 1984

• HCの検索結果をキャッシュする• 各プロパティアクセス式にキャッシュ• インタプリタでは構⽂⽊にキャッシュ• 仮定：同じ式では同じ型のオブジェクトを扱う

47

inline caching

Deutsch, L. P. & Schiffman, A. M. :Efficient Implementation of the Smalltalk-80 System.
POPL ‘84, 1984

• HCの検索結果をキャッシュする• 各プロパティアクセス式にキャッシュ• インタプリタでは構⽂⽊にキャッシュ• 仮定：同じ式では同じ型のオブジェクトを扱う

function f(o) {o.x++;
return o[”y”];
}

o

HC

x

+4

+4

y

+8

+8

48

inline caching• HCの検索結果をキャッシュする• 各プロパティアクセス式にキャッシュ• インタプリタでは構⽂⽊にキャッシュ• 仮定：同じ式では同じ型のオブジェクトを扱う
offset+4function f(o) {o.x++;
return o[“y”];
}

o

HC

x

+4

+4

y

+8

+8

+8
49

inline cache(IC) の利⽤• 2回以降はHCの検索を省略• ICを利⽤eval(n) {…
// obj.prop
if(n->type == GET_PROP){o = eval(n->obj);HC
p = eval(n->prop);+4
if (!valid(n->IC)){+8// 1回⽬だけHCから検索
loc = lookup(o->HC,
c
+4p);
HC
n->IC->off= loc->off;
v
+8
+4
}
return+8*(o + n->IC->off);}

offset+4function f(o) {o.x++;
return o[“y”];
}
+8

}

50

ガード
• 違う型のオブジェクトが来るかもしれない• HCとプロパティ名を検証してから使う• 多くの場合
プロパティ名は検証不要

HC

offset

+4

HC 名

x

+4

HC
+4

function f(o) {o.x++;
return y[“v”];
}
y

+8

+8

a

+4

+12

x

+8

y

+12

+8
HC
+4
+8

x

+4

y

+8
51

インタプリタのコード
eval(n) {…
// obj.prop
if(n->type == GET_PROP){o = eval(n->obj);
p = eval(n->prop);
if (!valid(n->IC) ||HC prop offn->IC->HC != o->HC ||x +4n->IC->prop != p ) {// 1回⽬と検証に失敗した時
function f(o) {loc = lookup(o->HC, p);o.x++;n->IC->off = loc->off;
return o[“y”];}}
return *(o + n->IC->off);
}
y +8
52
}

polymorphism//図形を移動させる• 数個の型を受け取る関数 function move(obj,dx,dy){• 実際に結構使う
obj.x += dx;• ICが効かない
obj.y += dy;（すぐにICミスする）}
HC prop offmove(circle1, 10, 20);
x +4 move(rect1, -10, 10);
rect1

circle1

HC

HC

w

+4

+4

x

+4

+4

h

+8

+8

y

+8

+8

x

+12

+12

r

+12

+12

y

+16

+16

53

polymorphism//図形を移動させる• 数個の型を受け取る関数 function move(obj,dx,dy){• 実際に結構使う
obj.x += dx;• ICが効かない
obj.y += dy;（すぐにICミスする）}
HC prop offmove(circle1, 10, 20);
x +4 move(rect1, -10, 10);
rect1

circle1

HC+4

質問：ICミスを+8
+12
防ぐには？

HC

w

+4

x

+4

+4

h

+8

y

+8

+8

x

+12

r

+12

+12

y

+16

+16

54

Hölzle, U., Chambers, C. & Ungar, D. :Optimizing Dynamically-Typed Object-Oriented
Languages With Polymorphic Inline Caches.
ECOOP’91, 1991

polymorphic inline cache• 複数キャッシュする• 仮定：使われる型は限られている• エントリ数の上限• 上限を超えるとキャッシュ
ミス

x

+4

x

+12

//図形を移動させる
function move(obj,dx,dy){
obj.x += dx;
obj.y += dy;}move(circle1, 10, 20);move(rect1, -10, 10);
rect1

circle1

HC

HC

w

+4

+4

x

+4

+4

h

+8

+8

y

+8

+8

x

+12

+12

r

+12

+12

y

+16

+16

55

実験

function move(obj,dx,dy){
obj.x += dx;
obj.y += dy;}

moveに渡すオブジェクトの型の数(K)と実⾏時間• node.js v10.19.0• Linux Ubuntu 12.04, Intel core i9-10920X
a[0] = {“x”:0, “y”:0, “z0”:0};a[1] = {“x”:0, “y”:0, “z1”:0};…
a[9] = {“x”:0, “y”:0, “z9”:0};var start = performance.now();for (i = 0; i < 10000000; i++)move(a[i % K], 0, 0); // K種類渡す
var end = performance.now();

56

実験

function move(obj,dx,dy){
obj.x += dx;
obj.y += dy;}

moveに渡すオブジェクトの型の数(K)と実⾏時間• node.js v10.19.0• Linux Ubuntu 12.04, Intel core i9-10920X
実⾏時間(ms)a[0] = {“x”:0, “y”:0, “z0”:0};3000
a[1] = {“x”:0, “y”:0, “z1”:0};monomorphic
2500
…
2000
polymorphic
1500
a[9] = {“x”:0, “y”:0, “z9”:0};1000
megamorphic
var start = performance.now();500
for (i = 0; i < 10000000; i++)
0
1
2
3
4
5
6
7
8
9
10
move(a[i % K], 0, 0); // K種類渡す
K
実⾏時間(ms)
var end = performance.now();
57

実験2

function move(obj,dx,dy){
obj.x += dx;
obj.y += dy;}

同じプロパティを与える
a[0] = {“x”:0, “y”:0, “z0”:0};
質問：結果はどうなる？
a[1] = {“x”:0, “y”:0, “z1”:0};…
a[9] = {“x”:0, “y”:0, “z9”:0};for (i = 0; i < 10; i++)a[i].z0 = a[i].z1 = … = a[i].z9 = 0;
var start = performance.now();for (i = 0; i < 10000000; i++)move(a[i % K], 0, 0);var end = performance.now();
58

実験2

function move(obj,dx,dy){
obj.x += dx;
obj.y += dy;}

結果は変わらないa[0] = {“x”:0, “y”:0, “z0”:0};a[1] = {“x”:0, “y”:0, “z1”:0};…
a[9] = {“x”:0, “y”:0, “z9”:0};for (i = 0; i < 10; i++)a[i].z0 = a[i].z1 = … = a[i].z9 = 0;
var start = performance.now();for (i = 0; i < 10000000; i++)move(a[i % K], 0, 0);var end = performance.now();

実⾏時間(ms)
3000
2500

monomorphic

2000

polymorphic

1500

megamorphic

1000
500
0
1

2

3

4

5

6

7

8

9

実⾏時間(ms)
59

10K

タネ明かし• プロパティを追加する順序が異なる=> レイアウトが異なる

z0
x

+4

y

+8

z1

x

+4

y

+8

z0

+12

x

+4

y

+8

z1

+12

z1

z0

x

+4

y

+8

z0

+12

z1

+16

x

+4

y

+8

z1

+12

z0

+16

60

ここまででできたこと• 検索結果のキャッシュ• HCの検索コストが⻑期的には0になる• どうせHCの検索コストがないのなら…
• HCをコンパクトな表現にする• HCにもっと情報を載せる• プロパティの型とか61

advanced topics

62

プロパティの追加
質問：実装するときに困ることは？
hidden class 0

x

空

+4

obj.x=1HChidden class 2

hidden class 1

x

+4

y

+8

obj.y=2

HC

HC
+4

1

+4

1

+8

2
63

プロパティの追加• オブジェクトをその場で拡張できない• 直後に別のオブジェクトが割り当てられている• 移動させるのも難しい• どこから指されているか分からない
x

空

+4

obj.x=1

x

+4

y

+8

obj.y=2

HC

HC

HC

+4

+4

別のオブジェクト

HC
1

+4

1

+8

2
64

naïveな実装：プロパティ配列の分離• プロパティの配列をオブジェクト外部に⽤意• 必要に応じてreallocatex

空0

obj.x=1

x

0

y

1

obj.y=2

HC

HC

HC

prop

prop

prop

1
1

2
1

65

naïveな実装の問題• 遅い• reallocateが多い• プロパティアクセスが間接アクセスになる• 余分にメモリを割り当てるのは無駄
理想

x

+4

y

+8

x

0

y

1

HC
prop

HC
+4

1

+8

2

1

2
1

66

advancedな実装• オブジェクトを作るときに、いくつかオブジェ
クト内にスロットを割り当てる
x 内 +8• 溢れたら外部に割り当てる

function Point(x,y) {this.x = x;this.y = y;}var p = new Point(10,20);
p.z = 30;

y

内 +12

z

外

0

HC
prop
+8

10

+12

20

30
67

in-objectプロパティの数は？
質問：in-objectプロパティの数を決める
良い戦略は？

68in-objectプロパティの数は？
• ハードコードされた定数• オブジェクトを追跡調査して最終的なプロパ
ティ数を調べる• コンストラクタ関数ごと
slack tracking (https://v8.dev/blog/slack-tracking)• オブジェクトを作る式ごと
eJS (MoreVMs ’2021)

69プロトタイプ• 個々のオブジェクトがプロトタイプを持つ• プロパティを読み出すとき、プロトタイプも検
索する
x

+8

p.move(10,20);y+12

console.log(
p.toString());

HC

HC

HC

proto

proto

proto

+8

+8

+8

+12

move

+8

toString+8

valueOf +12

+12

70

inline caching• オーナオブジェクトも記録する

HC 名 owner offset
move

+8

x

+8

y

+12

move

+8

toString+8

valueOf +12

p.move(10,20);
console.log(
p.toString());
toString

+8

HC

HC

HC

proto

proto

proto

+8

+8

+8

+12

+12

71

プロトタイプへの書き込み• より“近い”オブジェクトに同じ名前のプロパ
ティを追加すると隠蔽される• 書き込みを検出してキャッシュを無効化

console.log(
p.toString());
toString

+8

x

+8

move

+8

toStringy+12

toString+12valueOf +12

HC

HC

HC

proto

proto

proto

+8

+8

+8

+12

+12

+12

+8

72

validity cell (VC)

https://mathiasbynens.be/notes/prototypes• キャッシュされたHCが最新であることを⽰す
フラグのオブジェクト• キャッシュを使う前にVCも検証する
true

HC

名
toString

VC owner offset
+8

true

VC

VC

toString+8HCvalueOf +12 proto

move

+8

HC
proto

x

+8

HC

y

+12

proto

73

プロパティの追加• VCを無効化する• キャッシュされている（かもしれない）VCをfalseにする• 新しいVCを作る
true

HC

名
toString

VC owner offset
+8

VC

true
false
true

VC

toString

+8

move

+8

HC
valueOf +12 proto

HC
move
+8
proto
toString +12
x

+8

HC

y

+12

proto

74

まとめ
• 動的⾔語を静的にコンパイルしてもなかなか性
能が出ない• プロパティ検索、メソッド検索• 型ディスパッチ• 実⾏時に分かる情報を使うと⾼速化できる• hidden class: オブジェクトに型を付ける• inline caching: 検索結果をキャッシュする• 静的⾔語と違ってキャッシュが利⽤できるかの検証（ガード）とバックアップの仕組みは必要

76

話さなかったこと• 配列の効率化• Storage storategy

C. F. Bolz, L. Diekmann, and L. Tratt. :Storage Strategies for Collections in Dynamically Typed Languages. OOPSLA ‘13, 2013

• pre-transitioning

Clifford, D., Payer, H., Stanton, M. & Titzer, B. L.: Memento Mori: Dynamic Allocation-site-based
Optimizations. ISMM ’15, 2015

• プリミティブデータの効率化• プロパティに型をつける
https://v8.dev/blog/react-cliff• Nan-boxing

JavaScript coreのJSCJSValue.hのコメント

https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/runtime/JSCJSValue.h77