SlideShare a Scribd company logo
プログラミング

パラダイム
Minero Aoki
なぜこんな講義があるのか
• すぐには役に立たない内容を学ぶため
• すぐに役立つ知識はすぐ役に立たなくなる
• 5日のうち1日くらいはツラいめに合うのもいいの
ではないかという親心
まえふり
プログラミング言語は
たくさんある
• このインターンで使ったものだけでも……
• Ruby(ウェブアプリ)
• Swift(iOSアプリ)
• Java(Androidアプリ)
• Python(機械学習)
プログラミングパラダイムも
たくさんある
• 手続き型
• 関数型
• オブジェクト指向型
• 論理型
パラダイムは混在している
• Rubyは純粋オブジェクト指向だけど…
• ブロック構文は関数型っぽくない?
• 手続き型でもある
• OCamlは誰しも認める関数型言語だけど…
• OはOOPのOだ
プログラミング言語の
抽象・具象階層
パラダイム
言語(仕様)
言語処理系(実装)
抽象的
具象的
複数の具象の共通部分、外せない部分が抽象。普通は具象から抽象の順で理解していくほうが
わかりやすい。つまりパラダイムを理解するには言語を理解せねばならず、言語を理解するに
は言語処理系を理解しなければいけない
課題
JavaScriptコンパイラを実装せよ
……というのは

さすがに時間的に無理なので、
課題
JavaScriptコンパイラの

コードジェネレーターを実装せよ
今日実装する機能
• 整数リテラル(1, 2, 3, ...)
• 加算(1 + 2)
• グローバル変数の参照と代入(gvar = 77; gvar)
• ローカル変数の参照と代入(var lvar = 77; lvar)
• 関数呼び出し(f(77))
• 非常に簡単な最適化
JavaScriptを厳密に知るなら
ECMAScript 2015 Language Specification
http://www.ecma-international.org/publications/
files/ECMA-ST/Ecma-262.pdf
※半分冗談だけど半分は本気です
言語を理解するとは
どういうことか?
• 言語を実装したら理解したことになると思うか?
• 「最小限の実装」はその言語の「本質」なのか?
• 「本質」は定義されているか?
• プログラミング言語の「本質」は、文法・意味・処
理系のどこにあるのか?
• 安易に本質とか真髄とか言わないほうがよい
余談
「なぜ」は曖昧である
• 一見深淵で本質っぽい「なぜ∼なのか?」という問いは答えが複数あ
り、無意義であることが多い。問いは具体的にせよ
• e.g. アリストテレスの四原因説
• 「なぜCookpadは動くのか?」
• 質料因:AWSのコンピューターが動いているからだ
• 形相因:毎日の料理をもっと楽しくしたいからだ
• 作用因:プログラマーが作ったからだ
• 目的因:レシピを投稿・検索できるようにするためだ
余談
「∼とは何か」も曖昧である
• 一見深淵で本質っぽい「∼とは何か?」という問いもまと
もな答えがない愚問。
• 「オブジェクトとは何か?」→答えられない
• 「関数とは何か?」→答えられない
• 「机とは何か?」→意外とうまく答えられない
• 初めて接する分野ではこの手の疑問を発してしまうことが
多いが、考えても無駄な問いは慎重に自制すべきである
余談
コンパイラの基礎知識
コンピューターが
プログラムを実行する仕組み
• コンピューターはCPU
とメモリーを持つ
• CPUが直接実行できる
のは機械語だけ
• メモリー上に機械語のプ
ログラムを配置し、CPU
はその指示にしたがって
メモリーの値を変更する
CPU
メモリー プログラム データ
おすすめ書籍
• 『はじめて読むPentium』
• http://www.amazon.co.jp/
dp/4756144667/
• 『はじめて読む486』
• http://www.amazon.co.jp/
dp/B00OCF5YUA
インタープリターによる実行コンパイラーによる実行
コンパイラーとインタプリター
機械語コード
ソースコード
コンパイラー
コンピューター
ソースコード
評価器
実行
実行
VMによる実行
バーチャルマシン
• ソフトウェアで構築したコ
ンピューターがバーチャル
マシン(VM: Virtual
Machine)
• Java VMが有名だが、最近
のインタープリターはほぼ
これ
• Java VMのコードはバイト
コードと呼ばれる
内部コード
ソースコード
コンパイラー
VM
実行
コンパイラの一般的な構成
パーサー(parser)
意味解析器(semantic analyzer)
オプティマイザー(optimizer)
コードジェネレーター(code generator)
ソースコード
実行可能コード
フェイズ1: 構文解析
フェイズ2: 意味解析
フェイズ3: 最適化
フェイズ4: コード生成
JetSpider
• JetSpiderは今回のインターン専用に開発した
JavaScriptコンパイラ
• FirefoxのJavaScript実装であるSpiderMonkey
VM用のバイトコードを生成する
• コンパイラのjetspiderコマンドと、VMのjsvmコ
マンドから成る
SpiderMonkey(C++)独自に作った部分(Ruby & C++)
JetSpiderのアーキテクチャ
バーチャルマシン
コンパイラー
JavaScript
バイトコード*.jscファイル
JavaScript
コンパイラー
ローダー
jsvmコマンド
jetspiderコマンド
JetSpiderの実装状況
パーサー(parser)
意味解析器(semantic analyzer)
オプティマイザー(optimizer)
コードジェネレーター(code generator)
JavaScript(*.js)
SpiderMonkeyバイトコード(*.jsc)
未完成
ない
JetSpiderの入手
• 以下のレポジトリをfork、cloneしてください
• https://github.com/aamine/jetspider-course
• clone先にcdしてbundle install
オプションは--helpに聞け
% ./bin/jetspider --help
jetspider [options] {JS_FILE | -e SOURCE}
--dump-tokens Dumps lexical tokens and quit.
--dump-ast Dumps AST and quit.
--dump-semantic Dumps semantic tree and quit.
--dump-object Dumps object file and quit.
-e, --source=TEXT Give source code from option.
--help Prints this message and quit.
% ./bin/jsvm --help
Usage: ./bin/jsvm [options] SOURCE
Options:
-p, --print Executes source and print the last result.
-t, --trace Executes source with tracing.
-d, --disassemble Disassembles source.
JetSpiderのソースツリー
• bin/
• jetspider コンパイラー
• jsvm VM
• lib/jetspider/
• compiler.rb コンパイラドライバー
• parser.rb パーサー
• resolver.rb 意味解析器
• code_generator.rb コードジェネレーター
• assembler.rb バイトコードDSL
• object_file.rb JSCファイル
ここを書く
コンパイルの過程
フェイズ1: 構文解析
• ソースコード(テキスト)
の構文をチェックする
と同時に、扱いやすい
ようツリー構造(AST,
Abstract Syntax Tree)
へ変換する
if (i < 3) {
print(i);
} else {
i = 5;
}
if
call
print i
=
i 5
then
else
lhs rhsfunction args
<
i 3
left right
cond
字句解析
• 構文解析ではまずソース
コード(文字の列)を
トークン(単語)の列
に分割する
• コメントや空白はこの
段階で消してしまう

(ことが多い。JSやRubyには暗黙の文
終端があるため改行の情報は残す必要
がある。JavaDocやSQLヒントなどコ
メントが必要な場合もある)
if (i < 3) {n print(i);n} else
{n i = 5;n}
if ( i <
3 ) { n
print ( i ) ;
n } else
{ n i ……
[op] トークン列を見る
% ./bin/jetspider exp/arith.js --dump-tokens
FUNCTION function
S " "
IDENT f
( (
IDENT x
) )
S " "
{ {
S "n "
RETURN return
S " "
NUMBER 1
S " "
+ +
S " "
IDENT x
S " "
* *
S " "
NUMBER 3
; ;
S "n"
} }
S "n"
jetspider --dump-tokens FILE.js
function f(x) {
return 1 + x * 3;
}
▼ソースコード(exp/arith.js)
if文
文
関数呼び出し比較式
狭義の構文解析
• トークン列のパターンから、より大きな構造を発見
していく
• 発見したらツリーにする
if ( i < 3 ) { print ( i ) ; ……
[op] ASTを見る
% ./bin/jetspider --dump-ast exp/arith.js
type: SourceElementsNode
value:
- type: FunctionDeclNode
value: f
arguments:
- type: ParameterNode
value: x
function_body:
type: FunctionBodyNode
value:
type: SourceElementsNode
value:
- type: ReturnNode
value:
type: AddNode
left:
type: NumberNode
value: 1
value:
type: MultiplyNode
left:
type: ResolveNode
value: x
value:
type: NumberNode
value: 3
jetspider --dump-ast FILE.js
function f(x) {
return 1 + x * 3;
}
▼ソースコード(exp/arith.js)
value
AddNode
NumberNode MultiplyNode
ResolveNode NumberNode
ReturnNode
value
left value
left
value = 3value = x
value = 1
フェイズ2: 意味解析
• 変数参照と変数宣言を結びつけたり、型チェックを
したりする
• ASTに上記の情報を付加したデータ構造(中間コー
ド)を出力する。ASTをそのまま使う場合もあれば、
バイトコードに近いコードを使うこともある
• JetSpiderでは参照情報付きのASTを出力する
[op] 中間コードを見る
% ./bin/jetspider --dump-sem exp/arith.js
- type: FunctionDeclNode
value: f
arguments:
- type: ParameterNode
value: x
function_body:
type: FunctionBodyNode
value:
type: SourceElementsNode
value:
- type: ReturnNode
value:
type: AddNode
left:
type: NumberNode
value: 1
value:
type: MultiplyNode
left:
type: ResolveNode
value: x
ref: "[function f]:param:x"
value:
type: NumberNode
value: 3
- type: SourceElementsNode
value:
-
jetspider --dump-semantic FILE.js
AddNode
NumberNode MultiplyNode
ResolveNode NumberNode
ReturnNode
value
left value
left value
value = 3value = x
value = 1
value = x
param: xParameterNode
FunctionDeclNode
arguments Variableオブジェクト
フェイズ3: 最適化
• プログラムの意味を変えずに実行速度を速くし
たり、使用メモリ量を減らすなどの改善を行う
• 伝統的には意味解析とコード生成の間に位置付けら
れるが、コンパイルのあらゆる段階で行うことがで
きる
• 最近はリンク時最適化というものもある e.g. LLVM
• JetSpiderでは最適化を(まだ)行っていない
フェイズ4: コード生成
• 意味解析済みのデータ
を元に、機械語やVMコー
ドを生成する
• 実際はコードだけでは
なく、シンボルテーブル
(変数表)やリンク情
報も含むオブジェクトファ
イルを生成する
AddNode
NumberNode MultiplyNode
ResolveNode NumberNode
ReturnNode
value
left value
left value
value = 3value = x
x: param
value = 1
dd 01 int8 1
54 00 00 getarg 0
dd 03 int8 3
1d mul
1b add
05 return
Visitorパターンで
ASTをトラバースする
• traverse: ツリーのノー
ドを順番にたどってい
く操作
• 手続き型言語なら再帰
呼び出し、OOPでは
Visitorパターンを使う
のが定石
トラバース(traverse)
AddNode
NumberNode MultiplyNode
ResolveNode NumberNode
ReturnNode
1
2
3 4
5 6
Visitorパターン
class NantokaVisitor
def visit_ReturnNode(node)
visit node.value ← return文の式をトラバース
end
def visit_AddNode(node)
visit node.left ← 左側の枝をトラバース
visit node.value ← 右側の枝をトラバース
end
def visit_NumberNode(node)
リーフなのでもうたどる枝はない
end
end
visitメソッドの実装
• 他の言語だと大変だがRubyなら__send__で一発
def visit(node)
__send__( visit_#{node.class} )
end
[op] コンパイルする
jetspider FILE.js
% ./bin/jetspider exp/arith.js
% ls -l exp/arith.jsc
-rw-r--r-- 1 minero-aoki staff 228 Aug 25 10:59 exp/arith.jsc
• *.jsファイルを指定するだけ
でOK
• 拡張子を*.jscに変えたファ
イル(JSCファイル)が生成
される
[op] JSCファイルを見る
jetspider --dump-object FILE.js
% ./bin/jetspider --dump-obj exp/arith.js
- n_vars: 0
n_args: 1
vars:
- x
prolog_length: 0
js_version: 185
n_fixed: 0
script_bits: 32
code:
- dd 01 int8 1
- 54 00 00 getarg 0
- dd 03 int8 3
- 1d mul
- 1b add
- 05 return
- c5 stop
srcnotes: ''
filename: exp/arith.js
lineno: 1
n_slots: 3
static_level: 1
atoms: []
blocks: []
upvars: []
regexps: []
closed_args: []
closed_vars: []
trynotes: []
consts: []
- n_vars: 0
n_args: 0
以下略
• バイトコードだけではなく、
JSCファイルの項目がすべて
表示される
• 当然だけどコードジェネレー
ターが完成していないとエ
ラーになってしまう
• 実装した結果を確認するた
めに使用
JSCファイルの構造
• SpiderMonkey内では関数
定義1つに対してstruct
JSFunctionが1つ生成され
る
• JSCファイルにはソースファ
イルで定義されたすべての関
数に対応するstruct
JSFunctionが格納される
• 最後の1つは常にstruct
JSScriptでメンバーが少ない
JSCファイル
struct JSFunction
struct JSFunction
struct JSScript (toplevel)
magic (0xDEAD000B)
関数の数 + 1(+1はトップレベル)
[op] JSCファイルを実行する
• 単に*.jscファイルを指定すると、そのコードを実行
する
• --printオプション付きだと、実行したあと最後の式
の値を表示する
• 関数呼び出しを実装するまでは値を表示できない
ので、このオプションで結果を確認する
jsvm FILE.jsc
jsvm --print FILE.jsc
VMによる実行過程
SpiderMonkey VM

実行の仕組み(1)PC
• 1命令は1∼バイトからなる(可
変長)。例えばかけ算をするmul
命令は0x1Dの1バイト
• PC (Program Counter)レジス
ターは現在実行中の(これから
実行する)バイトコード位置を
指す
• PCが指す命令を実行してPCを
次に進める、がVMの基本の動き
dd 01 int8 1
54 00 00 getarg 0
dd 03 int8 3
1d mul
1b add
05 return
PC
dd 01 int8 1
54 00 00 getarg 0
dd 03 int8 3
1d mul
1b add
05 return
PC
値=2
値=5
getarg 0を実行
SpiderMonkey VM
実行の仕組み(2)スタック
• SpiderMonkeyのVMはスタッ
クマシン
• 各命令は、VMスタックの先
頭に値を積む(push)、取
り出して計算する(pop)、
参照して計算する、のいず
れかの処理を行う(組み合
わせもアリ)
• c.f. レジスタマシン
int8 5
mul
int8 3
return
5
5
3
15
15
pop
push
push
push
get
[op] VMの動作を見る
• --traceオプションを付
けると以下の内容が出
力できる
• 実行しようとしてい
るバイトコード
• pushとpop
• 変化後のVMスタック
jsvm --trace FILE.jsc
% ./bin/jsvm --trace exp/lvar.js
1: 00000: 00 nop
stack:
6: 00001: f1 00 00 callglobal "f"
stack: function f() {n var a = 1, b;n
p(a);n} undefined
6: 00004: 3a 00 00 call 0
inputs: f, f @ 2
output: (void 0) @ 0
stack:
2: 00000: 3f one
output: 1 @ 1
stack: 1
2: 00001: 57 00 00 setlocal 0
inputs: 1 @ 1
output: (void 0) @ 0
stack:
2: 00005: 56 00 01 getlocal 1
output: b @ 1
stack: undefined
2: 00008: 51 pop
inputs: b @ 1
stack:
3: 00009: d9 00 00 callgname "p"
....
JetSpiderでバイトコードを
生成するには
• Assemblerオブジェクトに定義されている、バイ
トコードと同名のメソッドを呼べばよい
• CodeGeneratorクラス内では@asmにAssembler
オブジェクトが入っている
• 例
• @asm.pop
• @asm.getarg 2
課題1
• 整数定数(NumberNode)を実装せよ
• 符号付き8ビット整数をpushする int8 命令を使
う
• [追加課題]符号付き32ビット整数まで扱える
ようにせよ
• [追加課題]文字列リテラルを実装せよ
課題の実装ステップ
1. 実装すべきJavaScriptのコードを確認する
2. jetspider --dump-ast, -semでASTを見る
3. jsvm --disassembleで出力すべきバイトコードを見る
4. CodeGeneratorのメソッドを実装する
5. jetspider --dump-objで出力結果を確認する
6. 最初のJavaScriptのコードを動かして確認する
「正解」を見る
• SpiderMonkeyによるコンパイル結果を見ることが
できる。
• トップレベルを逆アセンブル
• 特定の関数を逆アセンブル
jsvm --disassemble FILE.js
jsvm --disassemble FILE.js FUNCTION
課題2
• + 演算子(AddNode)を実装せよ
• - 演算子(SubtractNode)の実装が参考になる
• [追加課題]単項の + , - 演算子を実装せよ
• [追加課題]前置・後置の ++ 演算子を実装せよ
SpiderMonkey VM
実行の仕組み(3)無条件ジャンプ
• 無条件ジャンプ命令
gotoはPCを変更する
• 「goto 12」は

「PCを+12」する効果
がある
....
07 00 0f ifeq 15
d9 00 00 callgname 0
dd 07 int8 7
3a 00 01 call 1
51 pop
06 00 0c goto 12
d9 00 00 callgname 0
dd 09 int8 9
3a 00 01 call 1
51 pop
c5 stop
PC
値=17
....
07 00 0f ifeq 15
d9 00 00 callgname 0
dd 07 int8 7
3a 00 01 call 1
51 pop
06 00 0c goto 12
d9 00 00 callgname 0
dd 09 int8 9
3a 00 01 call 1
51 pop
c5 stop
PC
値=29
SpiderMonkey VM
実行の仕組み(4)条件付きジャンプ
• 「ifeq 15」は「スタッ
ク先頭がfalseならPC
を+15」

たぶん、「trueならそのまま次の命令に行き、falseな
ら飛べ」という意図だと思うけど……。

ちなみにifeqという名前のくせに等価比較もしない。
....
07 00 0f ifeq 15
d9 00 00 callgname 0
dd 07 int8 7
3a 00 01 call 1
51 pop
06 00 0c goto 12
d9 00 00 callgname 0
dd 09 int8 9
3a 00 01 call 1
51 pop
c5 stop
PC
値=5
....
07 00 0f ifeq 15
d9 00 00 callgname 0
dd 07 int8 7
3a 00 01 call 1
51 pop
06 00 0c goto 12
d9 00 00 callgname 0
dd 09 int8 9
3a 00 01 call 1
51 pop
c5 stop
PC
値=20
JetSpiderでの
逆順方向へのジャンプ
• 進行方向と逆へジャンプするときは、
@asm.locationを使う
loc = @asm.location
@asm.pop ← locはこの命令のオフセットを格納する
.....
@asm.goto loc ← locの位置へジャンプ
JetSpiderでの
進行方向へのジャンプ
• 進行方向へジャンプするときは、まだ生成していな
いコードのオフセットが必要になる
• @asm.lazy_locationで具体的な値の埋まっていな
いLocationオブジェクトを生成し、ジャンプした
い先でfix_locationを呼んで値を埋める
loc = @asm.lazy_location

@asm.goto loc ← locへジャンプ

.....

@asm.fix_location(loc) ← locの位置を定義
課題3
• if文(IfNode)を実装せよ。else節は省略できるこ
とを忘れないように注意。
• [発展課題]無駄なジャンプをできるだけ減らせ。
else節がある場合は2回、ない場合は1回が最小で
ある
• [発展課題]条件演算子(a?b:c)を実装せよ
課題4
• while文(WhileNode)を実装せよ
• [追加課題]break文とcontinue文を実装せよ
代表的な実行速度最適化
• 命令強度の低減
• int8 1 (dd 01) → one (3f)
• x * 4 → x << 2
• 定数のコンパイル時計算(定数畳み込み)
• print(i + 1 + 2 * 3) → print(i + 7)
• 処理のループ外への移動
• for (var i = 1; i < 10000; i++) { print(x * 2 + i); }

↓

var n = x * 2;

for (var i = 1; i < 10000; i++) { print(n + i); }
プログラムの意味とは
• プログラムの意味は変わっていないように思える
• 変わっていないもの( 意味)
• 式の値(3)
• プログラム外に見える変化(「3」と表示される)
• 変わったもの( 意味)
• 計算過程
• メモリ上の値の変更パターン
print(1 + 2); print(3);
最適化のため次のような定数畳み込みをしたとする
※ただし言語により「意味」は異なる
プログラムの意味を
定義・表現する3つの方法
• 操作的意味論(Operational Semantics)
• ある言語の表現を、より形式的な言語に変換すること
で意味を表現する。コンパイラーの定義に近い

→ c.f. チューリングマシン、ラムダ計算
• 表示的意味論(Denotational Semantics)
• 集合や関数など数学的な概念を用いて意味を表現する。
インタプリターの定義に近い。
• 公理的意味論(Axiomatics Semantics)
• なんか論理式で表現するらしい
詳しくは本で
『アンダースタンディングコン
ピュテーション』
http://www.amazon.co.jp/dp/
487311697X
課題5
• 整数が1のときはone命令を使うよう最適化せよ
• [追加課題]文内での定数畳み込みを実装せよ

ASTレベルで処理する方法と、バイトコードレベルで処理する方法の2つが
考えられる。今回はAddNode、1段に限って畳み込めばよい
JetSpiderでの変数の管理
• トップレベルと関数それぞれの変数スコープをScopeクラスで
管理している
• --dump-semanticで ref が表示されるノード(VarDeclNode,
ParameterNode, ResolveNode)にはvariableというプロパ
ティがあり、参照しているVariableオブジェクトが得られる
• グローバル・ローカルの区別やローカル変数IDなどはVariable
オブジェクトから得られる
• 詳細はjetspider/ast.rb, resolver.rb参照
課題6
• グローバル変数の参照と代入(VarStatementNode、
ResolveNode、OpEqualNode)を実装せよ
• 注意! グローバル変数の代入では左辺の評価時にbindgname命令を発行す
る必要がある
• ResolveNodeのn.variableにはVariableオブジェクトが入っていて、Variable
オブジェクトから変数名(name)やローカル変数ID(index)が取れる
• [追加課題]グローバル変数は常に $ で始まるとい
うルールを追加して、 var 宣言なしでもローカル変
数が定義されるように改造せよ(意味解析器Resolver
クラスの変更が必要)
課題7
• ローカル変数の参照と代入(VarStatementNode、
ResolveNode、OpEqualNode)を実装せよ
• 関数のパラメーターも別途扱う必要がある
課題8
• 関数呼び出し(FunctionCallNode)とreturn文
(ReturnNode)を実装せよ
• ローカル変数やグローバル変数を参照すれば関数が
得られるので、あとはcall命令で呼べばOK
• ……が、グローバル変数の場合は、変数参照のとき
と同じく関数を得る時点でcallgname NAMEが必
要
発展課題1
• 以下のオブジェクト指向機能を実装せよ
• new式
• プロパティの読み書き
• obj.prop形式だけでよい。obj[ prop ]は不要(文字列リテラルを実装して
いないと意味ないため)
• メソッド呼び出し
• メソッド定義はすでに定義されている関数をプロパティにセットすることで
代替する(下記参照)

function obj_m() { return 77; }

obj.m = obj_m;
発展課題2[難]
• 無名関数を実装せよ
• ただし外部の変数は参照できなくてよい
• 実はまだ青木も実装してないので何かあるかもしれ
ない。がんばれ。

More Related Content

Cookpad Summer Intern 2015 - Programming Paradigm