NEC FortranとSX-Aurora TSUBASAを使ってみた（その2）

# 概要
SX-Aurora TSUBASA (SXAT)を使える機会を得たので，Fortran Package ManagerとFortran Standard Libraryを使ってみました．

- [その1](https://qiita.com/implicit_none/items/8573afae2ac87532b7e9)
- [その2 - fpm, stdlibを使ってみた](https://qiita.com/implicit_none/items/abe5104e980ccc7d92b1)
- [その3 - stream ベンチマークやってみた](https://qiita.com/implicit_none/items/cebda1fbb26920326a41)
- [その4 - 姫野ベンチマークやってみた](https://qiita.com/implicit_none/items/ed280b88083d46044d1e)
- [その5 - ベクトル和](https://qiita.com/implicit_none/items/c0cf96b582bf62ba4d4e)

# 環境
- Vector Engine Type 10C
- nfort 3.5.1
- fpm 0.7.0
- stdlib 0.2.1

# Fortran Package Managerの利用
SXATを使うプログラムを開発するにあたり，Fortran Package Manager (fpm)を利用します．今回は，SXATが接続されているCPUのアーキテクチャがx86-64だったので，fpmの[リリースページ](https://github.com/fortran-lang/fpm/releases)からそれ用のバイナリfpm-0.7.0-linux-x86_64をダウンロードして利用することにしました．

fpmはいくつかのコンパイラをサポートしており，サポートしているコンパイラに対しては，debugやreleaseといったプロファイルを利用できます．今回使うNEC Fortran (nfort)はfpmのサポート外なので，fpmのオプションを利用して，コンパイラやコンパイル時のオプションを指定します．

```console
$ fpm new sample
 + mkdir -p sample
 + cd sample
 + mkdir -p sample/src
 + mkdir -p sample/app
 + mkdir -p sample/test
 + git config --get user.name > /tmp/filenJJm0v
 + git config --get user.email > /tmp/fileg2ZFgu
 + git config --get user.name > /tmp/fileDT7PTs
 + git init sample

$ fpm build --compiler nfort --flag "-fpp -std=f2008 -O0"
 + mkdir -p build/dependencies
<WARN> Unknown compiler nfort requested! Defaults for this compiler might be incorrect
sample.f90                             done.
libsample.a                            done.
main.f90                               done.
sample                                 done.
[100%] Project compiled successfully.

$ fpm run --compiler nfort --flag "-fpp -std=f2008 -O0"
<WARN> Unknown compiler nfort requested! Defaults for this compiler might be incorrect
Project is up to date
 Hello, sample!

$ fpm test --compiler nfort --flag "-fpp -std=f2008 -O0"
<WARN> Unknown compiler nfort requested! Defaults for this compiler might be incorrect
check.f90                              done.
check                                  done.
[100%] Project compiled successfully.
 Put some tests in here!
```

サポートしていないコンパイラなので，標準（のコンパイルオプションなど）は正しくないよと警告が出ています．ビルドに関わるnfortのコンパイルオプションが概ねgfortranと同じなので，ビルドも実行も問題ありませんでした．

# Fortran Standard Libraryのビルド
## ソースの入手
SXATでFortran Standard Library (stdlib)を使うために，nfortでのビルドに挑戦しました．stdlibはfpmを用いてビルドできます．fpm対応版を入手するには，下記のコマンドを実行します．

```console
git clone https://github.com/fortran-lang/stdlib -b stdlib-fpm
```

## ビルド
ビルドを実行すると，残念ながらと言うべきか案の定と言うべきか，エラーが生じます．

```console
$ fpm build --compiler nfort --flag "-fpp -std=f2008"
中略
stdlib_hashmap_chaining.f90            failed.
[ 39%] Compiling...
Error: ././src/stdlib_hashmap_chaining.f90, line 222: Invalid redeclaration of host symbol FREE_CHAINING_MAP
       detected at FREE_CHAINING_MAP@(
Warning: ././src/stdlib_hashmap_chaining.f90, line 383: Pointer SENTRY never dereferenced
[NEC Fortran Compiler pass 1 error termination, 1 error, 1 warning]
<ERROR> Compilation failed for object " src_stdlib_hashmap_chaining.f90.o "
<ERROR>stopping due to failed compilation
STOP 1
```

エラーが生じていると報告されたのは`stdlib_hashmap_chaining.f90`なのですが，エラーの原因は，`stdlib_hasmap.f90`で定義しているユーザ定義派生型の後始末手続（final subroutine）を，submodule機能を利用して実装していることです．案の定と言ったのは，NAG Fortranでも同じエラーが発生したためです．NAG Fortranについては，サポート契約をしている方を経由してベンダにエラーの詳細を伝えていただき，現在のビルドでは修正されています．規格上はstdlibの書き方で問題ないはずで，submoduleとfinal subroutineという使われない機能の重ね技で，コンパイラベンダが考慮していない状況が発生したのだと考えられます．

エラーの解決は簡単で，`stdlib_hasmap.f90`にある`free_chaining_map`と`free_open_map`の`module subroutine`宣言を削除し，それぞれ`stdlib_hashmap_chaining.f90`と`stdlib_hashmap_open.f90`に書かれている実装を`stdlib_hasmap.f90`に移動するだけです．

## テスト
ビルドした後，テストを実行します．
```console
$ fpm test --compiler nfort --flag "-fpp -std=f2008 -O0"
中略
<ERROR> Execution failed for object " test_getline "
<ERROR> Execution failed for object " test_string_derivedtype_io "
<ERROR>*cmd_run*:stopping due to failed executions
STOP 1
```

テストを実行したところ，2個のテストが失敗しました．

### test_getline
これは，`stdlib_io`モジュールで定義される手続`getline`に対するテストです．このテストが失敗した理由は，`newunit`を用いて得られた装置番号に関係しており，下記のようなコードで現れます．

```Fortran
        type(error_type), allocatable, intent(out) :: error

        integer :: io, i, stat
        character(len=:), allocatable :: line

        open(newunit=io, status="scratch", pad="no")
        write(io, "(a)") repeat("abc", 10), repeat("def", 100), repeat("ghi", 1000)
        rewind(io)

        do i = 1, 3
          call getline(io, line, stat)
          call check(error, stat)
          if (allocated(error)) exit
          call check(error, len(line), 3*10**i)
          if (allocated(error)) exit
        end do
        close(io)
```

`open(newunit=io, status="scratch", pad="no")`で得た装置番号`io`を，stdlibで提供される手続`getline(io, line, stat)`に渡す場合に問題が発生します．

Fortranの規格上，装置番号は正の値しか許可されません．一方で，`newunit`を用いた際に自動的に割り付けられる装置番号は，負の値になります．`newunit`で得られた装置番号を引数で渡して異なるスコープで使い回す場合，どのように判断するかはコンパイラ依存になります．Intel Fortran, gfortranは有効な装置番号と認識し，NAG Fortranはエラーとすることが判っています．NEC Fortranもエラーとするようです．

`test_getline`でテストしたいのは，`getline`の引数で指定した装置番号から1行を取得できるか否かなので，装置番号の問題でテストが失敗するのは本意ではありません．装置番号が負になることが問題なので，`newunit`で得られた装置番号の絶対値を取って正の値を割り当てることで回避しました．

```Fortran
        open(newunit=io, status="scratch")
        unit_num = abs(io)
        close(io)
        io = unit_num
        open(unit=io, status="scratch")
```

### test_string_derivedtype_io
これは，`stdlib_string_type`で定義されている派生型`string_type`の入出力に対するテストです．このテストが失敗した理由は，書式を指定しない場合（List Directed IO）に，出力時に余計な空白を入れてしまうことです．

Fortranでは，`write`文に書式を指定しない場合，先頭に空白が追加されることがあります．

```Fortran
                             !v先頭に空白が入る
write(*,*) "Hello SXAT"      ! Hello SXAT
write(*,'(A)') "Hello SXAT"  !Hello SXAT
```

`test_string_derivedtype_io`の中では，`"Important saved value"`という値を持つ文字列を`write(io, *) string`で出力した結果，先頭に空白が追加された`" Important saved value"`が出力されます．出力された文字列を`read(io, *, iostat=stat) string`で取得すると，先頭に空白が追加された値`" Important saved value"`が取得され，`"Important saved value"`との比較を通して失敗と判定されます．

```Fortran
    subroutine test_listdirected_io(error)
        !> Error handling
        type(error_type), allocatable, intent(out) :: error

        type(string_type) :: string
        integer :: io, stat
        string = "Important saved value"

        open(newunit=io, form="formatted", status="scratch")
        write(io, *) string
        write(io, *) ! Pad with a newline or we might run into EOF while reading

        string = ""
        rewind(io)

        read(io, *, iostat=stat) string
        close(io)

        call check(error, stat == 0)
        if (allocated(error)) return
        call check(error, len(string) == 21)
        if (allocated(error)) return
        call check(error, string == "Important saved value")
    end subroutine test_listdirected_io
```

### 他のテストの失敗
最適化レベルを`0`より大きくすると，test_rawmomentも失敗します．失敗しているのは，複素数型配列の要素のモーメントを計算する手続です．理由はまだ特定できていませんが，最適化に伴って引数の配列サイズの解釈に変化が生じ，最適化がない時とは異なる手続が呼ばれているのではないかと予想しています．

```Fortran
      module function moment_mask_2_csp_csp(x, order, dim, center, mask) result(res)
        complex(sp), intent(in) :: x(:,:)
        integer, intent(in) :: order
        integer, intent(in) :: dim
        complex(sp), intent(in), optional :: center(merge(size(x, 1), size(x, 2), mask=1<dim))
        logical, intent(in) :: mask(:,:)
        complex(sp) :: res(merge(size(x, 1), size(x, 2), mask=1<dim))

        integer :: i
        real(sp) :: n(merge(size(x, 1), size(x, 2), mask=1<dim))
        complex(sp), allocatable :: mean_(:)
```

# まとめ
Fortranの利便性を改善するために開発されているFortran Package ManagerとFortran Standard Libraryは，SX-Aurora TSUBASAでも利用ができることが判りました．
また，stdlibのビルドで問題が生じることとその回避策を示しました．現状では，複素数型配列のモーメントを計算する処理以外に問題は見つかっていません．CPUと比較してどのくらい高速になっているかなどを調べるのも面白いと思います．