abcdefGets
ゲッツ!
2020-03-04T20:01:24+09:00
brn_take
Hatena::Blog
hatenablog://blog/10328537792364434636
TypeScript3.8以降のECMAScript private fieldについて
hatenablog://entry/26006613530101048
2020-03-04T20:01:24+09:00
2020-03-04T20:01:24+09:00 現状targetがesnextじゃないならあまり使わないほうがよい Reason 以下のようなシンプルなケースを考える。 class A { #field = 1; something() {return this.field;} } targetがesnext以外だと var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attemp…
<p>現状targetがesnextじゃないならあまり使わないほうがよい</p>
<h1>Reason</h1>
<p>以下のようなシンプルなケースを考える。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">class</span> A <span class="synIdentifier">{</span>
#field <span class="synStatement">=</span> <span class="synConstant">1</span><span class="synStatement">;</span>
something<span class="synStatement">()</span> <span class="synIdentifier">{</span><span class="synStatement">return</span> <span class="synIdentifier">this</span>.field<span class="synStatement">;</span><span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<p><code>target</code>が<code>esnext</code>以外だと</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> __classPrivateFieldGet = (<span class="synIdentifier">this</span> && <span class="synIdentifier">this</span>.__classPrivateFieldGet) || <span class="synIdentifier">function</span> (receiver, privateMap) <span class="synIdentifier">{</span>
<span class="synStatement">if</span> (!privateMap.has(receiver)) <span class="synIdentifier">{</span>
<span class="synStatement">throw</span> <span class="synStatement">new</span> TypeError(<span class="synConstant">"attempted to get private field on non-instance"</span>);
<span class="synIdentifier">}</span>
<span class="synStatement">return</span> privateMap.get(receiver);
<span class="synIdentifier">}</span>;
<span class="synIdentifier">var</span> _field;
<span class="synStatement">class</span> A <span class="synIdentifier">{</span>
constructor() <span class="synIdentifier">{</span>
_field.set(<span class="synIdentifier">this</span>, 1);
<span class="synIdentifier">}</span>
something() <span class="synIdentifier">{</span> <span class="synStatement">return</span> __classPrivateFieldGet(<span class="synIdentifier">this</span>, _field); <span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
_field = <span class="synStatement">new</span> WeakMap();
</pre>
<p>というアウトプットになる。<br />
privateフィールドを実現するために<code>WeakMap</code>を活用しており、アクセスのたびに<code>WeakMap</code>を検索するためとにかく遅い。</p>
<p>そのため、よほどの理由がない限り(多分ほぼ無い)使うべきではない(現状はね)</p>
brn_take
2019 Javascript engine 俯瞰
hatenablog://entry/26006613480172222
2019-12-11T12:18:40+09:00
2019-12-11T12:18:40+09:00 2019 Javascriptエンジン俯瞰 こんにちは 2019 Javascript Advent Calendarの11日目です 2019はJSエンジンが新たに2つもリリースされた まずFacebook産のhermes もう一つがFFMPEG作者のbellardが実装したquickjs この2つを見ていこうと思う ちなみにhermesは以前にも書いたので正直あまり書くことは無い http://abcdef.gets.b6n.ch/entry/2019/07/22/142510 特徴 hermes C++ FacebookがReact Nativeの高速化用に実装したエンジン レジスタマシン…
<h2>2019 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a>エンジン俯瞰</h2>
<p>こんにちは
<a href="https://qiita.com/advent-calendar/2019/javascript">2019 Javascript Advent Calendar</a>の11日目です</p>
<p>2019はJSエンジンが新たに2つもリリースされた<br/>
まず<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>産の<a href="https://github.com/facebook/hermes">hermes</a><br/>
もう一つが<a class="keyword" href="http://d.hatena.ne.jp/keyword/FFMPEG">FFMPEG</a>作者のbellardが実装した<a href="https://bellard.org/quickjs/">quickjs</a></p>
<p>この2つを見ていこうと思う</p>
<p>ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a>は以前にも書いたので正直あまり書くことは無い<br/>
<a href="http://abcdef.gets.b6n.ch/entry/2019/07/22/142510">http://abcdef.gets.b6n.ch/entry/2019/07/22/142510</a></p>
<h3>特徴</h3>
<h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h4>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a></li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>がReact Nativeの高速化用に実装したエンジン</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>マシンの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%D7%A5%EA%A5%BF">インタプリタ</a>を搭載</li>
<li>flowを解釈できる</li>
<li>commonjsを解釈して実行できる</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>のexportとimportも可能でスタートアップタイムを高速化することが可能</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/JIT">JIT</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/x86">x86</a>_64の実装はあるが使うパスが無いためOFFになっている</li>
<li>let/constやclass、ES Moduleといった機能はサポートされていない</li>
<li>Reflectionやwith、Symbol.speciesといったものは今後もサポートしない</li>
<li>一部Ecma262と仕様が異なる</li>
</ul>
<h4>quickjs</h4>
<ul>
<li>すべてCで書かれている</li>
<li>とにかく小さい(<a class="keyword" href="http://d.hatena.ne.jp/keyword/x86">x86</a>で190 KiB)</li>
<li>スタックマシンの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%D7%A5%EA%A5%BF">インタプリタ</a>を搭載</li>
<li>Ecmascript2019はほぼ完全にサポートEcmascript2020(予定)もサポート</li>
<li>BigInt/BigFloatもサポート</li>
<li>jsをExecutableに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>できる</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%D7%A5%EA%A5%BF">インタプリタ</a></li>
</ul>
<h3>実装</h3>
<h4>パーサ</h4>
<p>両方ともシンプルな手書きの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>下降<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%BD%CA%B8%B2%F2%C0%CF">構文解析</a>でパース</p>
<h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h5>
<p><code>hermes</code>はパースしながらASTを生成する<br/>
<code>hermes</code>のASTは<a href="https://github.com/estree/estree/">ESTree</a>の仕様に準拠しているのでわかりやすい</p>
<h5>quickjs</h5>
<p><code>quickjs</code>は非常にドラスティックな実装となっており、パースしながらなんと<code>Bytecode</code>を直接出力する<br/>
ファイルサイズ削減のためにASTをスキップしていて、完全にコンパクト方面に振り切っている感じ<br/>
ここらへんはエンジンの色がでていて面白い</p>
<h4>Bytecode</h4>
<h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h5>
<p><code>hermes</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>は<code>BytecodeList.def</code>に全てまとまっており、全てマクロ呼び出しで後々再利用できるようになっている<br/>
<code>hermes</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>は可変長で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%E9%A5%F3%A5%C9">オペランド</a>数はMaxが6、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%E9%A5%F3%A5%C9">オペランド</a>のサイズも可変長<br/>
<code>NewObject</code>等の割と大きめな命令から、<code>BitXor</code>の様なプリミティブ命令まで実装されている</p>
<h5>quickjs</h5>
<p>対する<code>quickjs</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>は<code>quickjs-opcode.h</code>にすべてまとまっており、こちらも同じくマクロ呼び出しとなっている<br/>
まあ言語エンジンを実装するときは大体こうなるよね<br/>
<code>quickjs</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>だが、まあ大体<code>hermes</code>と同じような粒度かな<br/>
一部<code>push_i32</code>のようなスタックマシンぽいコードもあったり</p>
<p><code>Bytecode</code>は両者ともわりかし似ている感じ</p>
<h4>Object Model</h4>
<h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h5>
<p>以前も書いたが、<code>hermes</code>はNaN-Boxingで実装されている(NaN-Boxingについては<a href="http://abcdef.gets.b6n.ch/entry/2019/07/22/142510">FacebookのHermes Javascript Engineについて</a>にも書いた)<br/>
<code>hermes</code>のヒープオブジェクトは<code>GCCell</code>クラスを頂点としたモデルになっている</p>
<pre class="code" data-lang="" data-unlink>GCCell
|
+--------+------+-----------------------+-----------+----------------+------------+--------------+--------------+-----+-----+---------+
| | | | | | | | | | | |
JSObject Domain VariableSizeRuntimeCell HiddenClass PropertyAccessor HashMapEntry OrderedHashMap OrderedHashMap Type1 Type2 EmptyCell FinalizerCell
|
|--------+--------------+------------------------------------+---------+---------------+-------------+----------+------+-------+-----------+---------+-----------------+--------+----------------+-----------------+------------+----------------+
| | | | | | | | | | | | | | | | | |
Callable RequireContext HostObject ArrayImpl JSArrayIterator JSArrayBuffer JSDataView JSDate JSError JSGenerator JSMapImpl JSMapIteratorImpl JSRegExp JSTypedArrayBase JSWeakMapImplBase PrimitiveBox JSStringIterator SingleObject
| | | | |
+-------------+-----------------------------------+ +---------+ | | +--------+--------+---------+
| | | | | | | | | | |
BoundFunction NativeFunction JSFunction Arguments JSArray JSTypedArray JSWeakMapImpl JSString JSNumber JSBoolean JSSymbol
| |
NativeConstructor NativeConstructor JSGeneratorFunction GeneratorInnerFunction</pre>
<p>JSObject階層以外は省略したが、<code>hermes</code>は上記の様なオブジェクト階層を持っておりまあ割と複雑<br/>
<code>GCCell</code>クラスは<code>VTable</code>というオブジェクトに関する情報を保持しているクラスをラップしており、VTableが実際に<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>用の型情報やマーク情報を持っている<br/>
ただ、<code>VTable</code>クラス自体は<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>によってアドレスが変わる可能性があるため、<code>GCCell</code>クラスがハンドルの役目を果たしている<br/>
そのため、<code>GCCell</code>はメンバを持たない</p>
<p>ランタイムでのオブジェクトの型情報はその名の通り<code>HiddenClass</code>が保持しており、V8っぽく<code>HiddenClass</code>の中にプロパティのキャッシュを持っていたりする<br/>
当然プロパティ追加時の<code>Transition</code>も実装されていて、ちゃんとhidden classとして機能している</p>
<pre class="code" data-lang="" data-unlink>{symbol_id} = internal variable name id
{property_flag} = enumerable|writable|configurable|...
+------------------------+
| HiddenClass(root) |
+------------------------+
|
| (Transition HiddenClass(propertyA))
|
+------------------------+
| HiddenClass(propertyA) |
+------------------------+
| (TransitionMap {symbol_id}_{property_flag}: HiddenClass(propertyB), {symbol_id}_{property_flag}: HiddenClass(propertyC)})
+-------------------------+
| |
+------------------------+ +------------------------+
| HiddenClass(propertyB) | | HiddenClass(propertyC) |
+------------------------+ +------------------------+</pre>
<p>こんな風に1つの派生だけの場合はTransitionオブジェクトを直接参照して、複数の<code>Transition</code>がある場合は<code>symbol_id</code>と<code>property_flag</code>のキーと<code>HiddenClass</code>を直接ハッシュマップとして持つことで<code>Transition</code>を実現している</p>
<h5>quickjs</h5>
<p><code>quickjs</code>はCなので明示的なオブジェクト階層はないものの<code>JSObject</code>が多くのJS型のベースとなっておりメンバを<code>union</code>を選択する形となる<br/>
また<code>JSValue</code>という空の<code>struct</code>を<code>Generic</code>な値として利用しており、内部に持つ値は<code>void*</code>ではなく<code>JSValue*</code>型で保持している</p>
<pre class="code" data-lang="" data-unlink>JSObject
|
+-JSBoundFunction
+-JSCFunctionDataRecord
+-JSForInIterator
+-JSArrayBuffer
+-JSTypedArray
+-JSFloatEnv
+-JSMapState
+-JSMapIteratorData
+-JSArrayIteratorData
+-JSRegExpStringIteratorData
+-JSGeneratorData
+-JSProxyData
+-JSPromiseData
+-JSPromiseFunctionData
+-JSAsyncFunctionData
+-JSAsyncFromSyncIteratorData
+-JSAsyncGeneratorData
+-func
+-cfunc
+-JSTypedArray
+-array
+-JSRegExp</pre>
<p>こんな感じでJSObjectに<code>union</code>を持っている<br/>
クラシックな<code>union</code>ベースのモデルなので読みやすくていい</p>
<p><code>JSObject</code>はルートの構造体となるので、型情報や、GCMark等の情報を保持している</p>
<p>型情報は全て<code>JSObject</code>内の<code>JSShape</code>という<code>struct</code>に持っている</p>
<p><code>JSShape</code>はプロパティ自体も保持しているが、現在のプロパティのバージョンをハッシュとしても持っており、プロパティが変化することで<code>JSShape</code>内の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>も変化する<br/>
さらに次の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>をプロパティのメモリアドレスから計算して、前の<code>JSShape</code>へ持たせることで、次の<code>JSShape</code>を検索することが可能になり、<code>Transition</code>を実現している</p>
<pre class="code" data-lang="" data-unlink>+-----------+
| JSShape A | |A(next_hash = null)| ...
+-----------+
|
| Add property
|
+-----------+
| JSShape B | |A(next_hash = 3) | |B(next_hash = null)|
+-----------+</pre>
<p>だいぶ簡略化して描いているが、next_hashに次のインデックスを格納するイメージ(本当は直接インデックスは格納しない)</p>
<h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a></h4>
<h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h5>
<p>再掲となるが<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>で利用される<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>は一応無限の仮想<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>となっているが、単純なリニア生存<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B6%E8%B4%D6">区間</a>解析をCFG上で行っている<br/>
一応無制限と書いたのは、Bytecodeの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>インデックスが1byteしか受けつけないので、実質256までしか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>割付ができないからである</p>
<p><a href="https://github.com/facebook/hermes/blob/master/doc/Design.md">https://github.com/facebook/hermes/blob/master/doc/Design.md</a>に書いてあるが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>調べでは256以上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>を使う関数は
見当たらなかったらしい(ので大丈夫ということか)</p>
<h5>quickjs</h5>
<p>スタックマシンなので単純に<code>stack pointer</code>を伸縮している</p>
<p>両者ともラベルアドレスへのダイレクトジャンプを実装している(もちろんラベルのアドレスが取得できない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>向けのswitch-caseもある)<br/>
ちなみにどんな機能かというと</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">Label_A</span>: {
...
}
<span class="synStatement">Label_B</span>:
.
.
.
<span class="synType">const</span> <span class="synType">void</span>* dispatch_tables = {&&Label_A, ...}
<span class="synStatement">goto</span> *dispatch_tables[<span class="synConstant">1</span>]
</pre>
<p>とすることでなんと<code>label</code>へと<code>goto</code>できるという素敵な仕様<br/>
これを使うことでジャンプの度にcmpする必要がなくなる(indexの計算のみ)さらにCPUの分岐予測もいらないので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>のループが高速化される</p>
<h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a></h4>
<h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a></h5>
<p><code>Generational</code>な<code>Mark-Sweep GC</code>でしたそれ以外特に言うこともないかな<br/>
オブジェクトグラフをひたすら<code>Visitor</code>がたどっていく<code>precise GC</code>の実装となっていた</p>
<h5>quickjs</h5>
<p>こっちはちょっと特殊で参照カウント + <code>mark-sweep</code>となっている<br/>
最初にオブジェクトグラフを巡回して参照カウントを<code>decref</code>したのち、参照カウントが0のオブジェクトを<code>delete_list</code>に詰め込んで回り、全て回収完了後に<code>JSObject</code>を解放する<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>の方式と同じ方法でオブジェクトを削除している</p>
<p>すべての割り当てられたオブジェクトはobj_list内にあるので、それを<code>tmp_obj_list</code>にコピーして、<br/>
巡回中の参照カウントが1以上のオブジェクトは<code>obj_list</code>に戻す</p>
<pre class="code" data-lang="" data-unlink>+----------------+
| global_env |
+----------------+
|
| +----------------+
| | tmp_obj_list |
| +----------------+
| |
| | +------------+ +------------+
| |-----| objectA 1 |------->| objectB 1 |
| | +------------+ | +------------+
| | |
| | | +------------+
| | +-->| objectC 2 |
| | +------------+
| REF | +------------+ ↑
|==============> |-----| objectD 2 |--------------+
| +------------+
|
| +------------+ REF
| ---->| objectF 2 |<=====+
| +------------+ | +------------+ |
|-----| objectE 1 |---+ |
+------------+ | +------------+ REF |
---->| objectG 2 |<=====+
+------------+</pre>
<p>これが巡回前
参照されているのはすでにObjectDのみの状態で、objectFとobjectGは循環参照している</p>
<pre class="code" data-lang="" data-unlink>+----------------+
| global_env |
+----------------+
|
| +----------------+
| | tmp_obj_list |
| +----------------+
| |
| | +------------+ +------------+
| |-----| objectA 0 |------->| objectB 0 |
| | +------------+ | +------------+
| | |
| | | +------------+
| | +-->| objectC 1 |
| | +------------+
| REF | +------------+ ↑
|==============> |-----| objectD 1 |--------------+
| +------------+
|
| +------------+ REF
| ---->| objectF 1 |<=====+
| +------------+ | +------------+ |
|-----| objectE 0 |---+ |
+------------+ | +------------+ REF |
---->| objectG 1 |<=====+
+------------+</pre>
<p>これが巡回後<br/>
親が外部から参照されているオブジェクト以外は参照カウントが0になる、そして親が0の場合は回収されるので、循環していても関係なく削除できるようになる</p>
<p>ちなみに<code>quickjs</code>はメモリ割り当てに通常の<code>malloc</code>を使用していて、<code>malloc</code>の管理ヘッダ分のサイズも計算して割当中のオブジェクトサイズを保持している<br/>
メモリを回収する場合も単純に<code>free</code>を呼び出しており、メモリ管理は基本的にすべて<code>glibc</code>にまかせている</p>
<h4>まとめ</h4>
<p>簡単に2つのエンジンを俯瞰したが、<code>quickjs</code>は軽い実装ながら面白い箇所が多く、またコードもきれいで読みやすいのでおすすめ<br/>
<code>quickjs.c</code>にほぼ全てのコードが書かれているので読むのも簡単だと思う<br/>
<code>hermes</code>は普通に読むの面倒だし、そんなに面白いことも無いかもしれない</p>
<p>疲れました</p>
<p>では</p>
brn_take
FacebookのHermes Javascript Engineについて
hatenablog://entry/26006613376027373
2019-07-22T14:25:10+09:00
2019-07-22T14:28:15+09:00 最近、JSエンジンが何故かいくつか出て来たのでいっちょ見て見ることに 最初はFacebookが実装したjavascriptエンジンHermes(エルメス)の実装を見てみた 面倒くさいのでコードとかは引用しない 概要 どうやらReactNativeの高速化のために実装したエンジンのようだ ReactNative側ですでに利用できるっぽい 売りとしてはバイトコードを出力・読み込みができるのでスタートアップタイムを高速化できるということらしい commonjsの静的解析機能もついており今風な感じ 仕様 サポートされる仕様はhttps://github.com/facebook/hermes/blob…
<p>最近、JSエンジンが何故かいくつか出て来たのでいっちょ見て見ることに<br/>
最初は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>が実装した<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>エンジン<a class="keyword" href="http://d.hatena.ne.jp/keyword/Hermes">Hermes</a>(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%EB%A5%E1%A5%B9">エルメス</a>)の実装を見てみた</p>
<p>面倒くさいのでコードとかは引用しない</p>
<h2>概要</h2>
<p>どうやらReactNativeの高速化のために実装したエンジンのようだ<br/>
ReactNative側ですでに利用できるっぽい<br/>
売りとしては<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を出力・読み込みができるのでスタートアップタイムを高速化できるということらしい</p>
<p><code>commonjs</code>の静的解析機能もついており今風な感じ</p>
<h2>仕様</h2>
<p>サポートされる仕様は<a href="https://github.com/facebook/hermes/blob/master/doc/Features.md">https://github.com/facebook/hermes/blob/master/doc/Features.md</a>にある</p>
<p>サポートしている言語仕様はES5 + α</p>
<p>let/constやclass、ES Moduleといった機能はサポートされていない<br/>
とりあえずbabelとかts使うから動くでしょ といったところか</p>
<p>また<code>Reflection</code>や<code>with</code>、<code>Symbol.species</code>といったものは今後もサポートしないらしい</p>
<p><code>Function.prototype.toString</code>も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を返さないなど、割と必要ないものはバッサリ切った感じ<br/>
<code>React Navtive</code>のためと言っているのでいいのかな</p>
<h2>ビルド</h2>
<p>成果物が結構あるのに解説があんまないので困る</p>
<p>以下が成果物</p>
<ul>
<li>bin/<a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>から実行する場合はこのバイナリ</li>
</ul>
</li>
<li>bin/hermesc
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を生成する場合はこれ</li>
</ul>
</li>
<li>bin/hdb
<ul>
<li>デバッガ</li>
</ul>
</li>
<li>bin/<a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a>-repl
<ul>
<li>repl</li>
</ul>
</li>
<li>bin/hbcdump
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>のダンパー</li>
</ul>
</li>
<li>bin/<a class="keyword" href="http://d.hatena.ne.jp/keyword/hbc">hbc</a>-deltaprep
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>のフォーマットを差分形式に変換する</li>
</ul>
</li>
<li>bin/<a class="keyword" href="http://d.hatena.ne.jp/keyword/hbc">hbc</a>-diff
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>のdiff</li>
</ul>
</li>
<li>bin/<a class="keyword" href="http://d.hatena.ne.jp/keyword/hbc">hbc</a>-attribute
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>のメタ情報を<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>で出力</li>
</ul>
</li>
</ul>
<h2>中身</h2>
<p>とりあえず概要をみてみた<br/>
以下が基本的なパスとなりそう</p>
<p><code>ソースコード => AST => IR => (最適化) => バイトコード => 実行</code></p>
<p>また<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を読み込むことで</p>
<p><code>バイトコード => 実行</code></p>
<p>のパスもある</p>
<h3>AST</h3>
<p><code>Node</code>があって<a class="keyword" href="http://d.hatena.ne.jp/keyword/BNF">BNF</a>定義に近いASTがある<br/>
要は普通のAST実装<br/>
ただdecoratorで実装しているっぽいのでちょっと読みづらい</p>
<p>Visitorパターンでトラバーサルする</p>
<h3>IR</h3>
<p>ASTが生成されたらIRを生成する<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/hermes">hermes</a>のIRはCFGも兼ねるグラフとなっている<br/>
このIRは結構低レベルでInstructionレベルまで表現している</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a></h3>
<p>IRから変換して生成される<br/>
OPコードは1byteでOperandは可変長</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a></h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を実行する<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>ベースの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B2%BE%C1%DB%A5%DE%A5%B7%A5%F3">仮想マシン</a><br/>
教科書通り<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCC">GCC</a>拡張のアドレスへのgotoとLabelアドレスの取得機能でループ無しで実装されている<br/>
ただしサポートされない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>向けにループとSwitch構文での実行機能も持っている</p>
<h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a></h4>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>で利用される<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>は一応無限の仮想<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>となっているが、単純なリニア生存<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B6%E8%B4%D6">区間</a>解析をCFG上で行っている<br/>
一応無制限と書いたのは、Bytecodeの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>インデックスが1byteしか受けつけないので、実質256までしか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>割付ができないからである</p>
<p><a href="https://github.com/facebook/hermes/blob/master/doc/Design.md">https://github.com/facebook/hermes/blob/master/doc/Design.md</a>に書いてあるが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>調べでは256以上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>を使う関数は<br/>
見当たらなかったらしい(ので大丈夫ということか)</p>
<h3>ABI</h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Hermes">Hermes</a>はNaN-Boxingでオブジェクトを表現している</p>
<p>NaN-Boxingとは64bitフロートのNaN定義を利用したポインタと数値の表現方法<br/>
NaNは上位17bitが1であれば下位のBitがどんな値でも問題ないので、それを利用してポインタやタグ情報を埋め込んでいる<br/>
ただし64bitシステムだとポインタは64bit利用するので埋め込めないように見えるのだが、現状64bitシステムであっても全ての領域は使い切っていないので問題なく収まる
(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/x86-64">x86-64</a>で48bitまで利用と書いてあった)<br/>
ただし、そこまで考えなくても<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>のヒープが47bitで収めきればいいだけなので、そこまで問題は起きないだろう</p>
<h3>Hidden Class</h3>
<p>今のご時世、ShapeやらHidden Classは高速化に必須だろう<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Hermes">Hermes</a>もHidden Classを実装している<br/>
V8と同じようにオブジェクトレイアウトをclassとして認識して、プロパティの追加・削除等のレイアウト変更を行うと新たなクラスが生成されるようになっている</p>
<p>新たにHidden Classを生成した場合はtransitionをした結果を保存して検索するような仕組みになっている</p>
<h3>IC</h3>
<p>複雑なインラインキャッシングは実装されていないように見える<br/>
一応プロパティのキャッシュとhidden classの保持はしているのでプロパティキャッシュ自体はしているが</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a></h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>は独自<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を使った<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>型のエンジンを実装している<br/>
既存のエンジンの<a class="keyword" href="http://d.hatena.ne.jp/keyword/JIT">JIT</a>に比べるとちょっと見劣りするかもしれない</p>
<h3>最適化</h3>
<p>主にIRに対しての最適化が実行されている<br/>
内容はIRのloweringとかinline化など<br/>
面倒だったのであんまりちゃんと読んでない</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/JIT">JIT</a></h3>
<p>実は<a class="keyword" href="http://d.hatena.ne.jp/keyword/JIT">JIT</a>エンジンももっている がまだリリースされていなのでOFFになっている<br/>
中を見た感じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/x86-64">x86-64</a>から提供する様だ</p>
<p>ARMは...?</p>
<h2>まとめ</h2>
<p>React Nativeのために作ったというだけあって、色々省いてあったり<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>ローディングなど工夫があった<br/>
後発のエンジンなだけにcommonjs対応してたり、今風な感もあって面白い</p>
<p>ただ、言語仕様が<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>のエディションと変わってきてしまっている(withがなかったり)のはちょっと気になっている</p>
<p>最近感じていることだが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>も仕様が大きくなってきていてエンジンの実装も大変になってきているし、<br/>
使用目的がはっきりしているエンジンにとっては多分サポートする意味の無い機能(withとかnot strictなモード、多言語対応等)は省いた<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>のサブセットがあってもいいのかもしれない<br/>
というか個人的にはほしい</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RegEx">RegEx</a>実装するだけで<a class="keyword" href="http://d.hatena.ne.jp/keyword/ICU">ICU</a>必要になったり面倒なことが多い</p>
brn_take
JavascriptのObjectリテラルとJSON.parseについて
hatenablog://entry/17680117127216458948
2019-07-11T11:40:11+09:00
2019-07-11T11:43:51+09:00 V8のJSON.parseについて 最近(ちょっと前か)話題のオブジェクトリテラルよりもJSON.parseのほうが早い件について。 その理由を内部実装の観点から書く。 また注意点を最後に書いた。 話題のブログは以下から https://v8.dev/blog/cost-of-javascript-2019 パースについて V8はjavascriptコードをパースするにあたって、Lazy Parseを行っている。 Lazy Parse javascriptはブラウザという特殊な環境で実行される言語であるため、パースにも少々工夫が必要になる。 基本的にV8はすべてのソースコードをパースしない。 …
<h1>V8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>.parseについて</h1>
<p>最近(ちょっと前か)話題のオブジェクト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>よりも<code>JSON.parse</code>のほうが早い件について。<br/>
その理由を内部実装の観点から書く。</p>
<p>また注意点を最後に書いた。</p>
<p>話題のブログは以下から
<a href="https://v8.dev/blog/cost-of-javascript-2019">https://v8.dev/blog/cost-of-javascript-2019</a></p>
<h2>パースについて</h2>
<p>V8は<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>コードをパースするにあたって、Lazy Parseを行っている。</p>
<h3>Lazy Parse</h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>はブラウザという特殊な環境で実行される言語であるため、パースにも少々工夫が必要になる。<br/>
基本的にV8はすべての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>をパースしない。<br/>
一旦グロー<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%EB%A5%B9">バルス</a>コープにあるものだけをちゃんとパースして、それ以外はPreParserというパーサで関数だけをかき集める。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> foo() <span class="synIdentifier">{</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">function</span> bar() <span class="synIdentifier">{</span>
<span class="synIdentifier">function</span> baz() <span class="synIdentifier">{</span>
<span class="synStatement">const</span> value = 100;
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<p>この例だと、<code>foo</code>と<code>bar</code>、<code>baz</code>という関数が存在していることはパースするが、<code>const value = 100</code>は一切パースせずASTを生成しない。<br/>
<code>baz</code>が呼び出されて初めて<code>const value = 100</code>がパースされる。</p>
<h3>How to skip parsing</h3>
<p>ではどのようにコードのパースをスキップしているかというと、V8のパーサは手書きの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>下降<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%BD%CA%B8%B2%F2%C0%CF">構文解析</a>器になっており、<br/>
<code>ParserFunction</code>のようなメソッドが一杯あるクラスになっている。</p>
<p>さらにパーサは<code>PreParser</code>と<code>Parser</code>に分かれており、それをテンプレート引数で受け取って実行する<code>ParserBase</code>クラスが各パースのエントリーポイントを定義している</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">template</span> <<span class="synType">typename</span> Impl>
<span class="synType">class</span> ParserBase<Impl> {
...
<span class="synStatement">protected</span>:
...
ExpressionT ParseFunctionExpression();
...
<span class="synStatement">private</span>:
Impl* impl() {<span class="synStatement">return</span> parser_;}
Impl* parser_;
};
</pre>
<p>のような感じで<code>ParserBase</code>がパーサの<a class="keyword" href="http://d.hatena.ne.jp/keyword/BNF">BNF</a>に対応する各パース段階のエントリーポイントを持つ。<br/>
その中で<code>impl()->ParserFunctionLiteral()</code>のような形で実際のパーサを呼び出してパース処理を行っている。</p>
<h3>Parser and PreParser</h3>
<p>さて実際にパース処理を行うパーサは2種類ある。<br/>
<code>PreParser</code>はASTを生成せずに関数名やその他情報を集めるだけのパーサで、<code>Parser</code>は実際にASTの生成を行うパーサとなっている。<br/>
ここで大事なのが<code>PreParser</code>の実装である。</p>
<p><code>PreParser</code>はASTは生成しないものの実際に構文のパースは行う。<br/>
そのため、V8では<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>は2回パースされることとなる。</p>
<p>ここで<code>JSON.parse</code>の特殊性が影響してくる。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>.parse</h2>
<p><code>JSON.parse</code>の第一引数には文字列の<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>オブジェクトが渡される。<br/>
これがキモになる。何故かと言うと、文字列のパースコストは非常に低いのだ。<br/>
なぜなら文字列はパース段階ではなく、スキャン段階で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ン化されるため、何度パースされても文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを判定するだけで処理が完了する。<br/>
そのため、パースの負荷が非常に低くなる。</p>
<p>また、<code>JSON.parse</code>自体もランタイムで処理が行われるために、実際に呼び出されるまで処理が行われず、不要なパースをすべてスキップすることができる。<br/>
すなわち手動でLazy Parsingをしているに等しい状態となっている訳だ。</p>
<p>これらの要因が組み合わさってオブジェクト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>をべた書きするよりも、<code>JSON.parse</code>の方が早くなるという不思議な現象が発生する。</p>
<h2>注意点</h2>
<p>ただしこの方法には注意点があって、あくまでこの手法は<span style="font-size: 150%;font-weight: bold">startup timeの高速化しかできない</span>。<br/>
実際の実行時間は<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>.parseのほうが遅くなるので、FMPの高速化には寄与するかもしれないが、すべてのオブジェクト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>をこれで置き換えると結構遅くなりそうなので注意。<br/>
あくまでパースのLazy化と考えたほうが良い。</p>
<p>ちなみに<code>JSON.parse</code>が遅いのはASTを作らず、毎回パースの手間がかかるから。<br/>
一度しか実行されないケースに関してはそこまで速度の差はでない。</p>
<p>一応<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a></p>
<p><a href="https://jsperf.com/json-parse-vs-object-literal-in-parser">https://jsperf.com/json-parse-vs-object-literal-in-parser</a></p>
<p>自分の環境では80%くらい<code>JSON.parse</code>が遅い</p>
brn_take
React hooks for React-Redux
hatenablog://entry/17680117126996542280
2019-03-20T14:55:56+09:00
2019-03-20T14:58:07+09:00 久しぶりに時間が少しあったので、今更ながらReact hooksで遊んでみた。 Redux とりあえず、useReducerとか触ってみたけど、redux勢には物足りない感... reduxをReact hooksで使えるやつ探したらfacebookが出してるredux-react-hookを見つけた。 使ってるうちに、こんな仰々しいものじゃなくていいんだよなという思いが拭えず... 結局React Hooksの勉強がてら全てをhooksでこなすrrhというYet another redux-hookを作ってみた。 RRH 使い方は簡単でcreateStoreをhookにした感じ Provid…
<p>久しぶりに時間が少しあったので、今更ながらReact hooksで遊んでみた。</p>
<h1>Redux</h1>
<p>とりあえず、<code>useReducer</code>とか触ってみたけど、<code>redux</code>勢には物足りない感...
reduxをReact hooksで使えるやつ探したら<a class="keyword" href="http://d.hatena.ne.jp/keyword/facebook">facebook</a>が出してる<a href="https://github.com/facebookincubator/redux-react-hook">redux-react-hook</a>を見つけた。
使ってるうちに、こんな仰々しいものじゃなくていいんだよなという思いが拭えず...</p>
<p>結局React Hooksの勉強がてら全てをhooksでこなす<a href="https://github.com/brn/rrh">rrh</a>というYet another redux-hookを作ってみた。</p>
<h1>RRH</h1>
<p>使い方は簡単で<code>createStore</code>をhookにした感じ</p>
<p>Provider側</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> React from <span class="synConstant">'react'</span>;
<span class="synStatement">import</span> <span class="synIdentifier">{</span> useProvider <span class="synIdentifier">}</span> from <span class="synConstant">'rrh'</span>;
<span class="synStatement">import</span> reducer from <span class="synConstant">'./reducer'</span>;
<span class="synStatement">import</span> middleware from <span class="synConstant">'./middleware'</span>
<span class="synStatement">import</span> Child from <span class="synConstant">'./child'</span>;
<span class="synStatement">const</span> Component = () => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> Provider = useProvider(() => (<span class="synIdentifier">{</span>
reducer,
preloadedState: <span class="synIdentifier">{</span>count: 1<span class="synIdentifier">}</span>,
storeEnhancer: applyMiddleware(middleware)
<span class="synIdentifier">}</span>));
<span class="synStatement">return</span> <Provider><Child></Provider>
<span class="synIdentifier">}</span>
</pre>
<p>connectもhookにしてみた</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> React from <span class="synConstant">'react'</span>;
<span class="synStatement">import</span> <span class="synIdentifier">{</span> useSelector <span class="synIdentifier">}</span> from <span class="synConstant">'rrh'</span>;
<span class="synStatement">const</span> Child = (props) => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> selected = useSelector(
props,
(dispatch, props) => (<span class="synIdentifier">{</span>
increment: () => dispatch(<span class="synIdentifier">{</span>type: <span class="synConstant">'INCREMENT'</span>, payload: 1<span class="synIdentifier">}</span>),
decrement: () => dispatch(<span class="synIdentifier">{</span>type: <span class="synConstant">'DECREMENT'</span>, payload: -1<span class="synIdentifier">}</span>),
<span class="synIdentifier">}</span>),
(state, props) => (<span class="synIdentifier">{</span>count: state.count<span class="synIdentifier">}</span>),
state => <span class="synIdentifier">[</span>state.count<span class="synIdentifier">]</span>
);
<span class="synStatement">return</span> (
<div>
<span class="synIdentifier">{</span>selected.count<span class="synIdentifier">}</span>
<button onClick=<span class="synIdentifier">{</span>selected.increment<span class="synIdentifier">}</span>>INC</button>
<button onClick=<span class="synIdentifier">{</span>selected.decrement<span class="synIdentifier">}</span>>DEC</button>
</div>
)
<span class="synIdentifier">}</span>
</pre>
<p>使い勝手は素のreduxに近くなるようにしてみた。</p>
<h1>typescript</h1>
<p>tsで作ってるので、型定義も同包してます。</p>
<p>rrh <a href="https://github.com/brn/rrh">https://github.com/brn/rrh</a></p>
brn_take
WeJS2周年おめでとうございます
hatenablog://entry/10257846132680536147
2018-12-05T12:09:58+09:00
2018-12-05T12:09:58+09:00 久しぶりのブログはこちらのアドベントカレンダー adventar.org WeJSについて We are Javascritpers wajs.connpass.com 初心者登壇歓迎なJavascript勉強会 最近はJS初心者歓迎も兼ねていて、多方面で参加しやすい勉強会になってる 思い出 21Cafeでやってるときに参加して依頼、だいたい行ってると思う 3回目くらいからかな? 全ての勉強会の中でここで最もLTしてると思う いつもお世話になっております ここでいろんな人と知り合いになれました 良いなと思っていること 今の時代にjavascriptで初心者歓迎なLT会は貴重 とにかく登壇の敷居…
<p>久しぶりのブログはこちらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a></p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fadventar.org%2Fcalendars%2F2972" title="We Are JavaScripters!【執筆初心者歓迎】 Advent Calendar 2018 - Adventar" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://adventar.org/calendars/2972">adventar.org</a></cite></p>
<h2>WeJSについて</h2>
<h3>We are Javascritpers</h3>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwajs.connpass.com%2F" title="We Are JavaScripters!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://wajs.connpass.com/">wajs.connpass.com</a></cite></p>
<p>初心者登壇歓迎な<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a>勉強会<br/>
最近はJS初心者歓迎も兼ねていて、多方面で参加しやすい勉強会になってる</p>
<h2>思い出</h2>
<p>21Cafeでやってるときに参加して依頼、だいたい行ってると思う<br/>
3回目くらいからかな?</p>
<p>全ての勉強会の中でここで最もLTしてると思う<br/>
いつもお世話になっております</p>
<p>ここでいろんな人と知り合いになれました</p>
<h2>良いなと思っていること</h2>
<p>今の時代に<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>で初心者歓迎なLT会は貴重</p>
<p>とにかく登壇の敷居を下げてくれているので登壇しやすい<br/>
特に一度も登壇したことない人はぜひ申し込んで登壇してほしい</p>
<p>ただ、倍率が高いので抽選通るまで頑張ろう</p>
<p>あと毎回賑やかでよい<br/>
懇親会とかすごく話しやすい雰囲気だなぁと毎回思う</p>
<p>これもひとえに主催者の方々の人柄ですね</p>
<h2>これからWeJSでLTする方へ</h2>
<p>自分のネタがしょぼいかなとか、みんな知ってるしとか考えなくていいよ!<br/>
まずは自分が楽しもう</p>
<p>昔WeJSでLTした心得的なやつ</p>
<p><iframe id="talk_frame_422475" src="//speakerdeck.com/player/9e6b081019ba4e50814552f01f5e5bff" width="710" height="501" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/brn/autopututosiyou">speakerdeck.com</a></cite></p>
<h1>最後に</h1>
<p>いつもありがとうございます。毎月楽しみにしてます。感謝。</p>
brn_take
JSのproposal-numeric-separatorを実装したよ
hatenablog://entry/17391345971626182784
2018-03-16T13:20:48+09:00
2018-03-16T13:23:20+09:00 タイトルの通りなんだけど、書くのが遅れてしまった。 V8 のmasterにマージされた。 機能 javascriptのプロポーザルで数値リテラルの間にアンダースコアを挿入できるやつ 現在stage3にあってまだ正式に採用されているわけではない。 github.com このスライドにも書いた speakerdeck.com 例. 1_0_00_0 0xFF_FF_FF 0b0101_01_01 0o7_7_7 16進数、10進数、8進数、2進数の各リテラルに対応している。 あとBigIntにも当然対応している 39_950_934n ただ、実装したあとに修正してもらったんだけど、implicit…
<p>タイトルの通りなんだけど、書くのが遅れてしまった。<br/>
V8 のmasterにマージされた。</p>
<h2>機能</h2>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>のプロポーザルで数値<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>の間にアンダースコアを挿入できるやつ</p>
<p>現在stage3にあってまだ正式に採用されているわけではない。</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftc39%2Fproposal-numeric-separator" title="tc39/proposal-numeric-separator" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/tc39/proposal-numeric-separator">github.com</a></cite></p>
<p>このスライドにも書いた</p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="565" id="talk_frame_430377" mozallowfullscreen="true" src="//speakerdeck.com/player/22fc937a5dea4d77be5a5a7282aeb698" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/brn/numbers-in-javascript-2">speakerdeck.com</a></cite></p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>1_0_00_0
0xFF_FF_FF
0b0101_01_01
0o7_7_7
</pre>
<p>16進数、10進数、8進数、2進数の各<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>に対応している。</p>
<p>あとBigIntにも当然対応している</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>39_950_934n
</pre>
<p>ただ、実装したあとに修正してもらったんだけど、implicit octalには対応していない。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>0777_7 <span class="synComment">// Error!</span>
</pre>
<p>あと、以下の場合はすべてエラー</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>0x_000 <span class="synComment">// Error !</span>
0b_01 <span class="synComment">// Error !</span>
0o_10 <span class="synComment">// Error !</span>
</pre>
<p>アンダースコアの重複と末尾もだめ</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>12__00 <span class="synComment">// Error !</span>
1200_ <span class="synComment">// Error !</span>
</pre>
<h2>制限</h2>
<p>キャストされる数値には使えません!</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synType">Number</span>(<span class="synConstant">'1_0_0'</span>) <span class="synComment">// Error !</span>
parseInt(<span class="synConstant">'1_0_0'</span>, 10) <span class="synComment">// Error !</span>
</pre>
<p>以下のIssueに詳しい</p>
<p><a href="https://github.com/tc39/proposal-numeric-separator/issues/32">Numeric separator supported in implicit coercions, e.g., `+"0_0"`? · Issue #32 · tc39/proposal-numeric-separator · GitHub</a></p>
<p>簡単に話すと今までは</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>IsNaN(<span class="synType">Number</span>(<span class="synConstant">'1_0_0'</span>)) <span class="synComment">// true</span>
</pre>
<p>だったので急にアンダースコアを数値として使えるようになると、<code>NaN</code>であることを期待しているコードが壊れるからという理由。<br/>
そのようなコードが一体どれほどあるのか知らないけど安全側に倒した。<br/>
breaking webを避けるためには仕方ないかなと言う感じ。</p>
<p>以上です。</p>
brn_take
V8の Object.entries/values を高速化した
hatenablog://entry/8599973812337045758
2018-01-15T11:54:30+09:00
2018-01-15T11:54:30+09:00 V8のObject.entries/valuesを高速化したよというお話 Object.entires/values そもそもObject.entries、Object.valuesとは何かというと、 GitHub - tc39/proposal-object-values-entries: ECMAScript Proposal, specs, and reference implementation for Object.values/Object.entriesで提案されていた仕様である。 見事stage4まで到達して採用された。 仕様 機能をおさらいしておく Object.entrie…
<p>V8の<code>Object.entries/values</code>を高速化したよというお話</p>
<h2>Object.entires/values</h2>
<p>そもそも<code>Object.entries</code>、<code>Object.values</code>とは何かというと、<br/>
<a href="https://github.com/tc39/proposal-object-values-entries">GitHub - tc39/proposal-object-values-entries: ECMAScript Proposal, specs, and reference implementation for Object.values/Object.entries</a>で提案されていた仕様である。<br/>
見事stage4まで到達して採用された。</p>
<h3>仕様</h3>
<p>機能をおさらいしておく</p>
<p><b>Object.entries(O)</b></p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> ret = <span class="synType">Object</span>.entries(<span class="synIdentifier">{</span>a: 1, b: 2, c: 3<span class="synIdentifier">}</span>);
console.log(ret) <span class="synComment">// [['a', 1], ['b', 2], ['c', 3]];</span>
</pre>
<p>キーと値をすべて配列に書き出してくれる。</p>
<p><b>Object.values(O)</b></p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> ret = <span class="synType">Object</span>.values(<span class="synIdentifier">{</span>a: 1, b: 2, c: 3<span class="synIdentifier">}</span>);
console.log(ret) <span class="synComment">// [1, 2, 3];</span>
</pre>
<p>値のみを配列に書き出してくれる。</p>
<p>Ecma262のspecによると以下のように動作が規定されている</p>
<ul>
<li><code>ToObject(O)</code>を呼び出す</li>
<li><code>EnumerableOwnList(O)</code>を呼び出して結果を<code>nameList</code>に格納
<ul>
<li><code>Type(O)</code>がObjectかチェック</li>
<li><code>O.[[OwnPropertyKeys]]()</code>を呼び出してキー一覧を<code>ownKeys</code>に代入</li>
<li><code>properties</code>配列を初期化
<ul>
<li><code>for each key of ownKeys</code>
<ul>
<li><code>Type(key)</code>が文字列なら続行
<ul>
<li><code>desc</code>にプロパティデスクリプタを<code>O.[[GetOwnProperty]](key)</code>を呼び出して代入</li>
<li><code>desc</code>が<code>undefined</code>ではなく<code>desc.[[Enumerable]]</code>なら
<ul>
<li>もし<code>Object.keys</code>なら<code>key</code>を<code>properties</code>に追加</li>
<li><code>Object.values/entries</code>なら
<ul>
<li><code>Get(O, key)</code>を呼び出して<code>value</code>に代入</li>
<li><code>Object.values</code>の場合には<code>value</code>を<code>properties</code>に代入</li>
<li><code>Object.entries</code>の場合には<code>CreateArrayFromList(« key, value »).</code>を呼び出して<code>entry</code>に代入</li>
<li><code>properties</code>に<code>entry</code>を追加</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code>CreateArrayFromList(nameList)</code>を呼び出して<code>Return</code></li>
</ul>
<p>コードで表すと</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> ObjectEntries(O) <span class="synIdentifier">{</span>
O = ToObject(O);
<span class="synStatement">const</span> nameList = EnumerableOwnList(O, <span class="synConstant">'entry'</span>);
<span class="synStatement">return</span> <span class="synStatement">new</span> JSArray(nameList);
<span class="synIdentifier">}</span>
<span class="synIdentifier">function</span> ObjectValues(O) <span class="synIdentifier">{</span>
O = ToObject(O);
<span class="synStatement">const</span> nameList = EnumerableOwnList(O, <span class="synConstant">'value'</span>);
<span class="synStatement">return</span> <span class="synStatement">new</span> JSArray(nameList);
<span class="synIdentifier">}</span>
<span class="synIdentifier">function</span> EnumerableOwnList(O, kind) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> ownKeys = O.<span class="synIdentifier">[[</span>OwnPropertyKeys<span class="synIdentifier">]]</span>();
<span class="synStatement">const</span> properties = <span class="synIdentifier">[]</span>;
<span class="synStatement">for</span> (<span class="synStatement">const</span> key of ownKeys) <span class="synIdentifier">{</span>
<span class="synStatement">if</span> (Type(key) === <span class="synType">String</span>) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> desc = O.<span class="synIdentifier">[[</span>GetOwnProperty<span class="synIdentifier">]]</span>(key);
<span class="synStatement">if</span> (desc && desc.<span class="synIdentifier">[[</span>Enumerable<span class="synIdentifier">]]</span>) <span class="synIdentifier">{</span>
<span class="synStatement">if</span> (kind === <span class="synConstant">'key'</span>) <span class="synIdentifier">{</span>
properties.push(key);
<span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span>
<span class="synStatement">const</span> value = Get(O, key);
<span class="synStatement">if</span> (kind === <span class="synConstant">'value'</span>) <span class="synIdentifier">{</span>
properties.push(value);
<span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span>
<span class="synStatement">const</span> entry = <span class="synIdentifier">[</span>key, value<span class="synIdentifier">]</span>;
properties.push(entry);
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synStatement">return</span> properties;
<span class="synIdentifier">}</span>
</pre>
<p>て感じの動作になる。</p>
<p>基本的にはキー一覧を取得してから<code>PropertyDescriptor</code>を各々取得していって、<code>Enumerable</code>なら配列に追加していく形になる。</p>
<h2>Too Slow</h2>
<p>V8の<code>Object.entries/values</code>は<code>V6.5.172</code>までは非常に遅くて使うのを躊躇するほどだった。</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>をとるとこんな感じ</p>
<p><b>Fast Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 134 ms.
objectKeys: 552 ms.
objectKeysAndValues: 1010 ms.
objectEntries: 2541 ms.</pre>
<p><b>Slow Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 3117 ms.
objectKeys: 837 ms.
objectKeysAndValues: 1740 ms.
objectEntries: 2536 ms.</pre>
<p><code>objectEntries</code>と<code>objectKeysAndValues</code>が<code>forIn</code>に対して10倍以上遅いのがわかる。<br/>
ちなみにFast Pathはプロパティのみを含む通常のシンプルなオブジェクトで、Slow Pathは<code>Symbol</code>や<code>Getter</code>を含んでいる。</p>
<h2>なぜこんなに遅いのか</h2>
<p>こんなに遅い理由はRuntimeですべて実装されていたためである。<br/>
Runtimeとは<code>C++</code>で書かれたコードで<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>コードとして静的に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>される。<br/>
一見<code>C++</code>ならば早そうに見えるが、やはり直に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>に変換されるCSA等と比べると遅く、<br/>
さらに<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>の世界から呼び出す際にも大きなオーバーヘッドがある。</p>
<h2>どのように高速化したか</h2>
<p><code>CSA(CodeStubAssembler)</code>に移植した。<br/>
ちなみにRuntimeとかCSAに関しては<a href="http://abcdef.gets.b6n.ch/entry/2017/12/25/120000">V8 javascript engineについての細かい話 (Node.js Advent Calendar 2017) - abcdefGets</a>に記載している。</p>
<p>ただし、完全にすべてのコードを移植したわけではない。</p>
<h3>V8の方針</h3>
<p>最初、私はRuntimeにあった<code>Object.entries/values</code>のすべての実装をCSAで書き直した。<br/>
これは一見うまく行ったのだが、いくつかの問題をはらんでいた。</p>
<ul>
<li>高速化したのだがCSAの本来のポテンシャルを活かしきれていない</li>
<li>小さなRuntime呼び出しを残さざるを得なかった</li>
<li>修正箇所が多すぎる</li>
</ul>
<h4>高速化したのだがCSAの本来のポテンシャルを活かしきれていない</h4>
<p>これは私のCSA力不足が原因だった。<br/>
確かに最初の実装で信じられないほど高速化した。以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を見ていただきたい。</p>
<p><b>Fast Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 136 ms.
objectKeys: 561 ms.
objectKeysAndValues: 472 ms.
objectEntries: 617 ms.</pre>
<p><b>Slow Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 1307 ms.
objectKeys: 946 ms.
objectKeysAndValues: 1526 ms.
objectEntries: 1623 ms.</pre>
<p>確かにFast Pathのコードは非常に高速化しているものの、<br/>
Slow Pathの方は微かに高速化しただけだ。<br/>
これはFast Pathでさえ本来のCSAのポテンシャルで考えるとまだまだ遅いものだった。<br/>
しかもこのバージョンのコードは以下の問題を抱えていた。</p>
<h4>小さなRuntime呼び出しを残さざるを得なかった</h4>
<p>確かにCSAに移植はしたものの、CSAで記述するには明らかに複雑すぎるコード(数値プロパティの要素を取得するためのコードと、Slow Pathのコード)を含んでいたため、<br/>
結局小さなRuntimeを別途実装してCSAからRuntimeを呼び出すコードになっていた。<br/>
CSAからRuntimeを呼び出すのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>の世界から呼び出すのと同じく<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%C6%A5%AD%A5%B9%A5%C8%A5%B9%A5%A4%A5%C3%A5%C1">コンテキストスイッチ</a>のオーバーヘッドがかなり大きい。<br/>
そのためFast PathもSlow Pathも非効率なコードになっていた。<br/>
そしてさらに問題だったのはコード量だった。</p>
<h4>修正箇所が多すぎる</h4>
<p>明らかにV8という世界規模の<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>にコミットするコードとしては修正量が大きすぎた。<br/>
最初はレビュアーもなんとなく見てくれていたが、さすがにこれは一旦方針を変えようという話になった。</p>
<p><b>小さくすすめる</b></p>
<p>大切なのはいきなり大きな変更をしないことだ。<br/>
V8ほど複雑なコードベースなら尚更だ。<br/>
V8はすでに一部分の変更がどう影響するか、机上のみで推測するのは不可能なほどの複雑さを持っていた。<br/>
そこでレビュアーと話し合って、以下の方針で再実装することにした。</p>
<ul>
<li>既存のRuntimeは残す</li>
<li>Slow Pathは移植しない</li>
<li>数値プロパティ検索も移植しない</li>
<li>もし途中でSlow Pathに入る場合にはRuntime呼び出しを行い、Runtimeですべて行う。</li>
</ul>
<p>結局泣く泣くSlow Pathのコードを全削除し、Fast Pathで最速のケースのみをCSAに移植した。</p>
<h2>最<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BD%AA%B7%EB">終結</a>果</h2>
<p>上述の過程を踏まえた上でコードを修正した結果以下の様になった。</p>
<p><b>Fast Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 134 ms.
objectKeys: 525 ms.
objectKeysAndValues: 320 ms.
objectEntries: 469 ms.</pre>
<p><b>Slow Path</b></p>
<pre class="code" data-lang="" data-unlink>forIn: 1307 ms.
objectKeys: 945 ms.
objectKeysAndValues: 1304 ms.
objectEntries: 2043 ms.</pre>
<p>Slow Pathは以前と変わらない速度だが、Fast Pathに至っては、</p>
<ul>
<li><code>Object.etnries</code> は <code>2541 ms</code> から <code>469 ms</code></li>
<li><code>Object.values</code> は <code>1010 ms</code> から <code>320 ms</code></li>
</ul>
<p>という結果に。約<b style="font-size: 130%">3 ~ 5.5倍</b>ほど高速化した。</p>
<p>やはりSlow Pathの移植を一旦止めて、Fast Pathのみに絞ったことがより洗練されたコードを可能にし、<br/>
その結果としてCSAの力を振り絞ることができたようだ。<br/>
ちなみにSlow Pathに関しては今のところはそこまで高速化するチャンスがないのと、使用用途的にあまり通らないパターンが多いので、<br/>
また議論が起きたときに高速化する予定。</p>
<p>なので、現状の実装は以下のパターンの場合のみ超高速に動作する。</p>
<ul>
<li><code>Symbol</code>を含まない</li>
<li>Getterを含まない</li>
<li>数値プロパティを含まない</li>
<li><code>Proxy</code>ではない</li>
</ul>
<h2>まとめ</h2>
<p>V8ほどの規模になると、小さく始めていくのがレビュアーにとっても実装者にとっても重要である。<br/>
という学びを得た。</p>
<p>ちなみにこのコードはV8 6.5.173にShipされました。</p>
<p>※<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>に使ったコードは以下のものです。</p>
<p>Fast Path<br/>
<a href="https://gist.github.com/brn/682348bbee9fd07d289666c5e2b3501a">object-entries-values-fast-path-bench.js · GitHub</a></p>
<p>Slow Path<br/>
<a href="https://gist.github.com/brn/f7047016369a9828bee6c717ce426f4b">object-entries-values-slow-path-bench.js · GitHub</a></p>
brn_take
ブラウザベンダーのSpectre and Meltdown対応
hatenablog://entry/8599973812335354215
2018-01-09T11:50:21+09:00
2018-01-09T11:50:21+09:00 Spectre and Meltdownについては前回書いたが、 各ブラウザベンダーがとっている対応について今回は書く。 といっても大したことではないのだが。 対応一覧 Google Chrome SharedArrayBufferのデフォルト無効化とPerformance.nowの解像度を下げる(どこまでかは不明) Per Site Isolationを有効化 Firefox SharedArrayBufferのデフォルト無効化とPerformance.nowの解像度を20μsまで下げる Safari SharedArrayBufferのデフォルト無効化とPerformance.nowの解像…
<p>Spectre and Meltdownについては前回書いたが、<br/>
各ブラウザベンダーがとっている対応について今回は書く。</p>
<p>といっても大したことではないのだが。</p>
<h2>対応一覧</h2>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google%20Chrome">Google Chrome</a>
<ul>
<li><code>SharedArrayBuffer</code>のデフォルト無効化と<code>Performance.now</code>の解像度を下げる(どこまでかは不明)</li>
<li><code>Per Site Isolation</code>を有効化</li>
</ul>
</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Firefox">Firefox</a>
<ul>
<li><code>SharedArrayBuffer</code>のデフォルト無効化と<code>Performance.now</code>の解像度を<code>20μs</code>まで下げる</li>
</ul>
</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Safari">Safari</a>
<ul>
<li><code>SharedArrayBuffer</code>のデフォルト無効化と<code>Performance.now</code>の解像度を<code>1ms</code>まで下げる</li>
</ul>
</li>
</ul>
<h2>SharedArrayBufferとPerformance.nowの関係</h2>
<p>なぜブラウザベンダーが<code>SharedArrayBuffer</code>のデフォルト無効化と<code>Performance.now</code>の解像度を下げるかというと、今回の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%C8%BC%E5%C0%AD">脆弱性</a>の攻撃手法に関係がある。<br/>
SpectreとMeltdownは両方共、攻撃時にL1<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A5%E3%A5%C3%A5%B7%A5%E5%A5%E1%A5%E2%A5%EA">キャッシュメモリ</a>の推測を行う必要がある。(具体的には、メモリアクセスを行い、かかった時間を計測してキャッシュされている値を推測する。これを<code>Side-Channel-Attack</code>と呼ぶ。)</p>
<p>この<code>Side-Channel-Attack</code>を<code>SharedArrayBuffer</code>と<code>performance.now</code>を使うことでかなり効率化できる。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>
<span class="synComment">// Worker</span>
<span class="synStatement">self</span>.on(<span class="synConstant">'message'</span>, (e) => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> buf = <span class="synStatement">new</span> SharedUint32Array(e.data.buffer);
<span class="synStatement">do</span> <span class="synIdentifier">{</span>
Atomic.store(buf, 0, performance.now() - buf<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>);
<span class="synIdentifier">}</span> <span class="synStatement">while</span>(<span class="synConstant">true</span>)
<span class="synIdentifier">}</span>);
<span class="synComment">// main</span>
<span class="synComment">// Create worker before.</span>
<span class="synStatement">const</span> sab = <span class="synStatement">new</span> SharedUint32Array(1);
sab<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span> = peformance.now();
worker.postMessage(<span class="synIdentifier">{</span>buffer: sab.buffer<span class="synIdentifier">}</span>, <span class="synIdentifier">[</span>sab.buffer<span class="synIdentifier">]</span>);
<span class="synComment">// do memory access.</span>
console.log(Atomic.load(buf, 0)); <span class="synComment">// Get high resolution relative time.</span>
</pre>
<p>こんな感じでワーカースレッドでクロックを実装し、<code>performance.now</code>の高精度タイマーで<code>SharedArrayBuffer</code>に結果を書き込むことで、高精度なクロックを実装することができる。<br/>
このクロックを使うことで、そこそこ正確なメモリアクセスの相対時間を計測することができ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A5%E3%A5%C3%A5%B7%A5%E5%A5%E1%A5%E2%A5%EA">キャッシュメモリ</a>のチェックに利用することが可能であった。<br/>
このソフトウェアクロックの実装精度を下げるために<code>SharedArrayBuffer</code>の無効化と<code>performance.now</code>の精度変更が行われた。</p>
<h2>それ以外</h2>
<p><code>Per Site Isolation</code></p>
<p>各<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>プロセスを分離する。ただしメモリ使用量が20%ほど増える。</p>
<p>それ以外の部分での対応、BTBの制御やOut-of-orderの抑制は不可能かパフォーマンスペナルティがでかすぎるため、<code>Side-Channel-Attack</code>を非効率化することにとりあえず重点が置かれている。</p>
<h2>まとめ</h2>
<p>これも完全な対応ではない上に、<br/>
<code>SharedArrayBuffer</code>が使えなくなるというWebの後退が起きてしまった。</p>
brn_take
Spectre and Meltdownについて
hatenablog://entry/8599973812333255630
2018-01-04T17:57:19+09:00
2018-01-04T18:06:51+09:00 今回CPUに依存する脆弱性が発見されて大きな問題となっている。 そこで正月休みを使ってこの問題の解説を試みる。 Disclaimer 私はセキュリティの専門家ではないので間違えた情報があるかもしれない。 一次情報を載せておくので怪しい場合にはそこにアクセスして自分で確認するように。 この情報を鵜呑みにしておきたいかなる問題にも責任は負いかねる。 今回の脆弱性についてのWEBページ 業務で問題の解決を行う場合にはこのページを参照すること。 Meltdown and Spectre 概要 今回は2つの脆弱性が問題になっている。 1つ目はMeltdownと名付けられており、CPUのout-of-or…
<p>今回CPUに依存する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%C8%BC%E5%C0%AD">脆弱性</a>が発見されて大きな問題となっている。<br/>
そこで正月休みを使ってこの問題の解説を試みる。</p>
<h1>Disclaimer</h1>
<p>私はセキュリティの専門家ではないので間違えた情報があるかもしれない。<br/>
一次情報を載せておくので怪しい場合にはそこにアクセスして自分で確認するように。<br/>
この情報を鵜呑みにしておきたいかなる問題にも責任は負いかねる。</p>
<p>今回の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%C8%BC%E5%C0%AD">脆弱性</a>についてのWEBページ<br/>
業務で問題の解決を行う場合にはこのページを参照すること。</p>
<p><a href="https://meltdownattack.com/#faq-why-spectre">Meltdown and Spectre</a></p>
<h2>概要</h2>
<p>今回は2つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%C8%BC%E5%C0%AD">脆弱性</a>が問題になっている。<br/>
1つ目はMeltdownと名付けられており、CPUのout-of-order実行を利用したもの。<br/>
2つ目はSpectreというCPUの投機的実行を利用したもの。<br/>
今回この問題が深刻なのは、どちらもCPUのパフォーマンス最適化のために存在する機構であり、モダンなほぼすべてのCPUに存在すること、<br/>
悪意のあるコードとそうでないコードの見極めが非常に難しいことなどから、解決の難しい問題となっている。</p>
<h3>Meltdown</h3>
<p>Meltdownから解説していく。<br/>
Meltdownと名付けられたこの問題はCPUのout-of-order実行の隙を突いた攻撃方法である。</p>
<h4>out-of-order</h4>
<p><code>out-of-order</code>と言うのはモダンなCPUが搭載しているパフォーマンス最適化手段である。<br/>
現在のCPUは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>命令を<code>μOps</code>という更に小さなCPUのオペレーションに分割して実行する。<br/>
この<code>μOps</code>に分解された命令は<code>Reorder-Buffer</code>と<code>Scheduler</code>によってデータの依存関係が計算されて、実行順が変更される。<br/>
結果としてそれぞれ依存関係から独立した命令は順序に関係なく実行され、依存がある部分は依存先の命令が完了次第即実行される。</p>
<p>以下が今回の問題となる疑似コードだが、</p>
<p><script src="https://gist.github.com/4ad97b3858f184ca74efbbfe54e845ef.js"> </script></p>
<p><a href="https://gist.github.com/4ad97b3858f184ca74efbbfe54e845ef">gist4ad97b3858f184ca74efbbfe54e845ef</a></p>
<p>4行目の<code>mov</code>命令と5行目の<code>shl</code>命令は依存関係があるものの、それ以外はすべて予め<code>μOps</code>に分解されて実行される。</p>
<p>以下は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wikipedia">Wikipedia</a>からの引用</p>
<blockquote><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/OoO">OoO</a>では、命令及び実行結果を一時溜めておく場所を作り、命令の実行を次のように細分化する。</p>
<p>1 命令フェッチ。<br/>
2 命令にリオーダ・バッファ(reorder buffer)のエントリを割り当てる。<br/>
3 命令を命令<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3">待ち行列</a>または命令発行キュー(reservation station, issue queue)に送る(dispatch)。<br/>
4 命令<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3">待ち行列</a>内の命令は、入力<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%E9%A5%F3%A5%C9">オペランド</a>が得られるまで実行されない。入力<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%E9%A5%F3%A5%C9">オペランド</a>が得られた段階で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3">待ち行列</a>内にそれより古い命令があっても先に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3">待ち行列</a>から取り除かれ、実行されることになる。<br/>
5 命令が適当な実行ユニットに対して発行(issue)され、実行される。<br/>
6 実行結果がリオーダ・バッファに格納される。<br/>
7 リオーダ・バッファ内の命令のうち、最も古い命令の実行が完了すると、その実行結果は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>ファイルに書き戻され、命令はリオーダ・バッファから取り除かれる。これを卒業ないしリタイア(graduation, retire)ステージと呼ぶ。命令<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3">待ち行列</a>とは異なり、より新しい命令が実行完了状態であっても、それより古い命令がリオーダ・バッファ内にリタイアせずに残っている場合は、その(より新しい)命令がリタイアすることはできない。</p></blockquote>
<p><a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A6%E3%83%88%E3%83%BB%E3%82%AA%E3%83%96%E3%83%BB%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E5%AE%9F%E8%A1%8C">アウト・オブ・オーダー実行 - Wikipedia</a></p>
<h4>Preventing reading Karnel Memory from User space</h4>
<p>上の例で示した疑似コードは実行すると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>の特権メモリをユーザースペースで読み込んでいるため例外を発生させる。<br/>
そのため、当然ユーザースペースから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>メモリを読み込むことは不可能である。<br/>
しかし後に述べる<code>Transient Instruction</code>を経由することで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>メモリをCPUキャッシュに載せることが可能になる。</p>
<h4>Transient Instruction</h4>
<p>ちょっとどう翻訳していいかわからないのだが、これは<code>out-of-order</code>実行によって実行される、<br/>
本来の実行パスでは実行されるはずのない命令である。</p>
<p>以下に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>を示す。<br/>
このコードは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>であり実際には動作しない。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">int</span> main() {
<span class="synType">char</span> arr[<span class="synConstant">1</span>];
Exception e;
<span class="synStatement">throw</span> e;
arr[data * <span class="synConstant">4096</span>]
}
</pre>
<p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>の<code>arr[data * 4096]</code>は本来<code>throw e</code>が例外を投げることによって実行が中断されるため実行されないのだが、<br/>
<code>out-of-order</code>によって<code>arr[data * 4096]</code>のメモリアクセスが実行される。</p>
<h4>Reading Privilleged Memory</h4>
<p>以上のテクニックを駆使することによって特権がなくても特権が必要なメモリの値をロードすることができる。</p>
<p>コードを再掲する。</p>
<p><script src="https://gist.github.com/4ad97b3858f184ca74efbbfe54e845ef.js"> </script></p>
<p><a href="https://gist.github.com/4ad97b3858f184ca74efbbfe54e845ef">gist4ad97b3858f184ca74efbbfe54e845ef</a></p>
<ul>
<li>まずは<code>line 4</code>で<code>mov</code>命令を発行し<code>rcx</code>にある<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>メモリの内容を読み込む</li>
<li><code>line 5</code>で<code>al</code>に格納した命令にアクセスして依存を生成する</li>
<li><code>line 6</code>は最適化に対する対応なので無視</li>
<li><code>line 7</code>で<code>rax</code>つまり、先程<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>メモリの内容を読み込んだアドレスに対して間接参照をする。</li>
</ul>
<p>これらの処理が<code>μOps</code>に分解され<code>out-of-order</code>によって、<code>line 7</code>で<code>line 5/6</code>の命令完了待ち状態になり、<br/>
<code>line 5</code>で例外が発生するよりも先に<code>line 7</code>が実行される。<br/>
こうすることで例外が発生するよりも先に格納されたメモリにアクセスするすることが可能になる。</p>
<h4>Side-Channel-Attack</h4>
<p>しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BE%E5%B5%AD">上記</a>の方法を利用しても<code>out-of-order</code>の実行結果は巻き戻されて取得ができない。<br/>
ここで<code>Side-Channel-Attack</code>を利用する。<br/>
<code>Side-Channel-Attack</code>とは外からハードやCPUの状態を観測することで値を取得するという方法である。<br/>
今回のペーパーでは<code>Flush+Reload</code>という方法を利用していた。<br/>
<code>Flush+Reload</code>については以下</p>
<p><a href="https://eprint.iacr.org/2013/448.pdf">https://eprint.iacr.org/2013/448.pdf</a></p>
<p>この方法を使うことで<code>line 7</code>でのメモリアクセスを計測し、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A5%E3%A5%C3%A5%B7%A5%E5%A5%E1%A5%E2%A5%EA">キャッシュメモリ</a>を調べて、そのメモリアクセスにかかった時間を計測することでどのアドレスがキャッシュされているかを取得できれば、<br/>
秘密の値のみに依存したアドレスが取得できるようだ。</p>
<p>また攻撃手法として例外を握りつぶすためにプロセスの<code>fork</code>を行い、その中でこれらの攻撃を行い、<br/>
メインプロセスでメモリアドレスを調べる方法が提案されている。</p>
<h3>Spectre</h3>
<p>こちらはCPUの投機的実行という機能を利用したものでMeltdownと似ているが、より実行しやすい。</p>
<h4>投機的実行 (Speculative Execution)</h4>
<p>CPUは分岐命令(if文とか)を実行する際に、パフォーマンス最適化のために過去に分岐した履歴から分岐先を予想して、<br/>
条件分岐の結果評価が完了する前に<code>then</code>節や<code>else</code>節を実行する。<br/>
その後条件分岐が確定し、投機的実行した結果の値が必要なくなれば、その結果はそのまま捨てられる。<br/>
つまりMeltdownみたいに実行されてほしくないコードが実行されることがありうる。
ちょっと嫌な雰囲気になってきたね。</p>
<h4>Side-Channel-Attack</h4>
<p>やはりここでもCPUキャッシュの状態を観測することで値を取得する。<br/>
以下に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>を示す。</p>
<pre class="code" data-lang="" data-unlink>if (x < array1_size)
y = array2[array1[x] * 256];</pre>
<p>以下はペーパーに記載されていたシナリオである。</p>
<p>ここで<code>array2[array1[x] * 256]</code>は<code>x < array1_size</code>が確定する前に実行されるとし、<code>x</code>は取得したいアドレスの値とする。<br/>
すると<code>array1[x]</code>がキャッシュから取得され、アドレス<code>k</code>が手に入る。<br/>
その後<code>array2[k]</code>にアクセスすることで、キャッシュミスが起こり、その間に<code>x < array1_size</code>が確定することで、すべての結果が破棄される。<br/>
しかし<code>array2[k]</code>への投機的実行はすでにCPUキャッシュに変化を起こしてしまっているのでそれを観測することで目的のデータを取得することができる。</p>
<h4>Javascirpt</h4>
<p>ペーパーによると<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>でも実行できるためそれなりに危険そうである。<br/>
V8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>シェルであるd8で実行して成功したようなので結構ダメそう。</p>
<h3>Vendors</h3>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>
<ul>
<li><a href="https://security.googleblog.com/2018/01/todays-cpu-vulnerability-what-you-need.html">Google Online Security Blog: Today's CPU vulnerability: what you need to know</a></li>
<li>Cloud系はそれぞれアップデートすること</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Chrome">Chrome</a>は<code>SharedArrayBuffer</code>等を無効にするらしい(影響範囲とか大丈夫なのだろうか?)</li>
</ul>
</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Mozilla">Mozilla</a>
<ul>
<li><a href="https://blog.mozilla.org/security/2018/01/03/mitigations-landing-new-class-timing-attack/">Mitigations landing for new class of timing attack | Mozilla Security Blog</a></li>
<li><code>SharedArrayBuffer</code>の無効化、<code>performance.now()</code>のresolutionを20µsに低減</li>
</ul>
</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Apple">Apple</a>
<ul>
<li>わからん!</li>
</ul>
</li>
</ul>
<h2>まとめ</h2>
<p>ちょっと内容が複雑なため間違いを含んでいる箇所があったら申し訳ない!<br/>
とにかく基本は1次情報にあたってほしい。</p>
<p>より詳しい方、コメント等で修正があったらしていただけると助かります。</p>
<p>もう一度掲載しとく</p>
<p><a href="https://meltdownattack.com/#faq-why-spectre">Meltdown and Spectre</a></p>
brn_take
V8 javascript engineについての細かい話 (Node.js Advent Calendar 2017)
hatenablog://entry/8599973812329211565
2017-12-25T12:00:00+09:00
2017-12-25T10:26:57+09:00 Node.js Advent Calendar 2017 25日目の記事です。トリとなります。 さて先日11/26・27日に行われたNode学園祭でv8について発表させて頂いたが、 30分という制約上色々カットせざるを得なかった。 またv8のコードを読む・コントリビュートする上で伝えられる事も色々と溜まったので一度アウトプットすることにした。 というわけでまとまりのない記事になる可能性が高いがご容赦いただけると助かります。 事前資料 以下のスライドがNode学園祭の発表資料なので読んどいていただけると理解がはやいかも speakerdeck.com 前準備 チェックアウト v8はGitHubに…
<p>Node.js Advent Calendar 2017 25日目の記事です。トリとなります。</p>
<p>さて先日11/26・27日に行われたNode学園祭でv8について発表させて頂いたが、<br/>
30分という制約上色々カットせざるを得なかった。<br/>
またv8のコードを読む・コントリビュートする上で伝えられる事も色々と溜まったので一度アウトプットすることにした。<br/>
というわけでまとまりのない記事になる可能性が高いがご容赦いただけると助かります。</p>
<h2>事前資料</h2>
<p>以下のスライドがNode学園祭の発表資料なので読んどいていただけると理解がはやいかも</p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="565" id="talk_frame_418128" mozallowfullscreen="true" src="//speakerdeck.com/player/43a76db9eadc4df7ba3aa4aa856a9dbb" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/brn/source-to-binary-journey-of-v8-javascript-engine">speakerdeck.com</a></cite></p>
<h2>前準備</h2>
<h3>チェックアウト</h3>
<p>v8は<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>に直接は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%B9%A5%C6%A5%A3%A5%F3%A5%B0">ホスティング</a>されていない。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>上にあるv8<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>はミラーで実際には<a href="https://chromium.googlesource.com/v8/v8">chromium.googlesource.com</a>に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%B9%A5%C6%A5%A3%A5%F3%A5%B0">ホスティング</a>されている。<br/>
ただし開発の際には<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>をフォークして<code>git remote add fork git@github.com:<user-name>/v8.git</code>とかして<code>fork</code>リモートを追加してやるとよい。
あとは実際にpushして保存したい場合にはこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のフォークに対して行う。<br/>
なぜこんな面倒な事をしているかというと、v8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>ではないため<code>git push</code>を使わない。<br/>
<code>git cl</code>というコマンドを利用するのだが、このコマンドで<code>git cl upload</code>としてもGerritにレビューを送るだけで<code>push</code>相当のことはできない。<br/>
その為<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>をブランチの保存先に利用している。</p>
<p>さて、v8を実際に開発するためには最初にこのページを確認すると良い。<br/>
<a href="https://github.com/v8/v8/wiki/Contributing">Contributing · v8/v8 Wiki · GitHub</a></p>
<p>ただしあんまり親切ではないので軽く説明すると、まず最初に以下のステップに従ってdepot_toolsをインストールする<br/>
<a href="http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up">depot_tools_tutorial(7)</a><br/>
インストールしてパスを通したら、<code>gclient</code>コマンドが叩けるか確認する。<br/>
その後コードを以下の要領でチェックアウトする。<br/>
<a href="https://github.com/v8/v8/wiki/Using-Git">Using Git · v8/v8 Wiki · GitHub</a><br/>
あとは<code>git checkout -b foo-branch</code>で適当なブランチを作って作業を開始する。</p>
<h3>ビルド</h3>
<p>一旦チェックアウトできたらビルドをしてみる。<br/>
現在v8はGNというメタプロジェクトビルドツールを利用しており、以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/python">python</a>コマンドでビルド設定を出力できる。</p>
<p>X64の例</p>
<p><b>DEBUG</b></p>
<pre class="code" data-lang="" data-unlink>./tools/dev/v8gen.py x64.debug -vv</pre>
<p><b>OPT_DEBUG</b></p>
<pre class="code" data-lang="" data-unlink>./tools/dev/v8gen.py x64.optdebug -vv</pre>
<p><b>RELEASE</b></p>
<pre class="code" data-lang="" data-unlink>./tools/dev/v8gen.py x64.release -vv</pre>
<p>次にビルドを行うのだが、ビルドにはNinjaというビルドツールを利用しており、以下のコマンドでビルドが開始できる。</p>
<p><b>DEBUG</b></p>
<pre class="code" data-lang="" data-unlink>ninja -C out.gn/x64.debug</pre>
<p><b>OPT_DEBUG</b></p>
<pre class="code" data-lang="" data-unlink>ninja -C out.gn/x64.optdebug</pre>
<p><b>RELEASE</b></p>
<pre class="code" data-lang="" data-unlink>ninja -C out.gn/x64.release</pre>
<p>後はビルド完了を待つだけ。<br/>
ちなみにフルビルドには<code>2.5 GHz Intel Core i7</code> <code>16GB Memory</code>で30分近くかかるので辛抱強く待つ。</p>
<h3>コミット</h3>
<p>作業が完了したらいつも通り<code>git add .</code>して、<code>git commit</code>でコミットコメントを書く。<br/>
コードをコミットする場合には<code>git cl format</code>で最初に全ファイルをフォーマットし、<code>git cl upload</code>でコードをGerritにコミットする。<br/>
この際<code>chromium.org</code>メールアドレスを持っていないとWarningが表示されるもののここはYを押して先に進む。<br/>
その後コミットメッセージの入力を求められるが、gitのコミットメッセージを利用する場合にはそのままEnterでオッケー。</p>
<h3>レビュー</h3>
<p>もしレビューを開始する場合には<code>git cl owners</code>コマンドでレビュアーを探し出して、GerritのReviewsにそのレビュアーを追加して待つ。</p>
<h2>エディタ・<a class="keyword" href="http://d.hatena.ne.jp/keyword/IDE">IDE</a></h2>
<p>自分は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> + RTagsを利用している。
この辺の記事が参考になった。<a href="https://qiita.com/alpha22jp/items/90f7f2ad4f8b1fa089f4">C++11時代のEmacs C++コーディング環境 - Qiita</a><br/>
またv8のコミッターに直接聞いたところ、CLionを使ってると言っていた。<a href="https://www.jetbrains.com/clion/">CLion: A Cross-Platform IDE for C and C++ by JetBrains</a><br/>
ただし、自分の環境ではしょっちゅうフリーズしていたので諦めた。<br/>
あとは<a class="keyword" href="http://d.hatena.ne.jp/keyword/vim">vim</a>が多いみたいだが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/vim">vim</a>はよくわからんので頑張って<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>環境作ってください。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/VSCode">VSCode</a>は無理だった。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Atom">Atom</a>ってなんだっけ?</p>
<h2>v8のコード構成</h2>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成概要</h3>
<p>v8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>は全て<code>src</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに格納されており、以下のような構成になっている。</p>
<pre class="code" data-lang="" data-unlink>src ---+
|
+---arm
+---arm64
+---mips
+---mips64
A +---ia32
+---x64
+---ppc
+---s390
+---wasm
+---asmjs
|
+---ast
+---compiler
B +---compiler-dispatcher
+---interpreter
+---parsing
|
+---js
+---builtins
C +---runtime
+---snapshot
+---regexp
+---profiler
|
D +---ic
|
+---heap
E +---heap-symbols.h
+---zone
+---objects
|
F +---inspector
|
+---base
+---debug
+---tracing
+---extensions
G +---libplatform
+---libsampler
+---third_party
+---trap-handler
|
+---*.cc/*.h
.
.
.</pre>
<p>ファイル数が非常に多くすべてを説明し切るのは非常に難しいので一旦概略を述べると、</p>
<ul>
<li>Aグループ
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ群はassemblerコードやdisassembler、macro-assembler、simulator等、CPU毎の個別コードが格納されている。</li>
</ul>
</li>
<li>Bグループ
<ul>
<li>パース、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>等のコード生成系のコードが格納されている。</li>
</ul>
</li>
<li>Cグループ
<ul>
<li>JSのビルトイン関数・v8内部の実行時ヘルパ関数等が格納されている。</li>
</ul>
</li>
<li>Dグループ
<ul>
<li>Inline Cache周りのコードが格納されている。</li>
</ul>
</li>
<li>E
<ul>
<li>オブジェクトモデルとメモリ周りのコードが格納されている。</li>
</ul>
</li>
<li>F
<ul>
<li>インスペクタ</li>
</ul>
</li>
<li>G
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>やプラットフォーム抽象化層のコードを格納している。</li>
</ul>
</li>
</ul>
<p>とこんな感じで分類できる。<br/>
ただしこれも結構苦しい分類で実際にはどこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リにも属していない<code>src</code>直下のファイルが各グループに相当するコードを実装していたりと、結構見境がない感じ。</p>
<h3>主要ファイル</h3>
<p>恐らくよく見ることになるソースを列挙しておく。</p>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>.h/<a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>.cc
<ul>
<li>Embedder向けの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>が定義されている。</li>
</ul>
</li>
<li>objects.h/objects.cc
<ul>
<li>v8のオブジェクトモデルすべてが定義されており <code>objects.cc</code>に関しては<b>19366</b>行ある。</li>
</ul>
</li>
<li>compiler/compiler.cc
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>のエントリポイントになるのでここからコードを追うことが多い。</li>
</ul>
</li>
<li>compiler/pipeline.cc
<ul>
<li>compiler.ccからつながってここにたどり着く。TurboFanと呼ばれている箇所</li>
</ul>
</li>
<li>runtime/runtime-*.cc
<ul>
<li>ランタイム関数が定義されている。ここもよく見る。</li>
</ul>
</li>
<li>builtins/builtin-*.cc
<ul>
<li>より高速なランタイム関数群。CodeStubAssembler(あとで解説)かAssemblerで記述されている。</li>
</ul>
</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/interpreter">interpreter</a>/*.cc
<ul>
<li>いわゆるIgnitionのコードがここに収まっている。</li>
</ul>
</li>
<li>ic/*.cc
<ul>
<li>Inline Caching周りの実装・ランタイムが格納されている。</li>
</ul>
</li>
</ul>
<h2>v8の内部実装</h2>
<p>ここからは実際に内部で使われている主要な機能を紹介していく。</p>
<h3>公開<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a></h3>
<h4>v8::HandleScope</h4>
<p>v8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>から割り当てられたオブジェクトを<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>の世界でも監視するための仮想スコープを生成する。<br/>
下の<code>v8::Local</code>で詳しく解説する。</p>
<h4>v8::Local</h4>
<p>おそらく最もよく見ることになるクラス。<br/>
v8は<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>を持っているが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>には<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>は無い。<br/>
代わりにRAII(Resource Acquisition Is Initialization)と呼ばれるリソースの確保・破棄をメモリと紐付けて行う方法が一般的である。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>にはデストラクタと呼ばれる、スタックに割り当てられたクラスがスコープを抜けて破棄される際に必ず呼ばれる関数があり、<br/>
そのクラスでポインタをラップすることでスコープを抜けた時に一緒にポインタを破棄するという使い方をよくする。いわゆるスマートポインタと呼ばれる機能である。<br/>
<code>v8::Local</code>はヒープに割り当てたオブジェクトを<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>の世界で監視するためのラッパクラスで、デストラクタが呼ばれれたタイミングで現在の<code>HandleScope</code>と共に削除される。</p>
<p>例.</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> test() {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope;
v8::Local<v8::Array> array = v8::Array::New(isolate, <span class="synConstant">3</span>);
...
}
</pre>
<p>一度<code>v8::HandleScope</code>が生成されると<code>v8::Local</code>はすべてその<code>v8::HandleScope</code>に割り当てられる。<br/>
そのため、test関数が終了したタイミングで<code>handle_scope</code>のデストラクタが呼び出されると、その<code>v8::HandleScope</code>に紐づくすべての<code>v8::Local</code>も同時に削除される。</p>
<h4>v8::Handle</h4>
<p><code>v8::Local</code>でラップされているが、実際に<code>v8::HandleScope</code>に紐付いているクラス。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>によってはこっちの<code>v8::Handle</code>を返すやつもあるが、まあ基本的には<code>v8::Local</code>と同じように使えば良い。</p>
<h4>v8::Isolate</h4>
<p>最初に説明すべきか悩んだが、一旦<code>v8::Local</code>を先に回した。<br/>
<code>v8::Isolate</code>はv8のコードベースを貫く基礎になる部分でかなり特殊な作りになっている。<br/>
もともとv8は<code>static</code>メソッドが非常に多く、マルチスレッドについてあまり考えていない作りになっていた。<br/>
まあ、これは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Chromium">Chromium</a>側でプロセス分離してv8を起動すればよかったので問題はなかったのだが。<br/>
さて、いざEmbedder側でマルチスレッド化しようとするとかなり問題を引き起こすことがわかった。<br/>
そのため、この<code>v8::Isolate</code>という仕組みを結構無理やり組み込んだ。</p>
<p><code>v8::Isolate</code>がどんなものかというと、Thread Local Storage(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>)に格納された巨大なオブジェクトになっており、<br/>
実行コンテキストに紐づくグローバルな情報をほぼ全て格納している。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>に格納されているのでスレッド毎に違う<code>v8::Isolate</code>を透過的に持つことが可能でEmbedder側はスレッドをあまり意識せずにコードを書くことが可能になっている。<br/>
内部で利用する様々なオブジェクト(<code>FixedArray</code>)やHidden Classを表す<code>Map</code>クラス等もこの<code>v8::Isolate</code>から生成している。<br/>
このクラスはほぼすべての箇所に渡されていて、<code>v8::Isolate</code>無しではコードを書くのは難しい状態になっている。</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%BE%E5%B5%AD">上記</a>のサンプルコードを再度使うと</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> test() {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope;
v8::Local<v8::Array> array = v8::Array::New(isolate, <span class="synConstant">3</span>);
...
}
</pre>
<p>この関数でも<code>v8::Isolate</code>を渡しているのがわかる。<br/>
<code>v8::Array::New</code>などとしているが実際に<code>Array</code>を生成しているのは<code>v8::Isoalte</code>である。<br/>
そのため、v8の内部ではあまりスレッドの競合について考える必要が無く、まあなかなか便利な仕組みである。</p>
<h3>v8::internal</h3>
<p>外部公開<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>以外のクラス等はすべて<code>v8::internal</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>に定義されている。<br/>
長いので<code>v8::i</code>に省略する。</p>
<h4>オブジェクトモデル</h4>
<p>v8は非常に特殊な作りになっており、<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>の中に独自のオブジェクトモデルを作り出している。<br/>
そのオブジェクトモデルは<code>src/objects.h</code>の冒頭コメントに記載されており、<br/>
それを簡略化して抽出するとこうなる。</p>
<ul>
<li>Object
<ul>
<li>Smi (immediate small integer)</li>
<li>HeapObject (superclass for everything allocated in the heap)
<ul>
<li>JSReceiver (suitable for property <a class="keyword" href="http://d.hatena.ne.jp/keyword/access">access</a>)
<ul>
<li>JSObject</li>
<li>JSProxy</li>
</ul>
</li>
<li>FixedArrayBase
<ul>
<li>ByteArray</li>
<li>BytecodeArray</li>
<li>FixedArray</li>
<li>FixedDoubleArray</li>
</ul>
</li>
<li>Name
<ul>
<li>String</li>
<li>Symbol</li>
</ul>
</li>
<li>HeapNumber</li>
<li>BigInt</li>
<li>Cell</li>
<li>PropertyCell</li>
<li>PropertyArray</li>
<li>Code</li>
<li>AbstractCode, a wrapper around Code or BytecodeArray</li>
<li>Map</li>
<li>Oddball</li>
<li>Foreign</li>
<li>SmallOrderedHashTable</li>
<li>SharedFunctionInfo</li>
<li>Struct</li>
<li>WeakCell</li>
<li>FeedbackVector</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><code>v8::i::Object</code>を基底クラスとしたオブジェクトツリーを作り出しており、<br/>
v8内部で使用されるほぼすべてのクラスは<code>v8::i::Object</code>を継承している。<a class="keyword" href="http://d.hatena.ne.jp/keyword/java">java</a>みたいだね。<br/>
こういうわかりやすい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%A8%A5%E9%A5%EB%A5%AD%A1%BC">ヒエラルキー</a>があるのでさぞ読みやすいかと思いきや、コードは非常に複雑で...まあ読みやすくはない。<br/>
v8はこのオブジェクトモデルをうまく機能させるために<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>のやり方に従わない。<br/>
どういうことかと言うとこれらのクラスはフィールドを<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>のクラスを通じて実装していない。<br/>
これらのクラスはあくまでメモリレイアウトを<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>の世界で表現するためだけに存在しており、すべてのフィールドは<code>this</code>ポインタに対して直接オフセット指定して取得している。<br/>
つまり<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>のオブジェクトレイアウトを無視して、自分たちで完全にメモリレイアウトをコン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>している。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>で表現すると以下のようになる。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">class</span> SomeObject {
Value* get_field1() {
<span class="synType">char</span>* self = <span class="synStatement">reinterpret_cast</span><<span class="synType">char</span>*>(<span class="synStatement">this</span>);
self += header_offset;
<span class="synStatement">return</span> Value::Cast(self);
}
<span class="synType">void</span> Initialize() {
<span class="synType">char</span>* self = <span class="synStatement">reinterpret_cast</span><<span class="synType">char</span>*>(<span class="synStatement">this</span>);
self += header_offset;
*self = Smi::Cast(<span class="synConstant">1</span>);
}
};
<span class="synType">static</span> <span class="synType">const</span> <span class="synType">size_t</span> OBJECT_SIZE = <span class="synStatement">sizeof</span>(<span class="synType">char</span>) * <span class="synConstant">32</span>;
SomeObject* object = <span class="synStatement">reinterpret_cast</span><SomeObject*>(malloc(OBJECT_SIZE));
object->Initialize();
object->get_filed1(); <span class="synComment">// 1</span>
</pre>
<p>この様に自分でフィールドのオフセットを制御している。<br/>
さて、このオブジェクト階層の頂点からチェックしていこう。<br/>
まず<code>v8::i::Object</code>は以下の2つに分岐する。</p>
<ul>
<li><b>Smi</b>
<ul>
<li>31ビット整数、ポインタアドレスの末尾は常に0</li>
<li>ヒープに確保されることはない</li>
</ul>
</li>
<li><b>HeapObject</b>
<ul>
<li>4バイトアライメントの32ビットポインタ。アドレス末尾は1</li>
<li>ヒープに確保されたオブジェクト。<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>の対象になる。</li>
</ul>
</li>
</ul>
<h4>HeapObject</h4>
<p>まずは<code>v8::i::HeapObject</code>から。<br/>
<code>v8::i::Object</code>は上述のように直接メモリレイアウトをいじっているので<br/>
HeapObjectを継承したオブジェクトはフィールドにアクセスする場合には以下のようなマクロを使っている。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#define FIELD_ADDR(p, offset) \</span>
<span class="synPreProc"> (</span><span class="synStatement">reinterpret_cast</span><span class="synPreProc"><byte*>(p) + offset - kHeapObjectTag)</span>
<span class="synPreProc">#define READ_FIELD(p, offset) \</span>
<span class="synPreProc"> (*</span><span class="synStatement">reinterpret_cast</span><span class="synPreProc"><Object* </span><span class="synType">const</span><span class="synPreProc">*>(FIELD_ADDR_CONST(p, offset)))</span>
<span class="synComment">// こちらはGCのConcurrentマーキングがONになっている場合にアトミックにフィールドを更新するために </span>
<span class="synComment">// AtomicWordを使ったバージョンと通常のものに分岐している。</span>
<span class="synPreProc">#ifdef v8_CONCURRENT_MARKING</span>
<span class="synPreProc">#define WRITE_FIELD(p, offset, value) \</span>
<span class="synPreProc"> base::Relaxed_Store( \</span>
<span class="synPreProc"> </span><span class="synStatement">reinterpret_cast</span><span class="synPreProc"><base::AtomicWord*>(FIELD_ADDR(p, offset)), \</span>
<span class="synPreProc"> </span><span class="synStatement">reinterpret_cast</span><span class="synPreProc"><base::AtomicWord>(value));</span>
<span class="synPreProc">#else</span>
<span class="synPreProc">#define WRITE_FIELD(p, offset, value) \</span>
<span class="synPreProc"> (*</span><span class="synStatement">reinterpret_cast</span><span class="synPreProc"><Object**>(FIELD_ADDR(p, offset)) = value)</span>
<span class="synPreProc">#endif</span>
SMI_ACCESSORS(FixedArrayBase, length, kLengthOffset)
<span class="synPreProc">#define SMI_ACCESSORS_CHECKED(holder, name, offset, condition) \</span>
<span class="synPreProc"> </span><span class="synType">int</span><span class="synPreProc"> holder::name() </span><span class="synType">const</span><span class="synPreProc"> { \</span>
<span class="synPreProc"> DCHECK(condition); \</span>
<span class="synPreProc"> Object* value = READ_FIELD(</span><span class="synStatement">this</span><span class="synPreProc">, offset); \</span>
<span class="synPreProc"> </span><span class="synStatement">return</span><span class="synPreProc"> Smi::ToInt(value); \</span>
<span class="synPreProc"> } \</span>
<span class="synPreProc"> </span><span class="synType">void</span><span class="synPreProc"> holder::set_##name(</span><span class="synType">int</span><span class="synPreProc"> value) { \</span>
<span class="synPreProc"> DCHECK(condition); \</span>
<span class="synPreProc"> WRITE_FIELD(</span><span class="synStatement">this</span><span class="synPreProc">, offset, Smi::FromInt(value)); \</span>
<span class="synPreProc"> }</span>
<span class="synComment">// 実際には以下のように展開される。</span>
<span class="synType">int</span> FixedArrayBase::length() <span class="synType">const</span> {
DCHECK(condition);
Object* value = (*<span class="synStatement">reinterpret_cast</span><Object* <span class="synType">const</span>*>(
<span class="synStatement">reinterpret_cast</span><<span class="synType">const</span> byte*>(<span class="synStatement">this</span>) + kLengthOffset - kHeapObjectTag)
<span class="synStatement">return</span> Smi::ToInt(value);
}
<span class="synType">int</span> FixedArrayBase::set_length(<span class="synType">int</span> value) <span class="synType">const</span> {
DCHECK(condition);
base::Relaxed_Store(
<span class="synStatement">reinterpret_cast</span><base::AtomicWord*>(
<span class="synStatement">reinterpret_cast</span><byte*>(<span class="synStatement">this</span>) + kLengthOffset - kHeapObjectTag);
<span class="synStatement">reinterpret_cast</span><base::AtomicWord>(Smi::FromInt(value)));
}
</pre>
<p>重要なのは、</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">reinterpret_cast</span><<span class="synType">const</span> byte*>(<span class="synStatement">this</span>) + kLengthOffset - kHeapObjectTag
</pre>
<p>の部分で、<code>this</code>ポインタに特定のフィールドのオフセットを足したあと、<code>kHeapObjectTag</code>を引いているのがわかる。<br/>
ちなみに<code>kHeapObjectTag</code>の定義は以下。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">const</span> <span class="synType">int</span> kHeapObjectTag = <span class="synConstant">1</span>
</pre>
<p>ただの1、つまりポインタアドレス末尾に1を立てるだけ。<br/>
<code>v8::i::HeapObject</code>は割り当て時に<code>kHeapObjectTag</code>分多めに確保してから割り当てる。<br/>
以下がサンプルコード</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant"><stdio.h></span>
<span class="synPreProc">#include </span><span class="synConstant"><stdlib.h></span>
<span class="synPreProc">#include </span><span class="synConstant"><iostream></span>
<span class="synType">const</span> <span class="synType">int</span> kHeapObjectTag = <span class="synConstant">1</span>;
<span class="synType">const</span> <span class="synType">int</span> kHeapObjectTagSize = <span class="synConstant">2</span>;
<span class="synType">const</span> <span class="synType">intptr_t</span> kHeapObjectTagMask = (<span class="synConstant">1</span> << kHeapObjectTagSize) - <span class="synConstant">1</span>;
<span class="synType">inline</span> <span class="synType">static</span> <span class="synType">bool</span> HasHeapObjectTag(<span class="synType">const</span> <span class="synType">char</span>* value) {
<span class="synStatement">return</span> ((<span class="synStatement">reinterpret_cast</span><<span class="synType">intptr_t</span>>(value) & kHeapObjectTagMask) ==
kHeapObjectTag);
}
<span class="synType">int</span> main() {
<span class="synType">auto</span> allocated = <span class="synStatement">reinterpret_cast</span><<span class="synType">char</span>*>(
malloc(<span class="synStatement">sizeof</span>(<span class="synType">char</span>) * (<span class="synConstant">2</span> + kHeapObjectTag)));
<span class="synType">auto</span> heap_object = allocated + kHeapObjectTag;
heap_object[<span class="synConstant">0</span>] = <span class="synConstant">'m'</span>;
heap_object[<span class="synConstant">1</span>] = <span class="synConstant">'v'</span>;
printf(<span class="synConstant">"</span><span class="synSpecial">%ld</span><span class="synConstant"> </span><span class="synSpecial">%ld</span><span class="synConstant"> </span><span class="synSpecial">%p</span><span class="synConstant"> </span><span class="synSpecial">%p</span><span class="synConstant"> </span><span class="synSpecial">%d\n</span><span class="synConstant">"</span>, <span class="synStatement">reinterpret_cast</span><<span class="synType">intptr_t</span>>(allocated),
<span class="synStatement">reinterpret_cast</span><<span class="synType">intptr_t</span>>(heap_object), allocated, heap_object,
HasHeapObjectTag(heap_object));
free(allocated);
}
</pre>
<p>実行すると私の環境では以下の結果になる。</p>
<p><code>140289524108464 140289524108465 0x7f97b3400cb0 0x7f97b3400cb1 1</code><br/>
見事にアドレスの末尾1が立っている。</p>
<p>また<code>v8::i::HeapObject</code>は自分自身の型を識別するためにHidden Classを表す<code>v8::Map</code>オブジェクトを先頭に持っている。</p>
<p>そのため<code>v8::i::HeapObject</code>もメモリレイアウトは以下のようになる。</p>
<pre class="code" data-lang="" data-unlink>+-----+-----------------------+--------+
| Map | Derived Object Header | values |
+-----+-----------------------+--------+</pre>
<p>必ず先頭に型を表す<code>v8::Map</code>を持っているため、そこを見れば<code>v8::i::HeapObject</code>の型がわかる。<br/>
また<code>Derived Object Header</code>と書かれている部分は継承先のオブジェクトによって異なる(<code>v8::i::FixedArray</code>ならlengthフィールドだったり)。</p>
<p>以下がMapとJSObjectを簡略化した表した<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>コード</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant"><stdio.h></span>
<span class="synPreProc">#include </span><span class="synConstant"><stdlib.h></span>
<span class="synPreProc">#include </span><span class="synConstant"><iostream></span>
<span class="synType">const</span> <span class="synType">int</span> kHeapObjectTag = <span class="synConstant">1</span>;
<span class="synType">const</span> <span class="synType">int</span> kHeapObjectTagSize = <span class="synConstant">2</span>;
<span class="synType">const</span> <span class="synType">intptr_t</span> kHeapObjectTagMask = (<span class="synConstant">1</span> << kHeapObjectTagSize) - <span class="synConstant">1</span>;
<span class="synType">inline</span> <span class="synType">static</span> <span class="synType">bool</span> HasHeapObjectTag(<span class="synType">const</span> <span class="synType">char</span>* value) {
<span class="synStatement">return</span> ((<span class="synStatement">reinterpret_cast</span><<span class="synType">intptr_t</span>>(value) & kHeapObjectTagMask) ==
kHeapObjectTag);
}
<span class="synType">class</span> Map {
<span class="synStatement">public</span>:
<span class="synType">enum</span> InstanceType {
JS_OBJECT,
JS_ARRAY,
JS_STRING
};
<span class="synType">void</span> set_instance_type(InstanceType instance_type) {
instance_type_ = instance_type;
}
InstanceType instance_type() {
<span class="synStatement">return</span> instance_type_;
}
<span class="synStatement">private</span>:
InstanceType instance_type_;
};
<span class="synType">const</span> <span class="synType">int</span> kHeaderSize = <span class="synStatement">sizeof</span>(Map);
<span class="synType">typedef</span> <span class="synType">char</span> byte;
<span class="synType">typedef</span> <span class="synType">char</span>* Address;
<span class="synType">class</span> HeapObject {
<span class="synStatement">public</span>:
<span class="synType">char</span> value() {<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><Address>(<span class="synStatement">this</span>)[<span class="synConstant">0</span>];}
Map::InstanceType instance_type() {
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><Map*>(
<span class="synStatement">reinterpret_cast</span><Address>(<span class="synStatement">this</span>) - kHeaderSize)->instance_type();
}
<span class="synType">void</span> Free() {
<span class="synType">auto</span> top = <span class="synStatement">reinterpret_cast</span><Address>(<span class="synStatement">this</span>) - kHeaderSize - kHeapObjectTag;
free(top);
}
<span class="synStatement">protected</span>:
<span class="synType">static</span> Address NewType(Map::InstanceType instance_type, <span class="synType">size_t</span> size) {
<span class="synType">auto</span> allocated = <span class="synStatement">reinterpret_cast</span><Address>(
malloc(<span class="synStatement">sizeof</span>(byte) * (size + kHeaderSize + kHeapObjectTag)));
<span class="synType">auto</span> map = <span class="synStatement">reinterpret_cast</span><Map*>(allocated);
map->set_instance_type(instance_type);
<span class="synStatement">return</span> allocated + kHeaderSize + kHeapObjectTag;
}
};
<span class="synType">class</span> JSObject: <span class="synStatement">public</span> HeapObject {
<span class="synStatement">public</span>:
<span class="synType">static</span> JSObject* New() {
<span class="synType">auto</span> a = NewType(Map::JS_OBJECT, <span class="synConstant">1</span>);
a[<span class="synConstant">0</span>] = <span class="synConstant">'o'</span>;
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><JSObject*>(a);
}
};
<span class="synType">class</span> JSArray: <span class="synStatement">public</span> JSObject {
<span class="synStatement">public</span>:
<span class="synType">static</span> JSArray* New() {
<span class="synType">auto</span> a = NewType(Map::JS_ARRAY, <span class="synConstant">1</span>);
a[<span class="synConstant">0</span>] = <span class="synConstant">'a'</span>;
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><JSArray*>(a);
}
};
<span class="synType">class</span> JSString: <span class="synStatement">public</span> JSObject {
<span class="synStatement">public</span>:
<span class="synType">static</span> JSString* New() {
<span class="synType">auto</span> a = NewType(Map::JS_STRING, <span class="synConstant">1</span>);
a[<span class="synConstant">0</span>] = <span class="synConstant">'s'</span>;
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><JSString*>(a);
}
};
<span class="synType">int</span> main() {
JSObject* objects[] = {
JSObject::New(),
JSArray::New(),
JSString::New()
};
<span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i < <span class="synConstant">3</span>; i++) {
<span class="synType">auto</span> o = objects[i];
<span class="synStatement">switch</span> (o->instance_type()) {
<span class="synStatement">case</span> Map::JS_OBJECT:
printf(<span class="synConstant">"JSObject => </span><span class="synSpecial">%c\n</span><span class="synConstant">"</span>, o->value());
<span class="synStatement">break</span>;
<span class="synStatement">case</span> Map::JS_ARRAY:
printf(<span class="synConstant">"JSArray => </span><span class="synSpecial">%c\n</span><span class="synConstant">"</span>, o->value());
<span class="synStatement">break</span>;
<span class="synStatement">case</span> Map::JS_STRING:
printf(<span class="synConstant">"JSString => </span><span class="synSpecial">%c\n</span><span class="synConstant">"</span>, o->value());
<span class="synStatement">break</span>;
}
}
<span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i < <span class="synConstant">3</span>; i++) {
objects[i]->Free();
}
}
</pre>
<p>実行すると<code>JSObject => o, JSArray => a, JSString => s</code>が出力される。<br/>
かなり例が巨大になってしまったが、これでヒープに割り当てられたオブジェクトの型を正確に分類できているのがわかると思う。</p>
<p>さらにSmiが何かを説明しよう。</p>
<h4>Smi</h4>
<p>SmiはSmall Integerの略で、31ビットまでの整数を直接ポインタ領域に確保する。<br/>
また<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でも同じ手法が取られているようだ。<br/>
通常ポインタはそれだけで32ビットCPUなら4バイト、64CPUなら8バイトを使う。<br/>
つまり31ビットまでの整数ならば直接ポインタの代わりに格納できるわけだ。<br/>
このようにポインタ領域に確保してヒープを使わないことでメモリ節約・高速化の両方を成し遂げている。</p>
<p>さてどのように格納するかというと直接<code>int</code>値を<code>reinterpret_cast<T*></code>してポインタに変換してしまう。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">class</span> Smi {
<span class="synStatement">public</span>:
<span class="synType">static</span> Smi* FromInt(<span class="synType">int</span> value) {
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><Smi*>(value);
}
<span class="synType">int</span> value() {
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><<span class="synType">intptr_t</span>>(<span class="synStatement">this</span>);
}
};
Smi* NewSmi(<span class="synType">int</span> value) {
<span class="synStatement">return</span> Smi::FromInt(value);
}
<span class="synType">int</span> main() {
printf(<span class="synConstant">"</span><span class="synSpecial">%d</span><span class="synConstant"> </span><span class="synSpecial">%d\n</span><span class="synConstant">"</span>, NewSmi(<span class="synConstant">120</span>)->value(), NewSmi(<span class="synConstant">110</span>)->value());
<span class="synComment">// out 120 110</span>
}
</pre>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%BE%E5%B5%AD">上記</a>の例を見ていただければわかるだろう。<br/>
さらに<code>v8::i::HeapObject</code>の場合には下位1ビットを立てたが、Smiの場合には末尾0をタグとして利用しているので、<br/>
キャストしただけで直接数値演算が可能である。そのためオーバーヘッドもない。<br/>
また64ビットCPUならばポインタは64ビットなのでより大きな整数が格納できるが、32ビットとの互換性のため31ビットの領域しか使用しない。<br/>
64ビットの場合のビットレイアウト</p>
<pre class="code" data-lang="" data-unlink>+----------------+-----------------+------------------+
| 31 bit integer | 32 bit zero bit | 1 bit smi tag(0) |
+----------------+-----------------+------------------+</pre>
<p>単純に下位32ビットをゼロ埋めするだけ。</p>
<h4>JSReceiver</h4>
<p>プロパティアクセス可能なJSオブジェクトを表す。つまりほぼすべてのJSオブジェクトを表す。<br/>
<code>JSReceiver</code>の下には上述の<code>JSObject</code>が存在し、これがjsの<code>Object</code>クラスを表す。<br/>
さらに<code>JSObject</code>の下には以下のような階層がある。</p>
<ul>
<li>JSArray</li>
<li>JSArrayBuffer</li>
<li>JSArrayBufferView
<ul>
<li>JSTypedArray</li>
<li>JSDataView</li>
</ul>
</li>
<li>JSBoundFunction</li>
<li>JSCollection
<ul>
<li>JSSet</li>
<li>JSMap</li>
</ul>
</li>
<li>JSStringIterator</li>
<li>JSSetIterator</li>
<li>JSMapIterator</li>
<li>JSWeakCollection
<ul>
<li>JSWeakMap</li>
<li>JSWeakSet</li>
</ul>
</li>
<li>JSRegExp</li>
<li>JSFunction</li>
<li>JSGeneratorObject</li>
<li>JSGlobalObject</li>
<li>JSGlobalProxy</li>
<li>JSValue
<ul>
<li>JSDate</li>
</ul>
</li>
<li>JSMessageObject</li>
<li>JSModuleNamespace</li>
<li>WasmInstanceObject</li>
<li>WasmMemoryObject</li>
<li>WasmModuleObject</li>
<li>WasmTableObject</li>
</ul>
<p>これらの<code>v8::i::JS~</code>クラスは我々Embedderが<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>経由で利用する<code>v8::String</code>や<code>v8::Array</code>等のクラスの本当の姿で、<br/>
<code>v8::String</code>等のクラスはただのラッパークラスでしかない。<br/>
実際の実装はすべて<code>v8::i::JS~</code>クラスが持っている。</p>
<h4>FixedArrayBase</h4>
<p>v8で頻出するクラスである<code>v8::i::FixedArray</code>のベースとなる実装。<br/>
v8は内部のいたるところでこの固定長配列を利用しており、何度もお目にかかることになる。
<code>v8::i::FixedArray</code>は更に以下の様な階層を持っている。</p>
<ul>
<li>DescriptorArray</li>
<li>FrameArray</li>
<li>HashTable
<ul>
<li>Dictionary</li>
<li>StringTable</li>
<li>StringSet</li>
<li>CompilationCacheTable</li>
<li>MapCache</li>
</ul>
</li>
<li>OrderedHashTable
<ul>
<li>OrderedHashSet</li>
<li>OrderedHashMap</li>
</ul>
</li>
<li>Context</li>
<li>FeedbackMetadata</li>
<li>TemplateList</li>
<li>TransitionArray</li>
<li>ScopeInfo</li>
<li>ModuleInfo</li>
<li>ScriptContextTable</li>
<li>WeakFixedArray</li>
<li>WasmSharedModuleData</li>
<li>WasmCompiledModule</li>
</ul>
<p>特に<code>v8::i::DescriptorArray</code>はプロパティ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%B9%A5%AF%A5%EA%A5%D7%A5%BF">ディスクリプタ</a>を格納している配列で、<br/>
プロパティアクセスの際によく取得する。</p>
<p>オブジェクトに関しては1記事では限界があるので次に進む。<br/>
次はv8内部のコード生成部分に関して</p>
<h3>CodeGeneration of v8</h3>
<p>v8はいくつかのコード生成方法がある。<br/>
v8-devグループでスライドがあったのでそれを参考にまとめると、</p>
<ul>
<li><b><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a></b>
<ul>
<li>cons
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>から呼び出すのが高速</li>
<li>実行も高速</li>
<li>比較的読みやすい</li>
<li>拡張、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>がやりやすい</li>
<li>メモリがすべてのIsolateを通して共有されるためOSがメモリ不足になった場合に破棄される。</li>
</ul>
</li>
<li>pros
<ul>
<li>JSから呼び出すのが遅い</li>
</ul>
</li>
<li>summary
<ul>
<li>巨大な関数やパフォーマンスクリティカルではない箇所には良い</li>
</ul>
</li>
</ul>
</li>
<li><b><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>(External Reference)</b>
<ul>
<li>runtimeではなく直接<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>の関数を登録する方式で登録した関数しか呼び出せないが直接C関数として呼べるので高速</li>
<li>cons
<ul>
<li>JSから呼び出すのが通常の<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>と比べて早い</li>
</ul>
</li>
<li>pros
<ul>
<li>allocationができない</li>
<li>初期化が結構面倒</li>
</ul>
</li>
<li>summary
<ul>
<li>頻繁に呼ばれるような小さな関数には便利</li>
</ul>
</li>
</ul>
</li>
<li><b>CodeStubAssembler(CSA)</b>
<ul>
<li>pros
<ul>
<li>JSから呼び出すのが早い、JSに戻るのも早い</li>
<li>実行も高速</li>
</ul>
</li>
<li>cons
<ul>
<li>冗長</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>が厳しい</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>から呼び出すと遅い</li>
<li>isolate毎にメモリを圧迫する</li>
</ul>
</li>
<li>summary
<ul>
<li>小さな関数やパフォーマンスが必要な箇所に向いている</li>
</ul>
</li>
</ul>
</li>
<li><b><a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a></b>
<ul>
<li>pros
<ul>
<li>JSから呼び出すのが高速</li>
</ul>
</li>
<li>cons
<ul>
<li>意図せぬ型情報の汚染が起こりうる。パフォーマンス問題も起きがち。</li>
<li>セキュア(getter関数を意図せず呼び出したり、monky-patchingされてしまったり)に作るのが非常に難しい</li>
<li>Compiler組み込み関数やruntime関数呼び出しが必要で、これが高コスト</li>
</ul>
</li>
<li>summary
<ul>
<li>使わないこと!</li>
</ul>
</li>
</ul>
</li>
<li><b><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a></b>
<ul>
<li>pros
<ul>
<li>とにかく早い</li>
<li>コールスタックを操作できる</li>
</ul>
</li>
<li>cons
<ul>
<li>メンテナンスコストが高すぎる</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>毎に必要で大変</li>
</ul>
</li>
<li>summary
<ul>
<li>使わないこと!</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>という感じでコードを書く手段が<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>, <a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>(Ex)、CSA、<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>の約5種類ある。<br/>
が<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>はすでに新規で書かれることはほぼない。<br/>
以前はv8は<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>でランタイムを書いていた時期があったがほぼなくなった。<br/>
現在は低速でも問題ない箇所や新たに実装されるSpecに関しては<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>で、それ以外のパフォーマンスが必要な箇所に関してはCSAで記述される。</p>
<p>さて次はCSAを解説する。</p>
<h3>CodeStubAssembler (CSA)</h3>
<p>v8の内部で利用される<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>言語。<br/>
実はv8ではすでに新しく<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA%B8%C0%B8%EC">アセンブリ言語</a>を記述することはあまりない。<br/>
その代わり<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>を出力するCSAを記述することでよりメンテナンス性が高く高速なコードを出力することができる。</p>
<p>以下にCSAの例を示す。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> fibonacci(num)<span class="synIdentifier">{</span>
<span class="synIdentifier">var</span> a = 1, b = 0, temp;
<span class="synStatement">const</span> result = <span class="synIdentifier">[]</span>;
<span class="synStatement">while</span> (num >= 0)<span class="synIdentifier">{</span>
result.push(a);
temp = a;
a = a + b;
b = temp;
num--;
<span class="synIdentifier">}</span>
<span class="synStatement">return</span> result;
<span class="synIdentifier">}</span>
</pre>
<p><a href="https://webkaru.net/clang/fibonacci-number/">C言語入門 - フィボナッチ数の計算 - サンプルプログラム - Webkaru</a></p>
<p>このフィボナッチ数を計算して配列に格納する<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>関数をCSAに変換すると以下のようなコードになる。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>TNode<JSArray> Fibonacci(TNode<Context> context) {
TVARIABLE(var_a, MachineType::PointerRepresentation(), IntPtrConstant(<span class="synConstant">0</span>));
TVARIABLE(var_b, MachineType::PointerRepresentation(), IntPtrConstant(<span class="synConstant">1</span>));
TVARIABLE(var_temp, MachineType::PointerRepresentation());
TVARIABLE(var_index, MachineType::PointerRepresentation());
Node* fixed_array = AllocateFixedArray(PACKED_ELEMENTS, IntPtrConstant(<span class="synConstant">11</span>),
INTPTR_PARAMETERS, kAllowLargeObjectAllocation)
Label loop(<span class="synStatement">this</span>), after_loop(<span class="synStatement">this</span>);
Branch(IntPtrGreaterThan(IntPtrConstant(<span class="synConstant">100</span>), var_index), &loop, &after_loop);
BIND(&loop);
{
StoreFixedArrayElement(fixed_array, SmiTag(var_index), var_a,
SKIP_WRITE_BARRIER);
var_temp.Bind(var_a);
var_a.Bind(IntPtrAdd(var_a, var_b));
var_b.Bind(var_temp);
Increment(&var_index, <span class="synConstant">1</span>);
Branch(IntPtrGreaterThan(IntPtrConstant(<span class="synConstant">100</span>), var_index),
&loop, &after_loop);
}
BIND(&after_loop);
Node* native_context = LoadNativeContext(context);
Node* array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
Node* array = AllocateUninitializedJSArrayWithoutElements(
array_map, SmiConstant(<span class="synConstant">12</span>), <span class="synConstant">nullptr</span>);
StoreObjectField(array, JSArray::kElementsOffset, fixed_array);
<span class="synStatement">return</span> array;
}
</pre>
<p>ある程度抽象化されているとは言えどうしても冗長なコードになってしまうが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>よりは遥かに読みやすいのではないだろうか?<br/>
これらは直接値を出力するのではなく、実行予定ツリーを組み立ててからツリーを巡回し、一つ一つの命令が自動的に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>に置き換えられる。<br/>
ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>していないのでもしかしたら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーが出るかもしれない。</p>
<p>長くなりすぎたので内部の解説はこのあたりで終了する。</p>
<h2>コードを読む</h2>
<p>v8のコードを読むのは非常に面倒だが、いくつか方法がある。<br/>
まずはそれぞれのエディタのコードジャンプを使う。<br/>
まあこれでとりあえず定義とかはある程度見れるはず。<br/>
ただしv8は大量のマクロを使っており、クラスの関数定義すらマクロで行う場合があるので、どうしても定義が見つからない場合には<code>find | grep</code>も駆使したほうが良い。<br/>
またマクロで文字列結合されている場合もあるので、<code>find src | grep '##FooBar'</code>みたいな感じで<code>##</code>を使って<code>grep</code>する必要があるかもしれない。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>する</h2>
<p>コードを読んでも実行時の状態がわからなかったり呼び出し階層がわからない場合もあるので、ランタイムでは以下の方法でチェックすると良い。</p>
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>
<ul>
<li><code>src/base/debug/stack_trace.h</code>に<code>StackTrace</code>クラスがあるので呼び出された箇所で<code>StackTrace st;st.Print()</code>を呼ぶと良い。</li>
<li>また<code>v8::Object</code>クラスを継承しているオブジェクトはもれなく<code>Print</code>メソッドを持っているので、<code>a->Print()</code>を呼ぶと中身が見れる。</li>
</ul>
</li>
<li>CSA
<ul>
<li><code>Print()</code>関数が<code>CodeStubAssembler</code>に定義されているので、そこに<code>Node*</code>を渡すことで<code>a->Print()</code>を行うコードを出力してくれる。ただし、<code>IntPtrT</code>を渡すと落ちるので注意。その場合は<code>SmiTag</code>すれば良い。</li>
</ul>
</li>
</ul>
<p>あとはこれを繰り返すしかない。</p>
<h2>まとめ</h2>
<p>なんか本当にまとまりのない文章になってしまった。申し訳ない。<br/>
まあつまりv8のコード見るのも書くのも大変だってことです。</p>
brn_take
mallocを再実装した話
hatenablog://entry/8599973812325315436
2017-12-12T12:10:31+09:00
2017-12-12T12:10:31+09:00 C++ AdventCalendarの12日目 普段私はWEBのフロントエンドを仕事にしている。 つまり使う言語はjavascript/typescript等のScript言語だ。 ただ前職や趣味、OSS等でC++によく触っていたので昔実装したmallocの話をすることにした。 mallocとは mallocとはC言語のstdlib.hに含まれるメモリ割り当て関数のことで、 C++やその他の多くの言語で内部的に利用されている。 ヒープを割り当てる方法はいくつかあるが、このmallocがもっともメジャーといえるだろう。 mallocを再実装した 今回はmallocを自分で再実装してちょっと早くし…
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a> AdventCalendarの12日目</p>
<p>普段私はWEBのフロントエンドを仕事にしている。<br/>
つまり使う言語は<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>/typescript等のScript言語だ。<br/>
ただ前職や趣味、<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>等で<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>によく触っていたので昔実装した<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>の話をすることにした。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>とは</h2>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>とは<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>のstdlib.hに含まれるメモリ割り当て関数のことで、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>やその他の多くの言語で内部的に利用されている。<br/>
ヒープを割り当てる方法はいくつかあるが、この<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>がもっともメジャーといえるだろう。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>を再実装した</h2>
<p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>を自分で再実装してちょっと早くした話を書く。<br/>
再実装した理由は色々あるが最も大きな理由はただの好奇心。<br/>
<code>yatsc</code>という<code>typescript</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>で書こうと思って実装を始めたときに作った。<br/>
ただし<code>yatsc</code>自体は未完で飽きて終了。<br/>
作ってみて思ったが、実際にプロダクトで使う場合はシンプルなメモリプールとか作ったほうがよっぽど利口だと思う。</p>
<h2>Inside <a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a></h2>
<p><code>malloc</code>関数は現在のモダンな環境では<a class="keyword" href="http://d.hatena.ne.jp/keyword/mmap">mmap</a>を利用してメモリを確保、それを各チャンクに分けて管理しているはず。<br/>
細かいことは以下のスライドに詳しい。</p>
<p><iframe src="https://www.slideshare.net/slideshow/embed_code/key/n9Ri2lLdM9bY7L" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/kosaki55tea/glibc-malloc" title="Glibc malloc internal" target="_blank">Glibc malloc internal</a> </strong> from <strong><a href="https://www.slideshare.net/kosaki55tea" target="_blank">Motohiro KOSAKI</a></strong> </div><cite class="hatena-citation"><a href="https://www.slideshare.net/kosaki55tea/glibc-malloc">www.slideshare.net</a></cite></p>
<h2>Direction of implementations</h2>
<p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>のTLMallocの実装と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BE%E5%B5%AD">上記</a>のスライドをベースにjust-fitアロケータを作成することにした。</p>
<p>基本的な構成は以下の通り。<br/>
メインスレッドにある<code>CentralArena</code>が各スレッドの<code>LocalArena</code>を管理しており、<br/>
それぞれのArenaは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>(Thread Local Storage)に格納されている。<br/>
また、各<code>LocalArena</code>はスレッドが終了すると、free-listに格納されて空き状態となり、<br/>
新たにスレッドが生成された場合に再利用される。<br/>
LocalArena自体もlinked-listになっており、<code>next</code>ポインタで各スレッドのLocalArenaはつながっている。<br/>
さらに<code>LocalArena</code>は内部にChunkを持ち、それらは<code>ChunkHeader</code>と後続の複数の<code>HeapHeader</code>によって管理されている。</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>にヒープを格納することでスレッド毎にヒープ領域を分割し、スレッド競合を防ぎつつjust-fitアロケータでメモリの無駄も防ぐ。<br/>
というのが、この<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>実装のキーポイントになる。<br/>
ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>を実装するのは面倒だったので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>実装をコピーした。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>のコールバックまで実装されており非常に便利。サンキュー<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a></p>
<p>以下は<code>CentralArena</code>から<code>LocalArena</code>までの図</p>
<pre class="code" data-lang="" data-unlink>+----------------+ +---------------+
| CentralArena |--| Main Thread |
+----------------+ +---------------+
| | +------------+ +--------------+
| +--| Thread-1 |--| LocalArena |
| | +------------+ +--------------+
| | +------------+ +--------------+
| +--| Thread-2 |--| LocalArena |
| | +------------+ +--------------+
| | +------------+ +--------------+
| +--| Thread-3 |--| LocalArena |
| +------------+ +--------------+
+------------+ +--------------+ +--------------+
| free list |--| LocalArena |--| LocalArena |-- ...
+------------+ +--------------+ +--------------+</pre>
<h4>CentralArena</h4>
<p>CentralArenaはメモリの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%ED%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">アロケーション</a>を要求されると、
<code>GetLocalArena</code>関数を呼び出して、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tls">Tls</a>からLocalArenaを取り出すか空いているLocalArenaをfree-listから探し出して割り当てる。</p>
<p>以下のような感じでAtomicにlockを行ってスレッド競合を防いでいる。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>LocalArena* CentralArena::FindUnlockedArena() YATSC_NOEXCEPT {
<span class="synComment">// If released_arena_count_ is zero,</span>
<span class="synComment">// that mean free arena is not exists.</span>
<span class="synComment">// So we simply allocate new LocalArena.</span>
<span class="synStatement">if</span> (released_arena_count_.load() == <span class="synConstant">0</span>) {
<span class="synStatement">return</span> StoreNewLocalArena();
}
LocalArena* current = local_arena_.load(std::memory_order_relaxed);
<span class="synStatement">while</span> (current != <span class="synConstant">nullptr</span>) {
<span class="synComment">// Released arena is found.</span>
<span class="synStatement">if</span> (current->AcquireLock()) {
<span class="synStatement">return</span> current;
}
current = current->next();
}
<span class="synComment">// If released arena is not found,</span>
<span class="synComment">// allocate new LocalArena.</span>
<span class="synStatement">return</span> StoreNewLocalArena();
}
</pre>
<p>ちなみにAcquireLockは以下のような感じ。Atomicにロックしている。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>YATSC_INLINE <span class="synType">bool</span> AcquireLock() YATSC_NOEXCEPT {
<span class="synType">bool</span> ret = !lock_.test_and_set();
<span class="synStatement">if</span> (ret) {
central_arena_->NotifyLockAcquired();
}
<span class="synStatement">return</span> ret;
}
</pre>
<p>さて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BE%E5%B5%AD">上記</a>の<code>FindUnlockedArena</code>を使って空いたLocalArenaを探し出すのに失敗した場合、
そこで初めてメモリを割り当てることになる。<br/>
このメモリ割り当ては以下のような感じで行う。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>LocalArena* CentralArena::StoreNewLocalArena() YATSC_NOEXCEPT {
Byte* block = <span class="synStatement">reinterpret_cast</span><Byte*>(
GetInternalHeap(<span class="synStatement">sizeof</span>(LocalArena) + <span class="synStatement">sizeof</span>(InternalHeap)));
LocalArena* arena = <span class="synStatement">new</span> (block) LocalArena(
<span class="synStatement">this</span>, <span class="synStatement">new</span>(block + <span class="synStatement">sizeof</span>(LocalArena)) InternalHeap(
block + <span class="synStatement">sizeof</span>(LocalArena) + <span class="synStatement">sizeof</span>(InternalHeap)));
arena->AcquireLock();
LocalArena* arena_head = local_arena_.load(std::memory_order_acquire);
<span class="synComment">// Run CAS operation.</span>
<span class="synComment">// This case ABA problem is not the matter,</span>
<span class="synComment">// because the local_arena_ allowed only one-way operation</span>
<span class="synComment">// that is append new LocalArena.</span>
<span class="synComment">// So head of local_arena_ is change only when new local arena is appended.</span>
<span class="synStatement">do</span> {
arena->set_next(arena_head);
} <span class="synStatement">while</span> (!local_arena_.compare_exchange_weak(arena_head, arena));
<span class="synStatement">return</span> arena;
}
<span class="synComment">// Get heap space that used to allocate LocalArena and ChunkHeader.</span>
YATSC_INLINE <span class="synType">void</span>* CentralArena::GetInternalHeap(<span class="synType">size_t</span> additional) {
<span class="synStatement">return</span> VirtualHeapAllocator::Map(
<span class="synConstant">nullptr</span>, <span class="synStatement">sizeof</span>(ChunkHeader) * kChunkHeaderAllocatableCount + additional,
VirtualHeapAllocator::Prot::WRITE | VirtualHeapAllocator::Prot::READ,
VirtualHeapAllocator::Flags::ANONYMOUS
| VirtualHeapAllocator::Flags::PRIVATE);
}
</pre>
<p>解説すると、まず最初に<code>VirtualHeapAllocator</code>を利用してメモリを割り当てる。<br/>
この<code>VirtualHeapAllocator</code>は<code>mmap</code>のプラットフォーム毎の差分を吸収したクラス。<br/>
この<code>VirtualHeapAllocator</code>から<code>LocalArena</code>と<code>InternalHeap</code>という内部向けの管理ヘッダ分のメモリを割り当てる。<br/>
それをinplacement newを利用してLocalArenaに割り当てる。<br/>
つまりLocalArenaは以下のようなメモリレイアウトになる。</p>
<pre class="code" data-lang="" data-unlink> sizeof(InternalHeap) + sizeof(LocalArena)
+-------------------------------+ +--------------+
| InternalHeap(hidden header) |---| LocalArena |
+-------------------------------+ +--------------+</pre>
<p>あとは割り当てた<code>LocalArena</code>のロックを獲得して、local-arenaのリストに格納する。<br/>
このリストに追加するのも当然atomicに行わなければならないので、CAS(Compare And Swap)を利用してリストに追加する。<br/>
CASを使うとABA問題を気にしなければならないが、コメントにある通りlocal_arena_のリストは追加しか行わないのでABA問題は気にしなくて良い。<br/>
これでめでたくCentralArenaからロックフリーでLocalArenaを確保することに成功した。<br/>
次はLocalArenaを実装する。</p>
<h4>LocalArena</h4>
<p><code>LocalArena</code>こそがメモリ割り当てのコアになっていて、この部分が一番面倒くさい。<br/>
<code>LocalArena</code>はメモリ割り当て要求を受けると<code>small_bin_</code>とよばれる小さなメモリ専用のツリーにメモリを割り当てていく。<br/>
今回はこのツリーはRedBlackTreeを利用した。<br/>
この<code>small_bin_</code>に要求メモリサイズ毎のリストを作りそこに割り当てていく。</p>
<pre class="code" data-lang="" data-unlink>+--------------+
| LocalArena |
+--------------+
|
| +--------------+
+--------| small_bin_ |
+--------------+
|
+-----------------------+
| |
+-------+ +-------+
| 1byte | | 2byte |
+-------+ +-------+
| |
+-----------+ +-----------+
| | | |
+-------+ +-------+ +-------+ +-------+
| 3byte | | 4byte | | 5byte | | 6byte |
+-------+ +-------+ +-------+ +-------+</pre>
<p>こんな感じでsmall_bin_には各サイズ毎のchunkのリストが格納される。<br/>
これでjust-fitなアロケータになる。<br/>
ただし面倒だったのは、この<code>small_bin_</code>をstd::mapとかで実装するのはちょっと難しく、<br/>
結局自分でRBTreeを実装する羽目になった。<br/>
何故かと言うと、今はメモリ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%ED%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">アロケーション</a>の途中なのでヒープから値を確保することは不可能で、<br/>
各Chunkに追加のメモリ割り当てで格納できるコンテナが必要だったからである。<br/>
自分で実装したRBTreeは<code>IntrusiveRBTree</code>というクラスで、ある条件を満たせばヒープからメモリの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%ED%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">アロケーション</a>をしなくてもツリーを構成することができる。
その条件とは格納される値自身がコンテナの実装をしていること。
つまり格納される値が<code>RbTreeNode</code>というRedBlackTreeの<code>Node</code>を継承していれば、<br/>
RBTree自身はそれをつなぎ合わせる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>の実装のみでよく、<br/>
メモリ割り当てを必要としないで済む。</p>
<p>ただし、<code>small_bin_</code>は名の通り小さなメモリのみを格納する場所なので、<code>64KB</code>を制限とし、<br/>
それを超える場合には直接<code>CentralArena</code>から巨大なメモリ領域を<code>mmap</code>で割り当てる。<br/>
そのときには<code>SpinLock</code>でロックをかけるので当然遅くなる。</p>
<p>これでjust-fitなbinが実装できた。<br/>
さて<code>small_bin_</code>に格納されるメモリを実装していく。<br/>
<code>small_bin_</code>に格納されるメモリは<code>ChunkHeader</code>という管理ヘッダと実際に値を割り当てる後続のブロックで構成される。</p>
<h4>ChunkHeader</h4>
<p><code>ChunkHeader</code>クラスは実際に値を割り当てるヒープを管理している。<br/>
<code>ChunkHeader</code>の後ろには各Heapを管理するHeapHeaderがつながっている。
確保されているヒープは<code>heap_list_</code>に格納され、<br/>
空いているヒープは<code>free_list_</code>に格納される。</p>
<pre class="code" data-lang="" data-unlink>+---------------+
| ChunkHeader |
+---------------+
| |
| +--------------+ +------------+ +------------+ +------------+
| | heap_list_ |--| HeapHeader |--| HeapHeader |--| HeapHeader |
| +--------------+ +------------+ +------------+ +------------+
|
|
+--------------+ +------------+ +------------+ +------------+
| free_list_ |--| HeapHeader |--| HeapHeader |--| HeapHeader |
+--------------+ +------------+ +------------+ +------------+</pre>
<p>そしてChunkHeaderはメモリ確保の要求が来ると、最初に<code>free_list_</code>を探索する。<br/>
もし<code>free_list_</code>にHeapHeaderがあればそれを<code>heap_list_</code>にAtomicに繋いで返す。<br/>
空きがなければ、最後につながれた<code>HeapHeader</code>の使用量を確認して、まだ格納できるのであれば<code>HeapHeader</code>に値を追加する。<br/>
もし<code>HeapHeader</code>に空きが無ければ、新たに<code>HeapHeader</code>を割り当てる。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>YATSC_INLINE <span class="synType">void</span>* ChunkHeader::Distribute() {
<span class="synType">auto</span> free_list_head = free_list_.load(std::memory_order_acquire);
<span class="synStatement">if</span> (free_list_head == <span class="synConstant">nullptr</span>) {
<span class="synComment">// If heap is not filled.</span>
<span class="synStatement">if</span> (heap_list_->used() < max_allocatable_size_) {
<span class="synComment">// Calc next positon.</span>
Byte* block = <span class="synStatement">reinterpret_cast</span><Byte*>(heap_list_) + heap_list_->used();
<span class="synComment">// Update heap used value.</span>
heap_list_->set_used(heap_list_->used() + size_class_);
<span class="synStatement">return</span> block;
} <span class="synStatement">else</span> {
<span class="synComment">// If heap is exhausted allocate new memory.</span>
Byte* block = InitHeap(AllocateBlock());
<span class="synType">auto</span> heap_header = <span class="synStatement">reinterpret_cast</span><HeapHeader*>(
block - <span class="synStatement">sizeof</span>(HeapHeader));
heap_header->set_used(heap_header->used() + size_class_);
<span class="synStatement">return</span> block;
}
}
<span class="synComment">// Swap free list.</span>
<span class="synStatement">while</span> (!free_list_.compare_exchange_weak(
free_list_head, free_list_head->next())) {}
<span class="synStatement">return</span> <span class="synStatement">reinterpret_cast</span><<span class="synType">void</span>*>(free_list_head);
}
</pre>
<h4>HeapHeader</h4>
<p><code>HeapHeader</code>クラスは実際にヒープの値を直接管理しているクラス。<br/>
この<code>HeapHeader</code>は<code>ChunkHeader</code>から割り当てられる、1 MBでアライメントされたメモリブロックとなっている。<br/>
なぜ1 MBでアライメントするかというと、メモリの節約のために<code>HeapHeader</code>が確保しているメモリブロックは管理ヘッダを持っていない。<br/>
本来はメモリを削除する場合に<code>HeapHeader</code>を参照するための情報が必要なのだが、1 MBでアライメントすることによって、<br/>
メモリの下位16ビットをマスクするだけで、<code>HeapHeader</code>の先頭アドレスを取得することができるようになる。<br/>
こうすることで1割当ごとにX64なら最低でも8 byte、<a class="keyword" href="http://d.hatena.ne.jp/keyword/x86">x86</a>なら4 byteの節約になる。</p>
<p><code>CentralArena</code>にメモリの<code>free</code>要求が来た場合には以下のようにポインタのアドレス下位16ビットをマスクして、<br/>
<code>HeapHeader</code>を取得する。</p>
<p>※ <code>ChunkHeader::kAddrMask</code>は<code>~0xFFFF</code></p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">auto</span> h = <span class="synStatement">reinterpret_cast</span><HeapHeader*>(
<span class="synStatement">reinterpret_cast</span><<span class="synType">uintptr_t</span>>(ptr) & ChunkHeader::kAddrMask);
ASSERT(<span class="synConstant">true</span>, h != <span class="synConstant">nullptr</span>);
ChunkHeader* chunk_header = h->chunk_header();
ASSERT(<span class="synConstant">true</span>, chunk_header != <span class="synConstant">nullptr</span>);
chunk_header->Dealloc(ptr);
</pre>
<p>構造は以下の図の通り</p>
<pre class="code" data-lang="" data-unlink>+----------------------------+ +----------------------+ +-----------------------+
| HeapHeader(0x103e600000) |--| value(0x103e600008) |--| value(0x103e000010) |
+----------------------------+ +----------------------+ +-----------------------+</pre>
<p>削除する場合</p>
<pre class="code" data-lang="" data-unlink>+----------------------+ +----------------------------+
| value(0x103e600008) | => 0x103e600008 => | HeapHeader(0x103e600000) |
+----------------------+ ^^^^^ Mask heare. +----------------------------+</pre>
<p>概ねこんな感じでjust-fitでロックを必要としないメモリアロケータを実装することができた。<br/>
パフォーマンスだが、シングルスレッドでは<code>malloc</code>の約5倍ほど高速化することができたが、<br/>
マルチスレッドでは<code>1.2</code> ~ <code>1.1</code>倍ほどしか高速化せずちょっとなぁ〜という感じ。<br/>
また64 KB以上のビックな割当を行うとほとんど<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>と変わらないのでここは改善の余地がありそう。</p>
<p>という感じで<a class="keyword" href="http://d.hatena.ne.jp/keyword/malloc">malloc</a>を再実装した話でした。</p>
<p>全体像は以下のようになる。</p>
<pre class="code" data-lang="" data-unlink>+----------------+ +---------------+
| CentralArena |--| Main Thread |
+----------------+ +---------------+
| | +------------+ +--------------+
| +--| Thread-1 |--| LocalArena |
| | +------------+ +--------------+
| | |
| | | +--------------+
| | ---------| small_bin_ |
| | +--------------+
| | |
| | ------------+------------
| | | |
| | +-------+ +-------+
| | | 1byte | | 2byte |
| | +-------+ +-------+
| | | |
| | ------+------ ------+------
| | | | | |
| | +-------+ +-------+ +-------+ +-------+
| | | 3byte | | 4byte | | 5byte | | 6byte |
| | +-------+ +-------+ +-------+ +-------+
| | +---------------+
| | | ChunkHeader |
| | +---------------+
| | | |
| | | +--------------+ +------------+ +------------+ +------------+
| | | | heap_list_ |--| HeapHeader |--| HeapHeader |--| HeapHeader |
| | | +--------------+ +------------+ +------------+ +------------+
| | | +----------------------------+ +----------------------+ +-----------------------+
| | | | HeapHeader(0x103e600000) |--| value(0x103e600008) |--| value(0x103e000010) |
| | | +----------------------------+ +----------------------+ +-----------------------+
| | |
| | +--------------+ +------------+ +------------+ +------------+
| | | free_list_ |--| HeapHeader |--| HeapHeader |--| HeapHeader |
| | +--------------+ +------------+ +------------+ +------------+
| | +------------+ +--------------+
| +--| Thread-2 |--| LocalArena |
| | +------------+ +--------------+
| | +------------+ +--------------+
| +--| Thread-3 |--| LocalArena |
| +------------+ +--------------+
+------------+ +--------------+ +--------------+
| free list |--| LocalArena |--| LocalArena |-- ...
+------------+ +--------------+ +--------------+ </pre>
<h2>まとめ</h2>
<p>結果得られたのはロックフリーな実装の難しさとメモリ割当関連のバグは発見が不可能に近いという教訓であった。<br/>
意味不明なSegvがきつい。</p>
<h2>参考</h2>
<p><a href="https://www.slideshare.net/kosaki55tea/glibc-malloc">https://www.slideshare.net/kosaki55tea/glibc-malloc</a></p>
<p>ソースは <a href="https://github.com/brn/yatsc/tree/master/src/memory">https://github.com/brn/yatsc/tree/master/src/memory</a> どす〜</p>
brn_take
TypeScript 2.6 変更点と注意点
hatenablog://entry/8599973812308783895
2017-11-01T10:13:58+09:00
2017-11-01T12:04:58+09:00 TypeScript2.6が出たので変更点を記載 RCからほぼ変更点がない。 Strict Function Typeフラグの導入 --strictFunctionTypesというフラグが導入される。 このフラグは--strictフラグに内包されており、--strictの場合は自動でONになるが、 --strictFunctionTypesをfalseにすることで個別にOFFにすることもできる。 動作 関数の引数に対するVarianceの動作を変更する。 TypeScriptの関数のVarianceについては以前下のスライドで説明したので参照。 speakerdeck.com 今回の--str…
<p>TypeScript2.6が出たので変更点を記載<br/>
RCからほぼ変更点がない。</p>
<h2>Strict Function Typeフラグの導入</h2>
<p><code>--strictFunctionTypes</code>というフラグが導入される。<br/>
このフラグは<code>--strict</code>フラグに内包されており、<code>--strict</code>の場合は自動でONになるが、<br/>
<code>--strictFunctionTypes</code>を<code>false</code>にすることで個別にOFFにすることもできる。</p>
<p><b>動作</b></p>
<p>関数の引数に対するVarianceの動作を変更する。<br/>
TypeScriptの関数のVarianceについては以前下のスライドで説明したので参照。</p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="565" id="talk_frame_408186" mozallowfullscreen="true" src="//speakerdeck.com/player/b93e09294cc14d749da9393bf2f03fd7" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/brn/deep-dive-into-typescript">speakerdeck.com</a></cite></p>
<p>今回の<code>--strictFunctionTypes</code>フラグがONになると、関数がBivariantではなくてContravariantになる。<br/>
つまり以下のような代入が許可されなくなる。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink>
<span class="synStatement">class</span> Animal <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> Dog <span class="synStatement">extends</span> Animal <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synStatement">declare</span> <span class="synIdentifier">let</span> acceptAnimal: <span class="synStatement">(</span>animal: Animal<span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synType">void</span><span class="synStatement">;</span>
<span class="synStatement">declare</span> <span class="synIdentifier">let</span> acceptDog: <span class="synStatement">(</span>dog: Dog<span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synType">void</span><span class="synStatement">;</span>
acceptAnimal <span class="synStatement">=</span> acceptDog <span class="synComment">// Error</span>
acceptDog <span class="synStatement">=</span> acceptAnimal <span class="synComment">// OK!</span>
</pre>
<p>またこの変換が不可能になると、以下のように総称型の代入規則が変化する。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">class</span> Animal <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> Dog <span class="synStatement">extends</span> Animal <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> GenericAnimal<span class="synStatement"><</span>T<span class="synStatement">></span> <span class="synIdentifier">{</span>
someFn<span class="synStatement">(</span>animal: T<span class="synStatement">)</span> <span class="synIdentifier">{}</span>
<span class="synIdentifier">}</span>
delcare <span class="synIdentifier">let</span> animalAnimal: GenericAnimal<span class="synStatement"><</span>Animal<span class="synStatement">>;</span>
delcare <span class="synIdentifier">let</span> animalDog: GenericAnimal<span class="synStatement"><</span>Dog<span class="synStatement">>;</span>
animalAnimal <span class="synStatement">=</span> animalDog <span class="synComment">// Error.</span>
animalDog <span class="synStatement">=</span> animalAnimal <span class="synComment">// OK.</span>
<span class="synComment">// someFn(animal: Dog)</span>
<span class="synComment">// someFn(animal: Animal)</span>
</pre>
<p>これは関数がBivariantではなくなるために起こる。<br/>
<code>Animal</code>を受け取る関数に<code>Dog</code>を受け取る関数を渡すと、<code>Dog</code>が<code>Animal</code>の派生型である限り、<br/>
<code>Dog</code>固有の処理を行っていた場合にRuntimeエラーを起こす可能性が高く危険な代入になるが、<br/>
<code>Animal</code>を受け取る関数に<code>Dog</code>を渡すのは安全であるため、このような変換規則になる。<br/>
そのため外から見ると<code>Dog <= Animal</code>の変換をしているように見えるが、関数の引数という文脈で考えると、<br/>
この変換は<code>Animal <= Dog</code>の変換となり自然に見える。</p>
<h3>注意</h3>
<p><code>--strict</code>モードでこの機能が有効になるため、<code>--strict</code>オプションをONにしているコードに対しては、<br/>
この変換規則が強制的に適用される。<br/>
なので今まで逆の変換を行っているコードはすべて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーになる。<br/>
それが困る場合は<code>--strictFunctionTypes false</code>でOFFにすることができる。</p>
<h2>タグ付きテンプレート<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>をキャッシュするようになった</h2>
<p>タイトル通り。
一度作ったテンプレート<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>オブジェクトをキャッシュするようになったので、以下のコードが<code>true</code>になるようになった。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">export</span> <span class="synIdentifier">function</span> id<span class="synStatement">(</span>x: TemplateStringsArray<span class="synStatement">)</span> <span class="synIdentifier">{</span>
<span class="synStatement">return</span> x<span class="synStatement">;</span>
<span class="synIdentifier">}</span>
<span class="synStatement">export</span> <span class="synIdentifier">function</span> templateObjectFactory<span class="synStatement">()</span> <span class="synIdentifier">{</span>
<span class="synStatement">return</span> id<span class="synConstant">`hello world`</span><span class="synStatement">;</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">let</span> result <span class="synStatement">=</span> templateObjectFactory<span class="synStatement">()</span> <span class="synStatement">===</span> templateObjectFactory<span class="synStatement">();</span> <span class="synComment">// true in TS 2.6</span>
</pre>
<p>Emit Result in v2.5</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synConstant">"use strict"</span>;
exports.__esModule = <span class="synConstant">true</span>;
<span class="synIdentifier">function</span> id(x) <span class="synIdentifier">{</span>
<span class="synStatement">return</span> x;
<span class="synIdentifier">}</span>
exports.id = id;
<span class="synIdentifier">function</span> templateObjectFactory() <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (_a = <span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>, _a.raw = <span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>, id(_a));
<span class="synIdentifier">var</span> _a;
<span class="synIdentifier">}</span>
exports.templateObjectFactory = templateObjectFactory;
<span class="synIdentifier">var</span> result = templateObjectFactory() === templateObjectFactory(); <span class="synComment">// true in TS 2.6</span>
</pre>
<p>Emit Result in v2.6</p>
<pre class="code" data-lang="" data-unlink>"use strict";
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
function id(x) {
return x;
}
var _a;
function templateObjectFactory() {
return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"])));
}
var result = templateObjectFactory() === templateObjectFactory();</pre>
<p>ここに注目</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// v2.5</span>
<span class="synIdentifier">function</span> templateObjectFactory() <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (_a = <span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>, _a.raw = <span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>, id(_a));
<span class="synIdentifier">var</span> _a;
<span class="synIdentifier">}</span>
<span class="synComment">// v2.6</span>
<span class="synIdentifier">var</span> _a;
<span class="synIdentifier">function</span> templateObjectFactory() <span class="synIdentifier">{</span>
<span class="synStatement">return</span> id(_a || (_a = __makeTemplateObject(<span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>, <span class="synIdentifier">[</span><span class="synConstant">"hello world"</span><span class="synIdentifier">]</span>)));
<span class="synIdentifier">}</span>
</pre>
<p>変数<code>_a</code>に生成済みテンプレートオブジェクトをバインドして再利用しているのがわかる。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーメッセージ・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>ヘルプが多言語化</h2>
<p>localeに合わせて各種言語ファイルが使用されるようになった。</p>
<h2>// @ts-ignoreコメントでjsファイルのエラーを抑制できるようになった。</h2>
<p><code>--allowJS</code>オプションを使ってJSファイルをロードした際に出る<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーを、</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">if</span> (<span class="synConstant">false</span>) <span class="synIdentifier">{</span>
<span class="synComment">// @ts-ignore: Unreachable code error</span>
console.log(<span class="synConstant">"hello"</span>);
<span class="synIdentifier">}</span>
</pre>
<p>のような形で指定することで抑制できる。</p>
<h2>--watchモードが高速化</h2>
<p><code>--watch</code>機能をすべて書き直して高速化した。
プロジェクト全体を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>するのではなく、変化したファイルのみを処理することで処理速度が向上した。</p>
<h2>WriteOnlyな値をunusedとして処理するように</h2>
<p>TypeScript2.6では、<code>--noUnusedLocals</code>と<code>--noUnusedParameters</code>オプションを刷新した。<br/>
値が宣言されているか書き込まれていても、その値が読み出されない限りunusedとしてマークされる。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synIdentifier">function</span> test<span class="synStatement">(</span>unused1: <span class="synType">number</span><span class="synStatement">)</span> <span class="synIdentifier">{</span>
unused1 <span class="synStatement">=</span> <span class="synConstant">0</span><span class="synStatement">;</span>
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> Test <span class="synIdentifier">{</span>
<span class="synStatement">private</span> unused2: <span class="synType">number</span><span class="synStatement">;</span>
<span class="synStatement">constructor()</span> <span class="synIdentifier">{</span>
<span class="synIdentifier">this</span>.unused2 <span class="synStatement">=</span> <span class="synConstant">0</span><span class="synStatement">;</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<p>上記のサンプルコードでは<code>unused1</code>と<code>unused2</code>はともに書き込みはあるものの、<br/>
読み出しがないのでunusedとしてエラーになるので注意。</p>
<h2>追記</h2>
<p>LanguageService系の更新書くの忘れてた</p>
<h2>implicit anyのQuick fixを追加</h2>
<p>implicit anyが見つかった場合にnoImplicitAnyがtrueならQuickFixで推測した型を提案する。</p>
<h2>JSDocをTypeScriptの型<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>に変換する</h2>
<p>JSDocに書いた型注釈をTypeScriptの型に変換できるようになった。</p>
<h2>呼び出しが必要なDecoratorにWarningを出すようになった</h2>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%E2%B3%AC%B4%D8%BF%F4">高階関数</a>型の呼び出しが必要なDecoratorを呼び出し無しで使った場合に、<br/>
呼び出しを提案する。</p>
<h2>@typesから自動でinstall</h2>
<p>型定義がない場合に@typesから自動でインストールを提案する。</p>
<h2>Ambient宣言内のdefault exportで式が使えなくなった</h2>
<p>タイトルの通り</p>
<h2>Intersection Typeの結果を変更</h2>
<pre class="code" data-lang="" data-unlink>number & string, "foo" & 42,</pre>
<p>のような不可能な型はnever型を返すように</p>
<h2>lib.d.ts変更</h2>
<h2>Deprecation</h2>
<p><code>getSymbolDisplayBuilder</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>が廃止になった。2.7で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%EF%BD%FC%CD%BD%C4%EA">削除予定</a><br/>
基本的には代わりに<code>TypeChecker#symbolToString</code>を使う。<br/>
もっと入り組んだ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>の場合にはバージョン2.7で<code>symbolToString</code>を導入予定なのでそれを使う。</p>
<h2>まとめ</h2>
<p>今回はちょっと破壊的変更がいくつかあるので注意。<br/>
特に<code>--strict</code>をONにしている人は総称型のアップキャストをしていないかをチェックした方がいい。</p>
brn_take
Ecmascriptのprotocolについて
hatenablog://entry/8599973812307210359
2017-10-12T14:21:57+09:00
2017-10-12T14:29:23+09:00 Ecmascriptにprotocolを実装するという提案がある。 proposal-first-class-protocol もともとGotanda.jsで発表した内容だけどいろいろ追記した。 資料はこれ speakerdeck.com 内容 元々はinterfaceの提案だった。 それが名前と形を変えてprotocolの提案になった。 protocolとは? classが実装すべき規約。 interfaceと違って型ではなく規約を提供する。 型クラスとかが近いのかな? Ecmascript protocol 基本 今回の提案はちょっと特殊な形のprotocolの実装で、 実態はSymbol…
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>にprotocolを実装するという提案がある。</p>
<p><a href="https://github.com/michaelficarra/proposal-first-class-protocols">proposal-first-class-protocol</a></p>
<p>もともとGotanda.jsで発表した内容だけどいろいろ追記した。<br/>
資料はこれ</p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="565" id="talk_frame_410180" mozallowfullscreen="true" src="//speakerdeck.com/player/ceb66507e8fe4615bae442042e86a8f9" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/brn/ecmascript-proposal-first-class-protocol">speakerdeck.com</a></cite></p>
<h2>内容</h2>
<p>元々はinterfaceの提案だった。<br/>
それが名前と形を変えてprotocolの提案になった。</p>
<h3>protocolとは?</h3>
<p>classが実装すべき規約。<br/>
interfaceと違って型ではなく規約を提供する。<br/>
型クラスとかが近いのかな?</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a> protocol</h2>
<h3>基本</h3>
<p>今回の提案はちょっと特殊な形のprotocolの実装で、<br/>
実態はSymbolのコレクションになっている。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol ProtocolName <span class="synIdentifier">{</span>
<span class="synComment">// 実装が必要なシンボルを宣言</span>
thisMustBeImplemented;
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> ClassName <span class="synStatement">implements</span> ProtocolName <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span>ProtocolName.thisMustBeImplemented<span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<p>上の実装を見るとわかるけど、protocolのメンバーは自動的にSymbolになる。<br/>
そしてprotocolを実装するクラスはそのSymbolを実装しなければいけない。<br/>
さらにprotocolは複数実装できる。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol A <span class="synIdentifier">{</span> a; <span class="synIdentifier">}</span>
protocol B <span class="synIdentifier">{</span> b; <span class="synIdentifier">}</span>
<span class="synStatement">class</span> ClassName <span class="synStatement">implements</span> A, B <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span>A.a<span class="synIdentifier">]</span>() <span class="synIdentifier">{}</span>
<span class="synIdentifier">[</span>B.b<span class="synIdentifier">]</span>() <span class="synIdentifier">{}</span>
<span class="synIdentifier">}</span>
</pre>
<p>またprotocolは実装を持つこともできる。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol ProtocolName <span class="synIdentifier">{</span>
<span class="synComment">// 実装が必要なシンボルを宣言</span>
thisMustBeImplemented;
<span class="synComment">// メソッド実装</span>
youGetThisMethodForFree() <span class="synIdentifier">{</span>
<span class="synStatement">return</span> <span class="synIdentifier">this[</span>ProtocolName.thisMustBeImplemented<span class="synIdentifier">]</span>();
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> ClassName <span class="synStatement">implements</span> ProtocolName <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span>ProtocolName.thisMustBeImplemented<span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synStatement">const</span> className = <span class="synStatement">new</span> ClassName();
className.youGetThisMethodForFree(); <span class="synComment">// ClassNameのインスタンスで呼べる</span>
</pre>
<p>このprotocolは実装を持っていて、それを実装したクラスは同時に実装も引き継ぐ。<br/>
なのでtraitの様に振る舞うことができる。</p>
<h3>拡張</h3>
<p>さらに既存のクラスも拡張することができる。<br/>
たとえばArrayを拡張する場合。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol Functor <span class="synIdentifier">{</span>
map;
<span class="synIdentifier">}</span>
Promise.prototype<span class="synIdentifier">[</span>Functor.map<span class="synIdentifier">]</span> = () => <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>
<span class="synComment">// このimplement関数でFunctorを実装したことを宣言</span>
Protocol.implement(Promise, Functor);
</pre>
<p>このように既存クラスのprototypeを拡張し、implement関数を呼び出すことで、<br/>
既存のクラスにもprotocolを適用することができる。</p>
<h3>チェック</h3>
<p>あるクラスがprotocolを実装しているかどうかは、<code>instanceof</code>では判定できない。<br/>
なので、<code>implements</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>が提案されている。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>Promise <span class="synStatement">implements</span> Functor <span class="synComment">// true</span>
<span class="synStatement">if</span> (MyClass <span class="synStatement">implements</span> SomeProtocol) <span class="synIdentifier">{</span> <span class="synIdentifier">}</span>
</pre>
<p>上記の例のように<code>implements</code>キーワードが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>のように振る舞う。</p>
<h3>extends</h3>
<p>protocol自体は既存のprotocolを拡張することができる。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol A <span class="synIdentifier">{</span> a; <span class="synIdentifier">}</span>
protocol B <span class="synIdentifier">{</span> b; <span class="synIdentifier">}</span>
protocol C <span class="synStatement">extends</span> A, B <span class="synIdentifier">{</span> c; <span class="synIdentifier">}</span>
</pre>
<p>こんな感じでprotocolは複数のprotocolを拡張することができる。</p>
<h3>デフォルト実装</h3>
<p>上記の拡張を利用するとデフォルト実装を持つprotocolを定義することができる。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol A <span class="synIdentifier">{</span> a; <span class="synIdentifier">}</span>
protocol B <span class="synStatement">extends</span> A <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span>A.a<span class="synIdentifier">]</span>() <span class="synIdentifier">{</span> ... <span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> Class <span class="synStatement">implements</span> B <span class="synIdentifier">{}</span>
<span class="synStatement">const</span> c = <span class="synStatement">new</span> Class();
c<span class="synIdentifier">[</span>A.a<span class="synIdentifier">]</span>();
</pre>
<p>protocol Bはprotocol Aを拡張し更にaを実装している。<br/>
なので、<code>Class</code>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に対して<code>A.a</code>を呼び出すことが可能になる。</p>
<h3>static protocol</h3>
<p>protocolはなんと静的プロパティ(!)に対しても作用させられる。</p>
<p>例.</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>protocol A <span class="synIdentifier">{</span>
<span class="synStatement">static</span> b() <span class="synIdentifier">{}</span>
<span class="synIdentifier">}</span>
<span class="synStatement">class</span> C <span class="synStatement">implements</span> A <span class="synIdentifier">{</span> <span class="synIdentifier">}</span>
C<span class="synIdentifier">[</span>A.b<span class="synIdentifier">]</span>();
</pre>
<p>これはどうなんだ...</p>
<h3>TypeScript</h3>
<p>とりあえず既存のinterfaceは残すであろうとのこと。<br/>
protocolをどのような型として扱うかは未定。<br/>
<a href="https://github.com/Microsoft/TypeScript/pull/15473">Allow dynamic name in types</a>という型のプロパティ名にsymbolを使うことができるPRが進んでいるので、<br/>
これが実装されるとprotocolもスムーズにいけるかも。<br/>
ちなみにここで議論しています。</p>
<p><a href="https://github.com/Microsoft/TypeScript/issues/18814">Question: TypeScript and proposal-first-class-protocols · Issue #18814 · Microsoft/TypeScript · GitHub</a></p>
<h2>prototypeへの直接代入について</h2>
<p>先程、既存のクラスにprotocolを実装するケースでprototypeへ直接代入するという例を出したが、<br/>
prototypeへの直接代入は何か気持ち悪い...(Object.definePropertyすらない時代からjsを書いていたせいなのか?)ので<br/>
それはやめてくれ!その代わり既存のクラスをopen-classにして拡張できるようにすれば良くない?<br/>
というアバウトな提案をしてみた。</p>
<pre class="code" data-lang="" data-unlink>protocol Functor {
map;
}
// We can implements protocol after declaration.
Array implements Functor {
[Functor.map]: Array.prototype.map
}</pre>
<p>こんな感じ。</p>
<h3>結果</h3>
<p>とりあえず、classがprototypeを隠蔽したのにこれじゃES5時代じゃないか!<br/>
って思いを伝えたら。</p>
<blockquote><p>classはprototypeを隠蔽したわけじゃないよ。prototypeへの代入もES5時代的ではない。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>のモンキーパッチ方法は常にprototypeだし、これからもjsは未来永劫変わらずこの方法なのさ。</p></blockquote>
<p>とこのような調子で言われた。<br/>
まあ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ecmascript">Ecmascript</a>がprototypeを隠蔽して、もっと <b>普通</b> のclassベース言語になりたがってるように見えていた私にとって、<br/>
これが目指す世界ならもう言うことは無かったので議論を打ち切った。</p>
<p><span style="font-size: 120%;font-weight:bold">でもprototypeはもう触りたくないんじゃ!</span></p>
<p>おしまい。</p>
brn_take
HTML5 Conference 2017に登壇します
hatenablog://entry/8599973812297937153
2017-09-14T15:34:50+09:00
2017-09-14T15:34:50+09:00 ひょんなことから HTML5 Conference に登壇する機会をいただけることに。 タイトルはDeep dive into TypeScriptということで、TypeScriptの紹介や、RoadMap・Issuesの話とかをさせて頂くつもり 同じ時間帯に任天堂の方のセッションとかあってアレなんですが、TypeScriptに興味のある方はぜひ。
<p>ひょんなことから</p>
<p><a href="http://events.html5j.org/conference/2017/9/">HTML5 Conference</a></p>
<p>に登壇する機会をいただけることに。</p>
<p>タイトルはDeep dive into TypeScriptということで、TypeScriptの紹介や、RoadMap・Issuesの話とかをさせて頂くつもり</p>
<p>同じ時間帯に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C7%A4%C5%B7%C6%B2">任天堂</a>の方のセッションとかあってアレなんですが、TypeScriptに興味のある方はぜひ。</p>
brn_take
Typescript 2.5 リリース
hatenablog://entry/8599973812293951769
2017-09-01T14:36:01+09:00
2017-09-01T14:36:01+09:00 Typescript 2.5がでたので変更点をメモ Optional-catch-bindingの導入 catch節のエラーオブジェクトが省略可能になった。 現在Stage3のOptional catch bindingが導入された形に。 サンプル let input = "..."; try { JSON.parse(input); } catch { // ^ catch節の変数宣言を省略している。 console.log("Invalid JSON given\n\n" + input) } jsにJSDocベースの型アサーションとキャストを導入 javascriptファイルもJSDoc…
<p>Typescript 2.5がでたので変更点をメモ</p>
<h2>Optional-catch-bindingの導入</h2>
<p>catch節のエラーオブジェクトが省略可能になった。<br/>
現在Stage3の<a href="https://tc39.github.io/proposal-optional-catch-binding/">Optional catch binding</a>が導入された形に。</p>
<p>サンプル</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">let</span> input = <span class="synConstant">"..."</span>;
<span class="synStatement">try</span> <span class="synIdentifier">{</span>
JSON.parse(input);
<span class="synIdentifier">}</span>
<span class="synStatement">catch</span> <span class="synIdentifier">{</span>
<span class="synComment">// ^ catch節の変数宣言を省略している。</span>
console.log(<span class="synConstant">"Invalid JSON given</span><span class="synSpecial">\n\n</span><span class="synConstant">"</span> + input)
<span class="synIdentifier">}</span>
</pre>
<h2>jsにJSDocベースの型<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A1%BC%A5%B7%A5%E7%A5%F3">アサーション</a>とキャストを導入</h2>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>ファイルもJSDocコメントで型チェック可能になった。<br/>
コメントで@ts-checkをつけるか、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>オプションでcheckJsオプションをtrueにすれば動作する。</p>
<p>型チェックの詳細は以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wiki">Wiki</a>に書いてある。</p>
<p><a href="https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files/">Type Checking JavaScript Files · Microsoft/TypeScript Wiki · GitHub</a></p>
<h2>重複したパッケージのリダイレクト</h2>
<p>簡単に言うとnode_modulesのパッケージの同一性チェックをする。</p>
<p>つまり、moduleResolutionがnodeの場合node_modulesからモジュールをインポートするが、<br/>
その際にそのモジュールのpackage.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>をチェックして、すでにインポートした事のあるモジュールかどうか確認する。<br/>
もし同一のモジュールであれば、すでにimport済みのモジュールにリダイレクトして同じモジュールをロードしない。<br/>
ちなみにpackage.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>のversionとnameをチェックして同一性判定をする。</p>
<pre class="code" data-lang="" data-unlink>node_modules -
|
[A-package] -
| |
| node_modules -
| |
| [C-package]
[B-package] -
|
node_modules -
|
[C-package]</pre>
<p>この例だと、A-packageとB-packageが同じC-pacakgeを必要としているが、typescript<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>は一度しかC-packageをインポートしない。<br/>
B-packageの必要とするC-pacakgeはA-packageのC-packageを参照する。</p>
<h2>–preserveSymlinks<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>フラグの導入</h2>
<p>–preserveSymlinksオプションが導入される。<br/>
このオプションは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%DC%A5%EA%A5%C3%A5%AF%A5%EA%A5%F3%A5%AF">シンボリックリンク</a>されたモジュールが他のモジュールをimportする場合に、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%DC%A5%EA%A5%C3%A5%AF%A5%EA%A5%F3%A5%AF">シンボリックリンク</a>元のパスを起点とした<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%EA%C2%D0%A5%D1%A5%B9">相対パス</a>ではなく、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%DC%A5%EA%A5%C3%A5%AF%A5%EA%A5%F3%A5%AF">シンボリックリンク</a>が置かれている場所からの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%EA%C2%D0%A5%D1%A5%B9">相対パス</a>となる。</p>
<pre class="code" data-lang="" data-unlink>A -
|
B -
| |
| C'(Symblink of C)
C</pre>
<p>この例だと、C'モジュールのパス解決は–preserveSymlinksをセットしない場合は、<br/>
A/Cから始まることになる。<br/>
しかし–preserveSymlinksをセットすると、<br/>
A/B/C'からパス解決が行われる。</p>
<h2>まとめ</h2>
<p>意外と新機能は少なかったですね。</p>
brn_take
typescript 2.4 の新機能
hatenablog://entry/8599973812274716641
2017-06-28T10:57:52+09:00
2017-06-28T11:02:53+09:00 typescript2.4がでたので新機能を確認。 Dynamic Import Expressionsのサポート import('...')式がサポートされた。 import式を使うことで多くのバンドラーがコード分割をすることが可能になるので、 module: esnextで出力するのがおすすめだそう。 async function getZipFile(name: string, files: File[]): Promise<File> { const zipUtil = await import('./utils/create-zip-file'); const zipContents…
<p>typescript2.4がでたので新機能を確認。</p>
<h2>Dynamic Import Expressionsのサポート</h2>
<p><code>import('...')</code>式がサポートされた。</p>
<p><code>import</code>式を使うことで多くのバンドラーがコード分割をすることが可能になるので、<br/>
<code>module: esnext</code>で出力するのがおすすめだそう。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">async</span> <span class="synIdentifier">function</span> getZipFile<span class="synStatement">(</span>name: <span class="synType">string</span><span class="synStatement">,</span> files: File<span class="synIdentifier">[]</span><span class="synStatement">)</span>: Promise<span class="synStatement"><</span>File<span class="synStatement">></span> <span class="synIdentifier">{</span>
<span class="synStatement">const</span> zipUtil <span class="synStatement">=</span> <span class="synStatement">await</span> <span class="synStatement">import(</span><span class="synConstant">'./utils/create-zip-file'</span><span class="synStatement">);</span>
<span class="synStatement">const</span> zipContents <span class="synStatement">=</span> <span class="synStatement">await</span> zipUtil.getContentAsBlob<span class="synStatement">(</span>files<span class="synStatement">);</span>
<span class="synStatement">return</span> <span class="synStatement">new</span> File<span class="synStatement">(</span>zipContents<span class="synStatement">,</span> name<span class="synStatement">);</span>
<span class="synIdentifier">}</span>
</pre>
<h2>String Enumsのサポート</h2>
<p>待望?の文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>がサポートされた。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">enum</span> Colors <span class="synIdentifier">{</span>
Red <span class="synStatement">=</span> <span class="synConstant">"RED"</span><span class="synStatement">,</span>
Green <span class="synStatement">=</span> <span class="synConstant">"GREEN"</span><span class="synStatement">,</span>
Blue <span class="synStatement">=</span> <span class="synConstant">"BLUE"</span><span class="synStatement">,</span>
<span class="synIdentifier">}</span>
</pre>
<p>ただし制限として、数値<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>の際に可能だった、メンバプロパティからプロパティ名の取得はできない。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink>Colors<span class="synIdentifier">[</span>Colors.Red<span class="synIdentifier">]</span> <span class="synComment">// これはできない。</span>
</pre>
<h2>interfaceのgeneric型のサポートを強化</h2>
<h3>戻り値の推論能力の強化</h3>
<p>戻り値から型パラメータを導出できるようになった。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synIdentifier">function</span> arrayMap<span class="synStatement"><</span>T<span class="synStatement">,</span> U<span class="synStatement">>(</span>f: <span class="synStatement">(</span>x: T<span class="synStatement">)</span> <span class="synStatement">=></span> U<span class="synStatement">)</span>: <span class="synStatement">(</span>a: T<span class="synIdentifier">[]</span><span class="synStatement">)</span> <span class="synStatement">=></span> U<span class="synIdentifier">[]</span> <span class="synIdentifier">{</span>
<span class="synStatement">return</span> a <span class="synStatement">=></span> a.map<span class="synStatement">(</span>f<span class="synStatement">);</span>
<span class="synIdentifier">}</span>
<span class="synStatement">const</span> lengths: <span class="synStatement">(</span>a: <span class="synType">string</span><span class="synIdentifier">[]</span><span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synType">number</span><span class="synIdentifier">[]</span> <span class="synStatement">=</span> arrayMap<span class="synStatement">(</span>s <span class="synStatement">=></span> s.<span class="synSpecial">length</span><span class="synStatement">);</span>
</pre>
<p>Promiseもこのようにエラーとすることができるように。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synIdentifier">let</span> x: Promise<span class="synStatement"><</span><span class="synType">string</span><span class="synStatement">></span> <span class="synStatement">=</span> <span class="synStatement">new</span> Promise<span class="synStatement">(</span>resolve <span class="synStatement">=></span> <span class="synIdentifier">{</span>
resolve<span class="synStatement">(</span><span class="synConstant">10</span><span class="synStatement">);</span>
<span class="synComment">// ~~ Error!</span>
<span class="synIdentifier">}</span><span class="synStatement">);</span>
</pre>
<h3>文脈からの型パラメータの導出</h3>
<p>いかのような定義があった場合</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synIdentifier">let</span> f: <span class="synStatement"><</span>T<span class="synStatement">>(</span>x: T<span class="synStatement">)</span> <span class="synStatement">=></span> T <span class="synStatement">=</span> y <span class="synStatement">=></span> y<span class="synStatement">;</span>
</pre>
<p><code>y</code>は<code>any</code>になってしまっていた。<br/>
そのため、</p>
<pre class="code" data-lang="" data-unlink>let f: <T>(x: T) => T = y => y() + y.foo.bar;</pre>
<p>のような式は型チェックをスルーしてしまっていたが、<br/>
2.4から<code>y</code>が正しく導出され、エラーとなるようになった。</p>
<h3>generic関数の方チェックを厳格化</h3>
<p>以下のような関数があった場合、</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> A <span class="synStatement">=</span> <span class="synStatement"><</span>T<span class="synStatement">,</span> U<span class="synStatement">>(</span>x: T<span class="synStatement">,</span> y: U<span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synIdentifier">[</span>T<span class="synStatement">,</span> U<span class="synIdentifier">]</span><span class="synStatement">;</span>
<span class="synStatement">type</span> B <span class="synStatement">=</span> <span class="synStatement"><</span>S<span class="synStatement">>(</span>x: S<span class="synStatement">,</span> y: S<span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synIdentifier">[</span>S<span class="synStatement">,</span> S<span class="synIdentifier">]</span><span class="synStatement">;</span>
<span class="synIdentifier">function</span> f<span class="synStatement">(</span>a: A<span class="synStatement">,</span> b: B<span class="synStatement">)</span> <span class="synIdentifier">{</span>
a <span class="synStatement">=</span> b<span class="synStatement">;</span> <span class="synComment">// Error</span>
b <span class="synStatement">=</span> a<span class="synStatement">;</span> <span class="synComment">// Ok</span>
<span class="synIdentifier">}</span>
</pre>
<p>AがBと互換性があるかをチェックできるようになり、互換性があれば、代入も可能になった。</p>
<h3>コールバック関数の方の反変性チェックを厳格化</h3>
<p>2.4以前のバージョンでは以下のプログラムではエラーが起きなかった。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">interface</span> Mappable<span class="synStatement"><</span>T<span class="synStatement">></span> <span class="synIdentifier">{</span>
map<span class="synStatement"><</span>U<span class="synStatement">>(</span>f: <span class="synStatement">(</span>x: T<span class="synStatement">)</span> <span class="synStatement">=></span> U<span class="synStatement">)</span>: Mappable<span class="synStatement"><</span>U<span class="synStatement">>;</span>
<span class="synIdentifier">}</span>
<span class="synStatement">declare</span> <span class="synIdentifier">let</span> a: Mappable<span class="synStatement"><</span><span class="synType">number</span><span class="synStatement">>;</span>
<span class="synStatement">declare</span> <span class="synIdentifier">let</span> b: Mappable<span class="synStatement"><</span><span class="synType">string</span> | <span class="synType">number</span><span class="synStatement">>;</span>
a <span class="synStatement">=</span> b<span class="synStatement">;</span>
b <span class="synStatement">=</span> a<span class="synStatement">;</span>
</pre>
<p>なぜなら、<code>a</code>と<code>b</code>がmap関数のパラメータ<code>f</code>を通して変換可能であると判断されていたため。<br/>
しかし2.4以降は実際の<code>a</code>と<code>b</code>の型を比較を行うため、このプログラムは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーとなる。<br/>
<strong>この変更は破壊的変更となるので注意</strong></p>
<h3>Weak Typesの導入</h3>
<p>以下のように、すべてがoptionalな型をWeakTypeとして区別することになった。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">interface</span> Options <span class="synIdentifier">{</span>
data?: <span class="synType">string</span><span class="synStatement">,</span>
timeout?: <span class="synType">number</span><span class="synStatement">,</span>
maxRetries?: <span class="synType">number</span><span class="synStatement">,</span>
<span class="synIdentifier">}</span>
</pre>
<p>2.4からはこのWeakTypeに対しても、存在しないプロパティを持つ型を代入するとエラーになる。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synIdentifier">function</span> sendMessage<span class="synStatement">(</span>options: Options<span class="synStatement">)</span> <span class="synIdentifier">{</span>
<span class="synComment">// ...</span>
<span class="synIdentifier">}</span>
<span class="synStatement">const</span> opts <span class="synStatement">=</span> <span class="synIdentifier">{</span>
payload: <span class="synConstant">"hello world!"</span><span class="synStatement">,</span>
retryOnFail: <span class="synConstant">true</span><span class="synStatement">,</span>
<span class="synIdentifier">}</span>
<span class="synComment">// Error!</span>
sendMessage<span class="synStatement">(</span>opts<span class="synStatement">);</span>
<span class="synComment">// optsとOptions型で一致するプロパティがないためエラー</span>
</pre>
<p><strong>この変更は破壊的変更となるので注意</strong></p>
<p>現在以下のWorkaroundが提案されている。</p>
<ul>
<li>実際にプロパティが存在する場合のみ宣言する。</li>
<li>WeakTypeには<code>{[propName: string]: {}}</code>のようなインデックス型を定義する。</li>
<li><code>opts as Options</code>のように型<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A1%BC%A5%B7%A5%E7%A5%F3">アサーション</a>を使って変換する。</li>
</ul>
<h2>まとめ</h2>
<p>型チェックの強化がメインの変更点になった。<br/>
破壊的変更が幾つかありますが、WeakTypeのとこはちょっと注意した方が良さそう。<br/>
あとは<code>import</code>式をどう使うか。</p>
brn_take
babelのAsyncIterationバグ
hatenablog://entry/8599973812273079730
2017-06-23T10:14:44+09:00
2017-06-23T10:14:44+09:00 問題 for-await-ofのボディで配列への分割代入を行うと、 Cannot read property 'file' of undefined というエラーを投げてトランスパイルに失敗する。 どうやらscopeの解析に失敗しているらしい。 サンプルコード async function g(t) { return new Promise(r => setTimeout(() => r([true]), t)); } async function r() { for await (const t of [1000, 2000, 3000]) { const [result] = await …
<h2>問題</h2>
<p>for-await-ofのボディで配列への分割代入を行うと、</p>
<pre class="code" data-lang="" data-unlink>Cannot read property 'file' of undefined</pre>
<p>というエラーを投げてトランスパイルに失敗する。<br/>
どうやらscopeの解析に失敗しているらしい。</p>
<p>サンプルコード</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>async <span class="synIdentifier">function</span> g(t) <span class="synIdentifier">{</span>
<span class="synStatement">return</span> <span class="synStatement">new</span> Promise(r => setTimeout(() => r(<span class="synIdentifier">[</span><span class="synConstant">true</span><span class="synIdentifier">]</span>), t));
<span class="synIdentifier">}</span>
async <span class="synIdentifier">function</span> r() <span class="synIdentifier">{</span>
<span class="synStatement">for</span> await (<span class="synStatement">const</span> t of <span class="synIdentifier">[</span>1000, 2000, 3000<span class="synIdentifier">]</span>) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> <span class="synIdentifier">[</span>result<span class="synIdentifier">]</span> = await g(t);
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
r();
</pre>
<p>package.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a></p>
<pre class="code lang-json" data-lang="json" data-unlink>"<span class="synStatement">dependencies</span>": <span class="synSpecial">{</span>
"<span class="synStatement">babel-cli</span>": "<span class="synConstant">^6.24.1</span>",
"<span class="synStatement">babel-plugin-transform-async-generator-functions</span>": "<span class="synConstant">^6.24.1</span>",
"<span class="synStatement">babel-plugin-transform-regenerator</span>": "<span class="synConstant">^6.24.1</span>",
"<span class="synStatement">babel-plugin-transform-runtime</span>": "<span class="synConstant">^6.23.0</span>",
"<span class="synStatement">babel-polyfill</span>": "<span class="synConstant">^6.23.0</span>",
"<span class="synStatement">babel-preset-es2015</span>": "<span class="synConstant">^6.22.0</span>",
"<span class="synStatement">babel-preset-stage-3</span>": "<span class="synConstant">^6.24.1</span>",
"<span class="synStatement">babel-runtime</span>": "<span class="synConstant">^6.23.0</span>"
<span class="synSpecial">}</span>,
"<span class="synStatement">babel</span>": <span class="synSpecial">{</span>
"<span class="synStatement">plugins</span>": <span class="synSpecial">[</span>
<span class="synSpecial">[</span>
"<span class="synConstant">transform-runtime</span>",
<span class="synSpecial">{</span>
"<span class="synStatement">helpers</span>": <span class="synConstant">false</span>,
"<span class="synStatement">polyfill</span>": <span class="synConstant">false</span>,
"<span class="synStatement">regenerator</span>": <span class="synConstant">true</span>,
"<span class="synStatement">moduleName</span>": "<span class="synConstant">babel-runtime</span>"
<span class="synSpecial">}</span>
<span class="synSpecial">]</span>
<span class="synSpecial">]</span>,
"<span class="synStatement">presets</span>": <span class="synSpecial">[</span>
"<span class="synConstant">es2015</span>",
"<span class="synConstant">stage-3</span>"
<span class="synSpecial">]</span>
<span class="synSpecial">}</span>
</pre>
<h2>解決</h2>
<p>とりあえずbugとしてbabel側にはissueを立てておいた。</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fbabel%2Fbabel%2Fissues%2F5880" title="Destructuring assignment inside of AsyncIterator cause error 'Cannot read property 'file' of undefined' · Issue #5880 · babel/babel" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/babel/babel/issues/5880">github.com</a></cite></p>
<h3>現状で取れる選択肢</h3>
<h4>配列ではなくオブジェクト形式で受け取る</h4>
<p>データ構造の変更が必要だがまあ許容できるか?</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>async <span class="synIdentifier">function</span> g(t) <span class="synIdentifier">{</span>
<span class="synStatement">return</span> <span class="synStatement">new</span> Promise(r => setTimeout(() => r(<span class="synIdentifier">{</span>result: <span class="synConstant">true</span><span class="synIdentifier">}</span>), t));
<span class="synIdentifier">}</span>
async <span class="synIdentifier">function</span> r() <span class="synIdentifier">{</span>
<span class="synStatement">for</span> await (<span class="synStatement">const</span> t of <span class="synIdentifier">[</span>1000, 2000, 3000<span class="synIdentifier">]</span>) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> <span class="synIdentifier">{</span>result<span class="synIdentifier">}</span> = await g(t);
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
r();
</pre>
<h4>分割代入で受け取らない</h4>
<p>その後にインデックスアクセスしなければならないのでちょっと面倒</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>async <span class="synIdentifier">function</span> g(t) <span class="synIdentifier">{</span>
<span class="synStatement">return</span> <span class="synStatement">new</span> Promise(r => setTimeout(() => r(<span class="synIdentifier">[</span><span class="synConstant">true</span><span class="synIdentifier">]</span>), t));
<span class="synIdentifier">}</span>
async <span class="synIdentifier">function</span> r() <span class="synIdentifier">{</span>
<span class="synStatement">for</span> await (<span class="synStatement">const</span> t of <span class="synIdentifier">[</span>1000, 2000, 3000<span class="synIdentifier">]</span>) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> result = await g(t);
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
r();
</pre>
<h2>まとめ</h2>
<p>はやく直ってくれると嬉しい</p>
brn_take
Webpackでpolyfillをちゃんと動かす
hatenablog://entry/10328749687260315613
2017-06-05T12:39:08+09:00
2017-06-05T12:39:08+09:00 面倒だったので備忘録 Polyfillの設定 とりあえずwebpackはrequireしないと駄目。 でも既存のコードを一切変えずにpolyfillをインストールしたい。 webpack.ProvidePluginを使う。 new webpack.ProvidePlugin({ 'Promise': 'es6-promise', 'Symbol': 'es6-symbol', 'fetch': 'imports?this=>global!exports?global.fetch?window.fetch!whatwg-fetch', 'Response': 'imports-loader?th…
<p>面倒だったので備忘録</p>
<h2>Polyfillの設定</h2>
<p>とりあえずwebpackはrequireしないと駄目。<br/>
でも既存のコードを一切変えずにpolyfillをインス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC">トー</a>ルしたい。</p>
<p><code>webpack.ProvidePlugin</code>を使う。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">new</span> webpack.ProvidePlugin(<span class="synIdentifier">{</span>
<span class="synConstant">'Promise'</span>: <span class="synConstant">'es6-promise'</span>,
<span class="synConstant">'Symbol'</span>: <span class="synConstant">'es6-symbol'</span>,
<span class="synConstant">'fetch'</span>: <span class="synConstant">'imports?this=>global!exports?global.fetch?window.fetch!whatwg-fetch'</span>,
<span class="synConstant">'Response'</span>: <span class="synConstant">'imports-loader?this=>global!exports-loader?global.Response!whatwg-fetch'</span>
<span class="synIdentifier">}</span>),,
</pre>
<p>こんな感じで書いた。</p>
<p>次に</p>
<p><code>npm i imports-loader exports-loader -D</code>を実行。</p>
<p>Promise、Symbolは<code>require('es6-promise')</code>、<code>require('es6-symbol')</code>がそれぞれ<code>Promise</code>、<code>Symbol</code>という変数名でモジュール毎に定義される。<br/>
fetchはとても読みづらいし考えたやつ頭おかしいが、<br/>
まず、<a class="keyword" href="http://d.hatena.ne.jp/keyword/whatwg">whatwg</a>-fetchの定義の下の<code>this</code>が<code>global</code>を参照している箇所で<code>module.exports = fetchg</code>を定義して、<br/>
fetchを参照しているモジュールに<code>(function(fetch){ ... })(require('whatwg-fetch'))</code>のようなコードを追加する。<br/>
Responseも同じく。</p>
<h2>まとめ</h2>
<p>webpackの独自仕様を追加すぎるとESModuleへの移行がつらそうなので程々に。</p>
brn_take
jspmからWebpackに移行した
hatenablog://entry/10328749687245222367
2017-05-11T13:47:14+09:00
2017-05-11T13:47:14+09:00 とりあえず、Webpackを導入したがそのままだと色々問題が多かったので以下の事をやった。 まあ今更感あるが。 typescriptのallowSyntheticDefaultImportsをfalseにする。 production用とdev用のconfigをいい感じにわける node_modulesのdllを生成する。 typescriptのallowSyntheticDefaultImportsをfalse 今回はtypescriptとwebpackの組み合わせだったのだが、webapckに移行する前にはjspmを利用していたので、 allowSyntheticDefaultImports…
<p>とりあえず、Webpackを導入したがそのままだと色々問題が多かったので以下の事をやった。<br/>
まあ今更感あるが。</p>
<ul>
<li>typescriptのallowSyntheticDefaultImportsをfalseにする。</li>
<li>production用とdev用のconfigをいい感じにわける</li>
<li>node_modulesのdllを生成する。</li>
</ul>
<h3>typescriptのallowSyntheticDefaultImportsをfalse</h3>
<p>今回はtypescriptとwebpackの組み合わせだったのだが、webapckに移行する前にはjspmを利用していたので、</p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synError">allowSyntheticDefaultImports</span>: <span class="synConstant">true</span>,
<span class="synError">module</span>: <span class="synError">system</span>,
</pre>
<p>で運用していた。ちなみにこの<code>allowSyntheticDefaultImports</code>が何かというと、<br/>
本来typescriptのES6 Moduleは<code>export default ...</code>の構文しか、直接importできない。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// a.js</span>
module.exports = <span class="synIdentifier">function</span>() <span class="synIdentifier">{}</span>
</pre>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// b.ts</span>
<span class="synStatement">import</span> fn <span class="synStatement">from</span> <span class="synConstant">'./a'</span><span class="synStatement">;</span> <span class="synComment">// Error</span>
<span class="synStatement">import</span> * <span class="synStatement">as</span> fn <span class="synStatement">from</span> <span class="synConstant">'./a'</span><span class="synStatement">;</span> <span class="synComment">// OK</span>
</pre>
<p>しかし、<code>allowSyntheticDefaultImports</code>を<code>true</code>にすると、commonjsスタイルのmoduleも直接importできるようになる。<br/>
のだが、webpackはsystemjsを取り扱えず、<code>module: commonjs</code>にしたところ、この<code>allowSyntheticDefaultImports</code>がうまく動作しなくなった。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// a.js</span>
module.exports = <span class="synIdentifier">function</span>() <span class="synIdentifier">{}</span>
</pre>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// b.ts</span>
<span class="synStatement">import</span> fn <span class="synStatement">from</span> <span class="synConstant">'./a'</span><span class="synStatement">;</span>
fn<span class="synStatement">();</span> <span class="synComment">// Error</span>
</pre>
<p>ので、<code>allowSyntheticDefaultImports</code>は<code>false</code>にして、すべて<code>import * as ...</code>の方式で読み込むことにした。</p>
<h3>production用とdev用のconfigをいい感じにわける</h3>
<p>まあ、webpack.config.js自体がnodejsのモジュールだったので、webpack.config.jsに設定を書いてもいいのだが、<br/>
凄くごちゃごちゃするので、webpack.dev.config.jsを作って、そこでwebpack.config.jsを読み込み、<br/>
<code>_.clone</code>で設定をコピーしてdev用の設定を追加するようにした。</p>
<h3>node_modulesのdllを生成する。</h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>があまりに遅かったので、<code>DllPlugin</code>を利用して、変更されない外部モジュールを全てまとめておいた。</p>
<p>dll側</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>output: <span class="synIdentifier">{</span>
filename: <span class="synConstant">'[name].dll.js'</span>,
library: <span class="synConstant">'vendor_library'</span> <span class="synComment">// Bundleが定義されるグローバル変数名。参照する時に使用する。</span>
<span class="synIdentifier">}</span>,
plugins: <span class="synIdentifier">[</span>
<span class="synStatement">new</span> webpack.DllPlugin(<span class="synIdentifier">{</span>
path: path.join(__dirname, <span class="synConstant">'dll'</span>, <span class="synConstant">'[name]-manifest.json'</span>), <span class="synComment">// manifestファイルを出力する。とりあえず-manifestにしとけばok</span>
name: <span class="synConstant">'vendor_library'</span> <span class="synComment">// output.libraryで指定した名前</span>
<span class="synIdentifier">}</span>)
<span class="synIdentifier">]</span>
</pre>
<p>使う側</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>plugins: <span class="synIdentifier">[</span>
...
<span class="synStatement">new</span> webpack.DllReferencePlugin(<span class="synIdentifier">{</span> <span class="synComment">// Dllを参照する</span>
context: __dirname,
manifest: require(<span class="synConstant">'./dll/vendor.production-manifest.json'</span>) <span class="synComment">// 上のmanifestファイルへのパス</span>
<span class="synIdentifier">}</span>)
<span class="synIdentifier">]</span>
</pre>
<p>こうすることでnode_modulesとかのあまり変化しないファイルを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>しておくことができ、<br/>
その<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>したファイルを参照することで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時間を短縮できる。<br/>
ただし、DllReferencePluginはconcatしてくれないので、dllは手動でconcatするか、別途scriptで読み込む必要がある。<br/>
今回はconcatした。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>gulp.task(<span class="synConstant">'minify'</span>, done => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> webpack = require(<span class="synConstant">'webpack'</span>);
<span class="synStatement">const</span> config = _.clone(require(<span class="synConstant">'./webpack.config.js'</span>));
config.output = _.clone(config.output);
config.output.path = path.join(__dirname, DIST);
<span class="synStatement">const</span> compiler = webpack(config);
compiler.run((err, stats) => <span class="synIdentifier">{</span>
<span class="synStatement">if</span> (err) <span class="synIdentifier">{</span>
<span class="synStatement">throw</span> err;
<span class="synIdentifier">}</span>
console.log(stats.toString(<span class="synConstant">'minimal'</span>));
<span class="synStatement">try</span> <span class="synIdentifier">{</span>
<span class="synStatement">const</span> main = `$<span class="synIdentifier">{</span>DIST<span class="synIdentifier">}</span>/main.js`;
<span class="synStatement">const</span> app = fs.readFileSync(main, <span class="synConstant">'utf8'</span>);
<span class="synStatement">const</span> vendor = fs.readFileSync(`dll/vendor.production.dll.js`, <span class="synConstant">'utf8'</span>);
fs.writeFileSync(main, `!<span class="synIdentifier">function</span>()<span class="synIdentifier">{</span>$<span class="synIdentifier">{</span>vendor<span class="synIdentifier">}</span>;<span class="synIdentifier">\</span>n$<span class="synIdentifier">{</span>app<span class="synIdentifier">}}</span>();`, <span class="synConstant">'utf8'</span>); <span class="synComment">//ここでconcatしてる。</span>
<span class="synIdentifier">}</span> <span class="synStatement">catch</span>(e) <span class="synIdentifier">{</span>
<span class="synStatement">throw</span> e;
<span class="synIdentifier">}</span>
done();
<span class="synIdentifier">}</span>);
<span class="synIdentifier">}</span>);
</pre>
<p>さらに新たにnode_modulesがロードされても更新されるように、package.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>にも以下の記述をした。</p>
<pre class="code lang-json" data-lang="json" data-unlink>"<span class="synStatement">scripts</span>": <span class="synSpecial">{</span>
"<span class="synStatement">postinstall</span>": "<span class="synConstant">node ./node_modules/.bin/gulp bundle-dll</span>"
<span class="synSpecial">}</span>
</pre>
<p>これで<code>npm install/yarn add</code>してもbundleが更新される。</p>
<p>あと、はまった点として、予めbundleしておく中にreactとかも入れていたので、process.env.NODE_ENVの値に困ってしまった。<br/>
なので、dllファイルをdev用、production用と2つ用意して対処した。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> bundleDll(env, done) <span class="synIdentifier">{</span>
<span class="synStatement">const</span> webpack = require(<span class="synConstant">'webpack'</span>);
<span class="synStatement">const</span> config = _.clone(require(<span class="synConstant">'./webpack.dll.config.js'</span>));
config.entry = _.clone(config.entry);
config.output = _.clone(config.output);
config.output.path = path.join(__dirname, <span class="synConstant">'dll'</span>);
config.plugins = config.plugins.slice();
<span class="synStatement">const</span> vendor = config.entry.vendor;
<span class="synStatement">const</span> keyName = `vendor.$<span class="synIdentifier">{</span>env<span class="synIdentifier">}</span>`;
config.entry = <span class="synIdentifier">{}</span>;
config.entry<span class="synIdentifier">[</span>keyName<span class="synIdentifier">]</span> = vendor;
<span class="synStatement">if</span> (env === <span class="synConstant">'production'</span>) <span class="synIdentifier">{</span>
config.plugins.push(<span class="synStatement">new</span> webpack.optimize.UglifyJsPlugin());
<span class="synIdentifier">}</span>
config.plugins.push(<span class="synStatement">new</span> webpack.DefinePlugin(<span class="synIdentifier">{</span>
<span class="synConstant">'process.env.NODE_ENV'</span>: `<span class="synConstant">'${env}'</span>`
<span class="synIdentifier">}</span>));
<span class="synStatement">const</span> compiler = webpack(config);
compiler.run((err, stats) => <span class="synIdentifier">{</span>
<span class="synStatement">if</span> (err) <span class="synIdentifier">{</span>
<span class="synStatement">throw</span> err;
<span class="synIdentifier">}</span>
console.log(stats.toString(<span class="synConstant">'minimal'</span>));
done();
<span class="synIdentifier">}</span>);
<span class="synIdentifier">}</span>
<span class="synComment">/**</span>
<span class="synComment"> * dev dll</span>
<span class="synComment"> */</span>
gulp.task(<span class="synConstant">'bundle-dev-dll'</span>, done => <span class="synIdentifier">{</span>
bundleDll(<span class="synConstant">'development'</span>, done);
<span class="synIdentifier">}</span>);
<span class="synComment">/**</span>
<span class="synComment"> * production dll</span>
<span class="synComment"> */</span>
gulp.task(<span class="synConstant">'bundle-prod-dll'</span>, done => <span class="synIdentifier">{</span>
bundleDll(<span class="synConstant">'production'</span>, done);
<span class="synIdentifier">}</span>);
gulp.task(<span class="synConstant">'bundle-dll'</span>, () => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> rs = require(<span class="synConstant">'run-sequence'</span>);
<span class="synStatement">return</span> rs(
<span class="synConstant">'clean-dll'</span>,
<span class="synConstant">'bundle-dev-dll'</span>,
<span class="synConstant">'bundle-prod-dll'</span>
);
<span class="synIdentifier">}</span>);
</pre>
<h2>まとめ</h2>
<p>jspmよりwebpackの方が快適でした。<br/>
さよならjspm</p>
brn_take
V8 Iginition Interpreter
hatenablog://entry/10328749687241997663
2017-05-01T17:34:33+09:00
2017-05-01T17:34:33+09:00 以前、東京Node学園25時限目で発表した内容を修正して書いていこうと思う。 というわけで、V8にバイトコードインタープリタ Ignition が搭載された。 このインタープリタは単純そうに見えて非常にわかりづらいので解説していく。 バイトコードインタープリタとは インタープリタとは、ソースコードを逐次実行する形式のエンジン。 今までのV8はソースコードを即アセンブラにコンパイルし実行していたが、 インタープリタはそれとは違い、一度ソースコードを高レベルなバイト命令に変換し、そのバイト命令を逐次実行していく。 高級アセンブラみたいな感じ。 Ignition概要 Ignitionはレジスタベース…
<p>以前、東京Node学園25時限目で発表した内容を修正して書いていこうと思う。</p>
<p>というわけで、V8に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a> Ignition が搭載された。<br/>
この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>は単純そうに見えて非常にわかりづらいので解説していく。</p>
<h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>とは</h2>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>とは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を逐次実行する形式のエンジン。<br/>
今までのV8は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を即<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%E9">アセンブラ</a>に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>し実行していたが、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>はそれとは違い、一度<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を高レベルなバイト命令に変換し、そのバイト命令を逐次実行していく。<br/>
高級<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%E9">アセンブラ</a>みたいな感じ。</p>
<h2>Ignition概要</h2>
<p>Ignitionは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>ベースの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>である。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>のスタックベースとは違って、CPUの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>に実際に値を割り付けて実行する。</p>
<p>Ignitionは<code>BytecodeHandler</code>と呼ばれる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>処理関数を予め生成しておき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>から配列のインデックスを取得、<br/>
そのインデックスに生成された処理関数を割り当て、Bytecodeの配列を次々巡回して、対応するインデックスの関数を呼び出しコードを実行する。</p>
<p>JSで非常に単<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BD%E3%B2%BD">純化</a>されたコードを書くと以下の様になる。</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> Bytecodes = <span class="synIdentifier">[</span>0,1,2,3,4,5<span class="synIdentifier">]</span>;
<span class="synIdentifier">var</span> index = 0;
<span class="synIdentifier">function</span> dispatch(next) <span class="synIdentifier">{</span>BytecodeHandlers<span class="synIdentifier">[</span>next<span class="synIdentifier">]</span>();<span class="synIdentifier">}</span>
<span class="synStatement">const</span> BytecodeHandlers = <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span><span class="synConstant">'0'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">[</span><span class="synConstant">'1'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">[</span><span class="synConstant">'2'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">[</span><span class="synConstant">'3'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">[</span><span class="synConstant">'4'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">[</span><span class="synConstant">'5'</span><span class="synIdentifier">]</span>() <span class="synIdentifier">{</span>...; dispatch(Bytecodes<span class="synIdentifier">[</span>index++<span class="synIdentifier">]</span>)<span class="synIdentifier">}</span>,
<span class="synIdentifier">}</span>
</pre>
<p>このモデルを念頭にV8のコードベースを確認していく。</p>
<h2>Ignitionの構造</h2>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>生成までの道のり</h3>
<p>Ignitionは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a> ASTから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を生成する。<br/>
この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>生成のステップを確認していく。</p>
<p><code>BytecodeGenerator</code>が<code>AstVisitor</code>を実装しているので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a> ASTを巡回しながら対応している<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を生成していく。
<code>BytecodeGenerator</code>は<code>src/interpreter/bytecode-generator.h</code>にあり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>生成メソッドは<code>BytecodeGenerator::GenerateBytecode</code>である。</p>
<p>さて、<code>BytecodeGenerator::GenerateBytecode</code>はどこから呼ばれるかというと、<code>InterpreterCompilationJob::ExecuteJobImpl(src/interpreter/interpreter.cc)</code>内で呼び出される。<br/>
<code>InterpreterCompilationJob::ExecuteJobImpl</code>は<code>static Interpreter::NewCompilationJob</code>で実行される。</p>
<p><code>Interpreter::NewCompilationJob</code>の階層は以下のようになっている。</p>
<pre class="code" data-lang="" data-unlink>Interpreter::NewCompilationJob
|
InterpreterCompilationJob::ExecuteJobImpl
|
BytecodeGenerator::GenerateBytecode</pre>
<p>この<code>static Interpreter::NewCompilationJob</code>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>パイプラインのジョブを生成するメソッドなので、<code>compiler.cc(src/compiler.cc)</code>を見ていこう。</p>
<p><code>compiler.cc(src/compiler.cc)</code>は非常に複雑でわかりづらい呼び出し階層をもっており、さらにオプションの設定パーサーの設定も相まって非常に読みづらい。<br/>
<code>static Interpreter::NewCompilationJob</code>を呼び出すまでのコールスタックは以下の様になっている。</p>
<pre class="code" data-lang="" data-unlink>ScriptCompiler::Compile
|
ScriptCompiler::CompileUnboundInternal
|
Compiler::GetSharedFunctionInfoForScript
|
Compiler::CompileToplevel
|
CompileUnoptimizedCode(compiler.cc)
|
CompileUnoptimizedInnerFunctions
|
GenerateUnoptimizedCode
|
GetUnoptimizedCompilationJob
|
---- Iginitionオプションによってfullcodegenと分岐
| |
Interpreter::NewCompilationJob
|
FullCodeGenerator::NewCompilationJob</pre>
<p><code>ScriptCompiler::Compile</code>がV8の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a> Compilerのエントリーポイントとなっており、そこから順次関数を呼び出し、最終的に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Interpreter">Interpreter</a>のJobを生成する。</p>
<p>最終的な<code>BytecodeGenerator::GenerateBytecode</code>までの呼び出しコールスタックは以下のようになる。</p>
<pre class="code" data-lang="" data-unlink>ScriptCompiler::Compile
|
ScriptCompiler::CompileUnboundInternal
|
Compiler::GetSharedFunctionInfoForScript
|
Compiler::CompileToplevel
|
CompileUnoptimizedCode(compiler.cc)
|
CompileUnoptimizedInnerFunctions
|
GenerateUnoptimizedCode
|
GetUnoptimizedCompilationJob
|
---- Iginitionオプションによってfullcodegenと分岐
| |
| FullCodeGenerator::NewCompilationJob
|
Interpreter::NewCompilationJob
|
InterpreterCompilationJob::ExecuteJobImpl
|
BytecodeGenerator::GenerateBytecode</pre>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>生成</h3>
<p>さて、呼び出し階層を把握したところで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>の生成方法を見ていく。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>生成は先程も書いたとおりAstVisitorを継承しているので、各種<code>Visit***</code>メソッドを実装する必要がある。<br/>
ので、各種<code>Visit***</code>の実装を見ていけば何をしているか理解できるはず。</p>
<p>ただ、闇雲にコードを見ても<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>自体は理解できないので、一旦trace_bytecodeでd8を実行してみる。</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a></p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> a = 1;
</pre>
<p>bytecodes</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">0 </span> [<span class="synIdentifier">generating</span> <span class="synIdentifier">bytecode</span> <span class="synIdentifier">for</span> <span class="synIdentifier">function</span>: ]
<span class="synConstant">1</span> <span class="synIdentifier">Parameter</span> <span class="synIdentifier">count</span> <span class="synConstant">1</span>
<span class="synConstant">2</span> <span class="synIdentifier">Frame</span> <span class="synIdentifier">size</span> <span class="synConstant">32</span>
<span class="synConstant">3</span> <span class="synConstant">0x3f5e20aafdf6</span> @ <span class="synConstant">0 </span>: 0<span class="synConstant">9</span> <span class="synConstant">00 </span> <span class="synIdentifier">LdaConstant</span> [0]
<span class="synConstant">4</span> <span class="synConstant">0x3f5e20aafdf8</span> @ <span class="synConstant">2</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f9</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r1</span>
<span class="synConstant">5</span> <span class="synConstant">0x3f5e20aafdfa</span> @ <span class="synConstant">4</span> : <span class="synConstant">02 </span> <span class="synIdentifier">LdaZero</span>
<span class="synConstant">6</span> <span class="synConstant">0x3f5e20aafdfb</span> @ <span class="synConstant">5</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f8</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r2</span>
<span class="synConstant">7</span> <span class="synConstant">0x3f5e20aafdfd</span> @ <span class="synConstant">7</span> : <span class="synConstant">20</span> <span class="synIdentifier">fe</span> <span class="synIdentifier">f7</span> <span class="synIdentifier">Mov</span> <<span class="synIdentifier">closure</span>>, <span class="synIdentifier">r3</span>
<span class="synConstant">8</span> <span class="synConstant">0x3f5e20aafe00</span> @ <span class="synConstant">10</span> : <span class="synConstant">55</span> <span class="synIdentifier">aa</span> <span class="synConstant">01 </span><span class="synIdentifier">f9</span> <span class="synConstant">03 </span> <span class="synIdentifier">CallRuntime</span> [<span class="synIdentifier">DeclareGlobalsForInterpreter</span>], <span class="synIdentifier">r1</span>-<span class="synIdentifier">r3</span>
<span class="synConstant">9</span> <span class="synConstant">0 </span><span class="synIdentifier">E</span>> <span class="synConstant">0x3f5e20aafe05</span> @ <span class="synConstant">15</span> : <span class="synConstant">92</span> <span class="synIdentifier">StackCheck</span>
<span class="synConstant">10</span> <span class="synConstant">116</span> <span class="synIdentifier">S</span>> <span class="synConstant">0x3f5e20aafe06</span> @ <span class="synConstant">16</span> : 0<span class="synConstant">9</span> <span class="synConstant">01 </span> <span class="synIdentifier">LdaConstant</span> [<span class="synConstant">1</span>]
<span class="synConstant">11</span> <span class="synConstant">0x3f5e20aafe08</span> @ <span class="synConstant">18</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f9</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r1</span>
<span class="synConstant">12</span> <span class="synConstant">0x3f5e20aafe0a</span> @ <span class="synConstant">20</span> : <span class="synConstant">02 </span> <span class="synIdentifier">LdaZero</span>
<span class="synConstant">13</span> <span class="synConstant">0x3f5e20aafe0b</span> @ <span class="synConstant">21</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f8</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r2</span>
<span class="synConstant">14</span> <span class="synConstant">0x3f5e20aafe0d</span> @ <span class="synConstant">23</span> : <span class="synConstant">03 01 </span> <span class="synIdentifier">LdaSmi</span> [<span class="synConstant">1</span>]
<span class="synConstant">15</span> <span class="synConstant">0x3f5e20aafe0f</span> @ <span class="synConstant">25</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f7</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r3</span>
<span class="synConstant">16</span> <span class="synConstant">0x3f5e20aafe11</span> @ <span class="synConstant">27</span> : <span class="synConstant">55</span> <span class="synIdentifier">ab</span> <span class="synConstant">01 </span><span class="synIdentifier">f9</span> <span class="synConstant">03 </span> <span class="synIdentifier">CallRuntime</span> [<span class="synIdentifier">InitializeVarGlobal</span>], <span class="synIdentifier">r1</span>-<span class="synIdentifier">r3</span>
<span class="synConstant">17</span> <span class="synConstant">0x3f5e20aafe16</span> @ <span class="synConstant">32</span> : <span class="synConstant">04 </span> <span class="synIdentifier">LdaUndefined</span>
<span class="synConstant">18</span> <span class="synConstant">118</span> <span class="synIdentifier">S</span>> <span class="synConstant">0x3f5e20aafe17</span> @ <span class="synConstant">33</span> : <span class="synConstant">96</span> <span class="synIdentifier">Return</span>
<span class="synConstant">19</span> <span class="synIdentifier">Constant</span> <span class="synIdentifier">pool</span> (<span class="synIdentifier">size</span> = <span class="synConstant">2</span>)
<span class="synConstant">20</span> <span class="synConstant">0x3f5e20aafda1</span>: [<span class="synIdentifier">FixedArray</span>]
<span class="synConstant">21</span> - <span class="synIdentifier">map</span> = <span class="synConstant">0x1cfd2a282309</span> <<span class="synIdentifier">Map</span>(<span class="synIdentifier">FAST_HOLEY_ELEMENTS</span>)>
<span class="synConstant">22</span> - <span class="synIdentifier">length</span>: <span class="synConstant">2</span>
<span class="synConstant">23</span> 0: <span class="synConstant">0x3f5e20aafd71</span> <<span class="synIdentifier">FixedArray</span>[<span class="synConstant">4</span>]>
<span class="synConstant">24</span> <span class="synConstant">1</span>: <span class="synConstant">0x2315b1a87ef9</span> <<span class="synIdentifier">String</span>[<span class="synConstant">1</span>]: <span class="synIdentifier">a</span>>
</pre>
<p>そうするとこのような結果が得られる。</p>
<p>さて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を出力したのはいいが、見方がわからないと意味が無いので、見方も解説。</p>
<p>ここは関数のbytecodeの場合に関数名が入る。今回はグローバルなので空。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">0 </span> [<span class="synIdentifier">generating</span> <span class="synIdentifier">bytecode</span> <span class="synIdentifier">for</span> <span class="synIdentifier">function</span>: ]
</pre>
<p>これはstackのパラメータの数。<br/>
今回の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>はグローバルなので無視。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">1</span> <span class="synIdentifier">Parameter</span> <span class="synIdentifier">count</span> <span class="synConstant">1</span>
</pre>
<p>FrameSizeは割り当てた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>の数 * ポインタのサイズ<br/>
ポインタのサイズは大体の環境で、32bitでは4byte、64bitでは8byteになる。<br/>
今回の場合、割り当てた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>数の数が4 64bit環境なので、ポインタサイズが8byte<br/>
<code>4 * 8 = 32</code>となる。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">2</span> <span class="synIdentifier">Frame</span> <span class="synIdentifier">size</span> <span class="synConstant">32</span>
</pre>
<p>各バイト列は
<code>現在のアドレス アドレスのオフセット バイトコードの数値 バイトコードの名前 オペランド</code>
となっている。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">3</span> <span class="synConstant">0x3f5e20aafdf6</span> @ <span class="synConstant">0 </span>: 0<span class="synConstant">9</span> <span class="synConstant">00 </span> <span class="synIdentifier">LdaConstant</span> [0]
</pre>
<p>ここは定数値プールの中身。<br/>
今回は変数名のaがプールされている。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">19</span> <span class="synIdentifier">Constant</span> <span class="synIdentifier">pool</span> (<span class="synIdentifier">size</span> = <span class="synConstant">2</span>)
<span class="synConstant">20</span> <span class="synConstant">0x3f5e20aafda1</span>: [<span class="synIdentifier">FixedArray</span>]
<span class="synConstant">21</span> - <span class="synIdentifier">map</span> = <span class="synConstant">0x1cfd2a282309</span> <<span class="synIdentifier">Map</span>(<span class="synIdentifier">FAST_HOLEY_ELEMENTS</span>)>
<span class="synConstant">22</span> - <span class="synIdentifier">length</span>: <span class="synConstant">2</span>
<span class="synConstant">23</span> 0: <span class="synConstant">0x3f5e20aafd71</span> <<span class="synIdentifier">FixedArray</span>[<span class="synConstant">4</span>]>
<span class="synConstant">24</span> <span class="synConstant">1</span>: <span class="synConstant">0x2315b1a87ef9</span> <<span class="synIdentifier">String</span>[<span class="synConstant">1</span>]: <span class="synIdentifier">a</span>>
</pre>
<p>さてこれらの情報を踏まえて、先程の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を見ていこう。</p>
<p>以下の部分はすっとばしてよい。ここは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>の準備なので。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink><span class="synConstant">3</span> <span class="synConstant">0x3f5e20aafdf6</span> @ <span class="synConstant">0 </span>: 0<span class="synConstant">9</span> <span class="synConstant">00 </span> <span class="synIdentifier">LdaConstant</span> [0]
<span class="synConstant">4</span> <span class="synConstant">0x3f5e20aafdf8</span> @ <span class="synConstant">2</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f9</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r1</span>
<span class="synConstant">5</span> <span class="synConstant">0x3f5e20aafdfa</span> @ <span class="synConstant">4</span> : <span class="synConstant">02 </span> <span class="synIdentifier">LdaZero</span>
<span class="synConstant">6</span> <span class="synConstant">0x3f5e20aafdfb</span> @ <span class="synConstant">5</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f8</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r2</span>
<span class="synConstant">7</span> <span class="synConstant">0x3f5e20aafdfd</span> @ <span class="synConstant">7</span> : <span class="synConstant">20</span> <span class="synIdentifier">fe</span> <span class="synIdentifier">f7</span> <span class="synIdentifier">Mov</span> <<span class="synIdentifier">closure</span>>, <span class="synIdentifier">r3</span>
<span class="synConstant">8</span> <span class="synConstant">0x3f5e20aafe00</span> @ <span class="synConstant">10</span> : <span class="synConstant">55</span> <span class="synIdentifier">aa</span> <span class="synConstant">01 </span><span class="synIdentifier">f9</span> <span class="synConstant">03 </span> <span class="synIdentifier">CallRuntime</span> [<span class="synIdentifier">DeclareGlobalsForInterpreter</span>], <span class="synIdentifier">r1</span>-<span class="synIdentifier">r3</span>
<span class="synConstant">9</span> <span class="synConstant">0 </span><span class="synIdentifier">E</span>> <span class="synConstant">0x3f5e20aafe05</span> @ <span class="synConstant">15</span> : <span class="synConstant">92</span> <span class="synIdentifier">StackCheck</span>
</pre>
<p>本番はここから<br/>
解説はコード中に書いていく。</p>
<pre class="code lang-asm" data-lang="asm" data-unlink> <span class="synComment">// 定数プールのインデックス1(変数名a)から値をaccumulatorにロードする。</span>
<span class="synConstant">10</span> <span class="synConstant">116</span> <span class="synIdentifier">S</span>> <span class="synConstant">0x3f5e20aafe06</span> @ <span class="synConstant">16</span> : 0<span class="synConstant">9</span> <span class="synConstant">01 </span> <span class="synIdentifier">LdaConstant</span> [<span class="synConstant">1</span>]
<span class="synConstant">11</span> <span class="synComment">// accumulator(変数名a)からr1レジスタに値をロードする。</span>
<span class="synConstant">12</span> <span class="synConstant">0x3f5e20aafe08</span> @ <span class="synConstant">18</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f9</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r1</span>
<span class="synConstant">13</span> <span class="synComment">// accumulatorに0をロードする。</span>
<span class="synConstant">14</span> <span class="synConstant">0x3f5e20aafe0a</span> @ <span class="synConstant">20</span> : <span class="synConstant">02 </span> <span class="synIdentifier">LdaZero</span>
<span class="synConstant">15</span> <span class="synComment">// accumulator(0)からr2レジスタに値をロードする。</span>
<span class="synConstant">16</span> <span class="synConstant">0x3f5e20aafe0b</span> @ <span class="synConstant">21</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f8</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r2</span>
<span class="synConstant">17</span> <span class="synComment">// accumulatorに即値1をロードする。</span>
<span class="synConstant">18</span> <span class="synConstant">0x3f5e20aafe0d</span> @ <span class="synConstant">23</span> : <span class="synConstant">03 01 </span> <span class="synIdentifier">LdaSmi</span> [<span class="synConstant">1</span>]
<span class="synConstant">19</span> <span class="synComment">// accumulator(1)からr3レジスタに値をロードする。</span>
<span class="synConstant">20</span> <span class="synConstant">0x3f5e20aafe0f</span> @ <span class="synConstant">25</span> : <span class="synConstant">1</span><span class="synIdentifier">f</span> <span class="synIdentifier">f7</span> <span class="synIdentifier">Star</span> <span class="synIdentifier">r3</span>
<span class="synConstant">21</span> <span class="synComment">// r1レジスタからr3レジスタの値(a, 0, 1)を使ってInitializeVarGlobalランタイムを呼び出す。</span>
<span class="synConstant">22</span> <span class="synConstant">0x3f5e20aafe11</span> @ <span class="synConstant">27</span> : <span class="synConstant">55</span> <span class="synIdentifier">ab</span> <span class="synConstant">01 </span><span class="synIdentifier">f9</span> <span class="synConstant">03 </span> <span class="synIdentifier">CallRuntime</span> [<span class="synIdentifier">InitializeVarGlobal</span>], <span class="synIdentifier">r1</span>-<span class="synIdentifier">r3</span>
<span class="synConstant">23</span> <span class="synComment">// accumulatorにundefinedをセット</span>
<span class="synConstant">24</span> <span class="synConstant">0x3f5e20aafe16</span> @ <span class="synConstant">32</span> : <span class="synConstant">04 </span> <span class="synIdentifier">LdaUndefined</span>
<span class="synConstant">25</span> <span class="synComment">// 終了</span>
<span class="synConstant">26</span> <span class="synConstant">118</span> <span class="synIdentifier">S</span>> <span class="synConstant">0x3f5e20aafe17</span> @ <span class="synConstant">33</span> : <span class="synConstant">96</span> <span class="synIdentifier">Return</span>
</pre>
<p>これが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>の実行である。<br/>
ちなみにCallRuntimeの場合、各Runtime毎に呼び出し規約が決まっているので、それぞれに合わせた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>の割り当てが必要になる。<br/>
<code>InitializeVarGlobal</code>ランタイム呼び出しは以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>を期待している。</p>
<ul>
<li>r0 = 束縛される変数名</li>
<li>r1 = LaunguageMode SLOPPY(通常) STRICT(strictモード) LAUNGUAGE_END(不明)</li>
<li>r2 = 束縛される値</li>
</ul>
<p>そのため、上記のコードは</p>
<ul>
<li>accumulatorに値をロード</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%BF">レジスタ</a>に値をロード</li>
</ul>
<p>を繰り返して、Runtime呼び出しのコードを生成している。</p>
<p>とこの調子でIgnitionは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を実行していくが、<br/>
その<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を実行しているのは<code>BytecodeHandler</code>とよばれるクラスである。</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>実行</h3>
<h4>BytecodeHandler</h4>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>の実行は<code>BytecodeHandler</code>によって行われる。<br/>
この<code>BytecodeHandler</code>はV8の初期化時に生成され、配置される。</p>
<p>以下が<code>BytecodeHandler</code>の例である。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>IGNITION_HANDLER(LdaZero, InterpreterAssembler) {
Node* zero_value = NumberConstant(<span class="synConstant">0.0</span>);
SetAccumulator(zero_value);
Dispatch();
}
</pre>
<p>LadZeroの処理を行う<code>BytecoeHandler</code>で、中では単純にaccumulatorに0をセットするだけ。<br/>
このような調子で各<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>につき一つの<code>BytecodeHandler</code>が実装されている。</p>
<p>各<code>BytecodeHandler</code>は直接次の<code>BytecodeHandler</code>を呼び出す。</p>
<p>このDispatchが次の<code>BytecodeHandler</code>を呼び出している。</p>
<pre class="code" data-lang="" data-unlink>Dispatch();</pre>
<p>しかし、この<code>BytecodeHandler</code>の実装をみるとわかるのだが、BytecodeHandlerはあくまで、<br/>
実行予定Nodeを組み立てているだけで、実際には何かを実行するわけではない。</p>
<p>Ignition<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>は最初にBytecodeの処理手順をグラフノードで生成し、生成したグラフからマシンコードを生成する。<br/>
これをBytecodeのdispatch-tableに設定することで、各<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>毎に行う処理が設定された<code>BytecodeHandler</code>が実装される。</p>
<p><strong>以下の図はBytecodeHandlerの生成</strong></p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brn_take/20170501/20170501171341.png" alt="f:id:brn_take:20170501171341p:plain" title="f:id:brn_take:20170501171341p:plain" class="hatena-fotolife" itemprop="image"></span></p>
<h4>InterpreterEntryTrampoline</h4>
<p>Ignitionは最終的に<code>BytecodeArray</code>を生成し終わった後に、<br/>
<code>InterpreterEntryTrampoline</code>というbuiltinsからIgnitionのDispatchTableを発火するコード生成し、<br/>
BytecodeArrayから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>を取り出し、対応するDispatchTableの処理を実行して回っていく。</p>
<p><strong>以下の図はIgnitionが実行される様子</strong></p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brn_take/20170501/20170501171955.png" alt="f:id:brn_take:20170501171955p:plain" title="f:id:brn_take:20170501171955p:plain" class="hatena-fotolife" itemprop="image"></span></p>
<h3>まとめ</h3>
<p>一通りIgnitionの実行パスを眺めた。<br/>
また、Ignitionの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%C8%A5%B3%A1%BC%A5%C9">バイトコード</a>が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%E9">アセンブラ</a>コードのキーとして振る舞い、<br/>
実際にはベースラインで生成されたコードが実行されている事を確認した。</p>
<p>TurboFan経由の最適化部分等については今後の記事を書く予定。</p>
brn_take
typescript 2.3 RC
hatenablog://entry/10328749687237535333
2017-04-17T12:12:31+09:00
2017-04-17T12:12:31+09:00 typescript 2.3 rcがアナウンスされた 主な変更点は以下の通り –strictオプションの追加 以下の型チェックオプションを有効にする –noImplicitAny –strictNullChecks –noImplicitThis –alwaysStrict 以下の様に部分的にOFFにもできる { "compilerOptions": { "strict": true, "noImplicitThis": false } } generateor、iteratorのES3、ES5対応 --downlevelIterationフラグをONにすることで、 generatorとite…
<p>typescript 2.3 rcがアナウンスされた</p>
<p>主な変更点は以下の通り</p>
<h2>–strictオプションの追加</h2>
<p>以下の型チェックオプションを有効にする</p>
<ul>
<li>–noImplicitAny</li>
<li>–strictNullChecks</li>
<li>–noImplicitThis</li>
<li>–alwaysStrict</li>
</ul>
<p>以下の様に部分的にOFFにもできる</p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>
"<span class="synStatement">compilerOptions</span>": <span class="synSpecial">{</span>
"<span class="synStatement">strict</span>": <span class="synConstant">true</span>,
"<span class="synStatement">noImplicitThis</span>": <span class="synConstant">false</span>
<span class="synSpecial">}</span>
<span class="synSpecial">}</span>
</pre>
<h2>generateor、<a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>のES3、ES5対応</h2>
<p><code>--downlevelIteration</code>フラグをONにすることで、<br/>
generatorと<a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>がES3、ES5共にトランスパイルできるようになった。</p>
<h2>Async generators & iterators</h2>
<p>ES Proposalのasync <a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>とasync generatorに対応した。</p>
<p>async <a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>の構文</p>
<pre class="code" data-lang="" data-unlink>for await (let item of items) {
/*...*/
}</pre>
<p>async generatorの構文</p>
<pre class="code" data-lang="" data-unlink>async function* asyncGenName() {
/*...*/
}</pre>
<p>ただし、Async generatorとAsync <a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>を使うためには、
<code>Symbol.asyncIterator</code>が必要なので、以下のようにして、polyfilを作る必要がある。</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">(</span>Symbol <span class="synStatement">as</span> <span class="synType">any</span><span class="synStatement">)</span>.asyncIterator <span class="synStatement">=</span> Symbol.asyncIterator <span class="synConstant">||</span> Symbol.<span class="synStatement">from(</span><span class="synConstant">"Symbol.asyncIterator"</span><span class="synStatement">);</span>
</pre>
<p>か</p>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">(</span>Symbol <span class="synStatement">as</span> <span class="synType">any</span><span class="synStatement">)</span>.asyncIterator <span class="synStatement">=</span> Symbol.asyncIterator <span class="synConstant">||</span> <span class="synConstant">"__@@asyncIterator__"</span><span class="synStatement">;</span>
</pre>
<p>まとめ</p>
<p>遂に(async) generator、<a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>がES3、ES5でも使えるようになってよかったね。</p>
brn_take
gulp-uglifyでプロパティ名をmangleする
hatenablog://entry/10328749687236837972
2017-04-14T16:00:18+09:00
2017-04-14T16:00:18+09:00 全然ドキュメントがなかったので備忘録。 ClosureCompilerみたいにプロパティ名もmangleしたい。 こんなの const x = { doSomething() {return ...} doNothing() {} } x.doSomething(); x.doNothing(); const x = { a() {return ...} b() {} } x.a(); x.b(); 設定 gulp.task('minify', () => { const uglify = require('gulp-uglify'); gulp.src(['src/index.js']) .…
<p>全然ドキュメントがなかったので備忘録。</p>
<p>ClosureCompilerみたいにプロパティ名もmangleしたい。<br/>
こんなの</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> x = <span class="synIdentifier">{</span>
doSomething() <span class="synIdentifier">{</span><span class="synStatement">return</span> ...<span class="synIdentifier">}</span>
doNothing() <span class="synIdentifier">{}</span>
<span class="synIdentifier">}</span>
x.doSomething();
x.doNothing();
</pre>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> x = <span class="synIdentifier">{</span>
a() <span class="synIdentifier">{</span><span class="synStatement">return</span> ...<span class="synIdentifier">}</span>
b() <span class="synIdentifier">{}</span>
<span class="synIdentifier">}</span>
x.a();
x.b();
</pre>
<h2>設定</h2>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>gulp.task(<span class="synConstant">'minify'</span>, () => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> uglify = require(<span class="synConstant">'gulp-uglify'</span>);
gulp.src(<span class="synIdentifier">[</span><span class="synConstant">'src/index.js'</span><span class="synIdentifier">]</span>)
.pipe(uglify(<span class="synIdentifier">{</span>
mangle: <span class="synConstant">true</span>,
compress: <span class="synConstant">true</span>,
mangleProperties: <span class="synIdentifier">{</span>
ignore_quoted: <span class="synConstant">true</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>));
<span class="synIdentifier">}</span>);
</pre>
<p>これが基本。</p>
<p>で、特定のプロパティ名のリネームを防ぎたい場合は、<code>reserved</code>という機能を使う</p>
<p><code>gulpfile.js</code></p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>gulp.task(<span class="synConstant">'minify'</span>, () => <span class="synIdentifier">{</span>
<span class="synStatement">const</span> uglify = require(<span class="synConstant">'gulp-uglify'</span>);
<span class="synStatement">const</span> Uglify = require(<span class="synConstant">'uglify-js'</span>);
<span class="synIdentifier">let</span> reserved = Uglify.readReservedFile(<span class="synConstant">'./reserved.json'</span>);
reserved = Uglify.readDefaultReservedFile(reserved);
gulp.src(<span class="synIdentifier">[</span><span class="synConstant">'src/index.js'</span><span class="synIdentifier">]</span>)
.pipe(uglify(<span class="synIdentifier">{</span>
mangle: <span class="synConstant">true</span>,
compress: <span class="synConstant">true</span>,
mangleProperties: <span class="synIdentifier">{</span>
reserved: reserved.props,
ignore_quoted: <span class="synConstant">true</span>
<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>));
<span class="synIdentifier">}</span>);
</pre>
<p><code>reserved.json</code></p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>
"<span class="synStatement">vars</span>": <span class="synSpecial">[</span> <span class="synSpecial">]</span>,
"<span class="synStatement">props</span>": <span class="synSpecial">[</span> "<span class="synConstant">doSomething</span>", "<span class="synConstant">doNothing</span>" <span class="synSpecial">]</span>
<span class="synSpecial">}</span>
</pre>
<p>これで<code>doSomething</code>と<code>doNothing</code>はmangleされなくなる。</p>
<p>上記の記述の</p>
<pre class="code" data-lang="" data-unlink>let reserved = Uglify.readReservedFile('./reserved.json');
reserved = Uglify.readDefaultReservedFile(reserved);</pre>
<p>の部分ではUglifyjs2が必要なので、別途<code>npm install</code>してくだはい。<br/>
後は必要なプロパティ名をガンガンpropsに突っ込んでいけばOK。</p>
<p>ただ、ClosureCompilerもそうだけど、プロパティ名のmangleにはそれなりのリスクがあるので、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>後にも統合テストをしたほうが良い。</p>
<h2>まとめ</h2>
<p>gulp-uglifyが不親切でつらい。</p>
brn_take
ES6のComputedPropertyNameとトランスパイラ
hatenablog://entry/10328749687236193381
2017-04-11T11:11:11+09:00
2017-04-11T11:11:11+09:00 ES6のComputed Property Nameは非常に便利だが、トランスパイラを併用すると問題が起きがちである。 それを確認していく。 Base const SYMBOL = Symbol('foo-bar-baz'); const obj = { [SYMBOL]: 1, name: 'brn', job: 'engineer' } typescript var SYMBOL = Symbol('foo-bar-baz'); var obj = (_a = {}, _a[SYMBOL] = 1, _a.name = 'brn', _a.job = 'engineer', _a); va…
<p>ES6のComputed Property Nameは非常に便利だが、トランスパイラを併用すると問題が起きがちである。<br/>
それを確認していく。</p>
<h3>Base</h3>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">const</span> SYMBOL <span class="synStatement">=</span> Symbol<span class="synStatement">(</span><span class="synConstant">'foo-bar-baz'</span><span class="synStatement">);</span>
<span class="synStatement">const</span> obj <span class="synStatement">=</span> <span class="synIdentifier">{</span>
<span class="synIdentifier">[</span>SYMBOL<span class="synIdentifier">]</span>: <span class="synConstant">1</span><span class="synStatement">,</span>
name: <span class="synConstant">'brn'</span><span class="synStatement">,</span>
job: <span class="synConstant">'engineer'</span>
<span class="synIdentifier">}</span>
</pre>
<h3>typescript</h3>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> SYMBOL = Symbol(<span class="synConstant">'foo-bar-baz'</span>);
<span class="synIdentifier">var</span> obj = (_a = <span class="synIdentifier">{}</span>,
_a<span class="synIdentifier">[</span>SYMBOL<span class="synIdentifier">]</span> = 1,
_a.name = <span class="synConstant">'brn'</span>,
_a.job = <span class="synConstant">'engineer'</span>,
_a);
<span class="synIdentifier">var</span> _a;
</pre>
<h3>babel</h3>
<pre class="code" data-lang="" data-unlink>'use strict';
var _obj;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var SYMBOL = Symbol('foo-bar-baz');
var obj = (_obj = {}, _defineProperty(_obj, SYMBOL, 1), _defineProperty(_obj, 'name', 'brn'), _defineProperty(_obj, 'job', 'engineer'), _obj);</pre>
<p>これを見るとすぐわかるが、トランスパイルされたjs内では、<br/>
オブジェクトを一度構築してからプロパティを追加している。<br/>
SYMBOLプロパティは仕方ないが、他のプロパティも別途追加になっているため、(Babelはもっとひどくて、definePropertyになっている…)<br/>
例えば、v8ではDictionaryモードになってしまう。つまりパフォーマンスに重大な問題を引き起こす。<br/>
そのため、パフォーマンスクリティカルな場合はComputedPropertyの利用は避けよう。</p>
brn_take
Ecmascript decorator を使ってキャッシュする
hatenablog://entry/10328749687232453026
2017-03-31T00:00:03+09:00
2017-03-31T00:00:35+09:00 表題の通り ES proposal のdecoratorをつかってメモ化のようなことをするライブラリを前に書いたので、 キレイにして、GitHubに公開した。 GitHub - brn/cache-decorator: javascript method/function cache decorator. インストール npm install cache-decorator --save か yarn add cache-decorator --save 使い方 メソッドのキャッシュ For javascript/babel import { cache, CacheType, CacheSc…
<p>表題の通り
ES proposal のdecoratorをつかってメモ化のようなことをするライブラリを前に書いたので、
キレイにして、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>に公開した。</p>
<p><a href="https://github.com/brn/cache-decorator">GitHub - brn/cache-decorator: javascript method/function cache decorator.</a></p>
<h2>インス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%EB">トール</a></h2>
<pre class="code" data-lang="" data-unlink>npm install cache-decorator --save</pre>
<p>か</p>
<pre class="code" data-lang="" data-unlink>yarn add cache-decorator --save</pre>
<h2>使い方</h2>
<h3>メソッドのキャッシュ</h3>
<p>For <a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>/babel</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span>
cache,
CacheType,
CacheScope
<span class="synIdentifier">}</span> from <span class="synConstant">'cache-decorator'</span>;
<span class="synStatement">class</span> Example <span class="synIdentifier">{</span>
@cache(<span class="synIdentifier">{</span>type: CacheType.MEMO, scope: CacheScope.INSTANCE<span class="synIdentifier">}</span>)
expensiveCalc(args) <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<p>For typescript</p>
<p>tsconfig.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a></p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>
"<span class="synStatement">compilerOptions</span>": <span class="synSpecial">{</span>
...
<span class="synError">paths</span>: <span class="synSpecial">{</span>
"<span class="synStatement">cache-decorator</span>": <span class="synSpecial">[</span>"<span class="synConstant">node_modules/cache-decorator/lib/index.d.ts</span>"<span class="synSpecial">]</span>
<span class="synSpecial">}</span>
<span class="synSpecial">}</span><span class="synError">,</span>
<span class="synError">}</span>
</pre>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span>
cache<span class="synStatement">,</span>
CacheType<span class="synStatement">,</span>
CacheScope
<span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'cache-decorator'</span><span class="synStatement">;</span>
<span class="synStatement">class</span> Example <span class="synIdentifier">{</span>
<span class="synSpecial">@cache</span><span class="synStatement">(</span><span class="synIdentifier">{</span><span class="synStatement">type</span>: CacheType.MEMO<span class="synStatement">,</span> scope: CacheScope.INSTANCE<span class="synIdentifier">}</span><span class="synStatement">)</span>
<span class="synStatement">public</span> expensiveCalc<span class="synStatement">(</span>args<span class="synStatement">)</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span>
<span class="synIdentifier">}</span>
</pre>
<h3>関数のキャッシュ</h3>
<p>For <a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>/babel</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span>
fcache,
CacheType
<span class="synIdentifier">}</span> from <span class="synConstant">'cache-decorator'</span>;
<span class="synStatement">const</span> cachedFn = fcache((args) => <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span>, <span class="synIdentifier">{</span>type: CacheType.MEMO<span class="synIdentifier">}</span>)
</pre>
<p>For typescript</p>
<p>tsconfig.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a></p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>
"<span class="synStatement">compilerOptions</span>": <span class="synSpecial">{</span>
...
<span class="synError">paths</span>: <span class="synSpecial">{</span>
"<span class="synStatement">cache-decorator</span>": <span class="synSpecial">[</span>"<span class="synConstant">node_modules/cache-decorator/lib/index.d.ts</span>"<span class="synSpecial">]</span>
<span class="synSpecial">}</span>
<span class="synSpecial">}</span><span class="synError">,</span>
<span class="synError">}</span>
</pre>
<pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span>
cache<span class="synStatement">,</span>
CacheType<span class="synStatement">,</span>
CacheScope
<span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'cache-decorator'</span><span class="synStatement">;</span>
<span class="synStatement">const</span> cachedFn <span class="synStatement">=</span> fcache<span class="synStatement">((</span>args: <span class="synSpecial">Object</span><span class="synStatement">)</span> <span class="synStatement">=></span> <span class="synIdentifier">{</span>
...
<span class="synIdentifier">}</span><span class="synStatement">,</span> <span class="synIdentifier">{</span><span class="synStatement">type</span>: CacheType.MEMO<span class="synIdentifier">}</span><span class="synStatement">)</span>
</pre>
<h2>cacheのオプション引数</h2>
<pre class="code" data-lang="" data-unlink>interface CacheOption {
type?: CacheType;
scope?: CacheScope;
ttl?: number;
compare?: (prev: any, next: any) => boolean;
}</pre>
<h3><code>type: CacheType</code></h3>
<p><em>default value:</em> <code>CacheType.SINGLETON</code></p>
<p><strong><code>SINGLETON</code></strong></p>
<ul>
<li>キャッシュを探す</li>
<li>もしキャッシュがあればその値を変えす。なければメソッドを実行して、その結果を保存する.</li>
<li>結果を返す</li>
</ul>
<p><strong><code>MEMO</code></strong></p>
<ul>
<li>キャッシュを引数のリストとともに検索し、保存されている引数リストと比較する.</li>
<li>もし値が見つかれば結果を返し、見つからなければメソッドを実行し、結果と引数のペアをキャッシュに保存する。</li>
<li>結果を返す</li>
</ul>
<h3><code>scope: CacheScope</code></h3>
<p><em>default value:</em> <code>CacheScope.INSTANCE</code></p>
<p><strong><code>INSTANCE</code></strong></p>
<p>キャッシュされた値は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>毎の領域に保存され、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>間で値が共有されない。</p>
<p><strong><code>GLOBAL</code></strong></p>
<p>すべてのキャッシュはグローバルな領域で管理され、すべての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>で値が共有される。</p>
<h3><code>ttl: number</code></h3>
<p><em>default value</em>: <code>null</code></p>
<p>指定されたミリ秒でキャッシュを破棄する。</p>
<h3><code>compare: Function</code></h3>
<p><em>default value</em>: <code>(a, b) => a.length === b.length && a.every((v, i) => v === b[i])</code></p>
<p>CacheType.MEMOが指定された場合に引数を比較するのに使う比較関数。</p>
<h2>まとめ</h2>
<p>メモ化は結構面倒なので、decoratorというシンプルな形でまとめられてよかった。<br/>
使い勝手も悪くないので結構重宝している。</p>
brn_take
Function.prototype.bind のパフォーマンスについて
hatenablog://entry/10328749687230229198
2017-03-24T15:23:03+09:00
2017-03-24T15:23:03+09:00 ふとパフォーマンスが気になったので調査した。 記憶が正しければ、callよりも遅いはず。 というわけでレッツ検証 事前準備 package.json { "name": "bench", "version": "1.0.0", "description": "", "main": "index.js", "author": "brn", "license": "MIT", "devDependencies": { "benchmark": "^2.1.3" } } bench.js /** * @fileoverview * @author Taketoshi Aono */ const Be…
<p>ふとパフォーマンスが気になったので調査した。<br/>
記憶が正しければ、callよりも遅いはず。</p>
<p>というわけでレッツ検証</p>
<p><strong>事前準備</strong></p>
<p>package.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a></p>
<pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>
"<span class="synStatement">name</span>": "<span class="synConstant">bench</span>",
"<span class="synStatement">version</span>": "<span class="synConstant">1.0.0</span>",
"<span class="synStatement">description</span>": "",
"<span class="synStatement">main</span>": "<span class="synConstant">index.js</span>",
"<span class="synStatement">author</span>": "<span class="synConstant">brn</span>",
"<span class="synStatement">license</span>": "<span class="synConstant">MIT</span>",
"<span class="synStatement">devDependencies</span>": <span class="synSpecial">{</span>
"<span class="synStatement">benchmark</span>": "<span class="synConstant">^2.1.3</span>"
<span class="synSpecial">}</span>
<span class="synSpecial">}</span>
</pre>
<p>bench.js</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">/**</span>
<span class="synComment"> * @fileoverview</span>
<span class="synComment"> * @author Taketoshi Aono</span>
<span class="synComment"> */</span>
<span class="synStatement">const</span> Benchmark = require(<span class="synConstant">'benchmark'</span>);
<span class="synStatement">const</span> suite = <span class="synStatement">new</span> Benchmark.Suite;
<span class="synStatement">const</span> bind = (() => <span class="synIdentifier">{}</span>).bind(<span class="synIdentifier">{}</span>);
<span class="synStatement">const</span> bindWithArgs = ((a, b, c) => <span class="synIdentifier">{}</span>).bind(<span class="synIdentifier">{}</span>, 1, 2, 3);
<span class="synStatement">const</span> call = () => <span class="synIdentifier">{}</span>;
<span class="synStatement">const</span> callargs = <span class="synIdentifier">{}</span>;
<span class="synStatement">const</span> callcall = () => call.call(callargs)
<span class="synComment">// add tests</span>
suite
.add(<span class="synConstant">'bind'</span>, () => <span class="synIdentifier">{</span>
bind();
<span class="synIdentifier">}</span>)
.add(<span class="synConstant">'bind with args'</span>, () => <span class="synIdentifier">{</span>
bindWithArgs();
<span class="synIdentifier">}</span>)
.add(<span class="synConstant">'call'</span>, () => <span class="synIdentifier">{</span>
callcall();
<span class="synIdentifier">}</span>)
.on(<span class="synConstant">'cycle'</span>, <span class="synIdentifier">function</span>(<span class="synStatement">event</span>) <span class="synIdentifier">{</span>
console.log(<span class="synType">String</span>(<span class="synStatement">event</span>.target));
<span class="synIdentifier">}</span>)
.on(<span class="synConstant">'complete'</span>, <span class="synIdentifier">function</span>() <span class="synIdentifier">{</span>
console.log(<span class="synConstant">'Fastest is '</span> + <span class="synIdentifier">this</span>.filter(<span class="synConstant">'fastest'</span>).map(<span class="synConstant">'name'</span>));
<span class="synIdentifier">}</span>)
.run(<span class="synIdentifier">{</span> <span class="synConstant">'async'</span>: <span class="synConstant">true</span> <span class="synIdentifier">}</span>);
</pre>
<p><code>node bench.js</code></p>
<p>結果は
V8限定ですが,</p>
<pre class="code" data-lang="" data-unlink>bind x 43,404,436 ops/sec ±2.07% (83 runs sampled)
bind with args x 35,140,882 ops/sec ±2.48% (84 runs sampled)
call x 59,048,983 ops/sec ±1.13% (85 runs sampled)
Fastest is call</pre>
<p>となりました。
予想通り、callが最速。</p>
<p>ただ、気になるのはbindに引数を束縛した場合さらに遅くなる点。</p>
<p>気になるので調べました。</p>
<p>V8のコミットID<br/>
<code>bdf32cf1bc96982ff5a22195d874617ffae03e79</code><br/>
時点のものです。</p>
<h2>コード検証</h2>
<p>色々さがして、とりあえず<a class="keyword" href="http://d.hatena.ne.jp/keyword/x87">x87</a>のbuiltin-<a class="keyword" href="http://d.hatena.ne.jp/keyword/x87">x87</a>.ccを見る。<br/>
x64でもいいけど、とりあえず<a class="keyword" href="http://d.hatena.ne.jp/keyword/x87">x87</a>を調べましょう。</p>
<p>でこれが、bindで生成した関数を呼び出す<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%E9">アセンブラ</a>コード生成関数。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> Builtins::Generate_CallBoundFunctionImpl(MacroAssembler* masm,
TailCallMode tail_call_mode) {
<span class="synComment">// ----------- S t a t e -------------</span>
<span class="synComment">// -- eax : the number of arguments (not including the receiver)</span>
<span class="synComment">// -- edi : the function to call (checked to be a JSBoundFunction)</span>
<span class="synComment">// -----------------------------------</span>
__ AssertBoundFunction(edi);
<span class="synStatement">if</span> (tail_call_mode == TailCallMode::kAllow) {
PrepareForTailCall(masm, eax, ebx, ecx, edx);
}
<span class="synComment">// Patch the receiver to [[BoundThis]].</span>
__ mov(ebx, FieldOperand(edi, JSBoundFunction::kBoundThisOffset));
__ mov(Operand(esp, eax, times_pointer_size, kPointerSize), ebx);
<span class="synComment">// Push the [[BoundArguments]] onto the stack.</span>
Generate_PushBoundArguments(masm);
<span class="synComment">// Call the [[BoundTargetFunction]] via the Call builtin.</span>
__ mov(edi, FieldOperand(edi, JSBoundFunction::kBoundTargetFunctionOffset));
__ mov(ecx, Operand::StaticVariable(ExternalReference(
Builtins::kCall_ReceiverIsAny, masm->isolate())));
__ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
__ jmp(ecx);
}
</pre>
<p>ここで注目したいのが、<br/>
<code>Generate_PushBoundArguments(masm);</code><br/>
の部分<br/>
束縛した引数をPushするコードだと想像できる。</p>
<p>で<code>Generate_PushBoundArguments</code>がこちら。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> Generate_PushBoundArguments(MacroAssembler* masm) {
<span class="synComment">// ----------- S t a t e -------------</span>
<span class="synComment">// -- eax : the number of arguments (not including the receiver)</span>
<span class="synComment">// -- edx : new.target (only in case of [[Construct]])</span>
<span class="synComment">// -- edi : target (checked to be a JSBoundFunction)</span>
<span class="synComment">// -----------------------------------</span>
<span class="synComment">// Load [[BoundArguments]] into ecx and length of that into ebx.</span>
Label no_bound_arguments;
__ mov(ecx, FieldOperand(edi, JSBoundFunction::kBoundArgumentsOffset));
__ mov(ebx, FieldOperand(ecx, FixedArray::kLengthOffset));
__ SmiUntag(ebx);
__ test(ebx, ebx);
__ j(zero, &no_bound_arguments);
{
<span class="synComment">// ----------- S t a t e -------------</span>
<span class="synComment">// -- eax : the number of arguments (not including the receiver)</span>
<span class="synComment">// -- edx : new.target (only in case of [[Construct]])</span>
<span class="synComment">// -- edi : target (checked to be a JSBoundFunction)</span>
<span class="synComment">// -- ecx : the [[BoundArguments]] (implemented as FixedArray)</span>
<span class="synComment">// -- ebx : the number of [[BoundArguments]]</span>
<span class="synComment">// -----------------------------------</span>
<span class="synComment">// Reserve stack space for the [[BoundArguments]].</span>
{
Label done;
__ lea(ecx, Operand(ebx, times_pointer_size, <span class="synConstant">0</span>));
__ sub(esp, ecx);
<span class="synComment">// Check the stack for overflow. We are not trying to catch interruptions</span>
<span class="synComment">// (i.e. debug break and preemption) here, so check the "real stack</span>
<span class="synComment">// limit".</span>
__ CompareRoot(esp, ecx, Heap::kRealStackLimitRootIndex);
__ j(greater, &done, Label::kNear); <span class="synComment">// Signed comparison.</span>
<span class="synComment">// Restore the stack pointer.</span>
__ lea(esp, Operand(esp, ebx, times_pointer_size, <span class="synConstant">0</span>));
{
FrameScope scope(masm, StackFrame::MANUAL);
__ EnterFrame(StackFrame::INTERNAL);
__ CallRuntime(Runtime::kThrowStackOverflow);
}
__ bind(&done);
}
<span class="synComment">// Adjust effective number of arguments to include return address.</span>
__ inc(eax);
<span class="synComment">// Relocate arguments and return address down the stack.</span>
{
Label loop;
__ Set(ecx, <span class="synConstant">0</span>);
__ lea(ebx, Operand(esp, ebx, times_pointer_size, <span class="synConstant">0</span>));
__ bind(&loop);
__ fld_s(Operand(ebx, ecx, times_pointer_size, <span class="synConstant">0</span>));
__ fstp_s(Operand(esp, ecx, times_pointer_size, <span class="synConstant">0</span>));
__ inc(ecx);
__ cmp(ecx, eax);
__ j(less, &loop);
}
<span class="synComment">// Copy [[BoundArguments]] to the stack (below the arguments).</span>
{
Label loop;
__ mov(ecx, FieldOperand(edi, JSBoundFunction::kBoundArgumentsOffset));
__ mov(ebx, FieldOperand(ecx, FixedArray::kLengthOffset));
__ SmiUntag(ebx);
__ bind(&loop);
__ dec(ebx);
__ fld_s(
FieldOperand(ecx, ebx, times_pointer_size, FixedArray::kHeaderSize));
__ fstp_s(Operand(esp, eax, times_pointer_size, <span class="synConstant">0</span>));
__ lea(eax, Operand(eax, <span class="synConstant">1</span>));
__ j(greater, &loop);
}
<span class="synComment">// Adjust effective number of arguments (eax contains the number of</span>
<span class="synComment">// arguments from the call plus return address plus the number of</span>
<span class="synComment">// [[BoundArguments]]), so we need to subtract one for the return address.</span>
__ dec(eax);
}
__ bind(&no_bound_arguments);
}
</pre>
<p>想像よりなが~いーーー</p>
<p><code>__ j(zero, &no_bound_arguments);</code>で引数束縛がなければjmpするコードを生成</p>
<p>その後は、BoundArgumentsを保存するstack領域を確保、リターンアドレスをstackに押し込んで、<br/>
BoundArgumentsをstackに押し込む。<br/>
これをループで行っているのでだいぶ遅そう。</p>
<p>Function.prototype.bindが引数束縛つきで遅くなるのはこれが理由っぽい。</p>
<h2>まとめ</h2>
<ul>
<li>最速は<code>() => fn.call(this)</code>形式</li>
<li>次点で<code>fn.bind(this)</code></li>
<li>引数の束縛はかなり遅くなるので、<code>() => fn.call(this, ...)</code>のがおすすめ。</li>
</ul>
<p>アロー関数のおかげで<code>Function.prototype.bind</code>使い所がない。</p>
brn_take
React.js meetup × React Native meetup に参加した
hatenablog://entry/10328749687227840134
2017-03-17T10:48:23+09:00
2017-03-17T10:52:29+09:00 パネルディスカッション 参加者 @yosuke_furukawa @koba04 @yositosi (Togetter CEO) @janus_wel (CureApp CTO) 全体的にReactNativeはWebの技術をどう活かせるか、 ワンソース・マルチユースができるか等の話だった。 あとReactNativeの将来性とか 全体的にReactNativeはWebの技術をどう活かせるか、 ワンソース・マルチユースができるか等の話だった。 あとReactNativeの将来性とか React Nativeの利点 アプリの開発に、使い慣れたReactの技術を活かせるのは大きいのかもしれない。…
<h2>パネルディスカッション</h2>
<p><strong>参加者</strong></p>
<p><em>@yosuke_furukawa</em></p>
<p><em>@koba04</em></p>
<p><em>@yositosi (Togetter CEO)</em></p>
<p><em>@janus_wel (CureApp CTO)</em></p>
<p>全体的にReactNativeはWebの技術をどう活かせるか、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%BD%A1%BC%A5%B9%A1%A6%A5%DE%A5%EB%A5%C1%A5%E6%A1%BC%A5%B9">ワンソース・マルチユース</a>ができるか等の話だった。<br/>
あとReactNativeの将来性とか
全体的にReactNativeはWebの技術をどう活かせるか、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%BD%A1%BC%A5%B9%A1%A6%A5%DE%A5%EB%A5%C1%A5%E6%A1%BC%A5%B9">ワンソース・マルチユース</a>ができるか等の話だった。<br/>
あとReactNativeの将来性とか</p>
<h3>React Nativeの利点</h3>
<p>アプリの開発に、使い慣れたReactの技術を活かせるのは大きいのかもしれない。<br/>
そのため、Webの開発者がアプリを開発するときには向いている。</p>
<h3>苦労したこと</h3>
<p>TogetterさんはReactNativeでアプリを実装した時に、広告の表示部分のブリッジが用意されていなかったので、<br/>
自分たちでブリッジを作った<br/>
でも、見よう見まねで作ったらすんなり動いた。</p>
<h3>ソース共通化できるのか</h3>
<p>画面・ページレベルの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>は分ける必要がある。<br/>
共通化するよりも、プラットフォーム毎の最適なデザインを目指すべき。<br/>
一つ一つの細かい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>は再利用できる。</p>
<h3>ngCore</h3>
<p>懐かしかったw<br/>
ReactNativeと似ている。</p>
<p><strong>ngCoreが犯した間違え</strong></p>
<ul>
<li>クローズドソースだった</li>
<li>ホットコードプッシュの正当性</li>
</ul>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Apple">Apple</a>からのBanは怖い</p>
<h2>React Hot Loaderで開発を更に加速する</h2>
<p><em>@endam</em></p>
<p>React Hot Loaderの使い方。<br/>
あんま使ったことなかったので、ちゃんと使わねばと思った。<br/>
jspmやめたら検討しよう。</p>
<h2>Inside Bdash</h2>
<p><em>@hokkacha</em></p>
<p><a href="https://github.com/bdash-app/bdash">Bdash</a>の作者による、Bdashの実装<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a><br/>
Fluxで実装していて、ページ毎にActionCreatorを作り、コントローラにしていた。<br/>
Springとかあのへんの<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>の残り香がした。</p>
<h2>hyperapp</h2>
<p><em>@jbucaran</em></p>
<p><iframe src="//hatenablog-parts.com/embed?url=http%3A%2F%2Fqiita.com%2Fjbucaran%2Fitems%2Fac7d54d862dcb33673f1" title="hyperapp - 1kbのビューライブラリ - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="http://qiita.com/jbucaran/items/ac7d54d862dcb33673f1">qiita.com</a></cite></p>
<p><a href="https://github.com/hyperapp/hyperapp">hyperapp</a>の作者による、hyperappの紹介。<br/>
わずか1kbのRedux + VirtualDomのライブラリ。<br/>
たったこれだけのコード量で実装できているのに驚愕。<br/>
全コード合わせても300行ほどらしい。<br/>
普通に選択肢になるかもしれない。 </p>
<h2>小回りの聞くWebViewの使い方</h2>
<p><em>@niryuu</em></p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="463" id="talk_frame_383593" mozallowfullscreen="true" src="//speakerdeck.com/player/4c528b6168cc49eab671105dc2c67b71" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/niryuu/xiao-hui-rifalsexiao-ku-webviewfalseshi-ifang">speakerdeck.com</a></cite></p>
<p>WebViewで<a class="keyword" href="http://d.hatena.ne.jp/keyword/WebGL">WebGL</a>を使ったアプリの実装についての話</p>
<h2>ReactNativeでFirebaseのネイティブ<a class="keyword" href="http://d.hatena.ne.jp/keyword/SDK">SDK</a>を操作する</h2>
<p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Ft.co%2FUcncssUx69" title="react nativeでfirebaseのネイティブSDKを操作する - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://t.co/UcncssUx69">t.co</a></cite></p>
<p>FirebaseをReactNativeから使う方法についての発表<br/>
自分たちはNativeからWebViewのFirebaseを使っていたので、逆の話も聞けてよかった。</p>
<h2>Androiderから見るReactNative</h2>
<p><em>@operandOS</em></p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="596" id="talk_frame_383591" mozallowfullscreen="true" src="//speakerdeck.com/player/c5461cc29c1241a186b66c40f4bc1ff4" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://t.co/pURPfP2OtS">t.co</a></cite></p>
<p>ガチの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>勢がReactNativeを使ってどうだったのかの発表。<br/>
やっぱ、Nativeの人はこれをメインで使う理由は無いよなぁと思った。<br/>
今からReactNative使う上での一番の障害かもしれない。<br/>
あと、ネイティブの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>とReactNativeのブリッジ書けばスター一杯のチャンスらしい。<br/>
書こうかな。</p>
<h2>SideCI(スポンサーLT)</h2>
<p>LintのCIツール。<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のPRから実行したり</p>
<h2>CureApp(スポンサーLT)</h2>
<p>CureAppの紹介。医師から<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>エンジニアになった異色のCEOの話。<br/>
面白そうなスタートアップだった。</p>
<h3>まとめ</h3>
<p>ReactNativeは非ゲームなら使っていってもいいと思う。<br/>
特にReduxで実装している場合は、Reducer等の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>は使いまわせるはずなので、<br/>
インターフェースの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リだけ分けて、後は使いまわすって言うのがかっこいいかも。<br/>
あとは、<em>operandOS</em>さんの発表にあったけども、<br/>
ガリガリにチューニングしたいところはネイティブで書いて、<br/>
それ以外の面倒なところはReactNativeで書くのがいいかもしれない。</p>
<p>とりあえず、ReactNativeは触っとかないとまずそう。</p>
brn_take
【RECRUIT Technologies NIGHT vol.5】リクルート流フロントエンド開発 に参加してきた
hatenablog://entry/10328749687225317984
2017-03-09T11:08:57+09:00
2017-03-09T17:23:43+09:00 React / Redux を活用したリクルートテクノロジーズのフロントエンド開発 古川陽介 さん(@yosuke_furukawa) speakerdeck.com 以下メモ 言いたいこと フレームワークは作るものに合わせて作る リクルートのWeb トップページに検索があり、 検索するとリストビューがでるような一般的なwebサイト 典型的なWebサイト 最近はチャットできたり、 インタラクティブなやつができた。 要望 とにかくパフォーマンス NatieアプリっぽいLook & Feel Interactiveな動き(Chat や いいね通知) これまでのWebフレームワークでは無理 Reac…
<h3>React / Redux を活用した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>テクノロジーズのフロントエンド開発</h3>
<p><strong>古川陽介 さん(@yosuke_furukawa)</strong></p>
<p><iframe allowfullscreen="true" allowtransparency="true" frameborder="0" height="596" id="talk_frame_382489" mozallowfullscreen="true" src="//speakerdeck.com/player/c8fb42543b8248c19256394fe8f67ba2" style="border:0; padding:0; margin:0; background:transparent;" webkitallowfullscreen="true" width="710"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/yosuke_furukawa/frontend-development-on-recruit-tech">speakerdeck.com</a></cite></p>
<p>以下メモ</p>
<h4>言いたいこと</h4>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>は作るものに合わせて作る</p>
<h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>のWeb</h4>
<p>トップページに検索があり、<br/>
検索するとリストビューがでるような一般的なwebサイト</p>
<p>典型的なWebサイト</p>
<p>最近はチャットできたり、<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>なやつができた。</p>
<p>要望</p>
<ul>
<li>とにかくパフォーマンス</li>
<li>NatieアプリっぽいLook & Feel</li>
<li>Interactiveな動き(Chat や いいね通知)</li>
</ul>
<p>これまでのWebフレームワークでは無理</p>
<p>React x Redux x Node で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>を作っている</p>
<p>事例</p>
<ul>
<li><a href="https://t.co/2YTJy1zBKQ">ブッキングテーブル - ランキングで飲食店探し&24時間簡単ネット予約</a></li>
<li><a href="https://raico.jp/"></a></li>
</ul>
<h4>フロントエンドを作る上でも気をつけていること</h4>
<ul>
<li>サーバでもクライアントでも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>する
<ul>
<li>nginxのCacheDirの保存</li>
<li>Above the foldのみ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a></li>
</ul>
</li>
<li>Historyを壊さない
<ul>
<li>戻る・進むで状態を壊さない</li>
<li>戻る・進む中には<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>スキップする</li>
</ul>
</li>
<li>Consumer Driven Contract
<ul>
<li>従来バックエンドが決めていた<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>の仕様をフロントエンドが手動して要求を書く</li>
<li>フロントエンドが使いやすい<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>になる。</li>
</ul>
</li>
</ul>
<p><a href="http://yosuke-furukawa.hatenablog.com/entry/2014/11/14/141415">【翻訳】リッチなWebアプリケーションのための7つの原則 - from scratch</a></p>
<h3>フロントエンドエンジニアが React Native を触ってみた話</h3>
<p>90%ほど<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>で共有できる<br/>
Hot/Live Reloadingが早い</p>
<p>疑似要素が使いたい<br/>
疑似要素は無理</p>
<p>shorthandも使えない</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>がちょっと違和感があるので戸惑うかもしれない。</p>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>の違い</p>
<p><strong>プラットフォーム毎にUI/UXのベストプラクティスは違う</strong></p>
<ul>
<li>ナビゲーション</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%F3%A5%D0%A1%BC%A5%AC">ハンバーガ</a>ー・タブ</li>
</ul>
<p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a></strong></p>
<ul>
<li><code>Platform.OS</code>で分ける</li>
<li>ファイル自体を分ける</li>
</ul>
<h4>何故ReactNativeなのか</h4>
<ul>
<li>ユーザー数の多いアプリで採用されている</li>
<li>Webとのコード共有ができる</li>
</ul>
<h4>ReactNative<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a></h4>
<p>WebのReactをつかったこと
Objc,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>の知識は今の所必要なしに使える。</p>
<p>ReactNativeは想像以上に良さそうだった。<br/>
簡単なアプリならWebの延長で作れそうでよい!<br/>
是非取り入れて評価してみたい。</p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>のフロントエンド改革に挑んだ話</h3>
<p><em>太田さん</em></p>
<h4>前提</h4>
<p>外注がZip納品して、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Svn">Svn</a>でBackendが開発されている</p>
<p>フロントエンドがViewを見れないので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の影響調査をすることができない</p>
<p>というまあひどい状況</p>
<h4>改革</h4>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SIer">SIer</a>の意識改革の話。<br/>
保守的なエンジニアのやり方を変えるにはどんなものでもいいから定量的な指標が必要で、<br/>
たとえ、LOC(Lines of codes 要はコード行数)であれ、前提あれば指標にするしかない。<br/>
でなんとかやり方の改革に成功した話。</p>
<p>エンジニアは保守的な人が多いのはまあ結構思う。<br/>
ただ、論理的に何%良くなったとか、定量的な指標をしっかり準備しておけば、説得できる人も多いのは確かだなと思ったし、<br/>
これはとても参考になる。</p>
<h4>成長</h4>
<p>バックエンド寄りのフロントエンドの人が<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を学習するのは早いけど、<br/>
フロントエンド寄りの人がバックエンドを学ぶのはやはり時間がかかる。</p>
<p>きちんとした学習コンテンツを用意すべきだった。</p>
<p>ここはかなり同意。<br/>
ただ、学習コンテンツがどんなものか、どう動機づけするかがかなりの課題だなと思う。</p>
<h3>まとめ</h3>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>テクノロジーズさんは、というか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>さんは結構外注が多いイメージだったが、<br/>
内部でもかなり進んだことにチャレンジしていたっていう印象を受けた。<br/>
特にReactNativeのところは今の業務にも活かしていきたい(難しいけど)<br/>
全体的に実りのある勉強会だったなぁ。</p>
brn_take
V8 の For In の話
hatenablog://entry/10328749687224463211
2017-03-08T16:54:34+09:00
2017-03-08T19:13:15+09:00 V8 blogに話が出ていたが、V8のfor in構文が高速化したらしいので、 コードと共におっていこうと思う。 For In とは for (const key in object) { } このような構文でObjectのキーをイテレーションする機能。 この構文を何故高速化したかというと、Facebookのコード実行の7% Wikipediaのコード実行の8%をForIn構文が占めていたらしく、 実行に負荷のかかる部分だったらしい。 一見この構文は単純に見えるが、処理系が行わなければならない仕事は以外に多く複雑だ。 まずはForIn構文の定義を見てみよう。上記のBlogにかかれているSpec…
<p><a href="https://v8project.blogspot.jp/2017/03/fast-for-in-in-v8.html?m=0">V8 blog</a>に話が出ていたが、V8のfor in構文が高速化したらしいので、<br/>
コードと共におっていこうと思う。</p>
<h2>For In とは</h2>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">for</span> (<span class="synStatement">const</span> key <span class="synStatement">in</span> object) <span class="synIdentifier">{</span>
<span class="synIdentifier">}</span>
</pre>
<p>このような構文でObjectのキーを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">イテレーション</a>する機能。<br/>
この構文を何故高速化したかというと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>のコード実行の7% <a class="keyword" href="http://d.hatena.ne.jp/keyword/Wikipedia">Wikipedia</a>のコード実行の8%をForIn構文が占めていたらしく、<br/>
実行に負荷のかかる部分だったらしい。</p>
<p>一見この構文は単純に見えるが、処理系が行わなければならない仕事は以外に多く複雑だ。</p>
<p>まずはForIn構文の定義を見てみよう。上記のBlogにかかれているSpecを抜粋する。</p>
<blockquote><p>EnumerateObjectProperties ( O )
抽象命令であるEnumerateObjectPropertiesが引数Oを伴って呼び出される時、以下のステップをとる。<br/>
1. Type(O)がObjectか確認する。<br/>
2. <a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>を返却する。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>のnextメソッドはOのenumerableな文字列のプロパティを全て列挙する。<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>オブジェクトは<a class="keyword" href="http://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>のコード中からアクセス不能である。プロパティの列挙順及びその方法は特に規定しないが、以下の規定に沿わなければならない。<br/>
A. <a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>のthrowとreturnメソッドはnullであり、決して呼び出されない。<br/>
B. <a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>のnextメソッドはオブジェクトのプロパティを処理して、次に<a class="keyword" href="http://d.hatena.ne.jp/keyword/iterator">iterator</a>の値として返すプロパティのキーを決める。<br/>
C. 戻り値のプロパティキーはSymbolを含まない<br/>
D. ターゲットのオブジェクトのプロパティは列挙中に削除されるかもしれない。<br/>
E. <a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>のnextメソッドに処理される前に削除されたプロパティは無視される。もし、列挙中に新たなプロパティが追加された場合、新たに追加されたプロパティは現在の列挙中に処理される保証はない。<br/>
F. プロパティ名は列挙中に一度しか<a class="keyword" href="http://d.hatena.ne.jp/keyword/Iterator">Iterator</a>のnextメソッドによって返却されない。<br/>
G. 列挙するターゲットのオブジェクトのプロパティは、ターゲットのprototype、そのprototypeのprototypeも含むが、一度でも列挙されたプロパティのキーと同じキーを含んだprototypeのキーは列挙されない。(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%E3%A5%C9%A5%A6%A5%A4%A5%F3%A5%B0">シャドウイング</a>されたprototypeは列挙されない)<br/>
H. ptototypeのプロパティが既に処理されていた場合、<code>[[Enumerable]]</code>属性は考慮しない。<br/>
I. prototypeの列挙可能プロパティ名を取得する場合は、必ず、<code>EnumerateObjectProperties</code>をprototypeを引数に呼び出して取得しなければならない。<br/>
J. <code>EnumerateObjectProperties</code>はターゲットオブジェクトのプロパティを<code>[[OwnPropertyKeys]]</code>内部メソッドを呼び出して取得しなければならない。</p></blockquote>
<p>かなり要件が複雑なのがわかる。</p>
<h2>概要</h2>
<p>Blogから抜粋</p>
<pre class="code" data-lang="" data-unlink>Map Descriptors Enum Cache
-------------------------- -> ------------------- -> ----------------- -> -------
|enumLength | 3 | | | length | 3 | | | Keys | ptr | -| | 'a' |
-------------------------- | ------------------- | ----------------- -------
|nofOwnDescriptors | 3 | | | EnumCache | ptr | --| | indices | ptr | | 'b' |
-------------------------- | ------------------- ----------------- -------
|DescriptorArray | ptr |--- | ... | ... | | 'c' |
-------------------------- ------------------- -------</pre>
<p>MapのDescriptorが持っているEnumCacheからプロパティのキーリストを取得するようにしたので、高速になったらしい。</p>
<h2>実装</h2>
<p>さて実装はどうなっているか。</p>
<p>V8のForInはRuntime呼び出しで実装されている。<br/>
試しにd8で確認すると…</p>
<pre class="code" data-lang="" data-unlink>./d8 --print_code -e "for (const i in {__proto__: {a:1}}){}"</pre>
<p>この行でForInEnumerateをRuntimeから呼び出しているのが確認できる。</p>
<pre class="code" data-lang="" data-unlink>0x1cb5dca0476e 398 b801000000 movl rax,0x1
0x1cb5dca04773 403 48bbc0ddc70001000000 REX.W movq rbx,0x100c7ddc0 ;; external reference (Runtime::ForInEnumerate)
0x1cb5dca0477d 413 e81efadfff call 0x1cb5dc8041a0 ;; code: STUB, CEntryStub, minor: 8</pre>
<p>ではRuntime::ForInEnumerateを確認しよう。</p>
<p>ちなみにMapとかV8の基礎情報は <a href="http://steps.dodgson.org/bn/2008/09/07/">V8祭り</a> を参照してほしい。</p>
<p>では確認ー</p>
<p>ForInEnumerateは<a href="https://github.com/v8/v8/blob/a9ace2989326eb51b2b1f4b9ab1185b050845944/src/runtime/runtime-forin.cc">src/runtime/runtime-forin.cc</a>にある。</p>
<p><code>Runtime::ForInEnumerate</code>を確認する前にこのファイルにある他のRuntimeも確認しておく。</p>
<p>このRuntimeに用意されているのは、</p>
<ul>
<li>Runtime_ForInEnumerate</li>
<li>Runtime_ForInPrepare</li>
<li>Runtime_ForInHasProperty</li>
<li>Runtime_ForInFilter</li>
</ul>
<p>の4つ。<br/>
ForInEnumerate以外はIgnition<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>から呼び出されているようだが、今回はFullCodegenから呼び出されるコードをメインに見ていく。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>RUNTIME_FUNCTION(Runtime_ForInEnumerate) {
HandleScope scope(isolate);
DCHECK_EQ(<span class="synConstant">1</span>, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, <span class="synConstant">0</span>);
RETURN_RESULT_OR_FAILURE(isolate, Enumerate(receiver));
}
</pre>
<p>これがForInEnumerateの本体だ。<br/>
中ではさらに<code>Enumerate</code>をreceiverを引数に呼び出している。</p>
<p>次はEnumerate</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synComment">// Returns either a FixedArray or, if the given {receiver} has an enum cache</span>
<span class="synComment">// that contains all enumerable properties of the {receiver} and its prototypes</span>
<span class="synComment">// have none, the map of the {receiver}. This is used to speed up the check for</span>
<span class="synComment">// deletions during a for-in.</span>
MaybeHandle<HeapObject> Enumerate(Handle<JSReceiver> receiver) {
Isolate* <span class="synType">const</span> isolate = receiver->GetIsolate();
JSObject::MakePrototypesFast(receiver, kStartAtReceiver, isolate);
FastKeyAccumulator accumulator(isolate, receiver,
KeyCollectionMode::kIncludePrototypes,
ENUMERABLE_STRINGS);
accumulator.set_is_for_in(<span class="synConstant">true</span>);
<span class="synComment">// Test if we have an enum cache for {receiver}.</span>
<span class="synStatement">if</span> (!accumulator.is_receiver_simple_enum()) {
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, keys, accumulator.GetKeys(GetKeysConversion::kKeepNumbers),
HeapObject);
<span class="synComment">// Test again, since cache may have been built by GetKeys() calls above.</span>
<span class="synStatement">if</span> (!accumulator.is_receiver_simple_enum()) <span class="synStatement">return</span> keys;
}
<span class="synStatement">return</span> handle(receiver->map(), isolate);
}
</pre>
<p>さて、このEnumerateで注目したいのは、<code>FastKeyAccumulator</code>である。このFastKeyAccumulatorがオブジェクトのprototypeの情報、プロパティの情報を取得し、<br/>
最適な処理に振り分けている。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> FastKeyAccumulator::Prepare() {
DisallowHeapAllocation no_gc;
<span class="synComment">// Directly go for the fast path for OWN_ONLY keys.</span>
<span class="synStatement">if</span> (mode_ == KeyCollectionMode::kOwnOnly) <span class="synStatement">return</span>;
<span class="synComment">// Fully walk the prototype chain and find the last prototype with keys.</span>
is_receiver_simple_enum_ = <span class="synConstant">false</span>;
has_empty_prototype_ = <span class="synConstant">true</span>;
JSReceiver* last_prototype = <span class="synConstant">nullptr</span>;
<span class="synStatement">for</span> (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
iter.Advance()) {
JSReceiver* current = iter.GetCurrent<JSReceiver>();
<span class="synType">bool</span> has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
<span class="synStatement">if</span> (has_no_properties) <span class="synStatement">continue</span>;
last_prototype = current;
has_empty_prototype_ = <span class="synConstant">false</span>;
}
<span class="synStatement">if</span> (has_empty_prototype_) {
is_receiver_simple_enum_ =
receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
!JSObject::cast(*receiver_)->HasEnumerableElements();
} <span class="synStatement">else</span> <span class="synStatement">if</span> (last_prototype != <span class="synConstant">nullptr</span>) {
last_non_empty_prototype_ = handle(last_prototype, isolate_);
}
}
</pre>
<p>これが<code>FastKeyAccumulator</code>の初期化処理。やっていることは</p>
<ul>
<li>Receiverオブジェクトのprototypeを辿る</li>
<li>prototypeにプロパティがあれば、<code>has_empty_prototype_</code>を<code>false</code>にする。</li>
<li>そして、全てのprototypeを辿り終わったら、<code>has_empty_prototype_ == true</code>の場合は、receiverのMapオブジェクトが<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を持っていて、Enumerableなプロパティを持っていないければ、<code>is_receiver_simple_enum_ = true</code>になる。</li>
<li>それ以外の場合は<code>last_non_empty_prototype_</code>に最後のprototypeを渡す。</li>
</ul>
<p>さて、runtime-forinの<code>Enumerate</code>関数に戻ると、</p>
<pre class="code" data-lang="" data-unlink>if (!accumulator.is_receiver_simple_enum()) {</pre>
<p><code>FastKeyAccumulator::is_receiver_simple_enum()</code>で処理を分けている。<br/>
つまり、自身にEnumerableなプロパティを持たず、prototypeにも持たず、Mapに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を持っているオブジェクトはこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%B9%A5%C8%A5%D1%A5%B9">ファストパス</a>に入る。<br/>
このパスでは<code>FastKeyAccumulator::GetKeys()</code>でObjectのプロパティキーを取得する。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
GetKeysConversion keys_conversion) {
<span class="synStatement">if</span> (filter_ == ENUMERABLE_STRINGS) {
Handle<FixedArray> keys;
<span class="synStatement">if</span> (GetKeysFast(keys_conversion).ToHandle(&keys)) {
<span class="synStatement">return</span> keys;
}
<span class="synStatement">if</span> (isolate_->has_pending_exception()) <span class="synStatement">return</span> MaybeHandle<FixedArray>();
}
<span class="synStatement">return</span> GetKeysSlow(keys_conversion);
}
</pre>
<p>GetKeysの定義は結構簡単で、<code>filter_</code>プロパティは今回は<code>ENUMERABLE_STRINGS</code>なのでifに入る。<br/>
そしたら<code>FastKeyAccumulator::GetKeysFast</code>を呼び出して<code>Handle<FixedArray></code>に変換する。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink>MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
GetKeysConversion keys_conversion) {
<span class="synType">bool</span> own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
Map* map = receiver_->map();
<span class="synStatement">if</span> (!own_only || !OnlyHasSimpleProperties(map)) {
<span class="synStatement">return</span> MaybeHandle<FixedArray>();
}
<span class="synComment">// From this point on we are certiain to only collect own keys.</span>
DCHECK(receiver_->IsJSObject());
Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
<span class="synComment">// Do not try to use the enum-cache for dict-mode objects.</span>
<span class="synStatement">if</span> (map->is_dictionary_map()) {
<span class="synStatement">return</span> GetOwnKeysWithElements<<span class="synConstant">false</span>>(isolate_, object, keys_conversion);
}
<span class="synType">int</span> enum_length = receiver_->map()->EnumLength();
<span class="synStatement">if</span> (enum_length == kInvalidEnumCacheSentinel) {
Handle<FixedArray> keys;
<span class="synComment">// Try initializing the enum cache and return own properties.</span>
<span class="synStatement">if</span> (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
<span class="synStatement">if</span> (FLAG_trace_for_in_enumerate) {
PrintF(<span class="synConstant">"| strings=</span><span class="synSpecial">%d</span><span class="synConstant"> symbols=0 elements=0 || prototypes>=1 ||</span><span class="synSpecial">\n</span><span class="synConstant">"</span>,
keys->length());
}
is_receiver_simple_enum_ =
object->map()->EnumLength() != kInvalidEnumCacheSentinel;
<span class="synStatement">return</span> keys;
}
}
<span class="synComment">// The properties-only case failed because there were probably elements on the</span>
<span class="synComment">// receiver.</span>
<span class="synStatement">return</span> GetOwnKeysWithElements<<span class="synConstant">true</span>>(isolate_, object, keys_conversion);
}
</pre>
<p><code>FastKeyAccumulator::GetKeysFast</code>の定義がこちら。<br/>
いろいろコードがあるのだが、重要なのは、<code>is_dictionary_map</code>のチェックである。<br/>
<code>is_dictionary_map</code>は拡張されるObjectの場合trueになっている。</p>
<p>現在の所、</p>
<ul>
<li>グローバルオブジェクトのMap、</li>
<li><code>Object.create(null)</code>の戻り値のMap</li>
</ul>
<p>が<code>dictionary_map</code>という扱いになっている。</p>
<p>このオブジェクトの場合はそもそもTransitionするMapを持っていない、EnumCacheも持っていないので、<br/>
<code>GetOwnKeysWithElements</code>のtemplate引数をfalseで呼び出す。<br/>
それ以外のオブジェクトの場合は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_cacheの有無をチェックし、<br/>
存在しなければ<code>GetOwnKeysWithUninitializedEnumCache</code>を呼び出し、その場で作成する。
<code>GetOwnKeysWithUninitializedEnumCache</code>は省略するが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_cacheをdescriptorから作成し、プロパティ一覧を返す。
<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_cacheを既に持っているオブジェクトの場合、<code>GetOwnKeysWithElements</code>がtemplate引数にtrueを渡して呼び出される。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">template</span> <<span class="synType">bool</span> fast_properties>
MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
Handle<JSObject> object,
GetKeysConversion convert) {
Handle<FixedArray> keys;
ElementsAccessor* accessor = object->GetElementsAccessor();
<span class="synStatement">if</span> (fast_properties) {
keys = GetFastEnumPropertyKeys(isolate, object);
} <span class="synStatement">else</span> {
<span class="synComment">// </span><span class="synTodo">TODO</span><span class="synComment">(cbruni): preallocate big enough array to also hold elements.</span>
keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
}
MaybeHandle<FixedArray> result =
accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
<span class="synStatement">if</span> (FLAG_trace_for_in_enumerate) {
PrintF(<span class="synConstant">"| strings=</span><span class="synSpecial">%d</span><span class="synConstant"> symbols=0 elements=</span><span class="synSpecial">%u</span><span class="synConstant"> || prototypes>=1 ||</span><span class="synSpecial">\n</span><span class="synConstant">"</span>,
keys->length(), result.ToHandleChecked()->length() - keys->length());
}
<span class="synStatement">return</span> result;
}
</pre>
<p>これが<code>GetOwnKeysWithElements</code>の実装。<br/>
template引数がtrueの場合は<code>GetFastEnumPropertyKeys</code>を呼び出す。</p>
<pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synComment">// Initializes and directly returns the enume cache. Users of this function</span>
<span class="synComment">// have to make sure to never directly leak the enum cache.</span>
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
Handle<JSObject> object) {
Handle<Map> map(object->map());
<span class="synType">bool</span> cache_enum_length = map->OnlyHasSimpleProperties();
Handle<DescriptorArray> descs =
Handle<DescriptorArray>(map->instance_descriptors(), isolate);
<span class="synType">int</span> own_property_count = map->EnumLength();
<span class="synComment">// If the enum length of the given map is set to kInvalidEnumCache, this</span>
<span class="synComment">// means that the map itself has never used the present enum cache. The</span>
<span class="synComment">// first step to using the cache is to set the enum length of the map by</span>
<span class="synComment">// counting the number of own descriptors that are ENUMERABLE_STRINGS.</span>
<span class="synStatement">if</span> (own_property_count == kInvalidEnumCacheSentinel) {
own_property_count =
map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
} <span class="synStatement">else</span> {
DCHECK(
own_property_count ==
map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS));
}
<span class="synStatement">if</span> (descs->HasEnumCache()) {
Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
<span class="synComment">// In case the number of properties required in the enum are actually</span>
<span class="synComment">// present, we can reuse the enum cache. Otherwise, this means that the</span>
<span class="synComment">// enum cache was generated for a previous (smaller) version of the</span>
<span class="synComment">// Descriptor Array. In that case we regenerate the enum cache.</span>
<span class="synStatement">if</span> (own_property_count <= keys->length()) {
isolate->counters()->enum_cache_hits()->Increment();
<span class="synStatement">if</span> (cache_enum_length) map->SetEnumLength(own_property_count);
<span class="synStatement">return</span> ReduceFixedArrayTo(isolate, keys, own_property_count);
}
}
<span class="synStatement">if</span> (descs->IsEmpty()) {
isolate->counters()->enum_cache_hits()->Increment();
<span class="synStatement">if</span> (cache_enum_length) map->SetEnumLength(<span class="synConstant">0</span>);
<span class="synStatement">return</span> isolate->factory()->empty_fixed_array();
}
isolate->counters()->enum_cache_misses()->Increment();
Handle<FixedArray> storage =
isolate->factory()->NewFixedArray(own_property_count);
Handle<FixedArray> indices =
isolate->factory()->NewFixedArray(own_property_count);
<span class="synType">int</span> size = map->NumberOfOwnDescriptors();
<span class="synType">int</span> index = <span class="synConstant">0</span>;
<span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i < size; i++) {
PropertyDetails details = descs->GetDetails(i);
<span class="synStatement">if</span> (details.IsDontEnum()) <span class="synStatement">continue</span>;
Object* key = descs->GetKey(i);
<span class="synStatement">if</span> (key->IsSymbol()) <span class="synStatement">continue</span>;
storage->set(index, key);
<span class="synStatement">if</span> (!indices.is_null()) {
<span class="synStatement">if</span> (details.location() == kField) {
DCHECK_EQ(kData, details.kind());
FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
<span class="synType">int</span> load_by_field_index = field_index.GetLoadByFieldIndex();
indices->set(index, Smi::FromInt(load_by_field_index));
} <span class="synStatement">else</span> {
indices = Handle<FixedArray>();
}
}
index++;
}
DCHECK(index == storage->length());
DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
<span class="synStatement">if</span> (cache_enum_length) {
map->SetEnumLength(own_property_count);
}
<span class="synStatement">return</span> storage;
}
</pre>
<p>で<code>GetFastEnumPropertyKeys</code>がこれ。<br/>
実は<code>GetFastEnumPropertyKeys</code>は先程省略した、<code>GetOwnKeysWithUninitializedEnumCache</code>からも呼び出される。<br/>
とても長いコードだが、やっていることはdescriptorが<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_cacheを持っていれば、それを返すし、なければ作成して返す。<br/>
これだけ。</p>
<p>さて、これがForInでキーを列挙する最速のパスなのだが、遅いパターンはどうだろうか。
<code>GetOwnKeysWithElements</code>のに戻るが、</p>
<pre class="code" data-lang="" data-unlink>keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);</pre>
<p>と、<code>KeyAccumulator::GetOwnEnumPropertyKeys</code>を呼び出すらしい。</p>
<pre class="code" data-lang="" data-unlink>Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
Isolate* isolate, Handle<JSObject> object) {
if (object->HasFastProperties()) {
return GetFastEnumPropertyKeys(isolate, object);
} else if (object->IsJSGlobalObject()) {
return GetOwnEnumPropertyDictionaryKeys(
isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
object->global_dictionary());
} else {
return GetOwnEnumPropertyDictionaryKeys(
isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
object->property_dictionary());
}
}</pre>
<p>これが実装<br/>
<code>object->HasFastProperties</code>であれば、<code>GetFastEnumPropertyKeys</code>に戻れるらしい。<br/>
<code>HasFastProperties</code>である条件は、</p>
<ul>
<li>mapがHashTableでなく、StringTableでも無いこと。</li>
</ul>
<p>その条件の場合はやはり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_cacheから取得される。<br/>
それ以外のグローバルオブジェクトの場合、<code>GetOwnEnumPropertyDictionaryKeys</code>のパスに入る。<br/>
GetOwnEnumPropertyDictionaryKeysでは<br/>
各dictionaryの<code>CopyEnumKeysTo</code>が呼び出されプロパティのコピーが行われる。</p>
<pre class="code" data-lang="" data-unlink>template <typename Derived, typename Shape, typename Key>
void Dictionary<Derived, Shape, Key>::CopyEnumKeysTo(
Handle<Dictionary<Derived, Shape, Key>> dictionary,
Handle<FixedArray> storage, KeyCollectionMode mode,
KeyAccumulator* accumulator) {
DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr);
Isolate* isolate = dictionary->GetIsolate();
int length = storage->length();
int capacity = dictionary->Capacity();
int properties = 0;
for (int i = 0; i < capacity; i++) {
Object* key = dictionary->KeyAt(i);
bool is_shadowing_key = false;
if (!dictionary->IsKey(isolate, key)) continue;
if (key->IsSymbol()) continue;
PropertyDetails details = dictionary->DetailsAt(i);
if (details.IsDontEnum()) {
if (mode == KeyCollectionMode::kIncludePrototypes) {
is_shadowing_key = true;
} else {
continue;
}
}
if (dictionary->IsDeleted(i)) continue;
if (is_shadowing_key) {
accumulator->AddShadowingKey(key);
continue;
} else {
storage->set(properties, Smi::FromInt(i));
}
properties++;
if (mode == KeyCollectionMode::kOwnOnly && properties == length) break;
}
CHECK_EQ(length, properties);
DisallowHeapAllocation no_gc;
Dictionary<Derived, Shape, Key>* raw_dictionary = *dictionary;
FixedArray* raw_storage = *storage;
EnumIndexComparator<Derived> cmp(static_cast<Derived*>(*dictionary));
Smi** start = reinterpret_cast<Smi**>(storage->GetFirstElementAddress());
std::sort(start, start + length, cmp);
for (int i = 0; i < length; i++) {
int index = Smi::cast(raw_storage->get(i))->value();
raw_storage->set(i, raw_dictionary->KeyAt(index));
}
}</pre>
<p>でこれが、<code>CopyEnumKeysTo</code>の実装。<br/>
forでループを回してプロパティをコピーしている。<br/>
まあこれが早いわけないよねということで、ForInの高速化の実装を確かめた。</p>
<h2>まとめ</h2>
<p>ForInのRuntime呼び出しでは、レシーバオブジェクトがFastPropertiesさえ持っていれば、<br/>
EnumCacheから値を取得するので高速。
IgnitionがForInを処理するパスについてはまたそのうち。</p>
brn_take
「Angular 4 の最新動向と、2017年再注目のDart、そしてAngular Dart」に行ってきた
hatenablog://entry/10328749687224334284
2017-03-07T10:26:35+09:00
2017-03-08T16:55:40+09:00 AngularとDartの勉強会でした。 以下メモ Angular4がやってくる!?新機能ダイジェスト Asai Masahiko Angular 4がやってくる!? 新機能ダイジェスト.pdf - Google ドライブ Semantic Versioningの導入 非推奨ポリシーの導入(2つのメジャーリリースを挟む) 変更点ダイジェスト ViewEngineの改善 templateタグが非推奨に @angular/animationsの独立 typescript2.1へのアップデート metaタグの追加・更新・削除が可能に Email Validator・EqualTo Valida…
<p>Angularと<a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>の勉強会でした。</p>
<p>以下メモ</p>
<h2>Angular4がやってくる!?新機能ダイジェスト</h2>
<p><strong>Asai Masahiko</strong></p>
<p><a href="https://drive.google.com/file/d/0B5RRsfHffoQURzBBWVVlTDdzTWc/view">Angular 4がやってくる!? 新機能ダイジェスト.pdf - Google ドライブ</a></p>
<ul>
<li>Semantic Versioningの導入</li>
<li>非推奨ポリシーの導入(2つのメジャーリリースを挟む)</li>
</ul>
<p><em>変更点ダイジェスト</em></p>
<ul>
<li>ViewEngineの改善</li>
<li>templateタグが非推奨に</li>
<li>@angular/animationsの独立</li>
<li>typescript2.1へのアップデート</li>
<li>metaタグの追加・更新・削除が可能に</li>
<li>Email Validator・EqualTo Validatorの追加</li>
</ul>
<h3>Angular2で作った社内向けツールを4に移行してみた</h3>
<p>ハーモニーお菓子管理
お菓子の在庫が見れる</p>
<p>14 Components
4 Service
5 Route</p>
<p>およそ半日くらいで移行完了</p>
<h3>まとめ</h3>
<p>SemanticVersioningが導入後発のMajor Release
思っていたより小さなアップデート
ViewEngineの改善はでかい</p>
<h2>15分で分かった気になる<a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a></h2>
<p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F12I69CaU1sfbB1kd3NyTbnvaVaA7TwFqTk1zfXbSQkq0%2Fedit%23slide%3Did.p" title="15分でわかった気になるDart" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://docs.google.com/presentation/d/12I69CaU1sfbB1kd3NyTbnvaVaA7TwFqTk1zfXbSQkq0/edit#slide=id.p">docs.google.com</a></cite></p>
<p><strong>小林達(さとし)</strong></p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>を取り巻く状況</h3>
<p>当初はjsを置き換える予定だった<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/AdSense">AdSense</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/AdWords">AdWords</a>がangular-<a class="keyword" href="http://d.hatena.ne.jp/keyword/dart">dart</a>にリプレイス<br/>
<a href="http://news.dartlang.org/2016/10/google-adsense-angular-dart.html">The new Google AdSense user interface: built with AngularDart</a><br/>
<a href="http://news.dartlang.org/2016/03/the-new-adwords-ui-uses-dart-we-asked.html">The new AdWords UI uses Dart — we asked why</a></p>
<h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>言語</h3>
<p>発表から6年だが、まだ活発<br/>
DartPadで試せる<br/>
(<a href="https://dartpad.dartlang.org/">DartPad</a>)</p>
<p>すべてオブジェクト<br/>
intなどは初期化しないとnull<br/>
boolはtrue以外はすべてfalse<br/>
<a class="keyword" href="http://d.hatena.ne.jp/keyword/dart">dart</a>:collectionパッケージには(<a class="keyword" href="http://d.hatena.ne.jp/keyword/java">java</a>)のような感じでコレクションがある。</p>
<ul>
<li>Map</li>
<li>Iterable(List,Set)</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%E2%B3%AC%B4%D8%BF%F4">高階関数</a>もつかえる</li>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%BF%BF%E4%CF%C0">型推論</a>はもうちょい</li>
<li>Future・async・await</li>
<li>ドット2つでビルダー化する(thisを返す様になる)</li>
<li>充実した標準ライブラリ</li>
<li>公式ドキュメントが充実</li>
</ul>
<p><strong>Sound <a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a></strong></p>
<p>Stroing Mode</p>
<h2>AngularDartで快適SPA</h2>
<p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F16Hyr8srWg4FTuY-IJnfwAsuA2ielmmr1h9YNa9ZMGI8%2Fedit" title="Angular+Dart=より快適なSPA開発" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://docs.google.com/presentation/d/16Hyr8srWg4FTuY-IJnfwAsuA2ielmmr1h9YNa9ZMGI8/edit">docs.google.com</a></cite></p>
<h3>AngularDartの歴史</h3>
<p>TypeScript版と分離した</p>
<p><em>ビジネスに使える</em>事</p>
<h3>AngularDartをさっと見る</h3>
<p>Streamが言語自体に組み込まれている</p>
<h2>サーバサイド<a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>を試してみる</h2>
<p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1BQRfmFn4-CCMrJVcYoC74zOyQQ6MWa_gXOQNJs4_unA%2Fedit%23slide%3Did.p" title="サーバーサイドDartを試してみる" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://docs.google.com/presentation/d/1BQRfmFn4-CCMrJVcYoC74zOyQQ6MWa_gXOQNJs4_unA/edit#slide=id.p">docs.google.com</a></cite></p>
<p>All-in-oneならAqueduct</p>
<p>シンプルなら純正のShelfがいいかも</p>
<p>初心者にはとっつきづらい</p>
<p>中間なのは<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/RedStone">RedStone</a></strong><br/>
Shelfを利用したWrapper</p>
<h2>まとめ</h2>
<p>とりあえず、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>の開発がまだ続いているのに驚いた(結構活発に) <br/>
けど、ES2015 + Typescriptの躍進があったので、他のオプショナル静的型の言語がフロントエンドで使われるには、<br/>
なかなか厳しい環境だろうなと思う。<br/>
ただ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Dart">Dart</a>はランタイムがかなり充実している印象なので、それはうらやましい。<br/>
AngularDartがAngularと全く別のラインで開発されてるのも知らんかった。<br/>
が、まあ使わないだろう。</p>
brn_take