Fortran で WebAssembly　dragonegg + emscripten 版

# dragonegg で WebAssembly

## 追記 2020.4.21
下記サイトの記事で、gfortran + dragonegg + llvm + emscripten + glibfortran を実現していて、LAPACK 計算を実行して見せてくれています。Docker ファイルなどもあって簡単に試せるのではないかと思われます。

[FORTRAN In The Browser](https://chrz.de/2020/04/21/fortran-in-the-browser/)

## 要点
gfortran4.8 + dragonegg 3.3 + llvm 3.3 + 最新 emscripten で　WebAssembly が出せるということなので試してみました。若干、問題がありましたが、一応結果は再現できました。 

ネタ元
https://github.com/DirkWillem/WebAssembly-Fortran-Demo
その元
https://github.com/smikes/femscripten


## ランタイムは不可
gfortran のランタイム (libgfortran) は相変わらず使えないので I/O などは出来ませんが、emscripten を使うせいか（？）数学関数は大体使えるようです。gamma 関数も使えました。allocatable も（一応）行けます。

複素数は四則演算は出来ましたが、exp などの複素数を引数に取れる関数の複素数版は「無い！」と言われてリンク時にエラーが出ます。

gfortran のランタイムについては、ドウジンテイスウさんのところに情報があります。flang ではコンパイルできるようです。 
https://t.doujin-constant.net/post/140860937824/fortran-and-llvm
emscripten でのコンパイルを試みましたが、うまくゆきませんでした。

## gfortran4.8 の文法対応状況
dragonegg は開発が止まっているようで、gfortran4.8/llvm3.3 とかなり古いままになっています。

文法の対応状況は、以下のサイトにまとめがあります。
https://www.scivision.co/major-gfortran-changes/

## gfortran8.x + llvm6.x 
以下の様なニュースがあるので色々試してみましたが、うまくゆきませんでした。中華龍卵。
[DragonEgg Now Works With GCC 8, LLVM 6](https://www.phoronix.com/scan.php?page=news_item&px=DragonEgg-GCC8-LLVM6)
[{llvm-dev} DragonEgg for GCC v8.x and LLVM v6.x is just able to work](http://lists.llvm.org/pipermail/llvm-dev/2017-August/116705.html)
https://github.com/xiangzhai/dragonegg

# 実践編
基本的に WSL ubuntu18.04 上で、以下の二つの情報をなぞってゆくだけですが dragonegg.so を用意するのが結構大変でした。 
https://github.com/DirkWillem/WebAssembly-Fortran-Demo
https://github.com/smikes/femscripten

femscripten の方には、コンパイル済みの dragonegg.so があるのですが、コンパイル環境などの差のためかうまく動きませんでした。また docker イメージとして環境を用意してくれているのですが、これもうまく動きませんでした。（なお最新版の docker は WSL 上で動かなくなっているらしく、旧版に docker 実行ソフトをバージョン・ダウンしてから試しました。）結局 llvm3.3 ソースからの自炊コンパイルで解決しました。


## インストール

### gfortran4.8
コンパイル済みバイナリで大丈夫。

```
sudo apt install gfortran-4.8
sudo apt install gcc-4.8
sudo apt install gcc-4.8-plugin-dev
```

### llvm-3.3
http://releases.llvm.org/download.html#3.3
コンパイル済みバイナリ不可。ソースからの自炊不可避。
以下では手抜きしましたが、余力があれば llvm のサイトをよく読んで、インストール・ディレクトリなどを指定した方がいいと思います。（デフォルトでは/usr/local 以下にインストールされます。）

```
wget http://releases.llvm.org/3.3/llvm-3.3.src.tar.gz
tar xvzf llvm-3.3.src.tar.gz
cd llvm-3.3.src
./configure
make -j 8
sudo make install
```

### dragonegg-3.3
ソースを一部いじらないと通りません。よく分かりませんが、エラーメッセージを見て、２か所適当にいじりましたｗ　まずいかもしれません。

#### ソース改変
dragonegg-3.3.src/src/x86/ABIHack.inc　

- error 1 構造体の多重定義

```
/home/pc/dragonegg-3.3.src/src/x86/ABIHack.inc:1470:8: error: redefinition of ‘struct ix86_frame’
 struct ix86_frame
        ^~~~~~~~~~
```
解決法：コメント・アウト

```
//struct ix86_frame
//{
//  int padding0;
//  int nsseregs;
//  int nregs;
//  int padding1;
//  int va_arg_size;
//  HOST_WIDE_INT frame;
//  int padding2;
//  int outgoing_arguments_size;
//  int red_zone_size;
//
//  HOST_WIDE_INT to_allocate;
//  /* The offsets relative to ARG_POINTER.  */
//  HOST_WIDE_INT frame_pointer_offset;
//  HOST_WIDE_INT hard_frame_pointer_offset;
//  HOST_WIDE_INT stack_pointer_offset;
//
// /* When save_regs_using_mov is set, emit prologue using
//     move instead of push instructions.  */
//  bool save_regs_using_mov;
//};
```

- error2 配列初期化ではみだし

```
In file included from /home/pc/dragonegg-3.3.src/src/x86/Target.cpp:68:0:
/home/pc/dragonegg-3.3.src/src/x86/ABIHack.inc:1666:36: error: ‘TARGET_CPU_DEFAULT_max’ was not declared in this scope
 static const char *const cpu_names[TARGET_CPU_DEFAULT_max] =
```
解決法：配列サイズを要素数 23 に手打ちで修正

```
//static const char *const cpu_names[TARGET_CPU_DEFAULT_max] =
static const char *const cpu_names[23] =
{
  "generic",
  "i386",
  "i486",
```

#### コンパイル
dragonegg-4.8.so が出来ればおｋです。

```
wget http://llvm.org/releases/3.3/dragonegg-3.3.src.tar.gz
tar xzf dragonegg-3.3.src.tar.gz
cd dragonegg-3.3.src/
GCC=/usr/bin/gcc-4.8  LLVM_CONFIG=/usr/local/bin/llvm-config  make -j 8
```

### emscripten
最新版を指示通りに入れれば大丈夫です。
[Download and install](https://emscripten.org/docs/getting_started/downloads.html#platform-notes-installation-instructions-sdk)

下準備
必要ならば

```
sudo apt-get install python2.7
sudo apt-get install cmake
sudo apt-get install default-jre
```
本体

```
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
sudo ./emsdk install latest
./emsdk activate latest
```
PATH 通し　

```
source ./emsdk_env.sh
```


## WebAssembly 
これでようやく、https://github.com/DirkWillem/WebAssembly-Fortran-Demo の実行環境が整いました。適当なディレクトリに git clone してから、path を整える必要があります。

```
git clone https://github.com/DirkWillem/WebAssembly-Fortran-Demo.git
```
make 改変 (c のラッパー消す。EXPORTED_FUNCTIONS 修正

```make
export PATH := bin:$(PATH)

RK4=rk.o

clean:
        rm -f *.bc *.o *.ll *.s *.js *.wasm

rk4.js: $(RK4)
        emcc -o $@ $(RK4) -s EXPORTED_FUNCTIONS="['_RungeKutta']" -s WASM=1 -s MODULARIZE=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0


%.o: %.f90
        emgf $<

.PHONY.: clean
```
bin/以下の修正　

```
cp ~/dragonegg-3.3.src/build/dragonegg.so  ~/WebAssembly-Fortran-Demo/bin  
```
emgf-1    
DRAGONEGG=~/WebAssembly-Fortran-Demo/bin/dragonegg.so
gfortran -> gfortran-4.8

```
#!/bin/sh

DRAGONEGG=~/WebAssembly-Fortran-Demo/bin/dragonegg.so

gfortran-4.8 -S -flto -m32 -fplugin=${DRAGONEGG} -fverbose-asm -c "$*" 
```
emgf-2 
llvm-as-3.3 -> llvm-as

```
#!/bin/sh

SRC="$1"
ASM="$(basename $SRC .f90).s"
LL="$(basename $SRC .f90).ll"
OBJ="$(basename $SRC .f90).o"

cat >${LL}<<EOF
; ModuleID = '$SRC'
target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128"
target triple = "asmjs-unknown-emscripten"
EOF

tail -n +4 ${ASM} >>${LL}
llvm-as ${LL} -o ${OBJ}
```

## Fortran 
ここから先が Fortran の世界になります。

### fortran source
元々のサンプル・プログラムを、少し修正して modern fortran にしてみました。元のソースでは c のラッパーを挟んでいましたが、bind(c, name = 'c 名') を使うことで fortran 単体で行けます。ただ emscripten を挟んでいるので、外部からの呼び出し名は c 名の前にアンダースコアが付くようです。

rk.f90

```fortran
module test
    implicit none
    integer, parameter :: kd = kind(1.0d0)
    real(kd), parameter :: e = exp(1.0_kd)

contains

    subroutine RungeKutta4(n, t, y) bind(c, name = 'RungeKutta')
        integer, value  :: n
        real(kd), intent(in out) :: t(n), y(n)
        real(kd), parameter :: h = 0.125_kd
        integer :: i

        do i = 1, n - 1
            t(i + 1) = i * h
            y(i + 1) = rk4(h, t(i), y(i))
        end do

    contains

        real(kd) function dy(t, y)
            real(kd), intent(in) :: t, y
            dy = sin(y)  ! (5 * t * t - y) / ( e ** (t + y ))
        end function dy

        real(kd) function rk4(h, t, y)
            real(kd), intent(in) :: h, t, y
            real(kd) :: f(4)
            real(kd), parameter :: Fscheme(4) = (/ 1, 2, 2, 1 /)

            f(1) = h * dy(t        , y           )
            f(2) = h * dy(t + h / 2, y + f(1) / 2)
            f(3) = h * dy(t + h / 2, y + f(2) / 2)
            f(4) = h * dy(t + h    , y + f(3)    )
            rk4 = y + dot_product(f, Fscheme) / 6
        end function rk4
    end subroutine RungeKutta4

end module test
```

### コンパイル

```
make rk4.js
```
rk.wasm と rk.js が出来ます。これら二つが必要です。rk.js は emscripten によるランタイムのようです。


### HTML source
HTML は WebAssembly 単体用とは異なる模様です（よく分かりません）。emscripten 対応の模様です。

```HTML
<!DOCTYPE html>

<html>
    <head>
        <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.21/c3.min.css"/>
    </head>
    <body>
        <div id="chart"></div>

        <script src="rk4.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.21/c3.min.js"></script>

        <script>
            var module = new Module();
            module.onRuntimeInitialized = () => {
                var nBytes = 101 * Float64Array.BYTES_PER_ELEMENT;
                var tPtr = module._malloc(nBytes);
                var yPtr = module._malloc(nBytes);
                var zPtr = module._malloc(nBytes);
                var t = [];
                var y = [];
                var z = [];

                // t(0) = 0.0
                module.HEAPF64[(tPtr/Float64Array.BYTES_PER_ELEMENT)] = 0.0;
                // y(0) = 0.1
                module.HEAPF64[(yPtr/Float64Array.BYTES_PER_ELEMENT)] = 0.1;
                module._RungeKutta(101, tPtr, yPtr); // call RungeKutta
                // z(0) = 1.5
                module.HEAPF64[(zPtr/Float64Array.BYTES_PER_ELEMENT)] = 1.5;
                module._RungeKutta(101, tPtr, zPtr); // call RungeKutta


                for(i = 0; i < 101; i++) {
                    let tmp = module.HEAPF64[(tPtr/Float64Array.BYTES_PER_ELEMENT) + i];

                    t[i] = Math.trunc(tmp * 10**10) / 10**10;
                    y[i] = module.HEAPF64[(yPtr/Float64Array.BYTES_PER_ELEMENT) + i];
                    z[i] = module.HEAPF64[(zPtr/Float64Array.BYTES_PER_ELEMENT) + i];
                }

                var chart = c3.generate({
                    bindTo: "#chart",
                    data: {
                        x: "t",
                        columns: [
                            ["t", ...t],
                            ["y", ...y],
                            ["z", ...z]
                        ]
                    }
                })
            }
        </script>
    </body>
</html>
```

###　実行結果
python2

```
ptrhon -m SimpleHTTPServer 8080
```

Runge-Kutta 法　
![rk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44522/3a275ad3-08c4-e559-f94f-ec2069cfa4f4.png)

# 追記　R1.7.22
Makefile の emcc の引数に -s ERROR_ON_UNDEFINED_SYMBOLS=0 を足してやれば、allocatable 配列も確保できる。