Calmery.me

みっかぼうずにならないようがんばる

LINEの40万インターンに参加してきた

LINE Summer Internship 2017 のエンジニア就業コース,通称 40 万インターンに参加してきた.詳しくは LINE株式会社 | インターンシップ 2017 エンジニア就業コース を見ればわかると思う.

linecorp.com

技術テスト

コーディングの問題が 3 問あり,それぞれを 1 時間くらいで解く.全完はできなかった.1 問目に至ってはテストケースが 1 つも通っていない(とても悲しい).聞いた限りだと他のインターン生も全完は出来ていないようだった.

面接

Skype で面接を行った.面接では今までどんなことをやってきたのか,インターンシップではどんなことをやりたいのか,興味のある技術は何かなどを話した.自分の場合は雑談混じりでラフな感じだったのだけど,しっかり面接だったというところもあったみたい.

インターンシップについて

初日は新宿駅から出るのに苦労したり,お洒落なオフィスに緊張したり.自分が配属されたのは LINE ゲームに関係するチームで,自分を含めて 2 人が配属された.

f:id:calmery:20170903104338j:plain

出社は 10 時とゆっくりで定時は 18 時半だった.定時になったら帰らせられる.帰りたくないよ.

あと,オフィスに席は用意されていたけど,基本作業をする場所は自由で,絶対にオフィスの中でということはなかった.自分はソファでまったり音楽を聴きつつ進めて,行き詰まったときに自分の席に戻り,メンターの方に相談しつつ進めていた.かなり自由にやらせてもらったと思う.

社内はお洒落.撮影スタジオや保育園,カフェまである.カフェでは毎朝,軽い朝ご飯が出るし最高.毎日 2 回は行ってました.

f:id:calmery:20170903104402j:plain

内容

簡単に言えば,配属されたチームの中の課題を解決するということ.

自分は配属先で使われているモニタリングツールの改良を課題にした.改良を行う上での技術の選択は自由だったので,前々から興味のあった技術を使って開発を行った.ほぼ初心者だったというのもあるけど,どこかで詰まると無限に時間が溶ける.そして最終的に完璧とは言い難いものが出来た.

最終日の講評で限られた時間内に作り上げること云々,時間が足りないのは実力不足云々と言われて,まさにその通りだなと思った.普通に時間足りませんでした.

インターンシップを終えて

感想としては楽しかったというのが一番かなと思う.

もはやインフラと化している LINE の内部のことを知れたり,実際のプロダクトがどのように作られているか,そのソースコードまで自由に見ることができた.

メンターの方にもコードのレビューなどサポートして頂いて本当に感謝しかない(ちなみにメンターの方も以前このインターンシップに参加していたとのことでした!)

運が良かっただけかもしれないけど,とても良い経験ができたし,参加することができて本当に良かった.ありがとうございました.

f:id:calmery:20170903104432j:plain

他のインターン生の記事

観測した範囲でメモ.

ak1t0.hatenablog.com
www.kagemiku.com
chigichan24.hatenablog.com

色々と

自分は CodeSprint を経由して申し込んだのだが,元々はエンジニア就業コースではなくサービス開発コースに申し込むつもりだった.だが CodeSprint を経由して申し込むとエンジニア就業コースに申し込んだことになってしまうらしく,それを知らずにサービス開発コースに申し込んだものだと思い込んでいた.面接後にこれはエンジニア就業コースの面接だよと言われて「?」となった.

あ…ありのまま 今 起こった事を話すぜ!
「おれは サービス開発コースの面接を受けていると思ったら いつのまにかエンジニア就業コースの面接を受けていた」
な… 何を言っているのか わからねーと思うが おれも 何をされたのか わからなかった…

それ以外にも面談だと聞いて行ったら面接だったとか何だかんだあったけど,色々なインターンシップを紹介していただいたり,企業の方と繋いでいただいたりとサポートも手厚くてすごく良かったです.

codesprint.jp

インターン中に連れて行ってもらったご飯はどれも豪華でおいしかった.

f:id:calmery:20170903104448j:plain
f:id:calmery:20170903104450j:plain
f:id:calmery:20170903104521j:plain
f:id:calmery:20170903104452j:plain

VirtualBox上のUbuntuでDockerを動かしてみる

セキュリティさくら 分科会(仮称) 第2回 : ATND のまとめ.

事前準備

VirtualBox 上で Ubuntu を動かせる状態にしておく.自分は Ubuntu 17.04 を使用した.

コンテナと VM の違い

VM はハードウェアシュミュレーションであってコンテナはあるプロセスに専用の実行空間を用意すること.注目を浴びる「Dockerコンテナ」、従来の仮想化と何が違うのか? | コラム | 東京エレクトロン デバイス株式会社 とかを読んだら雰囲気はわかると思う.

Docker がしてくれるもの

  • コンテナ管理
  • 環境構築
  • イメージ管理

Docker の使い方

  • Image を作成する
    • docker build
  • コンテナを実行する
    • docker run

インストー

Get Docker for Ubuntu - Docker Documentation を見る.インストールする対象ごとにドキュメントが用意されているので参考にする.

$ sudo apt-get remove docker docker-engine
$ sudo apt-get update
$ sudo apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce
$ apt-cache madison docker-ce
$ sudo docker run hello-world
$ sudo usermod -aG docker $(whoami)

usermod を実行後,ログインし直す必要がある.

Ubuntu の 17.04 では add-apt-repository の $(lsb_release -cs) を xenial に変更する必要がある.ここを変更していないと apt-get install docker-ce したときにそんなものないよって言われる.

$ sudo apt-get install docker-ce
  Readning package lists... Done
  Building dependency tree
  Reading state information... Done
  E: Unable to locate package docker-ce

Note: The lsb_release -cs sub-command below returns the name of your Ubuntu distribution, such as xenial.

Sometimes, in a distribution like Linux Mint, you might have to change $(lsb_release -cs) to your parent Ubuntu distribution. For example: If you are using Linux Mint Rafaela, you could use trusty.
Get Docker for Ubuntu - Docker Documentation

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   xenial \
   stable"

Hello World

試しに動かしてみる.

$ sudo docker run hello-world
  Unable to find image 'hello-world:latest' locally
  latest: Pulling from library/hello-world
  78445dd45222: Pull complete 
  Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7
  Status: Downloaded newer image for hello-world:latest

  Hello from Docker!
  This message shows that your installation appears to be working correctly.

  To generate this message, Docker took the following steps:
   1. The Docker client contacted the Docker daemon.
   2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
   3. The Docker daemon created a new container from that image which runs the
      executable that produces the output you are currently reading.
   4. The Docker daemon streamed that output to the Docker client, which sent it
      to your terminal.

  To try something more ambitious, you can run an Ubuntu container with:
   $ docker run -it ubuntu bash

  Share images, automate workflows, and more with a free Docker ID:
   https://cloud.docker.com/

  For more examples and ideas, visit:
   https://docs.docker.com/engine/userguide/

Struts2 を動かしてみる

何かと話題となった Struts2 を Docker で動かしてみる.

$ mkdir workspace
$ cd workspace
$ wget https://dist.apache.org/repos/dist/release/struts/2.5.10.1/struts-2.5.10.1-apps.zip
$ unzip struts-25.10.1-apps.zip
$ vim Dockerfile
$ cat Dockerfile
  FROM tomcat:7.0-jre8
  ADD struts-2.5.10.1/apps/struts2-rest-showcase.war /usr/local/tomcat/webapps/
  CMD ["catalina.sh", "run"]
$ ls
  struts-25.10.1 Dockerfile
$ docker build -t struts/st .
$ docker run -it --rm -p 8080:8080 struts/st
  ...
  May 13, 2017 5:10:29 AM org.apache.catalina.startup.Catalina start
  INFO: Server startup in 8858 ms

http://localhost:8080 を開き,以下のような画面が表示されたら成功.docker run の -p 8080:8080 ではコンテナ側の 8080 番ポートを実行環境の 8080 番ポートに割り当てているのだとか.コンテナに外部からアクセス(ポートフォワード) - Qiita あたりを参考に.
f:id:calmery:20170514204146p:plain

共有フォルダを作成する

分科会では Ubuntu 16.04 LTS : Samba : フルアクセスの共有フォルダ作成 : Server World を見ながら進めたが,ここでは sambaサーバによるファイル共有環境の構築手順 を見ながら進めることにする.

sambaサーバの構築手順
上図のファイル共有環境の構築のためには以下の手順が必要になります。
1. sambaのインストー
2. sambaユーザの作成
3. sambaユーザのグループへの追加
4. グループ共有用ディレクトリの作成とアクセス権の設定
5. sambaの設定ファイルの編集
sambaサーバによるファイル共有環境の構築手順

まずは samba を apt-get を使ってインストールする.ついでに ifconfig を使うための net-tools と vim もインストールした.

$ sudo apt-get install samba
$ sudo apt-get install vim net-tools

samba で使用するユーザを追加する.

sambaを使用するユーザを作成する場合、sambaが動作するLinux上にsambaを使用するユーザと同名のLinuxユーザがすでに存在している必要があります。
sambaサーバによるファイル共有環境の構築手順

ということなのでユーザを作成してから pdbedit で samba にユーザを追加する.既にあるユーザを追加する場合は pdbedit だけ実行すればいい.

$ sudo useradd -m user1
$ sudo pdbedit -a user1
 new password:
 retype new password:

グループを作成し,グループごとにアクセス権を与える.

sambaでは、sambaが動作するLinux上に存在するグループ毎に、共有するファイルやディレクトリのアクセス権を変更できます。今回はubuntu上にgroup1という名前のグループを作成し、group1に属するユーザだけがアクセスできるディレクトリを作成します。
sambaサーバによるファイル共有環境の構築手順

$ sudo groupadd group1
$ sudo gpasswd -a user1 group1
 Adding user user1 to group group1

ディレクトリの作成を行い,先ほど追加したグループへアクセス権限の追加を行う.

$ sudo mkdir /home/public
$ sudo chgrp group1 /home/public
$ sudo chmod -R 774 /home/public

samba の設定を変更する.

$ sudo vim /etc/samba/smb.conf
$ cat /etc/samba/smb.conf
  ...
  unix charset = UTF-8
  dos charset = CP932
  ...
  map to guest = never
  ...
  [public]
     path = /home/public
     browseable = yes
     writable = yes
     valid users = @group1

ここでは valid users にグループ名を指定しているが pdbedit で追加したユーザを直接指定することもできる.

valid users = calmery, user1, user2

ホスト側で開く.ここで指定するアドレスは ifconfig で調べられる.
f:id:calmery:20170514205819p:plain
先ほど登録したユーザ名とパスワードを入力する.
f:id:calmery:20170514205319p:plain
マウントするボリュームを選択する.
f:id:calmery:20170514205748p:plain
開けた.ここで追加したファイルは Ubuntu 上で確認できる.
f:id:calmery:20170514205537p:plain
f:id:calmery:20170514210032p:plain
右クリックで取り出せる.
f:id:calmery:20170514210110p:plain

ハマったところ

自分は分科会で共有フォルダを開くことができなかった.調べてみると

また、NATの使用時に、SSH等でホストOSからゲストOSへの接続を行いたい場合、ネットワークのアダプターにホストオンリーアダプターを別途使用することで、ゲストOSへホストOSから接続可能とすることができます。
VirtualBox CentOS6.7 64bitでNAT、ホストオンリーアダプターを使用 | kakiro-web カキローウェブ

ということらしく,VirtualBoxVM の設定のネットワークから「ホストオンリーアダプター」を追加すると共有フォルダを開くことができるようになった.
f:id:calmery:20170514204231p:plain

まとめ

なるほど(?)
ほとんど環境を整えただけで終わってしまったが,簡単な使い方はわかったので色々と調べてみようと思う.

Dockerを使って、Apache Struts2の脆弱性S2-037のやられ環境を手軽に作る - DARK MATTER
Dockerで作る開発環境 - Qiita
Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】

ちなみに共有フォルダでは会話も楽しむことができる.

WebAssemblyを試してみた

メモ.WebAssembly については WebAssembly のコンセプト - WebAssembly | MDN を参考にするとよさそう.

さらに追記

$ nvm install 8.0.0
$ nvm use 8.0.0
$ node -v
v8.0.0
$ node
> WebAssembly
{}

新しくリリースされた v8.0.0 の Node.js ではデフォルトで使えるようになってるみたい.
追記のところで main 関数がとか言っていたけど Emscripten を使えばその辺りもいい感じしてもらえるっぽい.

見直してみると,色々と勘違いしているところもあるけれど記録としてそのままに.

追記

main 関数があっても動かせた.と言うか足りない関数は補ってねという感じなのかな.
楓 software: wasm に clang を使った場合に C 内で関数呼び出しする場合に出るエラー

(index):25 Uncaught (in promise) LinkError: WebAssembly.Instance(): Import #0 module="env" function="printf" error: function import requires a callable
    at fetch.then.then.then.module (http://localhost/:25:24)
    at <anonymous>
$ s2wasm main.s -o main.wast --allocate-stack 1024
const loadWebAssembly = ( filename, imports ) => {
    return fetch( filename )
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}

            imports.env.printf = arg => console.log( arg )

            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 18,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    exports.main()
} )

Google Chrome Canary や Node.js で WebAssembly を読み込むための関数を作った(よくわかっていないのでものによっては動かないかも)
WebAssembly/loadWebAssembly.js at master · calmery/WebAssembly · GitHub

環境構築

Mac OS X 10.11.6
Google Chrome Canary 59.0.3030.0 canary (64-bit)
Firefox Nightly 54.0a1 (2017-03-05) (64 bit)
Node.js v8.0.0-pre

おまじない.

$ pyenv global 2.7.12

The LLVM Compiler Infrastructure Project

$ cd
$ mkdir workspace
$ cd workspace
$ git clone http://llvm.org/git/llvm.git
$ cd llvm/tools
$ git clone http://llvm.org/git/clang.git
$ cd ../projects
$ git clone http://llvm.org/git/compiler-rt
$ cd ~/workspace
$ mkdir llvm_build
$ cd llvm_build
$ cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ../llvm
$ make -j 8
$ sudo make install

GitHub - WebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++

$ cd ~/workspace
$ git clone https://github.com/WebAssembly/binaryen.git
$ cd binaryen
$ cmake . && make
$ sudo make install

GitHub - WebAssembly/wabt: The WebAssembly Binary Toolkit

$ cd /usr/local/src
$ sudo git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ sudo make gcc-debug-no-tests
$ cd
$ echo "export PATH=$PATH:/usr/local/src/wabt/out/gcc/Debug/no-tests/" >> .bash_profile

Node.js は v8.0.0-pre でないと Google Chrome Canary や Firefox Nightly で動く WebAssembly が動かない.そのため v8/nodenodejs/node を使う.Nightly builds の v8.0.0-nightly20170307efaab8fccf も試したが動かなかった.
GitHub - nodejs/node: Node.js JavaScript runtime

$ git clone https://github.com/nodejs/node.git
$ cd node
$ git checkout -b canary origin/canary
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

GitHub - v8/node: Node.js JavaScript runtime

$ git clone https://github.com/v8/node.git
$ cd node
$ git checkout -b vee-eight-lkgr origin/vee-eight-lkgr
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

WebAssembly に変換する

C や C++ で書かれたコードを WebAssembly に変換する.

extern "C" {
    int fib( int n ){
        switch( n ){
            case 0: 
            	return 0;
            case 1: 
            	return 1;
            default: 
            	return fib( n - 2 ) + fib( n - 1 );
        }
    }
}

C や C++ のコードをまず LLVM bitecode に変換し,次にアセンブリに変換する.その後 S 式に変換しバイナリに変換する.

$ clang++ -v
clang version 5.0.0 (http://llvm.org/git/clang.git fc2d9054c86c9b8acbc98b06f1b9d8aa0e5d40f9) (http://llvm.org/git/llvm.git 58580c59aefa59fb793a72dd5abeb6a60340a317)
$ clang++ main.cpp -emit-llvm --target=wasm32 -Oz -c -o main.bc
$ llc -version
LLVM (http://llvm.org/):
  LLVM version 5.0.0svn
...
$ llc main.bc -march=wasm32 -filetype=asm -o main.s
$ s2wasm main.s > main.wast
$ sexpr-wasm main.wast -o main.wasm

追記
sexpr-wasm ではなく wast2wasm を使うようにする.

$ wast2wasm main.wast -o main.wasm

動かしてみる

GitHub - 1984weed/webassembly-sample を参考にした.

const loadWebAssembly = ( filename, imports ) => {
    return fetch(filename)
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}
            imports.env.memoryBase = imports.env.memoryBase || 0
            imports.env.tableBase = imports.env.tableBase || 0
            
            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 0,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    console.log( exports ) // => Object {memory: W…y.Memory, fib: function}
} )

Node.js はオプションで --expose-wasm をつける必要がある.

// Node.js v8.0.0-pre
// node --expose-wasm
const fs = require( 'fs' )
const buffer = fs.readFileSync( './main.wasm' )

const arrayBuffer = new Uint8Array( buffer ).buffer

WebAssembly.compile( arrayBuffer ).then( module => {
    let imports = {}
    imports.env = {}
    imports.env.memoryBase = 0
    imports.env.tableBase = 0

    if( !imports.env.memory )
        imports.env.memory = new WebAssembly.Memory( {
            initial: 256
        } )

    if( !imports.env.table )
        imports.env.table = new WebAssembly.Table( {
            initial: 0,
            element: 'anyfunc'
        } )

    const instance = new WebAssembly.Instance( module, imports )
    console.log( instance.exports )
} )
// Node.js v7.7.1
Promise {
  <rejected> Error: WebAssembly.compile(): Wasm decoding failedResult = expected version 0c 00 00 00, found 01 00 00 00 @+4

ちなみに C++ では名前に修飾がついてしまうので extern "C" で囲う必要がある.

console.log( instance.exports ) // => Object {memory: W…y.Memory, _Z3fibi: function}
> WebAssembly.Instance( module ).exports
{ _Z3fibi: [Function: 0], memory: Memory {} }

サンプルなどを見ていると WebAssembly ではなく Wasm を使っているものがある.だが Google Chrome Canary や Firefox Nightly では Wasm がない.

// Replace
Wasm.instantiateModule() // => ReferenceError: WASM is not defined
WebAssembly.Instance()

Node.js v7.2.1

Getting Started With WebAssembly in Node.js | www.thecodebarbarian.com
この方法は v.7.7.1 では動かなかった.sexpr-wasm demo も s2wasm で作られた wast がそのままでは使えない.書き換えれば使えはしたが v7.7.1 で動かなかった.

Emscripten で変換する

Developer’s Guide - WebAssembly を参考にして導入する.
Main — Emscripten 1.37.22 documentation
latest ではなく incoming を使う.

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install sdk-incoming-64bit binaryen-master-64bit
$ ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
$ source ./emsdk_env.sh
$ emcc
INFO:root:(Emscripten: Running sanity checks)
WARNING:root:no input files
$ mkdir hello
$ cd hello
$ echo '#include <stdio.h>' > hello.c
$ echo 'int main(int argc, char ** argv) {' >> hello.c
$ echo 'printf("Hello, world!\n");' >> hello.c
$ echo '}' >> hello.c
$ emcc hello.c -s WASM=1 -o hello.html
$ ls
hello.c		hello.html	hello.js	hello.wasm
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello, world!

ここで生成された hello.wasm は動かしてみるのように読み込むことはできなかった.

> WebAssembly.compile( arrayBuffer ).then( ...
Promise { <pending> }
(node:92075) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): LinkError: WebAssembly.Instance(): Import #0 module="env" function="DYNAMICTOP_PTR" error: global import must be a number
(node:92075) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

どうやら main 関数があると動かないみたい.出力されたファイルをそのまま実行すると動きはする.Google Chrome Canary や Firefox Nightly も出力された hello.html では動くのだが動かしてみるのように読み込むと動かない.以下のようにオプションを指定すると動いた.だがこちらも main 関数があると動かない.この辺りはもうちょっと調べたい.(追記 未確認だけど main 関数というよりは存在しない関数を呼び出しているからっぽい.ここでは main 関数内で呼び出している printf 関数のせいかも)

$ emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm
$ ls
main.cpp main.wasm
$ node --expose-wasm
> const fs = require( 'fs' )
> const buffer = fs.readFileSync( 'hello.wasm' )
> const arrayBuffer = new Uint8Array( buffer ).buffer
...

動かしてみる のコードを使用して動かした結果はこうなった.

emcc hello.c -s WASM=1 -o hello.html

main 関数があってもなくても読み込めない.

emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm

main 関数がなければ Google Chrome Canary,Firefox Nightly,Node.js v8.0.0-pre で動かせた.
最適化の -Os を抜くと読み込めなかったりとよくわからない.

Rust から変換する

Compiling Rust to WebAssembly Guide – Hacker Noon
結局は Emscripten と同じみたい.

fn main(){
    println!( "Hello World" );
}
$ rustc --target=wasm32-unknown-emscripten hello.rs -o hello.html
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello World

まとめ

Emscripten を使えば良さそうだけどよくわからなかった.調べる.