chibi-scheme 0.8 / Sagittarius + MinGW / Racketは7でChezに移行する?

小ネタ3つ。

chibi-scheme 0.8.0

いくつかWin32対応を入れたchibi-scheme 0.8.0がリリースされた。
組込み言語として現状欠けているのはWin64サポートなのでどうしたもんか考え中。bignumさえ無ければどうにでもなる気がするけど、chibi-schemeのbignum実装はfixnum * fixnumを格納できる整数型が言語に存在することを前提にしていて、Win64のようにfixnumで64bit巾整数を使ってしまうと、もう処理系の整数型でbignumの期待を満たすものがなくなってしまう。(gccはint128_tが使えるため64bitアーキテクチャでもアーキテクチャのword * wordを直接処理できる。)
簡単には演算を全てマクロなりインライン関数でwrapしてしまうというのが考えられるけど、元のgcc実装のパフォーマンスに影響せずに置き代える方法が見つからなかった。当然メンテナンス性にも影響する。
もう一つはWin64ではfixnumを32bit巾に縮小するという手が考えられるが、chibi-scheme内部では言語ポインタをfixnumで表現できることを前提にしている箇所がいくつか有るため諦めた。
gcc専用の機能は一度依存するとなかなか抜け出せなくなってしまうのが絶妙と言える。yuniもネスト関数を使ってコールバックにコンテキストを渡すことを検討していた時期があった。

  • ネスト関数を使ってqsortのコールバックにctxを持たせる例:
#include <stdio.h>
#include <stdlib.h>

int compar_with_context(int ctx, const void* a, const void* b){ /* 本来はこれをスクリプトにしたい */
    int aa;
    int bb;

    aa = *(int*)a;
    bb = *(int*)b;

    printf("ctx: %x, a: %d b: %d\n", ctx, aa, bb);

    return (aa - bb);
}

void qsort_with_context(int ctx, void* base, size_t cnt, size_t w,
                        int (*cb)(int, const void*, const void*)){

    int invoke(const void* a, const void* b){
        /* ネスト関数を使用してフレーム外の自動変数 ctx にアクセスする */
        return cb(ctx, a, b);
    }

    qsort(base, cnt, w, invoke);
}

int main(int ac, char** av){
    (void)ac;
    (void)av;
    int context = 0x1234;
    int arr[] = {9, 2, 5, 5, 1, 0};
    qsort_with_context(context, arr, 6, sizeof(int), compar_with_context);
    for(int i; i!= 6; i++){
        printf("res(%d): %d\n", i, arr[i]);
    }
    return 0;
}

ネスト関数はclangではサポートされていないため諦めた。(ちなみにC++ lambdaはこの目的には使えないため、コールバックのABIを維持したまま、コールバック関数にコンテキストを持たせるのは現状の言語標準では実現不能な気がしている)

Sagittarius + MinGW

SagittariusMinGWでもビルドできるようになったので試してみたが、ちょっと自分の実力では安定して動作させられそうにないので諦め気味。。

  • hg-gitからhgにコミットする?

実はできると勝手に思っていたが、hg-gitミラー( https://github.com/ktakashi/sagittarius-scheme )の作業をhgに戻す簡単な方法は無さそう。hg-gitはgitリポジトリからは常に一意なhgリポジトリを作るが、hgリポジトリをgitに変換したリポジトリを取り込んでも一意なリポジトリには戻らない(git-svnと異なりコミットログに入っているメタデータを認識しない)ため素直にhg上で作業する(Gitとのコマンド対照表が https://www.mercurial-scm.org/wiki/GitConcepts に有る)か、hg-gitミラーでの作業をgraftによって持ってくるかという方法になる。
今回はpull request( https://bitbucket.org/ktakashi/sagittarius-scheme/pull-requests/11/win32-fix-mingw-build-with-mingw64-native/diff )をGitで作ったコミットをgraftで持ってくるという手法で送信したが普通にdefaultブランチに誤爆したので公開されるコミットはhgで作業した方が良いだろう。。

  • C/C++間でのDLL変数の扱いの差

Sagittariusの拡張モジュールはWin32上では何故かC++コンパイルされている。MinGWでこれをやると依存DLLが増えてしまう(あるいは明示的にstatic linkさせる必要がある)ためどうにかしたいところ。
拡張モジュールは以下のようなコードを持ち:

#ifdef __cplusplus
extern "C" {
#endif
    extern __declspec(dllimport) void* Sg_ProcedureClass;
#ifdef __cplusplus
};
#endif

struct tmp {
    void* addr;
};

struct tmp check = { &Sg_ProcedureClass + 7 };

Windows上では、このコードはC++ではビルドが通り、Cではビルドが通らないという結果になる。

Microsoft(R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

check.c
check.c(14): error C2099: 初期化子が定数ではありません。

これはdllimportを付けたオブジェクトは定数とは見做されなくなる制約に依る。C言語では静的変数の初期化は定数で行わなければならないが、C++ではコンストラクタが使用できる(ため定数でなくて良い)。

One drawback to using this attribute is that a pointer to a variable marked as dllimport cannot be used as a constant address. However, a pointer to a function with the dllimport attribute can be used as a constant initializer; in this case, the address of a stub function in the import lib is referenced. On Microsoft Windows targets, the attribute can be disabled for functions by setting the -mnop-fun-dllimport flag.

というわけで単にdllimportを止めるか、DLL/.soを超えた変数の参照を行わないようにする必要がある。chibi-schemeやyuniは拡張モジュールからは初期化用APIをエクスポートし、ロード時に明示的に呼び出す方式にしている。関数のみを動的モジュールとの交信に使用するのは歴史的事情に依る: 遅延ロードや呼び出しのhookといった処理は関数の呼び出しの方が適用しやすい。

  • 低確率でextlibsのテスト中に刺さる

1/20くらいの確率でテスト中に固まる現象がMinGWでビルドしたデバッグ版でのみ発生する。何らかの理由でSg_WaitWithTimeoutが無限待ちになっているような気がするが原因不明。

  • Visual Studioで見たparallel stack。(VSはDWARFが読めないためシンボルはあまり正しくない - 周辺の適当なDLLシンボルを出している)


Sagittariusは条件変数の絶対時刻タイムアウト(pthreadのもの)をsleepに使用しており、Windows上でもCritical Sectionを使用して条件変数をエミュレートすることで同じようなロジックを実現している。絶対時刻タイムアウトタイムアウトの設定の瞬間にシステム時刻が変動すると待ち時間が一緒に変動するという問題があり、原理的には待ち時間が異常になる可能性はある。
Windows APIは相対時刻を取るため、Sagittarius自身の移植層でAPI呼び出し前に相対時刻に変換しなおしている。そもそも、実際にはLinuxのような他のOSでも内部で相対時刻に変換しているためこの挙動自体はWindows固有では無いとも言える。。(例えば、Linuxのプリミティブであるfutex(2)はnanosleep(2)と同じような相対時刻を取る。)

Racketは7でChezに移行する?

Racket on Chezについての説明がRacketのblogに有る。Racketは現状PLT Schemeから発展した処理系となっているが、バックエンドとしてChez Schemeを使用し独自のScheme処理系から移行するようだ。Chez Scheme自体は非常にScheme的というかライブラリ等は基本的にバンドルしない方針となっているので、Racketがいわゆる"battery included"な処理系としての立場を確立するかもしれない。
... yuniはScheme処理系としてRacketを使っているので、Chezへの移行が完了したタイミングでRacket移植はおそらく不要になるだろう(パッケージシステムへの対応自体は必要かもしれないけど)。
この努力の中で、RacketのOS移植層はnon-blockingなrktioライブラリに整理されて従来のRacket6とChez版であるRacket7に共用されている。

Racketといえばtyped racketといった様々な言語が使える点が特徴なわけで、ランタイム自体の機能性はあまり押していかないというところなのだろうか。他にChezに移行できそうな環境といっても思いあたらないので、Chezへの移行がトレンドになるようなことも無さそうではあるけど。