Mercurial > hg > Events > OSC2019
annotate slide.md @ 17:a176ea5c0264
update
author | anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 19 Apr 2019 23:22:02 +0900 |
parents | d3036d998236 |
children | 1fc9d0bd924f |
rev | line source |
---|---|
0
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
1 title: Perl6の内部表現 |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
2 author: Takahiro Shimizu |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
3 profile: |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
4 lang: Japanese |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
5 |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
6 ## このセッションの内容 |
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
7 |
1 | 8 - Perl6の主要な実装であるRakudoの内部構造を探ります |
9 - Rakudoの内部で利用されているVMや, Perl6のサブセットなどについて探索します | |
3 | 10 - スクリプト言語で主に使われているバイトコードインタプリタの気持ちになります |
1 | 11 |
9 | 12 ## 内容 |
13 - Perl6とは? | |
14 - スクリプト言語処理系の動き | |
15 - Perl6の内部構造 | |
16 - NQP | |
17 - MoarVM | |
14 | 18 - NQPとMoarVMのバイトコード対応 |
19 - バイトコードインタプリタのC言語実装 | |
20 - MoarVMの詳細 | |
9 | 21 - MoarVMのバイトコード実行 |
22 - まとめ | |
23 | |
1 | 24 ## Perl6とは |
25 - 当初Perl5の時期バージョンとして開発されていたプログラミング言語 | |
9 | 26 - 現在は別の言語として開発がそれぞれ進んでいる |
1 | 27 - 仕様と実装が分離しており, 現在はテストが仕様となっている |
9 | 28 - 実装は歴史上複数存在しているが,主流な実装はRakudo |
29 - 言語的にはスクリプト言語であり, 漸進的型付き言語 | |
30 - 動作環境は、独自のVMのMoarVM, JVM、一部JavaScript上で動作する | |
1 | 31 |
14 | 32 <img src="fig/2000px-Camelia.svg.png" alt="" style="width: 31%; height: auto;"> |
1 | 33 |
9 | 34 ## 現在のPerl6 |
35 | |
36 - 現在のバージョンは `6.d` | |
37 - [ブラウザ上で実行可能な環境](https://perl6.github.io/6pad/)が存在する | |
38 - [IDE](https://commaide.com/)が開発されている | |
39 - WebApplicationFrameworkなども開発されており、 Perl5のモジュールを移行したものがいくつか存在する | |
40 - 日本では趣味のプロダクト以外社会では使用されていない | |
41 - 海外では実際に使われているケースも存在する | |
42 - 処理速度では一部Perl5に勝っているが、それでも大分遅い | |
43 | |
44 ## [参考]Perl5のソースコード | |
45 | |
46 - Perl5時代 | |
47 − スカラ、配列、ハッシュの3種類 | |
48 - それぞれの変数への参照であるリファレンスが使用可能 | |
49 | |
50 ```perl | |
51 use ustrict; | |
52 use warnings; | |
53 | |
54 my $scalar_value = "hello!"; | |
55 print "$scalar_value\n"; | |
56 | |
57 my @array = (1..10); | |
58 print "$array[0]\n"; | |
59 | |
60 my %hash = ( this_is_key => "this_is_value"); | |
61 print "$hash{this_is_key}\n"; | |
62 | |
63 my $hash_ref = \%hash; | |
64 print "$hash_ref->{this_is_key}\n"; | |
65 ``` | |
66 | |
1 | 67 ## Perl6のソースコード概要 |
68 | |
69 - Perl5の文法とは比較的変更が多い | |
9 | 70 - 雰囲気は似ている |
1 | 71 - 変数がオブジェクトと化した事により, 変数からsayメソッドを呼ぶことが可能 |
72 | |
73 ``` | |
74 my $str_value = 'hello world!'; | |
75 $str_value.say; # hello world! | |
76 ``` | |
77 | |
78 - Perl5と同様に,変数にはデフォルトでは型がないような振る舞いをする | |
0
19155754a586
create OSC2019 slide template
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
diff
changeset
|
79 |
1 | 80 ``` |
81 my $sample_value = 'hello world!'; | |
82 $sample_value.say; # hello world! | |
83 | |
84 $sample_value = '31'; | |
85 $sample_value.say; # 31 | |
86 | |
87 say($sample_value * 3); | |
88 ``` | |
89 | |
90 ## Perl6の言語的な特徴 | |
91 | |
92 - 漸進的型付き言語である為, 型を強制することも可能となる | |
93 | |
94 ``` | |
95 my Int $int_value = 31; | |
96 $int_value = "hello"; # Compile error! | |
97 ``` | |
98 | |
7 | 99 ``` |
100 $ perl6 type_invalid.p6 | |
101 Type check failed in assignment to $int_value; expected Int but got Str ("hello") | |
102 in block <unit> at type_invalid.p6 line 4 | |
103 ``` | |
104 | |
4 | 105 ## Perl6の言語的な特徴 |
106 | |
107 - 型を独自に定義することも可能 | |
108 - 入力の型によって実行する関数を変える事などができる | |
109 | |
110 ```perl6 | |
111 my subset Fizz of Int where * %% 3; | |
112 my subset Buzz of Int where * %% 5; | |
113 my subset FizzBuzz of Int where Fizz&Buzz; | |
114 my subset Number of Int where none Fizz|Buzz; | |
115 | |
116 proto sub fizzbuzz ($) { * } | |
117 multi sub fizzbuzz (FizzBuzz) { "FuzzBuzz" } | |
118 multi sub fizzbuzz (Fizz) { "Fizz" } | |
119 multi sub fizzbuzz (Buzz) { "Buzz" } | |
120 multi sub fizzbuzz (Number $number) { $number } | |
121 | |
122 fizzbuzz($_).say for 1..15; | |
123 ``` | |
9 | 124 |
125 - 型を利用したFizzBuzz | |
126 | |
127 ## スクリプト言語 | |
128 - Perl6は現状コンパイルすることはできない | |
129 - スクリプト言語の分類 | |
130 | |
131 - 現在広く使われているスクリプト言語(Perl,Python,Ruby...)などとPerl6の構成は類似している | |
132 - 今回はPerl6の実装を追いながら、最近のスクリプト言語処理系の大まかな実装を理解する | |
133 | |
7 | 134 ## スクリプト言語処理系 |
135 - スクリプト言語は入力として与えられたソースコードを、 直接評価せずにバイトコードにコンパイルする形式が主流となっている | |
136 - その為スクリプト言語の実装は大きく2つで構成されている | |
137 - バイトコードに変換するフロントエンド部分 | |
138 - バイトコードを解釈する仮想機械 | |
139 | |
140 <img src="fig/bytecode_sample_generally_lang.svg" width="80%"> | |
4 | 141 |
9 | 142 |
143 ## Perl6以外のスクリプト言語 | |
144 | |
145 - 現在使われているプロセスVMは言語に組み込まれているものが多い | |
146 - JVMやElixirなどのVMは複数の言語で使用されている | |
147 - Java | |
148 - JVM | |
149 - Ruby | |
150 - YARV | |
151 - Python | |
152 - PythonVM | |
12 | 153 - Erlang |
9 | 154 - Elixir |
155 - BEAM | |
156 | |
1 | 157 ## Perl6の処理系の構成 |
158 | |
9 | 159 - Perl6の処理系で現在主流なものはRakudoと呼ばれる実装である(歴史上複数存在する) |
2 | 160 - Rakudoは3つのレイヤーから構成されている |
161 - Perl6インタプリタ | |
162 - Perl6インタプリタを記述するPerl6のサブセットNQP | |
163 - Perl6のバイトコードを解釈するMoarVM | |
9 | 164 - Perl6/NQPがフロントエンドに相当し、MoarVMがバックエンドに相当する |
165 | |
166 ## Rakudoの構成図 | |
2 | 167 |
9 | 168 ![](fig/Rakudo_System_overview.png) |
7 | 169 |
9 | 170 (http://brrt-to-the-future.blogspot.com/2015/03/advancing-jit-compiler.html) |
7 | 171 |
2 | 172 ## Perl6とNQP |
173 | |
9 | 174 - NQP(NotQuitPerl Perl) |
175 - Perl6のサブセット。Perl6っぽい言語 | |
176 - Perl6、 NQP自体がNQPで記述されている | |
2 | 177 - NQPもNQPで記述されている為、 セルフビルド(自分自身で自分自身をコンパイルする)を行う |
178 - NQPはPerl6の文法をベースにしているが、 制約がいくつか存在する | |
9 | 179 - 元々はPerl6の主力実装がParrotだった時代に登場 |
180 - 文法がアップデートされており、当時の資料は古くなっている | |
181 | |
182 ``` | |
183 my $value := "hello!"; | |
184 say($value); | |
185 ``` | |
186 | |
187 ## NQPスクリプト | |
188 | |
189 - 変数は束縛 `:=` を使う | |
190 − 関数の間に空白を入れてはいけない | |
191 - 再帰呼び出しを使うフィボナッチ数列 | |
2 | 192 |
4 | 193 ``` |
194 #! nqp | |
195 sub fib($n) { | |
196 $n < 2 ?? $n !! fib($n-1) + fib($n - 2); | |
197 } | |
198 | |
199 my $N := 29; | |
200 | |
201 my $z := fib($N); | |
202 | |
203 nqp::say("fib($N) = " ~ fib($N)); | |
9 | 204 ``` |
205 | |
206 ## NQPスクリプト(nまでの整数の和) | |
207 | |
208 ```perl6 | |
209 sub add_test($n){ | |
10 | 210 my $sum := 0; |
9 | 211 while ( $n > 1) { |
212 $sum := $sum + $n; | |
213 --$n; | |
214 } | |
215 return $sum; | |
216 } | |
217 | |
218 say(add_test(10000)); | |
4 | 219 ``` |
220 | |
10 | 221 ## NQP |
9 | 222 |
223 - NQPはPerl6の中で一番レイヤーが低い言語 | |
224 - その為、 実行するVMのオペコード(処理単位)を使用することができる | |
10 | 225 - NQPオペコードは、 Perl6の内部の抽象構文木でも使用されている |
226 - また、 Perl6と同様に型を指定することが可能 | |
9 | 227 |
10 | 228 ```perl6 |
229 sub add_test(int $n){ | |
230 mu $sum := 0; | |
231 while nqp::isgt_i($n,1) { | |
232 $sum := nqp::add_i($sum,$n); | |
233 $n := nqp::sub_i($n,1); | |
234 } | |
235 return $sum; | |
236 } | |
237 ``` | |
7 | 238 |
9 | 239 ## NQPとMoarVM |
14 | 240 - NQPは実行する際にMoarVM/JVMが必要となる |
9 | 241 - NQPコンパイラが各VMに対応したバイトコードに変換する |
14 | 242 - MoarVMの場合は、MoarVMのバイナリ moar に、 NQPのインタプリタのバイトコードをライブラリや入力として与える |
9 | 243 |
244 ## Perl6のVM | |
245 - MoarVM, JVM , JavaScriptが選択可能 | |
246 - メインで開発されているのはMoarVMであり、 他のVMは機能が実装されていないものが存在する | |
247 - `rakudo-star` というPerl6のパッケージ環境では、 MoarVMがデフォルトでインストールされる | |
248 | |
249 ## MoarVM | |
14 | 250 - Metamodel On A Runtime |
9 | 251 - C言語で記述されているPerl6専用の仮想機械 |
7 | 252 - レジスタマシン |
253 - 型情報を持つレジスタに対しての演算として処理される | |
9 | 254 - Rubyなどはスタックマシンとして実装されている |
14 | 255 - Unicodeのサポートや、LuaJITなどを利用したJITコンパイルなども可能 |
7 | 256 - Perl6やNQPは、MoarVMに対してライブラリなどを設定して起動する |
257 | |
10 | 258 |
259 | |
7 | 260 ## バイトコード |
3 | 261 - Perl6も、Rakudo/NQPはバイトコードに変換され、 バイトコードをVMが実行する |
10 | 262 - Perl6/NQPはバイトコードにコンパイルすることが可能 |
263 - 直接実行することはできない | |
264 | |
265 ``` | |
266 $nqp --target=mbc --output=fib.moarvm fib.nqp | |
267 ``` | |
268 | |
12 | 269 ## バイトコード |
270 - バイナリ形式で表現される為、 VMがどのように読み取るかでバイトコードの意味が異なる | |
14 | 271 - スクリプト言語系のVMは、 VMという名前の通り、 計算機をエミュレートしている |
272 - その為、通常のCPUのストア命令などに相当する命令が実装されている | |
273 - スクリプト言語は、その命令の実行を繰り返すことでプログラムを評価する | |
12 | 274 - スクリプト言語で重要なバイトコード表現は、「仮想機械がどの命令を実行するか」のバイトコード |
14 | 275 - CPUに対するアセンブラの数値に対応する |
12 | 276 - どういった構成なのかは仮想機械によって異なる |
277 | |
278 | |
10 | 279 ## バイトコードとMoarVM |
280 | |
281 | |
282 - MoarVMバイトコードはMoarVMの実行バイナリ `moar` でディスアセンブルすることが可能 | |
283 | |
284 | |
285 ``` | |
286 annotation: add_test.nqp:1 | |
287 00003 const_i64_16 loc_2_int, 0 | |
288 00004 hllboxtype_i loc_3_obj | |
289 00005 box_i loc_3_obj, loc_2_int, loc_3_obj | |
290 00006 set loc_1_obj, loc_3_obj | |
291 label_1: | |
292 00007 decont loc_3_obj, loc_0_obj | |
293 00008 smrt_numify loc_4_num, loc_3_obj | |
294 00009 const_i64_16 loc_2_int, 1 | |
295 00010 coerce_in loc_5_num, loc_2_int | |
296 00011 gt_n loc_2_int, loc_4_num, loc_5_num | |
297 00012 unless_i loc_2_int, label_2(00031) | |
298 00013 osrpoint | |
299 annotation: add_test.nqp:3 | |
300 00014 decont loc_3_obj, loc_1_obj | |
301 00015 smrt_numify loc_5_num, loc_3_obj | |
302 00016 decont loc_3_obj, loc_0_obj | |
303 00017 smrt_numify loc_4_num, loc_3_obj | |
304 00018 add_n loc_4_num, loc_5_num, loc_4_num | |
305 00019 hllboxtype_n loc_3_obj | |
306 00020 box_n loc_3_obj, loc_4_num, loc_3_obj | |
307 00021 set loc_1_obj, loc_3_obj | |
308 00022 decont loc_3_obj, loc_0_obj | |
309 00023 smrt_numify loc_4_num, loc_3_obj | |
310 00024 coerce_ni loc_6_int, loc_4_num | |
311 00025 const_i64_16 loc_7_int, 1 | |
312 00026 sub_i loc_7_int, loc_6_int, loc_7_int | |
313 00027 hllboxtype_i loc_3_obj | |
314 00028 box_i loc_3_obj, loc_7_int, loc_3_obj | |
315 00029 set loc_0_obj, loc_3_obj | |
316 00030 goto label_1(00007) | |
317 ``` | |
318 | |
319 ## NQPとバイトコードの対応 | |
320 | |
321 ``` | |
322 say(add_test(10000)); | |
323 ``` | |
2 | 324 |
10 | 325 ``` |
326 annotation: add_test.nqp:1 | |
327 label_1: | |
328 00020 getlex_no loc_7_obj, '&say' | |
329 00021 decont loc_7_obj, loc_7_obj | |
330 00022 const_s loc_3_str, '&add_test' | |
331 00023 getlexstatic_o loc_8_obj, loc_3_str | |
332 00024 decont loc_8_obj, loc_8_obj | |
333 00025 const_i64_16 loc_5_int, 10000 | |
334 00026 prepargs Callsite_1 | |
335 00027 arg_i 0, loc_5_int | |
336 00028 invoke_o loc_8_obj, loc_8_obj | |
337 00029 prepargs Callsite_0 | |
338 00030 arg_o 0, loc_8_obj | |
339 00031 invoke_v loc_7_obj | |
340 00032 null loc_7_obj | |
341 00033 return_o loc_7_obj | |
342 ``` | |
343 | |
344 - Perl6の変数は直接実態を参照せず、中身が入っているコンテナを参照するようになっている。 | |
345 - その為 `decont` 命令で、コンテナの中身をレジスタに設定する必要がある | |
346 - `const_i64_16` などは64bitの数という意味で、 `int` 型としてレジスタに登録している | |
347 - `prepargs` で引数の確認を行い, `invoke_o` で実際にサブルーチンに移行する | |
348 | |
349 ## NQPとバイトコードの対応 | |
350 | |
351 ``` | |
352 my $sum := 0; | |
353 ``` | |
354 | |
355 ``` | |
356 annotation: add_test.nqp:1 | |
357 00003 const_i64_16 loc_2_int, 0 | |
358 00004 hllboxtype_i loc_3_obj | |
359 00005 box_i loc_3_obj, loc_2_int, loc_3_obj | |
360 00006 set loc_1_obj, loc_3_obj | |
361 ``` | |
362 | |
363 - まず `loc_2` レジスタをint型の整数0で初期化する | |
364 - 変数 `$sum` はint型の指定がないので、 obj型で登録しなければならない | |
365 - その為, 整数として登録された `loc_2` から、 obj型に一旦キャストし、 `loc_3` レジスタに設定したものを、 `loc_1` レジスタに設定する | |
366 | |
367 ## NQPとバイトコードの対応 | |
368 | |
369 ``` | |
370 while ( $n > 1) { | |
371 ``` | |
372 | |
373 ``` | |
374 label_1: | |
375 00007 decont loc_3_obj, loc_0_obj | |
376 00008 smrt_numify loc_4_num, loc_3_obj | |
377 00009 const_i64_16 loc_2_int, 1 | |
378 00010 coerce_in loc_5_num, loc_2_int | |
379 00011 gt_n loc_2_int, loc_4_num, loc_5_num | |
380 00012 unless_i loc_2_int, label_2(00031) | |
381 00013 osrpoint | |
382 ``` | |
383 | |
384 − 比較にもint型の指定がない為、 `num` 型にキャストし、 `num` 型のレジスタでの大小を比較する | |
385 - 比較命令は `gt_n` であり、 結果により `unless_i` 命令で、別のラベルにジャンプする | |
14 | 386 |
387 ## decode命令 | |
388 | |
389 ``` | |
390 while ( $n > 1) { | |
391 ``` | |
392 | |
393 ``` | |
394 00007 decont loc_3_obj, loc_0_obj | |
395 ``` | |
396 | |
397 | |
398 ![](fig/decont_perl6_loc3.svg) | |
399 | |
400 - 変数 `$n` と 整数 `1` を大小比較する為、 まず `$n` から値を取り出す | |
401 - とりだした時点では、何の型で使うかは決定していない為、 obj型として判定する | |
402 | |
403 ## smrt_nomify | |
404 | |
405 | |
406 ``` | |
407 while ( $n > 1) { | |
408 ``` | |
409 | |
410 ``` | |
411 00008 smrt_numify loc_4_num, loc_3_obj | |
412 ``` | |
413 | |
414 | |
17 | 415 - `smrt_numify` はレジスタ上のオブジェクトを、 num型に変換し、 別のレジスタに登録する命令 |
416 - 今回の整数の比較では、 int型の強制がない為、 数値として比較するためにnum型にキャストしている | |
417 | |
418 ![](fig/perl6_num_convert.svg) | |
419 | |
420 ## MoarVMのバイトコードインタプリタ部分 | |
421 MoarVMなどの言語処理系のバイトコードインタプリタは次のことを繰り返している | |
422 1. 入力されたバイトコード列から命令に対応する部分を読み取る | |
423 2. 読み込んだ数値から、 対応する命令を取得する | |
424 3. 命令部分を実行する | |
425 4. バイトコード列を次に進め、繰り返す | |
426 | |
427 - この部分の実装は大体次のような処理をしている | |
428 | |
429 ## 巨大なswitch文を使うケース | |
430 | |
431 - 命令に対応するバイトコードを数値に変換できるようにし、 switch-case文で分岐させる | |
432 - 実行のたびにループで先頭に戻り、次の命令を計算する必要があるので低速 | |
433 | |
434 ``` | |
435 ``` | |
436 | |
437 ## Cコンパイラのラベルgotoを使うケース | |
438 | |
439 - 巨大なcase文とループではなく、 次の命令の実行場所に直接jmpで移動する | |
440 - 次の命令に対応するラベルを取得する必要があるが、 ループする必要がなく高速 | |
441 - ラベルgotoであり、 Cコンパイラの拡張機能として搭載されている | |
442 - gccおよびLLVM/clangには実装されている | |
443 | |
444 ``` | |
445 ``` | |
446 | |
447 ## MoarVMでは | |
448 - ラベルgotoが利用できる場合は利用する | |
449 - 使えないコンパイラの場合は、 switch文を利用する | |
450 - この判断はマクロで処理をしている | |
451 − 一般的にはラベルgotoの方が高速である為、他のスクリプト言語でもラベルgotoが使われている |