Gauche:Windows/VC++:log:detail_2005

Gauche:Windows/VC++:log:detail_2005


VC++への移植、新たな試み

 はじめまして。千代郎といいます。  普段は、C++ がメインです。陰ながらGaucheを応援していますが、Schemeは、 入門書を読んだ程度で、まだまだ実用的に使いこなしているとはいえません。 しかし、C 関係のことなら参加できると思い、Gaucheの最新版 0.8.3 を VC++ で コンパイルできるようにしてみました。

goshをVC++でそのままコンパイルした例がないようなのと、VC++でコンパイル できるようにすれば、開発の輪が Windows 界の人にも、もっと広まっていくのでは、 というのが動機です。

目標:

 目標は、とりあえず、コンパイルできて動くこと。 POSIX関連は、ほとんど切り捨て。とにかく動けば、あとから いくらでもよくしていけるという方針。

場所:

 ここにおいています。→http://www.geocities.jp/chiyorou2005/Gauche/
すぐにビルド作業を開始できるアーカイブ全体と、Gauche-0.8.3.tgz との差分 を用意しました。

(差分については、手元では、patchを使ってうまく復元できません。差分元に ないフォルダが、うまく作成されないようです。ファイル内容自体は、差分に 含まれているので、なにか方法があるはずですが。UNIXのツールにはあまり詳しく ないので、どなたか詳しい方、お知恵を拝借できませんか?)

ビルド方法:

 詳しいビルド方法については、アーカイブを展開した後、win_utils という フォルダにある readme_1st.txt を読んでください。 最適化なしのデバッグ・ビルドになります。ソースを C++ としても ビルドできるように、変更を行いました(この理由については後日書きます)。

VC++の IDE を使わず、全て、コマンド・ラインで作業を行うので、 マイクロソフトがフリーで公開しているVC++コンパイラ
Microsoft Visual C++ Toolkit 2003
http://msdn.microsoft.com/visualc/vctoolkit2003/
があれば、コンパイルできると思います(試してはいません)。

現時点の結果:

テスト
○=pass, 成功
△=途中でエラー
×=gosh.exeがクラッシュ

<Gauche>
system.scm × selector.scm × www.scm × rfc.scm △ io.scm △ load.scm △ logger.scm △ process.scm △ file.scm △ vm-stack ○ test-arith ○ srfi.scm ○ primsyn.scm ○ error.scm ○ module.scm ○ macro.scm ○ number.scm ○ string.scm ○ keyword.scm ○ hash.scm ○ procedure.scm ○ dynwind.scm ○ object.scm ○ exception.scm ○ colseq.scm ○ regexp.scm ○ mb-chars ○ parseopt.scm ○ parameter.scm ○ hook.scm ○ text.scm ○ gettext.scm ○ util.scm ○ match.scm ○ io2.scm ○ version.scm ○ listener.scm ○ symcase.scm ○

<ext>
auxsys △ uvector ○ binary ○ vport ○ mt-random ○ charconv ○ digest ○ sxml ○ ※残りはまだ作業を行っていない。

おわりに:

このバージョンには、Gauche:Windows/VC++:log:detail_2005にあるように、すでに様々なバグが見つかっています。


移植作業メモ:

千代郎
 今回の作業中、問題になった点について、まとめてみたいと思います。
私の思い間違いの部分もあるかもしれません。遠慮なくコメントお願いします。
(Shiroさん、はじめまして。整理ありがとうございます。しかし、またごちゃごちゃしちゃいました。)

configure がらみの移植:

 cygwinを導入していない場合、Windows では、autotools や、シェル・スクリプトが動きません。そのため、configure で設定される項目の作成が問題になります。Windows環境では、Cヘッダーの有無について、大きな差異はなく、また、ディレクトリ構成(アプリケーションの置き場)がそれほど問題にならないので、configure の必要性を余り感じません。そこで、configure は、設定ファイルをもとに、テキスト中の @???@ という変数を置換するものだと割り切り、そのような処理をするスクリプトを、JavaScriptで用意しました。スクリプトは、Windows Scripting Host (WSH)でコマンドラインから起動することが出来ます。WSHは、最近のOSでは添付されており、また、フリーでダウンロードすることもできるため、標準環境であると考えました。ビルド作業に必須な gauche-config のようなスクリプトも、JavaScript 版を作成しました。ビルド環境は、VC++の nmake および、JavaScript, バッチ・ファイルの組み合わせで構築しました。

DLLのインポート/エクスポートの問題:

 動的にロードされるモジュールについて、UNIXでは、変数は単なる extern で、自動的に解決されるようですが、VC++では、厳密というか、融通が利かず、特にエクスポートは、明示的に指定してやる必要があります。もちろんすでに、SCM_EXTERN というマクロがその解決のために導入されていますが、ところどころで、うまく機能していない場所がありました。

・gauche.h において、
#define SCM_CLASS_DECL(klass) SCM_EXTERN ScmClass klass
とあるべきが、
#define SCM_CLASS_DECL(klass) extern ScmClass klass
となっており、Scm_...Classという変数名がエクスポートされず、libgauche.dll の外部から見えない。

・ext関連について。個々の .dll は、gauche.h 中の変数(libgauche.dllがエクスポートする)を参照します。こちらは、LIBGAUCHE_BODY および、SCM_EXTERN マクロの利用によって、うまくインポート/エクスポートが調整されています。しかし、自身のソース中の SCM_EXTERN(一つ前の項目↑の変更によって生じる。extern ScmClass klass としてあったのは、この問題を回避するためかもしれない。しかし、それはそれで別の問題を生むので、結局、このあと書くようにするしかないと思う) については、LIBGAUCHE_BODYが定義されないため、dllimport になってしまい、変数がエクスポートされません。また、extのDLLが、他のextのDLLから利用される場合(例:uvectorが、mt-randomから使われる)のような複雑な場合もあります。この解決のためには、.../ext の各 .dll ファイルについても、libgauche でしているように、LIBGAUCHE_BODY といういうようなマクロ変数によって、処理を切り替える必要がありました。

・extから利用される、libgauche中のhook関連の関数名が、extから見える場所で、SCM_EXTERN 宣言されていません。これらの宣言を、gauche.h の末尾に追加しました。

・ext では、Scm_Init_...というエントリーポイントが、外部から呼ばれます。しかし、この関数が SCM_EXTERN となっていません。これらの変数については、.defファイルというエクスポート名の設定ファイルで指定しました(もちろん、ソース中に SCM_EXTERN を埋め込むことでも対処できる。)

・builtin-symsで、Scm_BuiltinSymbols が、SCM_EXTERNされたものとして認識されていない。なぜなら、builtin-syms.cに、builtin-syms.hがインクルードされていないから。

ネストした構造体の問題:

 VC++では、入れ子のstructの内部structは、外部スコープから見えなくなります。そこで、内部の構造体を、先に、個別に宣言してやる必要がありました。たとえば、gauche.hの、ScmRegMatchSub などです。

ext の移植で、C++としてコンパイルする必要があった理由:

 libgauche, gosh については、C としても、C++ としてもコンパイルできます(「C++として」というのは、ソースを強制的にC++ソースと見なしコンパイルすることです)。しかし、../ext 以下のライブラリについては、C++ としてしか、コンパイルできませんでした。理由は、以下の通りです。

 各モジュールでは、.stub を変換したソース中で、SCM_DEFINE_... 系のマクロが構造体をstaticに定義することが頻出します。この構造体の初期化リスト中に、{ &Scm_...Class, ... } という項目があります。ここで、Scm_...Class は、libgauche.dll からインポートされる変数です。この場合、&Scm_...Class は、定数であるはずですが、VC++でコンパイルすると、「定数以外を構造体の初期化に用いている」というエラーが出て、なぜかコンパイルできません。しかし、C++として(-TPというオプションをつけて)コンパイルすると、コンパイルすることが出来ます。これは、VC++固有の問題だと思われます。

この状況を再現する短い例としては、以下のソースがあります。
> cl /c test.c
> cl /TP /c test.c
の両者のコンパイル結果を比較してみてください。

test.c

extern __declspec(dllimport) int outer_data; //DLLインポートする外部変数
extern int outer_data2; //ただの外部変数

typedef struct
{
        int *a;
        int b;
}str;


static str a = { &outer_data, 0 }; //Cとしてコンパイルすると、ここでエラー
static str b = { &outer_data2, 0 }; //こちらは、C or C++どちらでもOK


int main(int argc, char *argv[])
{
        return 0;
}

既存のもののお世話になり、うまく解決できた点:

・int64.h関連は、VC++ の __int64, __int32 を利用した。
・/src/gauche/mingw-compat.h をほとんどまる写しして、vc-compat.hを作った。
・getoptは、GNUのものを使った。

C++予約語の問題:

 C++としてコンパイルする場合に、ソース中の、new (class.cなど)および template (macro.hなど)という変数名が、C++の予約語であるため、問題が生じます。そこで、今回は、コンパイル・オプションで、この二つを -Dnew = new_ のように置換して凌いでいます(Cとしてコンパイルする場合には必要ありません)。

シンボリック・リンクの問題:

 extライブラリのテスト時に、gosh xlink を用いて、シンボリック・リンクが適切な場所に作られます。今回の build では、gosh xlink がうまく動かないため、スクリプトを利用したコピーで凌ぎました。Windowsでは、シンボリック・リンクが、Unixほどは簡単に使えないのがいやな点です(対応するジャンクションという機構は存在する)。よく似たハード・リンクや、ショートカット(例: cygwin)で代用する方法はあります。

ソースの不具合と思われたところ:

・config.hに指定があるにもかかわらず、
#include <unistd.h> を #ifdef HAVE_UNISTD_H
で囲っていない。

・core.c において、
ScmObj Scm_VMFinalizerRun(ScmVM *vm)
の戻り値が、ScmObjなのに、何もreturnしていない。

・static ScmObj ref_val(ScmObj ref) の戻り値指定(ScmObj)がない。→暗黙で、intになっている?

・error.c
Scm_Error("make-compound-condition: given non-condition object: %S", SCM_CAR(cp));
未初期化の変数 cp にアクセスしている。
→ SCM_CAR(conditions)の間違い?

・core.cで、
#define DEFSTR(n, s) \
static SCM_DEFINE_STRING_CONST(n, s, sizeof(s)-1, sizeof(s)-1)
として、ロード・パス関連の文字列を、
DEFSTR(libdir, GAUCHE_LIB_DIR);
のように定義しているが、日本語を含むパスを渡した場合、sizeof(s)-1 != 文字数(s) となり、*load-path* がおかしくなる。

・.../ext/uvector/uvlib.stub.tmpl
return Scm_MakeString((char *)(SCM_UVECTOR_ELEMENTS(v)+start), ...
→return Scm_MakeString((char *)SCM_UVECTOR_ELEMENTS(v)+start, ...
SCM_UVECTOR_ELEMENTS(v)が void*型なので、ポインタ演算できない

・その他、不具合というわけではありませんが、C++としてコンパイルする場合には、型付けにうるさいようで、いくつかキャストを追加する必要がありました。たとえば、void * から、他のポインタ型への暗黙のキャストが許されないことが問題になりました(例:.../ext/uvector/uvlib.c.templ の、${SWAPB}((void*)d); で、(void*)がひっかかる)。


テスト失敗の原因追究

千代郎
 次のバージョンに向けて、地道にやってます。

rfc.scm:

エラー内容

test mime-parse-message (7 - # binary body), 

expects ("multipart/mixed" 0 ("application/binary" 0 
"\0\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\f\r
\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\0\r") ("application/binary" 1 "\r") ("application/binary" 2 "--") ("application/binary" 3 "--bb-"))

==> ERROR: GOT ("multipart/mixed" 0 ("application/binary" 0 
"\0\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\f\r
\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"))

 最初は、なかなか原因がわかりませんでした。分かってみれば簡単で、上記の出力を良く見ると、\x1a (=Ctrl-Z) で出力が切れています。この文字が EOF として認識されていました。これは、(C言語ストリームの)テキスト・モードでのファイル読み込み動作です。では、なぜ、テキスト・モードで読まれているのでしょうか? このテストでは、バイナリ・モードでの読み込みを期待しています。

GREPで調べた限り、Gauche中では、1つの例外(pipeの場合)を除いて、O_BINARY も、O_TEXT も、フラグを立てず、ただ単純に O_RDONLY としています。この場合、どちらのモードで開くのか、VC++の open() のリファレンスは言及していません (UNIXの場合は、O_BINARYになるのでしょうか?)。しかし、VC++に付属のCライブラリ・ソースを見ると、以下のようになっていることが分かりました。

(Microsoft CRTライブラリ open.c より引用)

        if ((oflag & _O_BINARY) == 0)
            if (oflag & _O_TEXT)
                fileflags |= FTEXT;
            else if (_fmode != _O_BINARY)   /* check default mode */
                fileflags |= FTEXT;

となっており、O_BINARY も、O_TEXT も設定していない場合には、_fmode という変数に従うことになっています。問題は、この _fmode がどのように設定されているかです。

(Microsoft CRTライブラリ textmode.c より引用)

/***
*txtmode.c - set global text mode flag
*
*       Copyright (c) 1989-2001, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Sets the global file mode to text.  This is the default.
*
*******************************************************************************/
int _fmode = 0;                 /* set text mode */

ということで、デフォルトは、テキスト・モードということになっているようです(実際の挙動にも矛盾しません)。興味深いことに、デフォルトを変更したカスタム・ライブラリを作成するためのソースも存在しました。

(Microsoft CRTライブラリ binmode.c より引用)

/***
*binmode.c - set global file mode to binary
*
*       Copyright (c) 1989-2001, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Sets the global file mode flag to binary.  Linking with this file
*       sets all files to be opened in binary mode.
*
*******************************************************************************/
/* set default file mode */
int _fmode = _O_BINARY;

 という訳で、ごちゃごちゃ書きましたが、要は、「Gaucheでは、open()のデフォルト読み込みモードをバイナリ・モードだと仮定しているようだが、VC++でのデフォルトはテキスト・モードである」というのが、テスト失敗の原因のようです。文化摩擦といった感じです。

以上の仮説を確かめるため、フラグがない場合に、O_BINARY を明示的に指定するように Gaucheに変更を加えたところ、rfc.scm テストを pass しました。
(実際のパッチでは、_fmode = O_BINARY; を含む vc-compat.c なりをリンクすることになると思います。一番単純なので。)

io.scm:

第1のエラー

test open-output-file :if-exists :append, expects cdefghij
==> ERROR: GOT #<error "unsupported file access mode 9 to open tmp2.o">

 ファイルのオープンモード・フラグのうち、アクセス・モード(O_RDONLY/O_WRONLY のビット)を取り出すための、O_ACCMODEマスクがあります。この定義が、 VC++ にはないので、(VC++の)fcntl.hを見て、 O_ACCMODE = 0x000F と定義したのが誤りでした。これでは、O_APPEND のビットまで含んでしまい、上記のエラーになります。正しくは、O_ACCMODE = 0x0003 でした。完全に私のポカミスです。

第2のエラー

         (sys-unlink "test.o")

 で、test.oディレクトリが削除されず、別の場所でエラーを生じます。VC++には、一応 unlink() がありますが、UNIXとは異なり、ディレクトリの削除には対応していないことが原因でした(ライブラリのソースファイル(unlink.c)を見て分かりました)。Windowsでは、ファイルとディレクトリの扱いが区別されるのが習慣だからだと思います。そこで、ディレクトリの削除にも対応した unlink_() を自作し、解決しました。

 ここまでの修正で、最後のテストの直前までパスします。しかし、最後のエラーは、まだ解決できず、困っています。最後の部分に、coding aware port 関連のテストが並んでいます。これらのテストのうち、末尾の3つを、(1)(2)(3)と呼ぶことにします。

現象は、(1)(2)までパスし、そこから処理が無限ループに入ったかのように、反応を返さなくなるというものです(Ctrl-C で中断する必要があります)。そして、(3)を取り除くと、テストは、pass で終了します。ここまでなら、(3)のテスト中になにかエラーが生じていると思いますが、
(1)(2)(2)(3)
と、(2)を重複させると、今度は、一度目の(2)の直後で反応が返らなくなります。さらに、
(1)(1)(2)(3)
と、(1)を重複させると、全部passしてしまいます。
ちなみに、上に書いた、rfc.scm での問題は、パッチをあてて解決されている状況下での話です。

このような、処理の順序に依存したエラーは、やっかいです。デバッガを使って、どこで処理がループに陥っているのか調べるために、デバッグ用のシンボル情報(.pdbファイル)をリンクしてビルドしました。すると、今度は、何ごともなかったかのように、全部passしてしまいました。うーむ。困った。しかし、デバッガによって、libgauche.dll中でループしていることは分かりました。

次の手として、printf作戦を展開し、coding aware port がらみの関数実行をトレースしてみると、

...
Scm_MakeCodingAwarePort
coding_filler
coding_port_recognize_encoding
look_for_encoding
coding_filler
coding_filler
ok ←ここで、テスト(2)をパス
coding_closer
無限ループ

となりました。なぜか、coding_closer()が10回ほど繰り返し呼ばれた後、coding_closer()中から呼んでいる、Scm_ClosePort()の PORT_SAFE_CALL 中でループするようです。なにか、ファイルのロックがらみで、変なことが起こっているのでしょうか(libgauche.dll, gc.lib ともに、シングル・スレッド用にビルドしているため、スレッドがらみの問題ではないはずです)。PORT_SAFE_CALLをトレースしようとして、その中にprintfを追加すると、また、症状がなくなってしまい、なかなか手ごわいです。ここでの疑問点は、

2005/04/17 10:53:29 PDT追加:デバッガによって、port.hにあるマクロ、PORT_LOCK(p, vm)中の、以下の部分でループしていることをつきとめました。

          while (p->lockOwner != NULL) {                        \
            if (p->lockOwner->state == SCM_VM_TERMINATED) {     \
              break;                                            \
            }                                                   \
            (void)SCM_INTERNAL_COND_WAIT(p->cv, p->mutex);      \
          }                                                     \

シングル・スレッド用のビルドの場合、SCM_INTERNAL_COND_WAITは、なにもしない空の関数なので、breakしない条件になると、抜け出せないのもうなずけます。問題は、なぜそのようになる場合があるか、です。

2005/04/21 06:05:48 PDT追加:ソースから理解したところによると、上記の場所は、Gauche VM によるポートのロックに関係します。複数のVMが存在する場合、1つのVMが、port->lockOwnerになると、他のVMは、(lockOnwerのVMが終了しない限り)そのポートをcloseできなくなります。上記の場所でループしているとうことは、あるVMがポートをロックしたまま、ずっと終了していないことを意味します。次に、ループ時の、p->lockOwnerのポイント先(= ScmVM構造体)をデバッガで見てやると、メモリ内容が、破壊されていました。例えば、stateの値は、enum宣言された変数であり、実際の値は0〜3のどれかになるはずですが、見当違いの値になっています。

つまり、このループ状態は、「実体を失っているVMが、lockOwnerに設定されており、他のVMが、stateがSCM_VM_TERMINATED(=3)になるのを永久に待っている」ということのようです。次の問題は、なぜ、内容の破壊されたVMが、lockOwnerに設定されているか?です。ソースを追った限り、lockOwnerの値は、NULLであるか、適切にアロケートされたVMのアドレスのどちらかになるように見えました。これ以上は、まだ分かりません。ループ状態が、微妙な環境の違いで生じたり生じなかったりすることも考え合わせると、ソースの静的な解析からは分からないような、なんらかのタイミングが関係した問題のように感じます。例えば、VM用にアロケートしたメモリが、GCによってなんらかの瞬間に解放されたりすることはあるんでしょうか?

2005/04/23 03:36:28 PDT追加:
 あと少しのところまで来たと思います!
まず、アドバイスに従い、ひとつひとつ確認していきました。

シングルスレッドでビルドしているか? Yes

lockOwnerの指しているのはsrc/vm.cのtheVMの値と同じか? No

Scm_GCSentinel(theVM, "vm") でwarningが出るか? No

 これらの結果から、VMの実体(theVM)が破壊されたのではないと示唆されました。では、どこのメモリが上書きされたのか? port->lockOwner(=vm)が破壊されていないなら、次の可能性として、portの内容が上書きされている可能性があります。そこで、printf作戦によって、portの値をトレースすると、異常が検出できました

ループ状態に陥る直前の、Scm_ClosePort()に渡される port の値は、0x00C13678 です。しかし、トレース記録中で、この port への参照(iport)を持つ coding aware port を見つけると、作成時には、port の値は、0x00C159A0 なのです。全てのポート作成が一度は経由するはずの、make_port()での port のアドレスを全て見ても、0x00C13678 という port は存在しません。従って、port の値が、どこかで上書きされたことが分かりました。portのアドレスは、coding_port_data構造体に格納されており、この構造体のメモリーは、Scm_MakeCodingAwarePort()中で、SCM_NEWによってアロケートされています。

data = SCM_NEW(coding_port_data);

この、data のポイントするメモリ領域が、上書きされている可能性があるわけです。次に、この領域が、GCによって解放されているか、Scm_GCSentinel(data, "coding_port_data"); を追加して確認すると、確かに解放されています。ただし、Scm_GCSentinalを追加すると、ループ状態が再現されなくなってしまいました。

ちょっとやり方を変えて、

if(data == (void*)0xC136C0)DebugBreak();

として、上書きされているのでは予想した data (= 0x00C136C0) がアロケートされた直後に、デバッガをアタッチしました。このとき、port の値は、確かに、0x00C159A0 という、作成時の正しい値になっていました。この後のどこかで、この値が上書きされるはずです。そこで、data 指すメモリ領域に、メモリ・ブレーク・ポイントを作成し、書き込みがなされた瞬間に実行を停止するようにして、デバッガ上で実行を開始すると、見事に、port の値が、0x00C13678 に書き換えられる瞬間に停止しました。停止した瞬間のコンソール出力は、

...
Scm_MakeCodingAwarePort
coding_filler
coding_port_recognize_encoding
look_for_encoding
coding_filler
coding_filler

となっており、無限ループの始まる少し前に対応しています。このとき、libgauche.dll 中の 0x10069901 の命令を実行中でした。これは、関数 0x1006989E 内の命令であり、この関数は、コンパイラに出力させた map 情報とつきあわせると、

0001:0006889e       _GC_reclaim_clear          1006989e f   gc:reclaim.obj

に相当します。従って、まとめると、今回の症状は、「coding aware port の持つ、coding_port_data 構造体のメモリ領域が、coding aware port の存命中に、GC_reclaim_clear() によって上書きされるため、coding_closer()が無限ループに陥る」ということだと分かりました。GC_reclaim_clear()は、ソースのコメントによると、使用中のマークがついていないメモリ領域を、再利用する関数です。つまり、今回上書きされて困っているメモリ領域は、GCされたのだ、ということのようです。

2005/04/23 10:17:00 PDT追加: そろそろ、表題を「呪われたポートの謎」とでもしたくなってきました...(:-<
 ファイナライザというのは、これまで知りませんでした。GCシステムから呼ばれるデストラクタ(C++の)のようなものですね。ポートのファイナライザを以下のようにしてみました。

static void port_finalize(ScmObj obj, void* data)
{
    Scm_Warn("%S\n", obj);
    port_cleanup(SCM_PORT(obj));
}

これによって、ポートのGCをモニターすると、以下のように表示されました。

WARNING: #<iport (input string port) 00C15070>
WARNING: #<iport (input string port) 00C15150>
WARNING: #<iport (input string port) 00C36CB0>
WARNING: #<iport (output string port) 00C15A10>
ループ状態

最後に表示されているのが、問題の coding aware port であり、この port の GC 時に、ループ状態に入ってしまうことが(他のトレース情報も合わせ)分かりました。"(output string port)"という変な値になっているのは、port->name の指す先のオブジェクトも、上書きされたということなのでしょうか???(他の場所に、printfを加えたりして条件を変えると、crashするので、上の場合は、偶然 output string portのデータによって上書きされたと考えている)。

 ここで、もう一度、問題点を整理してみます。 GCシステムからアロケートされた2つのメモリ領域が、参照の関係にあります。

coding aware port → (coding_port_data*)src.buf.data
       (1)                         (2)

(1)が(2)を参照する。
そして、今回の問題は、「(1)がGCされるとき、(2)がすでにGCされてしまっている。(1)のファイナライザで呼ばれる関数は、(2)の内容が有効であることを必要とするので、困ったことになる」と捉えています。

そもそも、(1)(2)が上記のような参照関係にあるとき、(2)が(1)よりも先にGCされることが、Boehm GC下で起こるものなのでしょうか?

Boehm GC doc/gcdescr.html, Introduction より意訳
マーク処理
変数から開始して、ポインタ参照の連鎖をたどり、到達可能なオブジェクトをマークする。 しばしば、コレクターは、ヒープ中のどこに本物のポインターがあるか知ることが出来ないので、 静的データ領域/スタック/レジスタの全てを、ポインタを含む可能性があるものとする。 コレクターによって扱われるヒープ・オブジェクトの中に、アドレスを表現するようなビット・ パターンが見つかると、それらは全てポインタとして処理される。コレクターに、 ヒープ・オブジェクトの配置に関する情報を明示的に知らせないかぎり、変数から到達できる ヒープ・オブジェクトを、同様にスキャンし、ポインタを見つけることを繰り返す。

この通りであれば、(2)が(1)よりも先にGCされることはなさそうなのだが...。 (それとも、問題点の絞込み方を誤っているでしょうか? 今回は、アドバイスをあまり生かせませんでした。)

  1. GC候補のうち、ファイナライザがついているものがGC内部のキューに入れられる。
  2. ファイナライザ付きオブジェクトから指されているオブジェクトがmarkされる (ファイナライザ実行に必要なオブジェクトを回収してしまわないため)。
  3. 2.でmarkされなかったオブジェクトが回収される。
  4. ファイナライザが実行される。この実行は順不同で、(2)のファイナライザが (1)のそれより先に実行されることも有り得る。
  5. 一度ファイナライザを実行したら、該当オブジェクトからファイナライザが外される (ファイナライザが2回呼ばれることはない)

2005/04/24 02:07:54 PDT追加:
 おつきあいいただき、感謝します。分かりやすい説明で、GCの実装イメージが理解できました。ご紹介いただいた「Note on finalization」の、1つめの項目の、X, Y が、まさに今回の(1)(2)に対応しますね。(2)がポートでなく、ただのデータであることを除けば。

これで分かったような気がしましたが、考えているうちに、疑問が出てきました。

(冗談ですが、もし、ソフトがGCシステムを利用する飛行機があったとして、私は、乗りたくないなあ、と思ってしまいました。)

2005/04/24 04:40:52 PDT追加:
GC_JAVA_FINALIZATION (正しくは、JAVA_FINALIZATION ですね?)は、コンパイル時に定義していません(gc/NT_MAKEFILEというBoehm GC 付属の既定のMakefileを使っています)。定義してビルドすると、gc.lib以外は、同じ条件で、io.scm テストをパスするようになりました! まだ、油断はできないかもしれませんが、とりあえず、祝杯を上げております。今回は、いままであまり考えたことのないことに出会い、大変勉強になりました。ありがとうございます。


コメント、議論

emeitch(2005/04/10 20:01:42 PDT):移植お疲れ様です!さっそくコンパイルさせていただきました。 とりあえずこちらでのビルド環境での結果を報告させていただきます。(Windows2000 sp4、VC++ 7.1、WSH ver. 5.6)
初回、 readme_1st.txt に従い、make.bat まで行いました。その途中、Makefile.inで言うと、79行目 del gosh$(EXEEXT)で、削除対象ファイルが無いとのエラーで、delコマンドからエラーのリターンコードが返され、makeが途中で終了しました。とりあえず、srcフォルダに、ダミーのgosh.exeを突っ込んで、また0からmakeしたら通るようになりました。その後、gosh起動までは確認いたしました。とりいそぎ、ご報告まで。

千代郎(2005/04/25 03:21:15 PDT):そろそろ、これまでの改訂をもとに、Gauche-0.8.3w-02 にまとめたいと思います。-01に関する内容を、別ページに移動させました。

More ...