RokkoFortranBindingTips - t-sakashita/rokko GitHub Wiki

Fortranバインディング開発のメモ

offset(添字が0か1から始まるか)の問題

  • 内部にoffset_変数を持たせる必要はあるか?

Fortranの文字列

  • Cのように、一文字(char)か2文字(char*)以上かという区別はない。
  • Fortran2003からサポートされているdynamic allocatedを使って、文字列を宣言する。
  • これを使うと、trimをしなくても済む。
  • 文字列変数の使い回しも容易?
  character(len=:), allocatable :: str

通常のFortran文字列をC言語に渡す場合

trimした後にc_null_charを追加する必要がある。

  character(*), intent(in) :: key
  character(*), intent(in) :: val
  call rokko_parameters_set_string_c (params, trim(key)//c_null_char, trim(val)//c_null_char)

Fortranの関数の引数

  • 入力変数:valueを付けて渡す
  • 出力変数:valueを付けることはできない。

CとFortran間の文字列の関数の引数

  • 入力変数:ポインタ。Fortran側ではvalueを付ける。
  • 出力変数:2重ポインタにする。2重ポインタもiso_c_bindingtype(c_ptr)で受け取れる。それを受け取れるようなCラッパーを書く。
void rokko_split_solver_name_f(char* str, char** library_ptr, char** routine_ptr) {
  std::string tmp_library, tmp_routine;
  rokko::split_solver_name(str, tmp_library, tmp_routine);
  *library_ptr = copy_string(tmp_library);
  *routine_ptr = copy_string(tmp_routine);
}

戻り値が文字列のC関数をFortranから呼び出す

subroutine rokko_split_solver_name_f (str, library, routine) &
          bind(c,name='rokko_split_solver_name_f')
       use iso_c_binding
       implicit none
       character(c_char), intent(in) :: str(*)
       type(c_ptr), intent(out) :: library
       type(c_ptr), intent(out) :: routine
end subroutine rokko_split_solver_name_f
char* rokko_parameters_get_string(struct rokko_parameters* params, const char* key) {
  std::string tmp = static_cast<rokko::parameters*>(params->ptr)->get_string(key);
  char* p = copy_string(tmp);
  return p;
}
type(c_ptr) function rokko_parameters_get_string_c (params, key) &
          bind(c,name='rokko_parameters_get_string')
       use iso_c_binding
       import rokko_parameters
       implicit none
       type(rokko_parameters), intent(in) :: params
       character(c_char) :: key(*)
end function rokko_parameters_get_string_c
subroutine rokko_parameters_get_string (params, key, val)
    use iso_c_binding
    implicit none
    type(rokko_parameters), intent(in) :: params
    character(*), intent(in) :: key
    character(len=:), allocatable, intent(out) :: val
    type(c_ptr) :: ptr
    character, pointer, dimension(:) :: tmp_array
    character*255 :: tmp
    integer :: i
    integer(c_int) :: n
    n = rokko_parameters_get_key_size_c (params, trim(key)//c_null_char)
    ptr = rokko_parameters_get_string_c (params, trim(key)//c_null_char)
    call c_f_pointer(ptr, tmp_array, (/n/) )
    do i=1, n
       tmp(i:i) = tmp_array(i)
    enddo
    call free_c(ptr)
    val = trim(tmp(1:n))  ! automatically allocating suitable size
  end subroutine rokko_parameters_get_string
  • Rokko bindingで文字列をコピーし、最後にc_null_charを追加して、C bindingに渡す。
  • コピーし終わった後は、C側の文字列をfreeする。

文字列からなる配列の定義

type(string)をstring.F90で定義。 type(string)の割り付け配列を用いれば、配列のサイズを動的に指定できる。(使用例:parameterskeysや、登録されたソルバ名)

  type(string), allocatable :: names(:)

以下、全ての要素の文字列長が同じ配列。メモリ使用が非効率であらかじめ文字列長を決めておく必要があるが、古いFortranのバージョンでも使用可能であるため、対応したい。

  character(n), dimension(m) :: array

以下の方式は検討したが、使用していない。

  type array_strings
     type(string), allocatable :: string(:)
     integer :: size
  end type array_strings

diagonalize関数のオーバーロード

Fortranでは、function(戻り値ありの関数)において、戻り値を受け取らないことはできない。 params_outは、ユーザが必要なかったら、読み捨てたい。 そこで、params_outは受け渡しを省略できる引数とする。

ただし、実装には、optionalキーワードを使わない。 optionalキーワードを使うと、if(present(~))で分岐するルーチンを書かなければならないし、わずかだがオーバーヘッドがある。 引数と関数名の違うものを用意して、総称名で参照できるようにする。

C++において、引数をオプションにする代わりに、オーバーロードするのと似ている。

params_outが引数にないときに、ダミー変数を与えるのも気持ち悪い。 generic interfaceは、全部がsubroutineか全部がfunctionでなければならない:

Error: In generic interface 'kaijo' at (1) procedures must be either all SUBROUTINEs or all FUNCTIONs

params_outを指定するときは、params(入力パラメータ)は省略できない事に注意。 Fortran binding用のC wrapper関数は、最後に_fを付けて、C binding用のC wrapper関数と住み分ける。 void rokko_serial_dense_ev_diagonalize_f _f付きの関数もC bindingから呼び出せる。(params_outを返すC bindingがあるので、その必要はないが。) Fortranから呼ばれるためのものなので、このC wrapperの入力引数は、ポインタ渡しで良い。

Fortran関数での行列引数

rokko_distributed_matrixrokko_mapping_bcrokko_localized_matrixは構造体で、Fortran bindingでは以下のように定義されている。

  type, bind(c) :: rokko_mapping_bc
     type(c_ptr) ptr
     integer(c_int) major
  end type rokko_mapping_bc
  
  type, bind(c) :: rokko_distributed_matrix
     type(c_ptr) ptr
     integer(c_int) major
  end type rokko_distributed_matrix
  • 構造体ごとコピーする必要があるため、Fortranでの入力変数としてはvalue, intent(in)属性を指定する必要あり。
  • 行列の中身をいじる場合も、書き込む場合も、value, intent(in)とする。
  interface
     subroutine rokko_frank_matrix_generate_distributed_matrix(matrix) bind(c)
       use iso_c_binding
       import rokko_distributed_matrix
       implicit none
       type(rokko_distributed_matrix), value, intent(in) :: matrix
     end subroutine rokko_frank_matrix_generate_distributed_matrix
  end interface

Fortran bindingで、wrapしたクラスの解放

各destruct関数では、deleteした後に、ヌルポインタ(C++11以降ではnullptr)を代入する。

Fortranの予約語

  • Fortranには予約語という概念はないので、dimvalueは元々あるキーワード名をユーザ変数として使っても問題ない。→見やすくするために、nvalを使う。

C言語とFortranの関数名の衝突防止

C言語とFortranで同名の関数がある場合、シンボル名がぶつかるのを防止するために、関数宣言でbind(c)を外す。(シンボル名のアンダースコアの数が異なるため、ぶつからなくなる。)

  subroutine rokko_distributed_matrix_generate_function(matrix, func_in) !bind(c)
    use iso_c_binding