LoginSignup
5
1

More than 3 years have passed since last update.

Fortran で WebAssembly dragonegg + emscripten 版

Last updated at Posted at 2019-07-19

dragonegg で WebAssembly

追記 2020.4.21

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

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
{llvm-dev} DragonEgg for GCC v8.x and LLVM v6.x is just able to work
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

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

ソース改変

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 が出来ればおkです。

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

下準備
必要ならば

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 修正

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

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 対応の模様です。

<!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

追記 R1.7.22

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

5
1
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
5
1