アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

Perl で Compiler::CodeGenerator::LLVM を用いて WebAssembly を生成する LT をしました

卒研発表で LT をしました。そのスライドです。

今なって考えると、これ「コード生成器を用いた」だなぁ...

前回の続きなので前回のリンクはこちら。

codehex.hateblo.jp

今回の研究?で使った Compiler::CodeGenerator::LLVMimprovement/wasm ブランチ。

github.com

デモで用いたコードはこんな感じ

#!/usr/bin/env perl
use feature 'say';
say "Hello, World";
say 1 + 1;
say "---------------";


sub loop {
    my $a = 10;
    for (my $i = 1; $i < $a; $i++) {
        if ($i == 9) {
            say "Last!!";
        } else {
            say $i;
        }
    }
}
loop();

これは関数コール、ループ、条件分岐を交えたコードである。
これを次のスクリプトを使って WebAssembly へコンパイル、サーブを行った。

#!/usr/bin/env perl

use strict;
use warnings;
use feature 'say';

package Compiler {
    use Moo;
    use Carp 'croak';
    use Fcntl qw(:flock);
    use File::Basename;

    use Cwd 'realpath';
    use Plack::Loader;
    use Plack::App::Directory;
    use Plack::Middleware::Rewrite;

    use Compiler::Lexer;
    use Compiler::Parser;
    use Compiler::CodeGenerator::LLVM;

    has lexer => (
        is => 'ro',
        lazy => 1,
        default => sub { Compiler::Lexer->new }
    );

    has parser => (
        is => 'ro',
        lazy => 1,
        default => sub { Compiler::Parser->new }
    );

    has generator => (
        is => 'ro',
        lazy => 1,
        default => sub { Compiler::CodeGenerator::LLVM->new({emcc => 1}) }
    );

    sub tokenize {
        my ($self, $script) = @_;
        return $self->lexer->tokenize($script);
    }

    sub parse {
        my ($self, $tokens) = @_;
        return $self->parser->parse($tokens);
    }

    sub compile {
        my ($self, $filename) = @_;
        my $script = $self->_read($filename);
        my $tokens = $self->tokenize($script);
        my $ast = $self->parse($tokens);
        my $llvm_ir = $self->generator->generate($ast);

        my $trimmed_filename = $self->_trim_filename($filename);
        $self->_write("${trimmed_filename}.ll", $llvm_ir);
        $self->_execute_emcc($trimmed_filename);
    }

    sub _execute_emcc {
        my ($self, $trimmed_filename) = @_;
        system "emcc ${trimmed_filename}.ll -s ALLOW_MEMORY_GROWTH=1 -o index.html";
        if ($? == 0) {
            my $port = 8000;
            my $app = Plack::App::Directory->new({root => realpath('.')})->to_app;
            say "You can access to http://127.0.0.1:$port";
            $app = Plack::Middleware::Rewrite->wrap($app, rules => sub {
                    return 301 if s{^(.*)/$}{$1/index.html};
            });
            Plack::Loader->auto(port => $port)->run($app);
        }
    }

    sub _trim_filename {
        my ($self, $filename) = @_;
        my $basename = basename($filename);
        return $basename =~ s/\.[^.]+$//r;
    }

    sub _write {
        my ($self, $filename, $content) = @_;
        open my $fh, ">", $filename or croak "Failed to open a $filename";
        flock $fh, LOCK_EX or croak "Failed to lock a $filename";
        print $fh $content;
        flock $fh, LOCK_UN or croak "Failed to unlock a $filename";
        close $fh;
    }

    sub _read {
        my ($self, $filename) = @_;
        open my $fh, "<", $filename or croak "Failed to open a $filename";
        flock $fh, LOCK_EX or croak "Failed to lock a $filename";
        my $script = do { local $/, <$fh> };
        flock $fh, LOCK_UN or croak "Failed to unlock a $filename";
        return $script;
    }

    __PACKAGE__->meta->make_immutable;
};


Compiler->new->compile($ARGV[0]);

このスクリプトcompile.pl とかで保存して perl compile.pl target.pl とか実行すれば http://127.0.0.1:8000/ にアクセスしてくれやって表示される。表示すると実行結果を確認することができる。

perl で実行した結果(ネイティブ)

f:id:codehex:20180222105231p:plain

perl compile.pl target.pl で実行した結果(WebAssembly)

f:id:codehex:20180222105159p:plain

色々まだ問題はあるが、結果はある程度現実味がでてきた気がするぞ!!

卒研終わった皆さんお疲れ様でした!!