dragonegg で WebAssembly
追記 2020.4.21
下記サイトの記事で、gfortran + dragonegg + llvm + emscripten + glibfortran を実現していて、LAPACK 計算を実行して見せてくれています。Docker ファイルなどもあって簡単に試せるのではないかと思われます。
要点
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
追記 R1.7.22
Makefile の emcc の引数に -s ERROR_ON_UNDEFINED_SYMBOLS=0 を足してやれば、allocatable 配列も確保できる。