LoginSignup
11
3

More than 3 years have passed since last update.

Fortran で WebAssembly 令和元年版 

Last updated at Posted at 2019-06-29

WebAssembly

前回記事を書いてから状況が変化して、以前の手順のままではうまくゆかなくなってしまいました。
Fortran で WebAssembly ~または地獄めぐり~

しかしながら、llvm-8 で llvm が WebAssembly に対応するなどの事情もあって、以前より楽に環境構築できるようになりましたので、その手順についてまとめておきたいと思います。

まだ Fortran のランタイムなどの対応はなされていないので、I/O、数学関数の類、allocable 等は使えませんが、将来的には他言語のようにランタイムそのものを WebAssembly にするなどの対応が可能かもしれません。(また中間形式を C 言語に変換してそこで修正ののち emscripten に送るというアイデアをネットで見たこともあります。)

手順

おおよそ3段階に分かれます。時間がかかるのは flang の用意です。
- flang の用意
- llvm-8 の導入
- WebAseembly 実行の用意

Windows10 の WSL ubuntu18.04 のまっさらな状態から導入しました。

flang の用意

GitHub 上の flang 公式の Building Flang のページの指示に従って作ります。以前に比べて少し親切になりました。

llvm-7 系統から branch しているので、WebAssembly に対応するようなオプションを加えます。(-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly)公式バイナリはこのオプションが有効化されていないので、自分でコンパイルする必要があります。

前準備

make/cmake gcc/g++ が必要になるので導入します。

sudo apt update
sudo apt upgrade
sudo apt autoremove

sudo apt install g++
sudo apt install make
sudo apt install cmake

本番

flang 公式の指示は4段階に分かれています。前半二つは gcc/g++ でコンパイルし、後半二つは前半で生成した clang/clang++ でコンパイルします。

0 環境変数の設定

前準備として環境変数をセットします。いま ~/llvm/install の個人局所環境にバイナリをインストールすることにします。指示に従いディレクトリを作り cmake 用の環境変数をセットします。ここで忘れずに 『 -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly』 を加えておきます。これを忘れると、すべてが徒労に終わります。

mkdir llvm
cd llvm
mkdir install
export INSTALL_PREFIX=`pwd`/install

CMAKE_OPTIONS="-DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \
    -DLLVM_CONFIG=$INSTALL_PREFIX/bin/llvm-config \
    -DCMAKE_CXX_COMPILER=$INSTALL_PREFIX/bin/clang++ \
    -DCMAKE_C_COMPILER=$INSTALL_PREFIX/bin/clang \
    -DCMAKE_Fortran_COMPILER=$INSTALL_PREFIX/bin/flang \
    -DLLVM_TARGETS_TO_BUILD=X86         \
    -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly"

リリース・オプションをつけてもいいかもしれません。 -DCMAKE_BUILD_TYPE=Release

1 llvm-7

すごく時間がかかります。make -j n で n スレッド並列で実行してくれますが、タイミングで出来上がる順番が間に合わなくてエラー終了することがあります。何度か繰り返すと何とかなります。

git clone https://github.com/flang-compiler/llvm.git
cd llvm && git checkout release_70
mkdir -p build && cd build
cmake $CMAKE_OPTIONS -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ ..
make -j 8
make install
cd ../..

2 flang-driver

git clone https://github.com/flang-compiler/flang-driver.git
cd flang-driver && git checkout release_70
mkdir -p build && cd build
cmake $CMAKE_OPTIONS -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ ..
make -j 8
make install
cd ../..

3 openmp

2 のステップで 0 のステップで設定した環境変数 INSTALL_PREFIX の下に clang 等が生成されているので、ここに PATH を通しておきます。以降はこれらのコンパイラを使用します。gcc/g++ でやると失敗します。

export PATH=$INSTALL_PREFIX/bin:$PATH

git clone https://github.com/llvm-mirror/openmp.git
cd openmp && git checkout release_70
mkdir -p build && cd build
cmake $CMAKE_OPTIONS -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ ..
make -j 8
make install
cd ../..

なんか警告が出るので、気になる人は各人調べてください。ここでは気にしないことにします。

4 flang compiler

二段階に分かれています。初めに flang 用のランタイムを生成します(ただし x86 用)。次に本体を生成します。ここも時間がかかります。 

4-a runtime

cd flang/runtime/libpgmath
mkdir -p build && cd build
cmake $CMAKE_OPTIONS ..
make -j 8
make install 
cd ../../..

4-b flang 本体

mkdir -p build && cd build
cmake $CMAKE_OPTIONS ..
make -j 8
make install 

llvm-8 の導入

公式バイナリを導入します。https://releases.llvm.org/download.html#8.0.0

wget https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
xz -dv clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar xfv clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04.tar

Linux で tar.xz 形式のファイルを解凍する

PATH を通しておきます。ここで、先ほど flang 用に生成した llvm ツールは ver.7 系列なので、こちらの 8 系列が優先されるような PATH の並びが必要です。念のため llc --version などを実行してみて ver.8 系列が動いていることを確認してみてください。

export PATH=~/llvm/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH

WebAseembly 実行の用意

Fortran ソースコードから wasm ファイルへの変換は llvm-8 を用いると以前より簡単化されます。以下のサイトがとても参考になります。

Compiling C to WebAssembly without Emscripten

wasm の生成

flang --target=wasm32 -emit-llvm -c -S -Oz test.f90
llc --march=wasm32 -filetype=obj test.ll
wasm-ld --no-entry --export-all -o test.wasm test.o
function test() result(ires) bind(c, name = 'test')
    integer :: ires
    ires = 2
end function test

html ファイルの準備

html ファイルとして上記サイトの例を用いる場合、wasm を MIME タイプとして登録しておく必要があります。

Incorrect response MIME type. Expected 'application/wasm' エラーの 対応

sudo vi /etc/mime.types
 適当なところに追加 ==>  application/wasm wasm
<!DOCTYPE html>

<script type="module">
  async function init() {
    const { instance } = await WebAssembly.instantiateStreaming(
      fetch("./test.wasm")
    );
    console.log(instance.exports.test());
  }
  init();
</script>

サーバー起動

python を使うことにします。
pythonでローカルwebサーバを立ち上げる

sudo apt install python

python -m SimpleHTTPServer 8080

ここでブラウザを立ち上げて、http://localhost:8080/test.html に接続し、F12 でツールを開いて console タブを見ると 2 と書かれているはずです。

http://localhost:8080/test.html

実行例 円周率 pi と近似値 355/113 の差

$$ \int_0^1{x^8(1-x)^8(25+816x^2)\over3164(1+x^2)}dx = {355\over113}-\pi$$

台形積分で積分を計算し、左辺と右辺の値を求めて出力します。

注:実行時にはランタイム・ライブラリの問題で数学関数は使えませんが、コンパイル時の定数の計算には使えます。

      module m_pai
          implicit none
          integer, parameter :: kd = kind(1.0d0)
          real(kd), parameter :: pi = 4 * atan(1.0_kd)
      contains
          real(kd) function pai(n) bind(c, name = 'pai')
              integer, value :: n
              real(kd) :: x(129), y(129), h
              integer :: i
              h = 1.0_kd / (n - 1)
              forall (i = 1:n) x(i) = (i - 1) * h
              y = f(x(1:n))
              pai = h * ( sum(y(1:n)) - 0.5_kd *(y(1) + y(n)) )
          end function pai


          real(kd) pure elemental function f(x)
              real(kd), intent(in) :: x
              f = x**8 * (1.0_kd - x)**8 * (25.0_kd + 816.0_kd * x**2) / (3164.0_kd * (1.0_kd + x**2))
          end function f


          real(kd) function pai2() bind(c, name = 'pai2')
              pai2 = 355.0_kd / 113.0_kd - pi
          end function pai2
      end module m_pai
flang --target=wasm32 -emit-llvm -c -S -Oz pai.f90
llc --march=wasm32 -filetype=obj pai.ll
wasm-ld --no-entry --export-all -o pai.wasm pai.o
<!DOCTYPE html>
<html>
<body>
<script type="module">
    async function init() {
        const { instance } = await WebAssembly.instantiateStreaming(
        fetch("./pai.wasm")
    );
    const ex = instance.exports
    const n = 64
    const result = ex.pai(n + 1)
    const theory = ex.pai2()
    console.log(result);
    document.querySelector("#n_div").innerHTML = n
    document.querySelector("#result").innerHTML = result
    document.querySelector("#theory").innerHTML = theory
    }
    init();
</script>
Division of Interval
<div id="n_div"></div>

Result
<div id="result"></div>

Theoretical value
<div id="theory"></div>

</body>
</html>

paipai.png

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3