モダンになったFlang(llvm19.1.5)を使ってみる

# やりたいこと
- `flang-new` のインストール.
- いくつかのファイルをコンパイルしてみる.

# Fortran コンパイラ
たくさんあります.
Fortran コンパイラとして有名なのが `gfortran` と `ifx`(旧`ifort`) でしょう.
`gfortran` は GNU Compiler Collection のコンパイラです.
`ifx` は intel fortran compiler のコンパイラです. 今は(商用でなければ?)無料で使えるはずです.
他にも `lfortran` や PGI/NVIDIA, NAG, Cray, NEC などのコンパイラが[あるらしい](https://fortran-lang.org/en/compilers/)です.
また, [classic flang](https://github.com/flang-compiler/flang) もあり, これは `pgfortran` からの派生らしいです.

今回ビルドする `flang` は llvm-project によるコンパイラです.
[f18](https://github.com/flang-compiler/f18) プロジェクトから始まって, 前の flang の欠陥に対処して, LLVM project に[受け入れられたようです](https://github.com/llvm/llvm-project/tree/main/flang#readme).
つまり
```text
pgfortran
   ↓       ↘
   ↓         classic flang
   ↓           ↓ (修正)
   ↓         f18 project
   ↓ (買収)     ↓
nvfortran    flang-new(llvm)
```
ってことですかね?
特徴は... 外からだとLLVMを使うことしか分からないですね.
色々試してみたいです.

# [LLVM](https://llvm.org/)
ろーれべるなんちゃらのあたまもじ... ではないらしい.
C コンパイラの Clang が有名ですね.

## [Flang](https://flang.llvm.org/docs/)
Fortran のコンパイラ.
LLVM の力を備えているらしい.

# ビルドする
## 環境
```bash
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 22.04.5 LTS
Release:	22.04
Codename:	jammy
$ gfortran --version
GNU Fortran (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ ifx --version
ifx (IFX) 2025.0.1 20241113
Copyright (C) 1985-2024 Intel Corporation. All rights reserved.
$ nvfortran --version

nvfortran 23.9-0 64-bit target on x86-64 Linux -tp ivybridge 
NVIDIA Compilers and Tools
Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
$ flang --version
flang version 16.0.4 (https://github.com/flang-compiler/classic-flang-llvm-project.git 8d805dfcb66161beee81045a8ecd89051e919241)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/osd-yum/.local/bin
```

```bash
$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.6Gi       4.9Gi       307Mi       1.1Gi       2.4Gi       1.3Gi
Swap:           15Gi       5.1Gi        10Gi
```
メモリ8GBは今の時代は足りないので, もっと欲しいです.

## ビルド
:::note info
頻繁にバージョンが更新されるため, https://github.com/llvm/llvm-project/releases を確認しましょう.
:::


ビルド方法は https://github.com/llvm/llvm-project/blob/main/flang/docs/GettingStarted.md です.
`cmake` と `ninja` が必要です.
```bash
# Ubuntu なら
$ sudo apt install build-essential cmake ninja-build
```
llvm-project は 3GB くらいあるので, ストレージの容量には気をつけましょう.
また, 並列ビルドする際には, メモリが貧弱だと `ninja` が落ちます.
8GBメモリでは落ちるので, `ninja -j 1` で1コアのビルドにしています.
```bash
$ wget https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.5/llvm-19.1.5.src.tar.xz
$ tar xvf llvm-19.1.5.src.tar.xz
$ cd llvm-19.1.5.src
$ mkdir _build
$ cd _build
$ sudo mkdir -p /opt/flang
$ INSTALLDIR="/opt/flang"
$ cmake \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=$INSTALLDIR \
  -DCMAKE_CXX_STANDARD=17 \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
  -DCMAKE_CXX_LINK_FLAGS="-Wl,-rpath,$LD_LIBRARY_PATH" \
  -DFLANG_ENABLE_WERROR=OFF \ # OFFにしないと `-Werror=dangling-reference` に引っ掛かってしまう...
  -DLLVM_ENABLE_ASSERTIONS=ON \
  -DLLVM_TARGETS_TO_BUILD=host \
  -DLLVM_LIT_ARGS=-v \
  -DLLVM_ENABLE_PROJECTS="clang;mlir;flang;openmp" \
  -DLLVM_ENABLE_RUNTIMES="compiler-rt" \
  ../llvm
$ ninja -j 1 # 長い
$ ninja flang-check -j 1 # しなくても良い?
$ sudo ninja install -j 1
```
- `cmake` の[オプション](https://github.com/llvm/llvm-project/blob/main/flang/docs/GettingStarted.md)は一部変えてあります.
- 何故か `-DFLANG_ENABLE_WERROR=ON` にすると `-Werror=dangling-reference` に引っかかりました.
  + まあ, WARNING なので(使用者は)無視しても良いでしょう.
- メモリに余裕があるなら `ninja -j (CPUの数を表す数字)` に.
  + 並列にビルドされ, 速くなります.
  + 並列ビルドしない場合は14時間くらい掛かりました...
- この設定なら, `/opt/flang/bin` に色々インストールされます.

## バイナリをダウンロード

- ビルドしない場合はバイナリを取りましょう.
```bash
$ wget https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.5/LLVM-19.1.5-Linux-X64.tar.xz
$ tar xvf LLVM-19.1.5-Linux-X64.tar.xz
$ sudo mkdir -p /opt/flang
$ sudo mv LLVM-19.1.5-Linux-X64 /opt/flang
$ export PATH="/opt/flang/LLVM-19.1.5-Linux-X64/bin:$PATH"
```
- この設定では, `/opt/flang/LLVM-19.1.5-Linux-X64/bin` に色々インストールされます.

## (追記) apt からインストール
([Louise](https://zenn.dev/miosky521) さんより情報をいただきました. ありがとうございます)
[こちら](https://qiita.com/yoshiyasu1111/items/3815af53a25b3b1df0f4)のQiita の記事によると, [llvm の公式リポジトリ](https://apt.llvm.org/) から `apt` でインストールできるそうです.

# flang を試してみる
以下, ビルドしたものではなく, バイナリの `flang-new` でコンパイルしています.

## 出力してみた
```test.f90
program test
  use, intrinsic :: iso_fortran_env
  implicit none
  integer(int64), parameter :: x = 10_int64 ** 10
  print*, x
  write(output_unit, '(a)') "Hello, World!"
end program
```
```bash
$ export PATH="/opt/flang/bin:$PATH"
$ flang-new test.f90 && ./a.out
 10000000000
Hello, World!
$ gfortran test.f90 && ./a.out
          10000000000
Hello, World!
```
うん? 何か `gfortran` と違いますね.
`gfortran` では整数のバイト数に依存する空白が出ます.

```test2.f90
program hi
    use, intrinsic :: iso_fortran_env
    implicit none
    integer(int32) :: x
    integer(int64) :: x2
    x = 1; x2 = 1_int64
    print*, x, x2
    print*, x2, x
end program
```

```bash
$ flang-new test2.f90 && ./a.out
 1 1
 1 1
$ gfortran test2.f90 && ./a.out
           1                    1
                    1           1
$ ifx test2.f90 && ./a.out
           1                     1
                     1           1
$ nvfortran test2.f90 && ./a.out
            1                        1
                        1            1
$ flang test2.f90 && ./a.out
            1                        1
                        1            1
```

`flang-new` では整数のバイト数に依存する空白がないようです.
`classic flang` と `nvfortran` と異なるようです.

```test3.f90
program hi
    use, intrinsic :: iso_fortran_env
    implicit none
    integer(int64) :: x
    x = 1000000_int64
    print'(i0)', x
    print'(i4)', x
end program
```

```bash
$ flang-new test3.f90 && ./a.out
1000000
****
$ gfortran test3.f90 && ./a.out
1000000
****
$ ifx test3.f90 && ./a.out
1000000
****
$ nvfortran test3.f90 && ./a.out
1000000
****
$ flang test3.f90 && ./a.out
1000000
****
```

フォーマットを指定した場合はどれも同じです.

## 以前書いたやつをコンパイルしてみる.
```bash
$ find . -name '*f90' | xargs -I{} bash -c 'gfortran {} 2>/dev/null && echo {}' > success_compile.txt
$ wc -l success_compile.txt 
4427 success_compile.txt
$ cat success_compile.txt | xargs -I{} bash -c 'flang-new {} 2>> flang-errors.txt || echo {}' > failed_flang-new.txt
$ wc -l failed_flang-new.txt 
34 failed_flang-new.txt
```
`gfortran` でコンパイルできた 4427個(半分くらいは重複している)の `.f90` を `flang-new` でコンパイルすると34個はコンパイルに失敗しているようです.
といっても, ほとんどが以下の関数の引数の順番でエラーが起こっているようです.

### 関数の引数

<details open><summary> test_arg1.f90 </summary>

```test_arg1.f90
module test
  use, intrinsic :: iso_fortran_env
  implicit none
contains
  integer function f(arr, n) result(res)
    integer(int32), intent(in) :: arr(n)
    integer(int32), intent(in) :: n
    res = 0
  end function
end module
```
</details>

```bash
$ flang-new -c test_arg1.f90
$ gfortran -c test_arg1.f90
$ ifx -c test_arg1.f90
$ nvfortran -c test_arg1.f90
$ flang -c test_arg1.f90
```

関数の引数に配列があり, その配列のサイズを引数 `n` で決めている場合.
どのコンパイラでもうまくいきます.
... `n` の宣言が下にあっても...

<details open><summary> test_arg2.f90 </summary>

```test_arg2.f90
module test
  use, intrinsic :: iso_fortran_env
  implicit none
contains
  integer function f(arr, n, u) result(res)
    integer(int32), intent(in) :: arr(n, u)
    integer(int32), intent(in) :: n, u
    res = 0
  end function
end module
```

</details>

```bash
$ flang-new -c test_arg2.f90 
error: Semantic errors in test_arg2.f90
./test_arg2.f90:5:30: error: No explicit type declared for 'u'
    integer function f(arr, n, u) result(res)
                               ^
$ gfortran -c test_arg2.f90 
$ ifx -c test_arg2.f90 
test_arg2.f90(7): error #6415: This name cannot be assigned this data type because it conflicts with prior uses of the name.   [U]
    integer(int32), intent(in) :: n, u
-------------------------------------^
compilation aborted for test_arg2.f90 (code 1)
$ nvfortran -c test_arg2.f90
$ flang -c test_arg2.f90 
```

2次元配列のサイズを下に書くと, `flang-new` と `ifx` はエラーとなってしまいます.

<details open><summary> test_arg3.f90 </summary>

```test_arg3.f90
module test
  use, intrinsic :: iso_fortran_env
  implicit none
contains
  integer function f(arr, n, u) result(res)
    integer(int32), intent(in) :: u
    integer(int32), intent(in) :: arr(n, u)
    integer(int32), intent(in) :: n
    res = 0
  end function
end module
```

</details>

```bash
$ flang-new -c test_arg3.f90 
$ gfortran -c test_arg3.f90 
$ ifx -c test_arg3.f90 
$ nvfortran -c test_arg3.f90 
$ flang -c test_arg3.f90 
```

配列の上で宣言すればエラーは出ません...
Fortran の仕様まで踏み込まないと分からないかもしれません...

### `pure` な関数の外の配列の要素を `associate` できない
<details open><summary> test_associate.f90 </summary>

```test_associate.f90
program hi
  use, intrinsic :: iso_fortran_env
  implicit none
  integer(int32), allocatable :: g(:)
contains
  pure recursive subroutine dfs()
    associate(nv => g(1))
    end associate
  end subroutine dfs
end program hi
```
</details>

```bash
$ flang-new test_associate2.f90 
error: Semantic errors in test_associate2.f90
./test_associate2.f90:7:15: error: A pure subprogram may not have a variable with the SAVE attribute
      associate(nv => g(1))
                ^^
$ gfortran test_associate2.f90 
```

これが正しい挙動かどうかは分かりませんが, `save` 属性持っているわけではないので, エラーメッセージがおかしいかもしれません.


# flang で fortran-stdlib(v0.7.0) をビルドしてみよう...と思いましたができません...
試しに [fortran-stdlib](https://github.com/fortran-lang/stdlib)の[v0.7.0](https://github.com/fortran-lang/stdlib/releases/tag/v0.7.0) をビルドしてみます.
以下の方法では `python3-joblib` と `fypp` と `fpm` が必要です.
(`cmake` を使う方法もあります.)

```bash
$ wget https://github.com/fortran-lang/stdlib/archive/refs/tags/v0.7.0.tar.gz
$ tar xvf v0.7.0.tar.gz
$ cd stdlib-0.7.0
$ python3 config/fypp_deployment.py
$ fpm build --compiler="flang-new" --profile release
# (たくさん出力される)
./././src/temp/stdlib_specialfunctions_gamma.f90:384:49: error: Value of named constant 'pi' (acos(-1._16)) cannot be computed as a constant value
                               one = 1.0_qp, pi = acos(- one), sqpi = sqrt(pi)
                                                  ^^^^^^^^^^^
# (略)
```

どうやら, 4倍精度実数を扱うには[ビルド時に指定](https://flang.llvm.org/docs/Real16MathSupport.html)する必要があるようです.
また, `flang-new` (19.1.5) の時点で `assumed-rank-array` (select rank) に対応していないのでいくつかのファイルはコンパイルできません.
ビルドではなく, [`fpm.toml` の `dependencies` に書く方法](https://fpm.fortran-lang.org/ja/spec/metapackages.html)を使った方が良いかもしれません.

# まとめ
モダンになった `flang-new` を使ってみました.
`gfortran` とは仕様が違いそうですが, どちらでもコンパイルできるようにしておくと良いかもしれません.
今回は `flang-new` の利点が見えませんでしたが, いつか, LLVM の力を見られるようなことをしてみたいですね.

# 参考
## flang

https://fortran-lang.org/en/compilers/

https://github.com/flang-compiler/flang

https://github.com/flang-compiler/f18

https://github.com/llvm/llvm-project/tree/main/flang#readme

https://flang.llvm.org/docs/Real16MathSupport.html


https://flang.llvm.org/docs/

https://github.com/llvm/llvm-project/releases

https://github.com/llvm/llvm-project/blob/main/flang/docs/GettingStarted.md

https://github.com/llvm/llvm-project/blob/main/flang/docs/GettingStarted.md

## LLVM

https://llvm.org/

## fortran-stdlib

https://github.com/fortran-lang/stdlib

https://github.com/fortran-lang/stdlib/releases/tag/v0.7.0

https://fpm.fortran-lang.org/ja/spec/metapackages.html