AWS Lambda の Custom Runtimes で Brainfuck を動かす

# はじめに
先月末、ラスベガスで行われたAWS re:Invent 2018の2日目のキーノートにて、AWS Lambdaで任意の言語を動かすことを可能とする新機能Custom Runtimesが発表されました。
「COBOLも動かせるよ」という発言がフィーチャされ、一時「COBOL」がTwitterのトレンド入りするほどの反響でしたが、現地でこの発表を聞いた瞬間、私の脳裏によぎったのは「Brainfuck（以降お下品なのでBrainf*ckとする）も動くのかなぁ」という疑問でした。疑問に思ったからにはやるしかない。ということで、Brainf*ckをLambda上で動かしてみました。
なお、結論から言うと動くには動きましたが、「Custom Runtimesってこういうことでいいのか…？」という別の疑問がつきまとう結果になりました。

##### Brainf*ckとは？
~~変態~~ 難解プログラミング言語として有名な言語です。具体的に何が難解なのかはwikipediaを読めばわかります。多分。
https://ja.wikipedia.org/wiki/Brainfuck

ちなみに私も今まで話に聞いたことしかなく、今回はじめて書きました。

# Brainf*ckのコードを書く
まずは、何はともあれ今回Lambdaで実行したいBrainf*ckのコードを書きます。Brainf*ckは引数を取れない（という理解であってますよね？）ので、単純に記述された文字列を出力するコードにしました。Hello, Worldとほぼ同じですね。書いたコードは以下の通りです。

```text:handler.bf
++++++++[>+++++++++++<-]>+.++++++++.<++++[>++++++<-]>.>++++[>++++++++<-]>+.-.
>++++++++[>+++++++++<-]>+.<<.<<<+++[>-------<-]>-.<+++[>++++<-]>.++++++.---------.
--------.>>>.<<<<++[>+++++++<-]>.+++.-------.>>>.>-------.<<<<++++.<++++[>----<-]>-.
++++++++.+++++.--------.<++++[>++++<-]>-.<++++[>----<=-]>--.++++++++.>>.<<++++.
-.>>.>>-.<+++++[>++++<-]>++.----.<<.>>-------.<<<<<++++[>---<-]>-.<++++[>+++<-]>.
<++++[>---<-]>+.++.---.>>.<++++[>+++<-]>++++++++++++++.<++++[>---<-]>-.<+++++
[>+++++++<-]>.<++++++++++.
```

出オチ感が半端ないですが、このコードを実行するときちんと以下のような文字列が出力されます。

```bash
$ bf handler.bf
Yay! I could run Brainfuck on AWS Lambda :-P
```

なお、このプログラムはWindows Subsystem for LinuxのUbuntu上で実行しています。Brainf*ckの処理系はいくつかあるようですが、今回はbfを利用しました。bfはUbuntu上で`sudo apt-get install bf`でインストールできます。

# Custom Runtimesの準備
以下の公式チュートリアルを参考にしました。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html

Custom RuntimesでLambdaを実行するには、`bootstrap`という名前のシェルスクリプトと任意の言語でかかれたハンドラをパッケージに含める必要があります。通常のLambdaは呼び出し時に指定したランタイムでハンドラが実行されますが、「独自のランタイムを使う」設定されたLambdaの場合は、ハンドラではなく`bootstrap`が実行されます。なので、この`bootstrap`にハンドラを実行する処理とその前後に必要な処理を記述すればOKのようです。`bootstrap`の処理は非常に簡単で、以下の3ステップだけです。

1. curlでLambdaのAPIからリクエストデータをGETする
2. 受け取ったリクエストデータをハンドラに渡す
3. ハンドラから受け取ったデータをcurlでLambdaのAPIにPOSTする

チュートリアルはシェルスクリプトをLambdaで実行するものなので、これを参考にBrainf*ck用に変更した`bootstrap`が以下です。

```bash:bootstrap
#!/bin/sh
set -euo pipefail

# Processing
while true
do
    HEADERS="$(mktemp)"
    # Get an event
    EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
    REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

    # Execute the handler function from the script
    export PATH=$PATH:$LAMBDA_TASK_ROOT/bin
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LAMBDA_TASK_ROOT/lib
    RESPONSE=$(bf $(echo "$_HANDLER"))

    # Send the response
    curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done
```

チュートリアルからの変更は初期化処理の省略とハンドラ実行の部分です。今回はパッケージにbfの実行ファイルとライブラリファイルを同梱するので、環境変数`PATH`、`LD_LIBRARY_PATH`にファイルパスを追加します。その後、先ほど書いたBrainf*ckのコードを実行します。
なお、`$_HANDLER`にはLambda関数作成時に指定したハンドラファイル名が入ります。また、`$LAMBDA_TASK_ROOT`にはパッケージが展開されるパスが入ります。

# パッケージの作成とデプロイ
ここまでに作成したBrainf*ckのソースコードとbootstrap、処理系を一つのzipファイルにまとめます。zipの構成は以下の通りになります。

```
test_bf.zip
├─handler.bf
├─bootstrap
├─bin
│ └─bf
└─lib
  └─libslang.so.2
```

私の環境では`bf`は`/usr/bin/bf`、`libslang.so.2`は`/lib/x86_64-linux-gnu/libslang.so.2`に存在していたので、適宜コピーします。

Lambda関数の作成から、「ランタイム」欄で「独自のランタイムを使用する」を選択します。
<img width="741" alt="001.png" src="https://qiita-image-store.s3.amazonaws.com/0/91332/29db7988-6490-fb4c-0e97-1e62347394a2.png">

通常のLambda関数同様、zipファイルをアップロードします。ハンドラ欄には、ハンドラ用のファイル名を指定します。
<img width="945" alt="002.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/91332/b94c7ef4-899b-cf01-2f62-535a7f5688be.png">

以上でデプロイは完了です。

# 実行
あとは実行するだけです。今回作成した関数は引数を取らないので、テストデータはとにかく実行できる形であれば何でも良いです。

<img width="663" alt="003.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/91332/e3c2adda-e5c3-3252-4922-ade51262254d.png">

LambdaでBrainf*ckが実行できた…と思いきや、なぜか出力が「couldrunBrainfuck」とスペースが入らず連結されてしまっています。
ローカル（Ubuntu）の実行結果とLambda（Amazon Linux）の実行結果でズレがあります。はっきりと原因わかりませんが、Braif*ckは処理環境依存の仕様があるそうなので、そのせいなのかもわかりません。

幸いスペースを出力する方法は他にもあるので、ソースコードを以下のように修正します。（どこが変わったんだよって感じですが。）

```text:handler2.bf
++++++++[>+++++++++++<-]>+.++++++++.<++++[>++++++<-]>.>++++[>++++++++<-]>+.-.
>++++++++[>+++++++++<-]>+.<<.<<<+++[>-------<-]>-.<+++[>++++<-]>.++++++.
---------.--------.<++++++[>-----------<-]>--.<++++++[>+++++++++++<-]>++
<++[>+++++++<-]>.+++.-------.<++++++++[>----------<-]>++.<++++++++[>++++++++++<-]>--
>>>>-------.<<<<++++.<++++[>----<-]>-.++++++++.+++++.--------.<++++[>++++<-]>-.
<++++[>----<=-]>--.++++++++.>>.<<++++.-.>>.>>-.<+++++[>++++<-]>++.----.<<.>>
-------.<<<<<++++[>---<-]>-.<++++[>+++<-]>.<++++[>---<-]>+.++.---.>>.
<++++[>+++<-]>++++++++++++++.<++++[>---<-]>-.<+++++[>+++++++<-]>.<++++++++++.
```

<img width="650" alt="004.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/91332/27f542c5-d7f9-efd7-24b0-d98e89aa6afb.png">

今度こそLambdaでBrainf*ckが実行できました！

# これでおわり？
LambdaでBrainf*ckを動かす、という目標は実現したのですが、あまりにあっさり動くので拍子抜けしてしまいました。
もともとLambdaのパッケージにバイナリファイルを同梱して、Lambda上でFFmpegやMeCabを動かすようなことはCustom Runtimes以前から出来ていたので、今回のように処理系とソースコードを放り込んでシェルから実行すれば、うまく動くのは当たり前という感じです。
明示的には許可していなかったLambda関数のOSレベルでの制御を実行可能な機能として明確に公開した、ということなんでしょうか。それとも他の言語で動かそうとすると、また違う意味が見えてくるんでしょうか。時間があれば、もっと実用的な言語でも検証してみたいと思います。

ちなみに今回検証にかかった時間の8割ぐらいはBrainf*ckのコーディングでした。多分もう書くことは無いと思います(笑)