ECL (Embeddable Common-Lisp) 15.2.21をVisual Studio 2013でビルドした記録

ECLをLinux上でビルドするのは簡単だったが、Windowsではダメダメですな。

同根のINSTALLファイルによれば、Visual Studio 2008のコマンドプロンプトを使用して、

pushd <path\to\ecl-15.2.21>\msvc
nmake
nmake install

これで、最終的には\msvc\packageにビルド済みバイナリ一式が生成されるので、好きな場所に移動して使えばよいようだ。

しかし現実には全然ダメ。Visual Studio 2013を使っているからではないかとも思ったのだが、ネット上の情報を調査したところ、ECL 13.5.1をVisual Studio 2008でビルドできなかったという情報があった。当方で直面した問題のうち1つは、それと似たような現象だった。

最終的には、何とか成功したのだが……うーん、プロジェクトのリソース不足でメンテが滞っているのかなあ?

コンパイルエラーが発生する

まず、以下のファイルにてコンパイルに失敗する。

  • src/c/arch/ffi_x86.d
  • src/c/arch/ffi_x86_64.d
  • src/c/numbers/number_equalp.d

gotoのラベルに「INT」や「FLOAT」を使っているところがあるが、この2つはWindows APIでtypedefされているデータ型なので、コンフリクトが発生するようだ。

そこで適当な名前に変える。今回は、適切な名前を考えるのが面倒だったので、安直に「INT_XXX」と「FLOAT_XXX」に変更した。

補足:コンパイルエラーが発生する(古いVisual C++の場合)

ちなみに、Visual Studio 2005では、前項のファイルに加えて「src/c/file.d」もコンパイルに失敗する。

問題となるのは関数io_file_get_position()、io_file_set_position()だ。理由は単純で:

    /* src/c/file.dのio_file_set_position()より抜粋 */
    int f = IO_FILE_DESCRIPTOR(strm);
    if (isatty(f)) return(ECL_NIL);
    ecl_off_t disp;
    int mode;

「if (isatty(f)) return(ECL_NIL);」の1行の影響で、変数disp・modeの宣言位置がブロック先頭ではないため。src/c/file.dはfile.cに変換されてからコンパイルされる。つまりC言語のソースファイルとしてコンパイルされるのだが、Visual Studio 2005はC99未対応だ。C89〜C95では、変数の宣言はブロック先頭に配置しなくてはならない。

上記のコード片はio_file_set_position()のものだが、io_file_get_position()でも同じく変数の宣言位置に起因するコンパイルエラーが発生する。解決策は、「if (isatty(f)) return(ECL_NIL);」の後の変数宣言を前方に移動することだ。

この問題、Visual Studio 2008や2010でも起きる気がするのだが……。

ちなみに、「if (isatty(f)) return(ECL_NIL);」の1行は半角スペース8文字でインデントされているが、その前後の行は水平タブ1個でインデントされている。推測するに、元々は変数宣言が全てブロック先頭に書かれていて、後で「if (isatty(f)) return(ECL_NIL);」を付け足したのだろう。

ecl_minがクラッシュする(32bitビルド)

ECLのビルドは、大雑把に2つの工程に分けられるようだ。

  1. 最小限の実行ファイル(ecl_min)をビルドする。
  2. ecl_minを使用して、本物(?)の実行ファイル(ecl)をビルドする。

前項までのコンパイルエラーを解消すると、ecl_minのビルドまでは成功するようになるのだが、ecl_minを使って実際の実行ファイルをビルドする工程で、ecl_min自体がクラッシュしてしまう。

具体的には、この処理。

ecl_min < compile.lsp

エラーコードは0xc00000fd。ググった限り、スタックオーバーフローを起こしているようだ。

そこで、一旦32bit版バイナリのビルドを諦めて、64bit版のバイナリをビルドしてみることにした。

64bit版ビルドの際には、Makefileの「ECL_WIN64」を有効化する必要がある。

あと、Visual C++用のMakefileでは、「nmake clean」や「nmake distclean」で削除されないオブジェクトファイル等があるので、ecl-15.2.21.tgzからソース一式を展開しなおしてビルドした方がよいだろう。

ecl_minが終わらない(64bitビルド)

32bitビルドにてクラッシュしていた、この処理:

ecl_min < compile.lsp

64bitビルドでは、2コア(4スレッド)のCPUを25%(つまり1スレッド)占有したまま、1時間経っても終わらない……もしかして、無限ループ?

何が起きているのか?

printfデバッグで頑張ってみた。どうやら意図せず再帰呼び出しとなっている部分があるのが原因で、32bitビルドではスタックオーバーフローとなり、64bitビルドではスタックがなかなか尽きない or ループ状態になって延々と処理が終わらないようだ。

関数の呼び出しを追いかけたところ、こんな感じだった。

cinit.d::main(argc, args)
\-main.d::cl_boot(argc, args)
  \-main.d::maybe_fix_console_strea(cl_core.standard_output)
    \-file.d::si_stream_external_format_set(stream,
      |                                     external_format)
      \-file.d::set_stream_elt_type(stream,
        |                           stream->stream.byte_size,
        |                           stream->stream.flags, format)
        \-file.d::parse_external_format(stream,
          |                             external_format,
          |                             flags)
          \-cinit.d::si_make_encoding(format)
            \-internal.h::_ecl_funcall2(@'ext::make-encoding',
                                        mapping)
              eval.d::ecl_function_dispatch(ecl_process_env(),
                                            @'ext::make-encoding')(1, mapping)

cinit.dのsi_make_encoding()の中でecl_function_dispatch()を呼び出し、ecl_function_dispatch()の戻り値の関数ポインタを実行しているのだが、どうも戻り値がsi_make_encoding()のようで、結果的にsi_make_encoding()の中でsi_make_encoding()を呼び出す再帰状態になるようだ。

ソースを追ってみてもよく分からなかったが、printfデバッグでそういう風に解釈するしかない出力が得られたので、深く考えず「そうなっているのだろう」と納得しておくことにした。正直、釈然としないのだが……。

ここに至るそもそもの原因だが、src/c/main.dのmaybe_fix_console_strea()の中でWindows APIのGetConsoleCP()の戻り値(コードページ)を文字列にマッピングしているのだが、その文字列の大半("LATIN-1"以外)がparse_external_format()でチェックしているシンボル名に含まれていないのがマズイようだ。

なので、ここも修正。とりあえず強制的に"UTF-8"か"LATIN-1"にマッピングしてしまうことにした。

その他

Makefile中にマクロECL_SSEがあるが、これを有効にするとECLの実行ファイル本体(ecl2.exe)がビルドされないようだ。デフォルト(無効)のままにしておくこと。

その他の設定用マクロは、特にデフォルトから変更する必要はなさそうだ。

また、ecl-cc.batやecl-config.batといったバッチファイルが生成されるが、この中にてヘッダファイルやecl.libを参照している部分のパスが、ECLをビルドしたフォルダになってしまうようだ。ファイル一式を他の場所に移動して使用する場合には、そこを修正する必要がある。

(というか、ecl-cc.batとecl-config.batは微妙にバグっている気がする。具体的には引数なしで実行した場合の挙動とか)

パッチ

今回の修正点のパッチは以下の通り。とりあえず、このパッチを適用することで、Visual Studio 2013で32bitバイナリと64bitバイナリのどちらでもビルドできるようになった。Visual Studio 2005でコンパイルエラーが発生する件への対応は含まれていないので注意。

diff -ru ecl-15.2.21/src/c/arch/ffi_x86.d ecl-15.2.21-mod/src/c/arch/ffi_x86.d
--- ecl-15.2.21/src/c/arch/ffi_x86.d	2015-02-22 04:35:51.000000000 +0900
+++ ecl-15.2.21-mod/src/c/arch/ffi_x86.d	2015-03-06 23:43:42.272938700 +0900
@@ -31,19 +31,19 @@
 {
 	int i;
 	switch (type) {
-	case ECL_FFI_CHAR: i = data->c;	goto INT;
-	case ECL_FFI_UNSIGNED_CHAR: i = data->uc; goto INT;
-	case ECL_FFI_BYTE: i = data->b; goto INT;
-	case ECL_FFI_UNSIGNED_BYTE: i = data->ub; goto INT;
-	case ECL_FFI_SHORT: i = data->s; goto INT;
-	case ECL_FFI_UNSIGNED_SHORT: i = data->us; goto INT;
+	case ECL_FFI_CHAR: i = data->c;	goto INT_XXX;
+	case ECL_FFI_UNSIGNED_CHAR: i = data->uc; goto INT_XXX;
+	case ECL_FFI_BYTE: i = data->b; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_BYTE: i = data->ub; goto INT_XXX;
+	case ECL_FFI_SHORT: i = data->s; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_SHORT: i = data->us; goto INT_XXX;
 #ifdef ecl_uint8_t
-        case ECL_FFI_INT8_T: i = data->i8; goto INT;
-        case ECL_FFI_UINT8_T: i = data->u8; goto INT;
+        case ECL_FFI_INT8_T: i = data->i8; goto INT_XXX;
+        case ECL_FFI_UINT8_T: i = data->u8; goto INT_XXX;
 #endif
 #ifdef ecl_uint16_t
-        case ECL_FFI_INT16_T: i = data->i16; goto INT;
-        case ECL_FFI_UINT16_T: i = data->u16; goto INT;
+        case ECL_FFI_INT16_T: i = data->i16; goto INT_XXX;
+        case ECL_FFI_UINT16_T: i = data->u16; goto INT_XXX;
 #endif
 	case ECL_FFI_INT:
 	case ECL_FFI_LONG:
@@ -57,7 +57,7 @@
 	case ECL_FFI_CSTRING:
 	case ECL_FFI_OBJECT:
 		i = data->i;
-	INT:
+	INT_XXX:
 		ecl_fficall_align(sizeof(int));
 		ecl_fficall_push_int(i);
 		break;
@@ -209,22 +209,22 @@
 	ecl_foreign_data_set_elt(&output, tag, result);
 
 	switch (tag) {
-	case ECL_FFI_CHAR: i = output.c; goto INT;
-	case ECL_FFI_UNSIGNED_CHAR: i = output.uc; goto INT;
-	case ECL_FFI_BYTE: i = output.b; goto INT;
-	case ECL_FFI_UNSIGNED_BYTE: i = output.ub; goto INT;
+	case ECL_FFI_CHAR: i = output.c; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_CHAR: i = output.uc; goto INT_XXX;
+	case ECL_FFI_BYTE: i = output.b; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_BYTE: i = output.ub; goto INT_XXX;
 #ifdef ecl_uint8_t
-	case ECL_FFI_INT8_T: i = output.i8; goto INT;
-	case ECL_FFI_UINT8_T: i = output.u8; goto INT;
+	case ECL_FFI_INT8_T: i = output.i8; goto INT_XXX;
+	case ECL_FFI_UINT8_T: i = output.u8; goto INT_XXX;
 #endif
 #ifdef ecl_uint16_t
         case ECL_FFI_INT16_T:
 #endif
-	case ECL_FFI_SHORT: i = output.s; goto INT;
+	case ECL_FFI_SHORT: i = output.s; goto INT_XXX;
 #ifdef ecl_uint16_t
         case ECL_FFI_UINT16_T:
 #endif
-	case ECL_FFI_UNSIGNED_SHORT: i = output.us; goto INT;
+	case ECL_FFI_UNSIGNED_SHORT: i = output.us; goto INT_XXX;
 	case ECL_FFI_POINTER_VOID:
 	case ECL_FFI_OBJECT:
 	case ECL_FFI_CSTRING:
@@ -237,7 +237,7 @@
 	case ECL_FFI_LONG:
 	case ECL_FFI_UNSIGNED_LONG:
 		i = output.i;
-INT:
+INT_XXX:
 #ifdef _MSC_VER
 		__asm mov eax,i
 #else
diff -ru ecl-15.2.21/src/c/arch/ffi_x86_64.d ecl-15.2.21-mod/src/c/arch/ffi_x86_64.d
--- ecl-15.2.21/src/c/arch/ffi_x86_64.d	2015-02-22 04:35:51.000000000 +0900
+++ ecl-15.2.21-mod/src/c/arch/ffi_x86_64.d	2015-03-06 23:47:23.934728100 +0900
@@ -47,26 +47,26 @@
 	struct ecl_fficall *fficall = cl_env.fficall;
 	struct ecl_fficall_reg *registers = fficall->registers;
 	switch (type) {
-	case ECL_FFI_CHAR: i = data->c;	goto INT;
-	case ECL_FFI_UNSIGNED_CHAR: i = data->uc; goto INT;
+	case ECL_FFI_CHAR: i = data->c;	goto INT_XXX;
+	case ECL_FFI_UNSIGNED_CHAR: i = data->uc; goto INT_XXX;
 #ifdef ecl_uint8_t
-        case ECL_FFI_INT8_T: i = data->i8; goto INT;
-        case ECL_FFI_UINT8_T: i = data->u8; goto INT;
+        case ECL_FFI_INT8_T: i = data->i8; goto INT_XXX;
+        case ECL_FFI_UINT8_T: i = data->u8; goto INT_XXX;
 #endif
-	case ECL_FFI_BYTE: i = data->b; goto INT;
-	case ECL_FFI_UNSIGNED_BYTE: i = data->ub; goto INT;
+	case ECL_FFI_BYTE: i = data->b; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_BYTE: i = data->ub; goto INT_XXX;
 #ifdef ecl_uint16_t
-        case ECL_FFI_INT16_T: i = data->i16; goto INT;
-        case ECL_FFI_UINT16_T: i = data->u16; goto INT;
+        case ECL_FFI_INT16_T: i = data->i16; goto INT_XXX;
+        case ECL_FFI_UINT16_T: i = data->u16; goto INT_XXX;
 #endif
-	case ECL_FFI_SHORT: i = data->s; goto INT;
-	case ECL_FFI_UNSIGNED_SHORT: i = data->us; goto INT;
+	case ECL_FFI_SHORT: i = data->s; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_SHORT: i = data->us; goto INT_XXX;
 #ifdef ecl_uint32_t
-        case ECL_FFI_INT32_T: i = data->i32; goto INT;
-        case ECL_FFI_UINT32_T: i = data->u32; goto INT;
+        case ECL_FFI_INT32_T: i = data->i32; goto INT_XXX;
+        case ECL_FFI_UINT32_T: i = data->u32; goto INT_XXX;
 #endif
-	case ECL_FFI_INT: i = data->i; goto INT;
-	case ECL_FFI_UNSIGNED_INT: i = data->ui; goto INT;
+	case ECL_FFI_INT: i = data->i; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_INT: i = data->ui; goto INT_XXX;
 	case ECL_FFI_LONG:
 	case ECL_FFI_UNSIGNED_LONG:
 #ifdef ecl_uint64_t
@@ -77,7 +77,7 @@
 	case ECL_FFI_CSTRING:
 	case ECL_FFI_OBJECT:
 		i = data->l;
-	INT:
+	INT_XXX:
 		if (registers->int_registers_size < MAX_INT_REGISTERS) {
 			registers->int_registers[registers->int_registers_size++] = i;
 		} else {
@@ -277,23 +277,23 @@
 	ecl_foreign_data_set_elt(&output, tag, result);
 
 	switch (tag) {
-	case ECL_FFI_CHAR: i = output.c; goto INT;
-	case ECL_FFI_UNSIGNED_CHAR: i = output.uc; goto INT;
-	case ECL_FFI_BYTE: i = output.b; goto INT;
-	case ECL_FFI_UNSIGNED_BYTE: i = output.ub; goto INT;
+	case ECL_FFI_CHAR: i = output.c; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_CHAR: i = output.uc; goto INT_XXX;
+	case ECL_FFI_BYTE: i = output.b; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_BYTE: i = output.ub; goto INT_XXX;
 #ifdef ecl_uint8_t
-        case ECL_FFI_INT8_T: i = output.i8; goto INT;
-        case ECL_FFI_UINT8_T: i = output.u8; goto INT;
+        case ECL_FFI_INT8_T: i = output.i8; goto INT_XXX;
+        case ECL_FFI_UINT8_T: i = output.u8; goto INT_XXX;
 #endif
 #ifdef ecl_uint16_t
-        case ECL_FFI_INT16_T: i = output.i16; goto INT;
-        case ECL_FFI_UINT16_T: i = output.u16; goto INT;
+        case ECL_FFI_INT16_T: i = output.i16; goto INT_XXX;
+        case ECL_FFI_UINT16_T: i = output.u16; goto INT_XXX;
 #endif
-	case ECL_FFI_SHORT: i = output.s; goto INT;
-	case ECL_FFI_UNSIGNED_SHORT: i = output.us; goto INT;
+	case ECL_FFI_SHORT: i = output.s; goto INT_XXX;
+	case ECL_FFI_UNSIGNED_SHORT: i = output.us; goto INT_XXX;
 #ifdef ecl_uint32_t
-        case ECL_FFI_INT32_T: i = output.i32; goto INT;
-        case ECL_FFI_UINT32_T: i = output.u32; goto INT;
+        case ECL_FFI_INT32_T: i = output.i32; goto INT_XXX;
+        case ECL_FFI_UINT32_T: i = output.u32; goto INT_XXX;
 #endif
 	case ECL_FFI_POINTER_VOID:
 	case ECL_FFI_OBJECT:
@@ -307,7 +307,7 @@
         case ECL_FFI_UINT64_T:
 #endif
 		i = output.i;
-INT:
+INT_XXX:
 		{
 		register long eax asm("rax");
 		eax = i;
diff -ru ecl-15.2.21/src/c/main.d ecl-15.2.21-mod/src/c/main.d
--- ecl-15.2.21/src/c/main.d	2015-02-22 04:35:51.000000000 +0900
+++ ecl-15.2.21-mod/src/c/main.d	2015-03-07 00:55:36.036156300 +0900
@@ -457,6 +457,7 @@
 		int code;
 		const char *name;
 	} known_cp[] = {
+#if 0
 		{874, "WINDOWS-CP874"},
 		{932, "WINDOWS-CP932"},
 		{936, "WINDOWS-CP936"},
@@ -473,7 +474,8 @@
 		{1256, "WINDOWS-CP1256"},
 		{1257, "WINDOWS-CP1257"},
 		{1258, "WINDOWS-CP1258"},
-		{65001, "UTF8"},
+#endif
+		{65001, "UTF-8"},
 		{0,"LATIN-1"}
 	};
 	if (stream->stream.mode != ecl_smm_io_wcon)
diff -ru ecl-15.2.21/src/c/numbers/number_equalp.d ecl-15.2.21-mod/src/c/numbers/number_equalp.d
--- ecl-15.2.21/src/c/numbers/number_equalp.d	2015-02-22 04:35:51.000000000 +0900
+++ ecl-15.2.21-mod/src/c/numbers/number_equalp.d	2015-03-06 23:41:06.085464400 +0900
@@ -102,10 +102,10 @@
 		}
 	case t_singlefloat:
 		dx = ecl_single_float(x);
-		goto FLOAT;
+		goto FLOAT_XXX;
 	case t_doublefloat:
 		dx = ecl_double_float(x);
-	FLOAT:
+	FLOAT_XXX:
 		switch (ecl_t_of(y)) {
 		case t_fixnum:
 			return double_fix_compare(ecl_fixnum(y), dx) == 0;

このパッチは、元コードと同じくLGPLとする(――でいいのかなあ?)。