f2c において len_trim を組み込み関数扱いにするための改造

2022/07/02

FORTRAN77 を C/C++ に変換する f2c( Netlib )は、GCC(g77, gortran)と異なり len_trim が組み込み扱いになっていなくて、文字列を読み書きするときや C/C++ との混合言語プログラミングが必要な場合には非常に不便です。
代替方法として len_trim 関数の代替品 をすでに発表していますが、プログラミング安全性向上のための implicit none 宣言下ではいちいち external 宣言と型宣言を追記しないと使えず、何かと面倒です。
このため根本解決として、len_trim 自体を len 関数と同様の組み込み扱いにしてしまおう、という改造方法です。


ソースコード intr.c 変更差分

diff -u -w intr.org.c intr.c
--- intr.org.c  Tue Aug 21 13:01:42 2001
+++ intr.c      Sat Jul 02 13:59:19 2022
@@ -44,7 +44,7 @@

 LOCAL struct Intrblock
        {
-       char intrfname[8];
+       char intrfname[12];
        struct Intrbits intrval;
        } intrtab[ ] =
 {
@@ -223,6 +223,7 @@
 "ishft",       { INTRBGEN, TYUNKNOWN, OPBITSH,  0, 0, 2 },
 "ishftc",      { INTRBGEN, TYUNKNOWN, OPBITSHC, 0, 0, 2 },

+"len_trim",    { INTRSPEC, TYLONG, 95 },
 "" };


@@ -232,7 +233,7 @@
                                   this type */
        char rtype;             /* Result type */
        char nargs;             /* Number of arguments */
-       char spxname[8];        /* Name of the function in Fortran */
+       char spxname[12];       /* Name of the function in Fortran */
        char othername;         /* index into callbyvalue table */
        } spectab[ ] =
 {
@@ -366,6 +367,9 @@

        { TYREAL,TYREAL,2,"r_scal" },
        { TYDREAL,TYDREAL,2,"d_scal" },
+
+       { TYCHAR,TYSHORT,1,"h_len_trim" },
+       { TYCHAR,TYLONG,1,"i_len_trim" },

        { 0 }
 } ;

解説

spxname のサイズを 8 から 12 に拡大しているのは、 i_len_trim という文字列が 10 文字であり、もとの 8 だと入りきらないからである。この先頭の i_ が何であるかは、あとのほうで解説する。
intrfname も同様である。
それぞれ 11 と 9 でも足りるのだが、アラインメントを考えれば 12 にするのが合理的か。

次に、

LOCAL struct Intrblock
	{
	char intrfname[12];
	struct Intrbits intrval;
	} intrtab[ ] =
{
	(FORTRAN77 組み込み関数名称とその種別分類・戻り値型・引数型その他情報)
}
LOCAL struct Specblock
	{
	char atype;		/* Argument type; every arg must have
				   this type */
	char rtype;		/* Result type */
	char nargs;		/* Number of arguments */
	char spxname[12];	/* Name of the function in Fortran */
	char othername;		/* index into callbyvalue table */
	} spectab[ ] =
{
	(FORTRAN77 組み込み関数に対応する libf2c 関数の呼び出し方リスト)
}
がペアになることで、intrtab に示された FORTRAN77 の戻り値型暗黙と、spectab に示された C の戻り値型明示が、翻訳されている。intrtabspectab はおそらく intrinsic table、spec table の略なのだろう。

ここで FORTRAN77 の組み込み関数である len に着目すると、intr.c 122 行目 intrtab の初期化リスト中にある

"len", { INTRSPEC, TYLONG, 27 },
という行は、spectab の 27 番目から当該翻訳単位が入っているという意味のようであり、spectab の初期化リストをたどっていくと実際にそうなっている。
ただし intrtab の側には TYLONG しか記述がないが、intr.c 275 行目 spectab の側では次のように面白いことになっている。
{ TYCHAR,TYSHORT,1,"h_len" },
{ TYCHAR,TYLONG,1,"i_len" },
spectab の 27 番目は TYSHORT、28 番目は TYLONG を返すと定義されていて、システムに合った戻り値の型を検索してそれに合った翻訳を行うために、intrtab 側において見つかった番号に対し、その番号そのままの翻訳か、それとも戻り値型に合わせてひとつ次の番号のものを選ぶかを変えている。intr.c 989 行目付近 intraddr(Namep np) 内の case INTRSPEC: から始まるところにそれらしき選択をしている箇所がある。
だから、intrtab 側において 28 は飛び番とされ、len の次にある index には 29 という番号が与えられているのだ(まあ、CPU もメモリーも乏しかった古(いにしえ)の流儀としか言いようがないが、これはこれでまあそういうものだと思うことにする)。

これらからとにかくわかることは、ソースコードの可読性を気にして既存の組み込み関数定義の途中に今回の len_trim を挟み込むのはけっこう面倒であり、末尾に追加するのが簡単、ということである。

intrtab の初期化リストをたどっていくと、既存の組み込み関数定義のうち、種別 INTRSPEC が指定されている最後のものは scal であり 93 番を与えている(実際には fpscal と書かれているが頭の fp はおそらく翻訳における型候補を示すものだろう)。一方、spectab 側では 93 番目に r_scal が、94 番目に d_scal が存在している(これらは realdoublereal の使い分けである)。ということは、最初の空き番号が 95 番ということになる。

ということで結論は、len の場合に倣って、intrtab 側では len_trim に 95 番を与え、spectab 側では 95 番目を戻り値 short inth_len_trim、96 番目を戻り値 long inti_len_trim として定義すればよいことになる。なお、これらの番号自体は f2c の実行ファイルで閉じたものとなり、libf2c 側への波及はない。

本当は TYLONG ではなく TYFTNLEN を返したいところであるが、オリジナルの lenTYLONG なので、目くじらは立てないことにする。f2c 実行時に -i2 オプションを与えない限りにおいて ftnlenintegerlong int になるので一応問題はない。C言語から見た理想は size_t なのだがこれは FORTRAN77 では逆立ちしても扱えない型なので、妥協するしかない。

以上でソースコードの変更に関する解説は終わりだが、これを行うと intr.c のチェックサムが変わってしまうので、そのままでは make が失敗する。ただし、そのときにチェックサムの比較が表示されるので、xsum0.out に書かれている比較用チェックサムとバイト数をその値に書き換え、make を実行しなおせば、ビルドが通るようになる。( nmake -f makefile.vc なお makefile.vc 自体も CFLAGS = -Ot1 -nologo -DNO_LONG_LONGCFLAGS = -O2 -nologo に)

これで implicit none 宣言をしてあっても len_trim をしかるべき型の組み込み関数として使用することが可能となる。

FORTRAN77 改造後の f2c で翻訳した C
(f2c が出す定型冒頭コメントは省略)
      implicit none
      integer i
      i = len_trim("abcde")
      end
#include "f2c.h"

/* Main program */ int MAIN__(void)
{
    /* Builtin functions */
    integer i_len_trim(char *, ftnlen);

    /* Local variables */
    static integer i__;

    i__ = i_len_trim("abcde", (ftnlen)5);
    return 0;
} /* MAIN__ */

あとは、short inth_len_trimlong inti_len_trim を用意すればよい。

#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include "f2c.h"

integer i_len_trim(const char *s, ftnlen slen)
{
	const char *wp;
	
	if(slen < 0) slen=0;
	
	for(
		wp = s + (size_t)slen -1;
		wp >= s && (*wp == '\x20' || *wp == '\0');
		--wp
	){
		;
	}
	
	if(wp >= s){
		return (integer)(wp - s + 1);
	}
	return 0;
}

shortint h_len_trim(const char *s, ftnlen slen)
{
	assert(slen < SHRT_MAX);
	if(slen > SHRT_MAX - 1){
		abort();
	}
	return (shortint)i_len_trim(s, slen);
}
上記はエッセンスだけを書いているが、これに KR_headers__cplusplus への対応を加えたものを i_len_trim.c として置いておきます。
これを libf2c に追加してもいいのかもしれないが、netlib 公式配布ベースの libf2c の標準から外れたローカル実装は「おま環」になるため、あえて単独の obj のままにしておくほうが無難かもしれない。
long int 版は len_trim 関数の代替品len_trim.c とほとんど同じ。相違点は、戻り値の型が違うこと、関数名が違うこと(戻り値型の接頭辞)、関数名末尾のアンダースコアが無いこと、である。
short int 版も用意しているが、出番は事実上ないだろう。f2c 実行時に -i2 オプションを与える必要性は、余程のことがない限り 2020 年代には考えられないからだ。


略歴


トップページ / 計算機関係の雑多なこと / f2c において len_trim を組み込み関数扱いにするための改造
このサイトについて・著者連絡先