Hatena::ブログ(Diary)

枕を欹てて聴く

 

2012-02-01

How to write JS code generator

| 00:41 |

f:id:Constellation:20120202003445p:image

JSpretty printerを作りました. ECMAScriptのparserとしてesprimaを使っていて, 自作のcode generatorがASTから綺麗なJSをprintします.

で, 現在はesprima code baseに持って行こうとしています. 将来的にesprimaで使えるようになる予定なので, 使いたい!! という方はお待ちください.

operator precedence

ASTではparenthesisの情報が落ちています. もちろん, 全て括弧で囲めば評価順序は問題ないわけですから,

((((((1 + 1) + 1) + 1) + 1) + 1) + 1)

とprintすれば, 別段問題はないわけです. しかし, これはprettyですか? いやいやご冗談を...

つまり, 端的には

(1 + 2) * 3

はこのまま残すけれども,

1 + (2 * 3)

1 + 2 * 3

としたいわけです. はたしてどうするのか?

前提, precedence

parserではこのような優先順位の異なる結合をうまく扱う方法として, operator precedence parserが存在します.

では, code generatorはこれの逆をやればいいわけです.

precedenceは結合優先度が高ければ高いexprほど高くなります. つまり, LogicalOR < LogicalAND < ... < Additive < Multiplicative < Unary < ... < Primaryという風にです.

いま, 現在のgeneratorのprecedenceというものを設定します. これは現在generatorがどのprecedenceを最低レベルとしているかというもので, generatorはこれより低いprecedenceを持つoperatorのexprを出力するには括弧で囲む必要があります.

実例1
1 + (2 * 3)

という形のASTが与えられたとします. これからpretty-printを行いましょう.

すると, まずは最低のprecedence, Sequential(コンマ区切りのもの. expr, expr, expr)をgeneratorに設定してgenerateします. このexprはAdditiveなので, Sequentialよりも高いprecedenceです. よってこのexpr全体には括弧がいりません.

次にlhsの1です. AdditiveExpressionは

AdditiveExpression + MultiplicativeExpression

ですから, こちらはAdditiveのprecedenceでgenerateします. すると1はPrimaryExpression(というかNumericLiteral)です. これは最高precendenceなので気にすることなく1を出力します(括弧なし)

rhsの(2 * 3)です. 上のBNFより, Multiplicativeのprecedenceでgenerateします. すると対象は*でMultiplicativeですから, MultiplicativeとMultiplicativeなのでより低いということはありません. よって括弧なしでgenerateします.

この結果,

1 + 2 * 3

を得ることができます.

実例2
1 * (2 + 3)

次にという形のASTが与えられたとします.

すると, まずは最低のprecedence, Sequentialをgeneratorに設定してgenerateします. このexprはMultiplicativeなので, Sequentialよりも高いprecedenceです. よってこのexpr全体には括弧がいりません.

次にlhsの1です. MultiplicativeExpressionは

MultiplicativeExpression * UnaryExpression

ですから, こちらはMultiplicativeのprecedenceでgenerateします. するとPrimaryなので, 括弧なしで1を取り出します.

問題はrhsの(2 + 3)です. 上のBNFより, Unaryのprecedenceでgenerateします. すると対象は+でAdditiveですから, Unaryよりも低いprecedenceなわけです. そこでgeneratorはすかさず括弧でこのexprを囲みます!!! (2 + 3)

この結果,

1 * (2 + 3)

を得ることができます. よかった!!

blockとelse-ifの連結

非常に簡単なことに, 基本的にはstatementをとるstatementではひとつindentを下げるというそれだけの規則でうまく行きます.

if (cond)
    print("HELLO");

のようにです. ただし, ここでたった2つだけ例外を考慮することで, 綺麗なcodeを出力できます.

blockは全体のstatementにくっつく

blockは前のstatementにくっつきます. つまり,

if (cond)
    {
        print("HELLO");
    }

という風にblock自体がstatementとしてindentが下がるのではなく, if (cond)にくっついて,

if (cond) {
    print("HELLO");
}

となります. FunctionExpressionやらdo-whileやら, blcokらしきものを所持する可能性があるstatement, expressionでこのcheckを行うことで, 綺麗なcodeを出力できます.

else-ifはくっつく

今のままでは

if (cond) {
    print("HELLO");
} else
    if (cond2) {
    }

となってしまいます. else-ifでセットになっているのではなく, あれは単にelse statementのstatementにif文を突っ込んでいるだけであるためです. このため, elseが存在するとき, そのstatementがif文であったときにはelseに吸い付くようにします. すると,

if (cond) {
    print("HELLO");
} else if (cond2) {
}

となります. 綺麗!

まとめ

pretty-printerは思いの外simpleな規則で綺麗に実装できました.

2012-01-28

Announcing Lv5 new RegisterVM

| 20:25 |

lv5がRegisterVMになりました. これを持ってversion 0.0.2とします.

次の目標は, さらなるtuning, RegExp JIT, 部分的なES.next対応の開始です.

benchmark

RegisterVM

============================================
RESULTS (means and 95% confidence intervals)
--------------------------------------------
Total:             11453.9ms +/- 0.7%
--------------------------------------------

  v8:              11453.9ms +/- 0.7%
    crypto:         1085.9ms +/- 5.5%
    regexp:         1085.0ms +/- 0.5%
    earley-boyer:   1357.7ms +/- 0.8%
    splay:          1216.8ms +/- 3.6%
    deltablue:      2215.0ms +/- 0.6%
    raytrace:       1071.4ms +/- 1.8%
    richards:       3422.0ms +/- 0.5%

============================================
RESULTS (means and 95% confidence intervals)
--------------------------------------------
Total:                 1576.6ms +/- 0.7%
--------------------------------------------

  3d:                   154.2ms +/- 0.9%
    raytrace:            50.2ms +/- 0.7%
    cube:                45.1ms +/- 2.4%
    morph:               58.9ms +/- 0.7%

  date:                 265.7ms +/- 1.2%
    format-xparb:       136.3ms +/- 1.8%
    format-tofte:       129.4ms +/- 0.7%

  access:               216.7ms +/- 0.9%
    nbody:               53.0ms +/- 1.4%
    fannkuch:            66.4ms +/- 0.9%
    nsieve:              54.7ms +/- 1.2%
    binary-trees:        42.5ms +/- 1.0%

  regexp:                89.8ms +/- 1.1%
    dna:                 89.8ms +/- 1.1%

  controlflow:           15.7ms +/- 0.9%
    recursive:           15.7ms +/- 0.9%

  string:               527.2ms +/- 1.6%
    fasta:              157.9ms +/- 2.2%
    base64:              59.9ms +/- 2.7%
    unpack-code:        128.9ms +/- 3.8%
    tagcloud:            98.0ms +/- 0.6%
    validate-input:      82.6ms +/- 1.7%

  crypto:                67.7ms +/- 0.7%
    sha1:                16.5ms +/- 1.4%
    md5:                 16.0ms +/- 1.2%
    aes:                 35.2ms +/- 0.9%

  math:                 104.6ms +/- 0.7%
    cordic:              46.4ms +/- 0.9%
    partial-sums:        38.5ms +/- 0.8%
    spectral-norm:       19.8ms +/- 0.9%

  bitops:               134.9ms +/- 0.8%
    bits-in-byte:        24.9ms +/- 1.5%
    3bit-bits-in-byte:   14.5ms +/- 5.1%
    nsieve-bits:         24.3ms +/- 0.9%
    bitwise-and:         71.3ms +/- 0.6%

StackVM

============================================
RESULTS (means and 95% confidence intervals)
--------------------------------------------
Total:             11760.1ms +/- 0.8%
--------------------------------------------

  v8:              11760.1ms +/- 0.8%
    splay:          1158.8ms +/- 0.9%
    regexp:         1098.0ms +/- 1.4%
    raytrace:       1118.1ms +/- 0.6%
    richards:       3475.2ms +/- 0.6%
    crypto:         1387.5ms +/- 6.6%
    deltablue:      2148.9ms +/- 1.4%
    earley-boyer:   1373.7ms +/- 0.6%

============================================
RESULTS (means and 95% confidence intervals)
--------------------------------------------
Total:                 1641.6ms +/- 0.6%
--------------------------------------------

  access:               251.5ms +/- 1.3%
    nbody:               60.5ms +/- 0.9%
    fannkuch:            87.6ms +/- 2.8%
    nsieve:              62.2ms +/- 1.9%
    binary-trees:        41.1ms +/- 0.6%

  math:                 113.7ms +/- 2.0%
    cordic:              52.4ms +/- 4.0%
    partial-sums:        40.3ms +/- 1.1%
    spectral-norm:       20.9ms +/- 5.0%

  date:                 255.9ms +/- 0.7%
    format-xparb:       136.4ms +/- 1.1%
    format-tofte:       119.6ms +/- 0.7%

  regexp:                88.6ms +/- 0.4%
    dna:                 88.6ms +/- 0.4%

  bitops:               143.5ms +/- 0.8%
    bits-in-byte:        29.1ms +/- 1.7%
    3bit-bits-in-byte:   15.9ms +/- 4.4%
    nsieve-bits:         28.3ms +/- 0.8%
    bitwise-and:         70.2ms +/- 0.6%

  controlflow:           13.4ms +/- 1.4%
    recursive:           13.4ms +/- 1.4%

  string:               530.6ms +/- 1.5%
    fasta:              159.4ms +/- 3.5%
    base64:              60.3ms +/- 1.4%
    unpack-code:        127.2ms +/- 2.0%
    tagcloud:           100.1ms +/- 1.0%
    validate-input:      83.6ms +/- 1.1%

  3d:                   170.6ms +/- 0.7%
    raytrace:            55.2ms +/- 1.2%
    cube:                55.4ms +/- 1.9%
    morph:               60.0ms +/- 0.8%

  crypto:                73.8ms +/- 1.0%
    sha1:                17.7ms +/- 1.3%
    md5:                 16.4ms +/- 2.0%
    aes:                 39.7ms +/- 1.3%

高速化しました!

intro

StackVMがあったとして, そのbytecodeは通るpathごとにstackの深さが違うということがあり得るでしょうか? つまり,

if (cond) {
  ...何らかのpath
}
v = 1 + 2;  // ここでpushしてpushしてbinary addするstackの深さ

で1 + 2の部分でstackにpushしてーってやる場所がthen pathを通っているかいないかによって変わるかという話です. 通常考えればそんなことはありえません. ということは, 現在のframeから見て, 1 + 2は結局常に同じstackのoffsetに向かってpushしているはずなのです.

つまり,

v = 1 + 2;
1はstack[0]にpush. 完了後 sp = 1
2はstack[1]にpush. 完了後 sp = 2
stack[0]に1 + 2. 完了後 sp = 1

というのは常に同じなのであれば, そもそもstack pointerをincrementしたりdecrementしたりせずに

loadI r0, 1
loadI r1, 2
add r0, r0, r1

って書いてるのと同じです. ということで, stackの深さをそのままregisterに割り当てることで, RegisterVM bytecodeを作ることができます. registerは無限長です.

optimization

しかし, 上のbytecodeでは無駄があります. ここであるlocal変数(stack上に存在)iを考えるとき,

v = i + 20;

はstack VMでは,

pushLocal i  // local変数iがstackにpush
pushI 20
binaryAdd
...

となりますが, これをそのままRegister VM bytecodeに置き直すと, (iがr0に入っているとして)

move r1, r0  // push_localの処理を愚直にr1へのcopyとして
loadI r2, 20
add r3, r1, r2

となります. ここで, moveは明らかに無駄です. 本来的には

loadI r1, 20
add r2, r0, r1

となってしかるべきです. という訳で, 対象の変数がregister上に構築されているとき, そのままregisterを使う事ができ, copy costが削減されます.

RHS

しかし, 上のoptimizationについては気をつける必要があります. というのも, StackVMの場合はstack上に値をcopy, いわば評価していたわけですが, RegisterVMは評価していないからです. このままでは以下のような場合, (iはr0にあるとして)

i = 10;
v = i + (i = 20);
loadI r0, 10  // i = 10
              // ここでStackVMはcopyすることで評価しているが, 省いてしまっている.
loadI r0, 20  // i = 20の代入処理が...
add r1, r0, r0  // あれ?

というわけで, 10 + 20のはずが, 20 + 20になってしまいます.

これの解決法は, ある評価結果を使うまでの間に別のexprを評価する場合, そのexprが代入を含んでいればmoveしておけばいいです.

loadI r0, 10  // i = 10
move r1, r0   // RHSに代入があるので
loadI r0, 20
add r2, r1, r0

という訳で安心です. 最適化passなしで何とかしようとしたらこんな形でしょうか. 別にpass通せば, tmpのままのを後で置き換えできるのですが, なんとか1 passだとこのような形...? (from 『コンパイラ』 p396)

未評価

この間ゆるゆり2期に敬意を表してハーゲンダッツのラムレーズンを買いに行ったところ, ふと頭に浮かんだやり方があって,

上記のr0を, 「未評価だけど後で使うlist」に入れておいて, 書きこみ処理を行う命令をemitした時にそのlistを検索, dst対象と同じ物があれば, 退避させればいいのではないかと考えました. つまり,

loadI r0, 10  // i = 10
              // ここでStackVMはcopyすることで評価しているが, 省いてしまっている.
              // r0は未評価だけど後で使うlistに入っている.

              // で, loadI r0, 20をemitしようとする. この時r0はlistに入っている
move r1, r0   // すかさずmoveを挿入. listからr0をremoveしつつ, 使う値をr1に変更
loadI r0, 20
add r2, r1, r0  // 安心

という方法を考えたのですが, 大丈夫なのでしょうか? 問題がなさそうなら, この実装に変えようかなと考えています.

補足

なにぶん稚拙なので, より良いやり方がある場合は教えていただければありがたいです...

chick307chick307 2012/01/28 20:58 シロウト考えですが、最後のパターンは中間表現がSSA形式ならば解決できるきがします。
V8もSSA形式を使っているとどこかで見ました。

ConstellationConstellation 2012/01/28 21:47 コメントありがとうございます! 勉強になります.
SSA作成, コンパイル時間上問題ないものなのでしょうか? コンパイラの本などでは, すべてmoveするような形の中間コードを吐いて, それをDAGでoptimizeすれば良いというふうに書いてあったのですが, なにぶんVMのbytecode compilerなので, できるだけ時間をかけないでそこそこ品質のよいbytecodeを得ようというのがgoal担っていて, 上の例は全て1 passになっています.(AST => bytecode)
V8, optimizedなcompiler(Hydrogen/Lithium)はSSAな一方, 初回のfull-codegenはstack VM likeなmachine codeをemitしていたような記憶があります. 間違っていたらすみません...

2011-12-18

lv5.current 2011/12/18

| 21:47 |

lv5の実装進捗

test262はもちろん全てpassしているので安心です.

8bit string is landed in lv5

「天下のJSCさんもやってないからー」と言いつつやってなかったのですが... ある日,

https://lists.webkit.org/pipermail/webkit-dev/2011-November/018618.html

なーんてことがあり, JSCさんもやってしまったのでいよいよ言い訳が通じなくなって実装しました.

一部ASCII文字列ASCIIのまま持ちます. これで多くのStringLiteralが該当し, memoryが節約されます.

JSCの場合はWebCoreも含むので規模が大きく, はじめASCII, 場合によってはUChar*持つよという実装です.

その点lv5はそこまで大きくないことを利用して過激な方針, すなわち8bitなら8bit, 16bitなら16bitで完全に分離し, それぞれdown castでもしてtemplateで引き取るという実装にしました.

V8もそんな感じ(TwoByte or ASCII)ですが, あちらはStringに上げてaccessする度にdown castするので, String周りのalgorithmなら分岐回数小さい分高速に動作します. ただしcodeがシッチャカメッチャカになります.

また毎回templateで触るのも外部的には触りづらかろうということで, generalなinterfaceも用意しました. こっちで触れば綺麗にかけます. ただし, 速度が気になるなら...

変数解析をparserへ移動 + より進んだ不要code削除

大規模改修を行い, 変数解析をparserへ移しました.

これにより, compilerの時点で解析しながら確定したらrepatchingというろくでもないことをする必要がなくなり(以前はそんな実装だったのです...), よりagressiveなbytecodeを変数について吐けるようになりました.

またこの他, 細かな挙動, 例えば環境を作るとかinstantiationといったものまですべてbytecodeに埋め込まれる事になりました. これによりruntime処理のほとんどがbytecode自体に埋め込まれ, bytecodeがほぼ全ての情報を埋めこまれた状態で保持されることになりました.

この変更はbytecodeにほぼ全ての情報を埋め込むことにより, JITをbytecode to machine code compilerで済ませるということを見据えています.

結構agressiveで, 例えば

function test() {
  var i = "OK";
  function testing() {
  }
}

は以下のbytecodeに展開されます. (lv5 --dis 001.js)

[code] depth: 1 local: 0 heap: 0
00000: INSTANTIATE_DECLARATION_BINDING 0 0
00003: BUILD_FUNCTION 0
00005: STORE_GLOBAL 0 0 0
00009: POP_TOP
00010: STOP_CODE
[code] depth: 0 local: 0 heap: 0
00000: STOP_CODE

1つ目のcodeがglobal codeなので, それを考慮してみると, 実質的には,

[code] depth: 0 local: 0 heap: 0
00000: STOP_CODE

STOP_CODEしか残っていません. testingに至っては消去されます.

evalとかwithとか入るともちろんきちんと動作するようにbytecodeが変化します.

あと, Functionのdeclarationとかそういう情報まですべてbytecodeに入っています.

HEAP変数を作って, environmentが必要になると,

function test() {
  var i = 20;
  function inner() {
    print(i);
  }
  inner();
}
test();
[code] depth: 2 local: 0 heap: 0
00000: INSTANTIATE_DECLARATION_BINDING 0 0
00003: BUILD_FUNCTION 0
00005: STORE_GLOBAL 0 0 0
00009: POP_TOP
00010: CALL_GLOBAL 0 0 0
00014: CALL 0
00016: POP_TOP
00017: STOP_CODE
[code] depth: 3 local: 1 heap: 1
00000: BUILD_ENV 1
00002: INSTANTIATE_HEAP_BINDING 0 0 0
00006: BUILD_FUNCTION 0
00008: STORE_LOCAL 0
00010: POP_TOP
00011: PUSH_INT32 20
00013: STORE_HEAP 0 0 0
00017: POP_TOP
00018: CALL_LOCAL 0
00020: CALL 0
00022: POP_TOP
00023: STOP_CODE
[code] depth: 3 local: 0 heap: 0
00000: CALL_GLOBAL 0 0 0
00004: LOAD_HEAP 1 0 0
00008: CALL 1
00010: POP_TOP
00011: STOP_CODE

BUILD_ENVがbytecodeに入っています.

ちなみにLOAD_HEAPの1はSymbol引き, 0はenvironmentの階層という情報で, これとoffset情報0を利用してHEAP変数lookupは一度も辞書を引きません.

2011-12-01

constへの異常な愛情, または私は如何にして心配するのを止めてConstDeclarationを愛するようになったか

| 03:39 |

JS Advent Calendar, オレ標準コース 1日目, id:Constellation です.

あれから1年ですか...


前半で, ES.nextに導入されたDeclarationについてさっと, そして後半でタイトルの説明をします.

この文書は, 2011/12/01現在のES5 Engineの実装とES.next draftに基づいています.

また, ES.nextの文法がvalidかどうかについては, parserを用意してあるので, 是非ご利用ください. http://constellation.github.com/iv/js/es.next.html


Declaration

ES.nextでは新たにDeclarationという区切りが入り, LetDeclaration, ConstDeclaration, FunctionDeclarationが導入されました.

Declarationは全てblock scopedな変数の宣言です. VariableStatementと違い, 有効範囲がblockの範囲(もしくはFunctionBody)に収まります.

また, Declarationはそのblock scopedという性質上Block直下, もしくはFunctionBody直下でしか利用できません

つまり,

if (cond) const i = 20;

はBlock直下ではないのでSyntax Invalidです.


LetDeclaration

待ちに待った(Mozilla JSer)letです. ES.nextでは新たにblock scopeという概念が入りました. すなわち,

{
  let i = 20;
  print(typeof i);  // number
}
print(typeof i);  // undefined

blockの中でだけ見える, というかblockで1層scopeが新たに作られるようになります.

LetDeclarationはblock scopedなVariableStatementのようなもので, 変数の範囲を小さく抑えることができます. 大事. ちなみにLetExpressionとかは今のところ入っていません.

また, 現行draftでは,

for (let i = 0; i < 20; ++i) {
  print(i);
}

というふうなletも許可されていません. block scopedなので,

for (let i = 0; i < 20; ++i) print(i);

というようなblockが存在しない場合についての仕様を詰めていかないとということなのでしょうか.


ConstDeclaration

ConstDeclarationがやって来ました.

ConstDeclarationは再代入不可な変数です. 具体的には, 再代入しようとすると, スルーされます. ちなみに, これがstrict modeの場合はErrorが出ます.

{
  const i = 20;
  i = 30;
  print(i === 20);
}
print(typeof i);  // undefined

これももちろんblock scopedなのでblockの外からは参照することができません.

また, initializerが必須という特徴があります. すなわち,

{
  const i;  // Syntax Error
}

です. 必ず, const i = ...;という風にinitializerが存在しなければいけません.


FunctionDeclaration

FunctionDeclarationはDeclarationの一員となりました. よって, 以下のような,

if (cond) {
  function decl() {
  }
}

利用が可能になりました. これはES5では厳密にはSyntax Errorになる挙動です. なぜなら, ES5ではFunctionDeclarationはFunctionBody, つまり関数直下にしか許可されていなかった為です.

ただし, 注意しなければいけません. これはblock scopedです. ということは, 従来からFunctionStatement *1 として解釈していたSpiderMonkeyの仕様, V8, JSC他の単純な全域に見えるFunctionDeclarationと解釈する仕様とも互換性がない, 全く新しい解釈方式となります.



というわけで, Declarationというblock scopedな宣言という構文要素が新たに追加されたのでした.


Avoid const

後半開始です.

f:id:Constellation:20111201031526p:image

http://s3.mrale.ph/nodecamp.eu/#63

V8のperformance tuningのslideとしてあがったもので, そのtopicの1つとして, "Avoid const"がありました. つまり, constを使うなという内容です. これはなぜでしょうか?

というのも根源的にはES5においてConstDeclarationは仕様に入っていなかった為です.

ES5でConstDeclarationをとなった時, そのままVariableStatementの構文, semanticsが採用され, かつconstnessが付け加えられました. つまり, VariableStatementの付加機能のような, patchingのような形で実装されたのです.

結果ES5の一般的実装におけるConstDeclarationについて, const変数Syntax上から判別不可能な状態を持ってしまうという致命的な欠陥を抱えてしまったのです.

つまり,

  1. 初期化
  2. 初期化済み

の2つです.

function part1() {
  print(i);  // ここでiは未初期化
  const i = undefined;
  print(i);  // ここでiは初期化済みundefined

  const j = 20;
  print(j);  // ここでjは初期化済み値あり
}

ということです. 未初期化ならundefinedを入れておけばいいという簡単なことではありません. 具体的には代入処理のところです.

function part1() {
  const i = undefined;  // もちろんpass

  i = undefined;  // もちろんfailすべき. constnessが崩れている

  const j;
  if (cond) {
    j = "OK";
  }
  j = "TEST";  // condがtrueならfail!!!! これが問題
}

初期化初期化済みかどうかは代入を受け付けるかどうかの挙動が違います.

しかもcondがtrueならfailという例で示したとおり, syntax上からはこの代入がvalidなものかは判断できないので, 実行時にいちいち, 「この値は初期化済みか, それとも未初期化か」を検査する必要があります. 結果, 処理系内部では値読み込み時は,

if (初期化済み?) {
  return val;
} else {
  return undefined;
}

書き込み時は

if (初期化済み?) {
  fail();
} else {
  target = val;
}

という風になっています. 単なる変数なら, return valとtarget = valで済むのに, 読み込み, 書き込みのたびに初期化済みかどうかのcheckが必要という致命的なことになるのです.

これはもうはっきりと「ES5においてconstはvarよりずっと遅い」と言えます. これが冒頭のAvoid constの理由です.


in ES.next

しかし, ES.nextになって話は変わります. ここで先に説明したConstDeclarationについて重要な点をまとめましょう.

  1. Block Scopedである
  2. Initializerが必須である

そして, ここでわざわざDeclarationをStatementと分けblock scopedとした効果が生きてきます.

DeclarationをとれるのがBlock直下, 及びFunctionBody直下しか存在しない

というPointです. IfStatementもDoもWhileも, みんな"Statement"を取るはずです. そこにはDeclarationは含まれていません.

IfStatement:
  if ( Expression ) Statement else Statement
  if ( Expression ) Statement

ということは, 以下のScriptはES.nextにおいてSyntax Invalidです.

if (cond)
  const i = 20;

これらの条件を全て合わせると, ES5の時にはわからなかったconstの状態が, 全てSyntax Levelで解析することができるのです!

function part1() {
  const i = undefined;  // もちろんpass

  // const j;  // initializerが無いのでSyntax Error
  if (cond) {
    const j = "OK";  // ConstDeclationはBlock Scopedなので, このjは外部にもれない
  }
  const j = "TEST";  // これはpass. ifのblockのConstDeclarationとはScopeが異なる.

  // if (cond) const k = "OK";  // これがfail. DeclarationはBlock直下, FunctionBody直下にしか存在できない
}

ということを踏まえて,

function part1() {
  print(i);  // 未初期化const値であることが文脈上保証される. undefinedが必ず入ると解析可能. lookup必要なし

  const i = 10;

  print(i);  // 初期化済み値であることが解析可能.

  if (cond) {
    const j = "OK";  // ConstDeclationはBlock Scopedなので, このjは外部にもれない
  }

  print(typeof j);  // undefined. Block Scopedなので外部にもれない.

  // よってruntimeの条件分岐で未初期化/初期化済みのpathが分離しない
}

というように, 初期化済み, 未初期化, 代入不可といった情報が, syntax levelで解析することができます. runtimeの条件分岐で未初期化/初期化済みが分岐しなくなりました.

結果, Avoid constの理由であった初期化済みかどうかのcheckは全てparse時に行う事ができ, runtimeにおいてはzero overheadで参照することができます.

やりました! もうAvoid constとは言わせません! (in ES.next only)


まとめ

ES.nextにおいてconstの文法が精査された結果, constの不必要なoverheadは消え去り, costなしでconstnessの利点を享受できるようになりました.

ES5時代においてconstを使うことはperformance低下と標準を恐れぬ所業ですが, ES.nextではconstを使いまくりましょう!! constness万歳!


補足

今年の12/25は, ましろ色クリスマスですね! やったー!

2011-11-10

ES.next parser / lexer in JS

| 22:13 |

f:id:Constellation:20111110221124p:image

現時点でのdraftに対応したES.nextのparser / lexerをJSで書きました.

iv / js, ES.next test

ASTを表示します. BNF baseの方を信用したので, super()とかはSyntax Errorとします.

BNFはdraftと ECMAScript 6th の構文をまとめてみた - hogehoge @teramako を見て書きました. どうもです.

iv / jsを拡張して, ES.nextのformatに対応したものを書きました. それほどこったことしてない + 多分bugある + まだそこまで読み込んでいないなので, 普通にbugあると思いますが, とりあえずです. bug reportは iv の方かTwitterで言ってくれれば何とかします(今切羽詰まり気味なので, 遅くなるかもですが...)

書いておけば id:teramako さんがきっとES.next Engine書いてくれるはず...