title: CbCによるMoarVMの改良 author: Takahiro Shimizu profile: lang: Japanese # 研究目的 - Perl5の後継言語として開発されているPerl6はMoarVMと呼ばれるVMを搭載している. - Perl6はMoarVM,JVM,JavaScript上で動くRakudoと呼ばれる実装と,コンパイラ開発者用のサブセットであるNQPが主な実装となっている. - 現在Perl6及びMoarVMは全体的な速度がPerl5と比較し低下しており,実務として利用できるレベルに達していない. - さらにPerl6の実装自体巨大なcase-switch文など見通しが悪くなっている. - この問題を解決するために現在当研究室で開発している継続を中心にしたContinuation based Cを用いて改良を行う - CbCの設計理念からVMの実装と親和性が高い事も推測できる為,実際にCbCを用いてどのようにVMが実装できるかを検証する # 今週の進捗 * 暑くて死んでました * OSCの資料をwebにあげました * Perl6のコンパイルとJITのトレースをしました * 院試対策し始めてます # お知らせ * [並列研の公式page](http://www.cr.ie.u-ryukyu.ac.jp/)にスライド置き場を作りました * CSS力はなかった # Perl6のbuild * JITのトレースがしたかったのでPerl6をbuildしました ./Configure.pl --prefix=/Users/anatofuz/workspace/cr/Basic/build_perl6 --backends=moar --with-nqp=//Users/anatofuz/workspace/cr/Basic/build_perl6/bin/nqp # Perl6-lldb-m * `Perl6-lldb-m`というデバッグ用のスクリプトが生成されていた ``` #!/bin/sh /Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar --execname="$0" --libpath="." --libpath="blib" --libpath="/Users/anatofuz/workspace/cr/Basic/build_perl6/share/nqp/lib" --libpath="/Users/anatofuz/workspace/cr/Basic/build_perl6/share/nqp/lib" /Users/anatofuz/workspace/cr/Basic/perl6/MoarVM_basic/rakudo/perl6.moarvm --nqp-lib=blib -e ' say "=" x 96; say "This is Rakudo Perl 6 running in the LLVM debugger, which often allows the user to generate useful back-\ntraces to debug or report issues in Rakudo, the MoarVM backend or the currently running code.\n"; unless $*VM.config { say "The currently used MoarVM backend is not compiled with debugging symbols, you might want to\nreconfigure and reinstall MoarVM with --debug enabled.\n" } say "This Rakudo version is $*PERL.compiler.version() built on MoarVM version $*VM.version(),"; say "running on $*DISTRO.gist() / $*KERNEL.gist()\n"; say "Type `bt full` to generate a backtrace if applicable, type `q` to quit or `help` for help."; say "-" x 96;' lldb /Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar -- --execname="$0" --libpath="." --libpath="blib" --libpath="/Users/anatofuz/workspace/cr/Basic/build_perl6/share/nqp/lib" --libpath="/Users/anatofuz/workspace/cr/Basic/build_perl6/share/nqp/lib" /Users/anatofuz/workspace/cr/Basic/perl6/MoarVM_basic/rakudo/perl6.moarvm --nqp-lib=blib "$@" ``` # JITのトレース * 何も実行しないとBreakpointに引っかかる ``` $ ./perl6-lldb-m ================================================================================================ This is Rakudo Perl 6 running in the LLVM debugger, which often allows the user to generate useful back- traces to debug or report issues in Rakudo, the MoarVM backend or the currently running code. This Rakudo version is 2018.04.1 built on MoarVM version 2018.04.1, running on macosx (10.13.5) / darwin (17.6.0) Type `bt full` to generate a backtrace if applicable, type `q` to quit or `help` for help. ------------------------------------------------------------------------------------------------ b (lldb) target create "/Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar" Current executable set to '/Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar' (x86_64). (lldb) settings set -- target.run-args "--execname=./perl6-lldb-m" "--libpath=/Users/anatofuz/workspace/cr/Basic/build_perl6/share/nqp/lib" "--libpath=/Users/anatofuz/workspace/cr/Basic/build_perl6/share/perl6/lib" "--libpath=/Users/anatofuz/workspace/cr/Basic/build_perl6/share/perl6/runtime" "/Users/anatofuz/workspace/cr/Basic/build_perl6/share/perl6/runtime/perl6.moarvm" (lldb) b MVM_jit_compiler_init Breakpoint 1: where = libmoar.dylib`MVM_jit_compiler_init + 20 at compile.c:17, address = 0x00000000001e4d54 (lldb) run Process 4185 launched: '/Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar' (x86_64) Process 4185 stopped * thread #2, stop reason = breakpoint 1.1 frame #0: 0x0000000100287d54 libmoar.dylib`MVM_jit_compiler_init(tc=0x000000010084db80, cl=0x0000700007fa1c60, jg=0x000000010345ca7a) at compile.c:17 14 static const MVMuint16 MAGIC_BYTECODE[] = { MVM_OP_sp_jit_enter, 0 }; 15 16 void MVM_jit_compiler_init(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitGraph *jg) { -> 17 MVMint32 num_globals = MVM_jit_num_globals(); 18 /* Create dasm state */ 19 dasm_init(cl, 2); 20 cl->dasm_globals = MVM_malloc(num_globals * sizeof(void*)); Target 0: (moar) stopped. ``` # JIT用のコード ``` #!/usr/bin/env perl6 use v6; my @hoge = (1..300); sub foo(@a){ @a[0] = @a.elems; } say foo(@hoge); ``` ``` (lldb) run jit.p6 Process 4223 launched: '/Users/anatofuz/workspace/cr/Basic/build_perl6/bin/moar' (x86_64) Unhandled exception: Bytecode stream corrupt (missing magic string) Process 4223 exited with status = 1 (0x00000001) ``` * Bytecode stream corruptのエラーが発生する # missing magic string * `src/core/bytecode.c` 135行目で定義されているエラーらしい ``` /* Dissects the bytecode stream and hands back a reader pointing to the * various parts of it. */ static ReaderState * dissect_bytecode(MVMThreadContext *tc, MVMCompUnit *cu) { MVMCompUnitBody *cu_body = &cu->body; ReaderState *rs = NULL; MVMuint32 version, offset, size; /* Sanity checks. */ if (cu_body->data_size < HEADER_SIZE) MVM_exception_throw_adhoc(tc, "Bytecode stream shorter than header"); if (memcmp(cu_body->data_start, "MOARVM\r\n", 8) != 0) MVM_exception_throw_adhoc(tc, "Bytecode stream corrupt (missing magic string)"); version = read_int32(cu_body->data_start, 8); if (version < MIN_BYTECODE_VERSION) MVM_exception_throw_adhoc(tc, "Bytecode stream version too low"); if (version > MAX_BYTECODE_VERSION) MVM_exception_throw_adhoc(tc, "Bytecode stream version too high"); /* Allocate reader state. */ rs = (ReaderState *)MVM_calloc(1, sizeof(ReaderState)); rs->version = version; rs->read_limit = cu_body->data_start + cu_body->data_size; cu->body.bytecode_version = version; /* Locate SC dependencies segment. */ offset = read_int32(cu_body->data_start, SCDEP_HEADER_OFFSET); if (offset > cu_body->data_size) { cleanup_all(tc, rs); MVM_exception_throw_adhoc(tc, "Serialization contexts segment starts after end of stream"); } rs->sc_seg = cu_body->data_start + offset; rs->expected_scs = read_int32(cu_body->data_start, SCDEP_HEADER_OFFSET + 4); ``` # エラーの原因 * `memcmp(cu_body->data_start, "MOARVM\r\n", 8) != 0` でMoarVMと文字列比較をしているが,この部分でスクリプトすべてが流れ込んでいた ``` (lldb) p *cu_body (MVMCompUnitBody) $1 = { data_start = 0x00000001007e5000 "#!/usr/bin/env perl6\nuse v6;\n\nmy @hoge = (1..300);\n\nsub foo(@a){\n @a[0] = @a.elems;\n}\n\nsay foo(@hoge);\n" data_size = 106 num_extops = 0 ``` # ``` (MVMCompUnitBody) $2 = { data_start = 0x00000001007e5000 "#!/usr/bin/env perl6\nuse v6;\n\nmy @hoge = (1..300);\n\nsub foo(@a){\n @a[0] = @a.elems;\n}\n\nsay foo(@hoge);\n" data_size = 106 num_extops = 0 max_callsite_size = 0 coderefs = 0x0000000000000000 num_frames = 0 orig_frames = 0 main_frame = 0x0000000000000000 load_frame = 0x0000000000000000 deserialize_frame = 0x0000000000000000 callsites = 0x0000000000000000 num_callsites = 0 orig_callsites = 0 extops = 0x0000000000000000 strings = 0x0000000000000000 num_strings = 0 orig_strings = 0 string_heap_fast_table = 0x0000000000000000 string_heap_fast_table_top = 0 serialized_size = 0 string_heap_start = 0x0000000000000000 string_heap_read_limit = 0x0000000000000000 serialized = 0x0000000000000000 scs = 0x0000000000000000 num_scs = 0 deallocate = MVM_DEALLOCATE_NOOP scs_to_resolve = 0x0000000000000000 sc_handle_idxs = 0x0000000000000000 hll_config = 0x0000000000000000 hll_name = 0x0000000000000000 filename = 0x0000000000000000 handle = 0x0000000000000000 inline_tweak_mutex = 0x000000010084e040 deserialize_frame_mutex = 0x0000000101017900 bytecode_version = 0 invoked = '\0' } ``` # cuの生成部分 * compunit.cの `MVM_cu_map_from_file`から読んでいる`MVM_cu_from_bytes`で生成している ``` (lldb) f frame #4: 0x00000001000d4238 libmoar.dylib`MVM_cu_map_from_file(tc=0x0000000100802100, filename="jit.p6") at compunit.c:64 61 } 62 63 /* Turn it into a compilation unit. */ -> 64 cu = MVM_cu_from_bytes(tc, (MVMuint8 *)block, (MVMuint32)size); 65 cu->body.handle = handle; 66 cu->body.deallocate = MVM_DEALLOCATE_UNMAP; 67 return cu; ``` * 問題となっている箇所はこの時点で `block`に記録されている # blockの生成部分 ``` 33 /* Loads a compilation unit from a bytecode file, mapping it into memory. */ 34 MVMCompUnit * MVM_cu_map_from_file(MVMThreadContext *tc, const char *filename) { 35 MVMCompUnit *cu = NULL; 36 void *block = NULL; 37 void *handle = NULL; 38 uv_file fd; 39 MVMuint64 size; (lldb) 40 uv_fs_t req; 41 42 /* Ensure the file exists, and get its size. */ 43 if (uv_fs_stat(tc->loop, &req, filename, NULL) < 0) { 44 MVM_exception_throw_adhoc(tc, "While looking for '%s': %s", filename, uv_strerror(req.result)); 45 } 46 47 size = req.statbuf.st_size; 48 49 /* Map the bytecode file into memory. */ (lldb) 50 if ((fd = uv_fs_open(tc->loop, &req, filename, O_RDONLY, 0, NULL)) < 0) { 51 MVM_exception_throw_adhoc(tc, "While trying to open '%s': %s", filename, uv_strerror(req.result)); 52 } 53 54 if ((block = MVM_platform_map_file(fd, &handle, (size_t)size, 0)) == NULL) { 55 /* FIXME: check errno or GetLastError() */ 56 MVM_exception_throw_adhoc(tc, "Could not map file '%s' into memory: %s", filename, "FIXME"); 57 } 58 59 if (uv_fs_close(tc->loop, &req, fd, NULL) < 0) { (lldb) 60 MVM_exception_throw_adhoc(tc, "Failed to close filehandle: %s", uv_strerror(req.result)); 61 } 62 63 /* Turn it into a compilation unit. */ 64 cu = MVM_cu_from_bytes(tc, (MVMuint8 *)block, (MVMuint32)size); 65 cu->body.handle = handle; 66 cu->body.deallocate = MVM_DEALLOCATE_UNMAP; 67 return cu; 68 } ``` * 54行目 # 生成箇所? ``` 63 void *MVM_platform_map_file(int fd, void **handle, size_t size, int writable) 64 { 65 void *block = mmap(NULL, size, 66 writable ? PROT_READ | PROT_WRITE : PROT_READ, 67 writable ? MAP_SHARED : MAP_PRIVATE, fd, 0); 68 (lldb) 69 (void)handle; 70 return block != MAP_FAILED ? block : NULL; 71 } ``` # 実際に作成している箇所 ``` (lldb) l 10 10 MVMCompUnit * MVM_cu_from_bytes(MVMThreadContext *tc, MVMuint8 *bytes, MVMuint32 size) { 11 /* Create compilation unit data structure. Allocate it in gen2 always, so 12 * it will never move (the JIT relies on this). */ 13 MVMCompUnit *cu; 14 MVM_gc_allocate_gen2_default_set(tc); 15 cu = (MVMCompUnit *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTCompUnit); 16 cu->body.data_start = bytes; 17 cu->body.data_size = size; 18 MVM_gc_allocate_gen2_default_clear(tc); ``` ``` 16 MVMObject * MVM_repr_alloc_init(MVMThreadContext *tc, MVMObject *type) { 17 MVMObject *obj = REPR(type)->allocate(tc, STABLE(type)); 18 19 if (REPR(obj)->initialize) { 20 MVMROOT(tc, obj, { 21 REPR(obj)->initialize(tc, STABLE(obj), obj, OBJECT_BODY(obj)); (lldb) 22 }); 23 } 24 25 return obj; 26 } ```