Windowsでgcj （GNU Java コンパイラ）をソースからコンパイルする

# サマリー

Windowsで、gcc-5.3.0 / mingw-w64-v4.0.4 をソースコードからコンパイルしてみました。目標は、gcjでJavaの`*.class`ファイルを`exe`や`dll`にコンパイルすることです。

# 方針
コンパイラ本体はCygwinで動作し、出力するバイナリはMinGW-w64というクロスコンパイラを作ります。
開発環境、目標成果物の諸元は以下の通りです。

- 実行環境はWindows8.1 + cygwin 64bit版。
- MinGW-w64 64bit バイナリを生成する。multilibには対応しない（32bitバイナリは作れない。理由は後述）。
- 対応言語はJava, c, c++。lto (Link Time Optimization) には対応する。
- Javaコンパイラは、`*.class`ファイルを読み込んで実行可能バイナリを生成する。`*.java`ファイルを直接読み込むことはできない（理由は後述）。
- スレッドモデルはwin32、例外処理方式はsjlj (setjmp/longjmp)。
- boehm-gcは、gccに同梱のものを使わず、ソースからコンパイルする。
- インストール先は D:\gcj （環境に合わせて適宜変更してください）。

# コンパイル作業手順
## 事前準備
### Cygwinの準備

Cygwin自体のインストールは済んでいるものとして話を進めます。
まずは、一般的なコンパイルに必要なコンパイラ、関連ツールを（`binutils`、`gcc`など）一通りダウンロードします。`texinfo`も必要なので忘れずにダウンロードしておきましょう。

次に、ライブラリパッケージ。以下のものが必要です。
- `gmp`
- `mpfr`
- `mpc`
- `isl`
- `iconv`

また、`mingw64-x86_64`で始まる名前のパッケージは、インストールされているとコンパイルが正しく行われなくなる可能性があるため、アンインストールします。

### ソースコードのダウンロードと展開

以下のソースコードをダウンロードします。
- `binutils-2.25.1.tar.bz2`
- `gc-7.4.2.tar.gz`
- `gcc-5.3.0.tar.bz2`
- `libiconv-1.14.tar.gz`
- `libatomic_ops-7.4.2.tar.gz`
- `mingw-w64-v4.0.4.tar.bz2`

作業ディレクトリにsrcというフォルダを作成し、そこに展開します。

```shell-session:
mkdir src
pushd src
tar xaf ../binutils-2.25.1.tar.bz2
tar xaf ../gc-7.4.2.tar.gz
tar xaf ../gcc-5.3.0.tar.bz2
tar xaf ../libiconv-1.14.tar.gz
tar xaf ../libatomic_ops-7.4.2.tar.gz
tar xaf ../mingw-w64-v4.0.4.tar.bz2
popd
```

### 修正

ダウンロードしたそのままでは動きませんので、いくつか修正を加えます。
まず、gccに最初からインストールされているboehm-gcを削除します。

```shell-session:
$ rm -r src/gcc-5.3.0/boehm-gc/
```

次にパッチをあてます。

```diff:diff.txt
diff -urN src.org/gcc-5.3.0/libjava/boehm.cc src/gcc-5.3.0/libjava/boehm.cc
--- src.org/gcc-5.3.0/libjava/boehm.cc	2014-05-14 01:23:11.000000000 +0900
+++ src/gcc-5.3.0/libjava/boehm.cc	2016-01-04 14:21:28.000000000 +0900
@@ -51,10 +51,11 @@
 #include <gc_gcj.h>
 #include <javaxfc.h>  // GC_finalize_all declaration.  
 
-#ifdef THREAD_LOCAL_ALLOC
-# define GC_REDIRECT_TO_LOCAL
-# include <gc_local_alloc.h>
-#endif
+//#ifdef THREAD_LOCAL_ALLOC
+//# define GC_REDIRECT_TO_LOCAL
+//# include <gc_local_alloc.h>
+//#endif
+#include <gc.h>
 
   // From boehm's misc.c 
   void GC_enable();
@@ -468,7 +469,9 @@
 int
 _Jv_SetGCFreeSpaceDivisor (int div)
 {
-  return (int)GC_set_free_space_divisor ((GC_word)div);
+  int result = (int)GC_get_free_space_divisor();
+  GC_set_free_space_divisor ((GC_word)div);
+  return result;
 }
 
 void
diff -urN src.org/gcc-5.3.0/libjava/gcj/javaprims.h src/gcc-5.3.0/libjava/gcj/javaprims.h
--- src.org/gcc-5.3.0/libjava/gcj/javaprims.h	2012-12-20 02:03:15.000000000 +0900
+++ src/gcc-5.3.0/libjava/gcj/javaprims.h	2015-12-28 07:08:50.000000000 +0900
@@ -21,10 +21,10 @@
 // FIXME: this is a hack until we get a proper gcjh.
 // It is needed to work around system header files that define TRUE
 // and FALSE.
-#undef TRUE
-#define TRUE TRUE
-#undef FALSE
-#define FALSE FALSE
+//#undef TRUE
+//#define TRUE TRUE
+//#undef FALSE
+//#define FALSE FALSE
 
 // JNI calling convention also defined in jni.h  */
 #ifndef JNICALL
diff -urN src.org/gcc-5.3.0/libjava/include/win32.h src/gcc-5.3.0/libjava/include/win32.h
--- src.org/gcc-5.3.0/libjava/include/win32.h	2007-01-10 04:58:05.000000000 +0900
+++ src/gcc-5.3.0/libjava/include/win32.h	2016-01-02 10:25:02.000000000 +0900
@@ -35,6 +35,8 @@
 
 #include <io.h>
 
+#include <ffi.h>
+
 /* Begin UNICODE Support Classes and Functions */
 
 /* Helper class which creates a temporary, null-terminated,
@@ -93,7 +95,11 @@
 
 // Type of libffi ABI used by JNICALL methods.  NOTE: This must agree
 // with the JNICALL definition in jni.h
+#if defined(_WIN64)
+#define _Jv_platform_ffi_abi FFI_WIN64
+#else
 #define _Jv_platform_ffi_abi FFI_STDCALL
+#endif
 
 /* Useful helper classes and methods. */
 
diff -urN src.org/gcc-5.3.0/libjava/java/io/natVMConsole.cc src/gcc-5.3.0/libjava/java/io/natVMConsole.cc
--- src.org/gcc-5.3.0/libjava/java/io/natVMConsole.cc	2012-03-27 01:24:33.000000000 +0900
+++ src/gcc-5.3.0/libjava/java/io/natVMConsole.cc	2015-12-28 07:08:50.000000000 +0900
@@ -11,7 +11,7 @@
 
 #include <config.h>
 
-#include <termios.h>
+// #include <termios.h>
 #include <unistd.h>
 
 #include <gcj/cni.h>
@@ -23,27 +23,27 @@
 #define IUCLC 0
 #endif
 
-#define TERMIOS_ECHO_IFLAGS (IUCLC|IXON|IXOFF|IXANY)
-#define TERMIOS_ECHO_LFLAGS (ECHO|ECHOE|ECHOK|ECHONL|TOSTOP)
+// #define TERMIOS_ECHO_IFLAGS (IUCLC|IXON|IXOFF|IXANY)
+// #define TERMIOS_ECHO_LFLAGS (ECHO|ECHOE|ECHOK|ECHONL|TOSTOP)
 
 jstring
 java::io::VMConsole::readPassword(::java::io::Console *con)
 {
-  struct termios oldt, newt;
+  // struct termios oldt, newt;
   jstring result;
 
-  tcgetattr (STDIN_FILENO, &oldt);
+  // tcgetattr (STDIN_FILENO, &oldt);
 
-  tcgetattr (STDIN_FILENO, &newt);
+  // tcgetattr (STDIN_FILENO, &newt);
 
-  newt.c_iflag &= ~TERMIOS_ECHO_IFLAGS;
-  newt.c_lflag &= ~TERMIOS_ECHO_LFLAGS;
+  // newt.c_iflag &= ~TERMIOS_ECHO_IFLAGS;
+  // newt.c_lflag &= ~TERMIOS_ECHO_LFLAGS;
 
-  tcsetattr (STDIN_FILENO, TCSANOW, &newt);
+  // tcsetattr (STDIN_FILENO, TCSANOW, &newt);
 
   result = con->readLine ();
 
-  tcsetattr (STDIN_FILENO, TCSANOW, &oldt);
+  // tcsetattr (STDIN_FILENO, TCSANOW, &oldt);
 
   return result;
 }
diff -urN src.org/gcc-5.3.0/libjava/libgcj.spec.in src/gcc-5.3.0/libjava/libgcj.spec.in
--- src.org/gcc-5.3.0/libjava/libgcj.spec.in	2011-02-04 14:51:57.000000000 +0900
+++ src/gcc-5.3.0/libjava/libgcj.spec.in	2016-01-09 22:00:24.000000000 +0900
@@ -7,6 +7,6 @@
 *startfile: @THREADSTARTFILESPEC@ %(startfileorig)
 
 %rename lib liborig
-*lib: @LD_START_STATIC_SPEC@ @LIBGCJ_SPEC@ @LD_FINISH_STATIC_SPEC@ @LIBMATHSPEC@ @LDLIBICONV@ @GCSPEC@ @THREADSPEC@ @ZLIBSPEC@ @SYSTEMSPEC@ %(libgcc) @LIBSTDCXXSPEC@ %(liborig)
+*lib: @LD_START_STATIC_SPEC@ @LIBGCJ_SPEC@ @LD_FINISH_STATIC_SPEC@ @LIBMATHSPEC@ @LDLIBICONV@ @GCSPEC@ @THREADSPEC@ @ZLIBSPEC@ @SYSTEMSPEC@ -lgc %(libgcc) @LIBSTDCXXSPEC@ %(liborig)
 
 *jc1: @HASH_SYNC_SPEC@ @DIVIDESPEC@ @CHECKREFSPEC@ @JC1GCSPEC@ @EXCEPTIONSPEC@ @BACKTRACESPEC@ @IEEESPEC@ @ATOMICSPEC@ @LIBGCJ_BC_SPEC@ -fkeep-inline-functions
diff -urN src.org/gcc-5.3.0/libjava/Makefile.in src/gcc-5.3.0/libjava/Makefile.in
--- src.org/gcc-5.3.0/libjava/Makefile.in	2015-12-04 19:47:53.000000000 +0900
+++ src/gcc-5.3.0/libjava/Makefile.in	2016-01-06 17:46:44.000000000 +0900
@@ -9322,7 +9322,7 @@
 java/lang/Object.lo: java/lang/$(am__dirstamp) \
 	java/lang/$(DEPDIR)/$(am__dirstamp)
 libgcj.la: $(libgcj_la_OBJECTS) $(libgcj_la_DEPENDENCIES) 
-	$(libgcj_la_LINK) -rpath $(toolexeclibdir) $(libgcj_la_OBJECTS) $(libgcj_la_LIBADD) $(LIBS)
+	$(libgcj_la_LINK) -rpath $(toolexeclibdir) $(libgcj_la_OBJECTS) $(libgcj_la_LIBADD) $(LIBS) -liconv
 libgij.la: $(libgij_la_OBJECTS) $(libgij_la_DEPENDENCIES) 
 	$(libgij_la_LINK) -rpath $(toolexeclibdir) $(libgij_la_OBJECTS) $(libgij_la_LIBADD) $(LIBS)
 libjvm.la: $(libjvm_la_OBJECTS) $(libjvm_la_DEPENDENCIES) 
diff -urN src.org/gcc-5.3.0/libjava/win32-threads.cc src/gcc-5.3.0/libjava/win32-threads.cc
--- src.org/gcc-5.3.0/libjava/win32-threads.cc	2007-01-10 04:58:05.000000000 +0900
+++ src/gcc-5.3.0/libjava/win32-threads.cc	2016-01-04 15:39:14.000000000 +0900
@@ -16,6 +16,10 @@
 #ifdef HAVE_BOEHM_GC
 extern "C"
 {
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+#include <gc_config.h>
 #include <gc.h>
 // <windows.h> #define's STRICT, which conflicts with Modifier.h
 #undef STRICT
```

上のパッチを適当なファイルにプレーンテキストで保存し、patchコマンドでパッチをあてます。

```shell-session:
$ patch -p0 < diff.txt
```

### PATHの設定

インストール先にPATHを通します。
「システムのプロパティ」→「詳細設定」→「環境変数」で、環境変数PATHに以下のパスを追加します。

```shell-session:
D:\gcj\bin;D:\gcj\x86_64-w64-mingw32\bin;D:\gcj\x86_64-w64-mingw32\lib;D:\gcj\usr\local\bin
```

現時点でこれらのフォルダはまだありませんが、makeの途中でこのPATHが必要になりますので、今の段階で設定しておきましょう。

## コンパイル

コンパイル手順を以下に示します。カレントディレクトリは先ほどの通りとします（lsするとsrcが見えるはずです）。
全体としての注意点は、ソースコードのフォルダ上で直接 ./configure すると正常にコンパイルできないということです。ソースコードのフォルダとは別に、ビルド用のフォルダを作り、そこでコンパイルします。

### binutils

以下、実行するコマンドを羅列していきます。
ここはひとつ愛をこめて一行一行丹念に手打ちするのが清く美しいコンパイル道。。。かどうかは知りませんが、シェルスクリプトにして一気に実行しても構いません。

```shell-session:
mkdir binutils-build
pushd binutils-build
../src/binutils-2.25.1/configure --target=x86_64-w64-mingw32 --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj
make
make install
popd
```

### mingw-w64 第１段階（ヘッダファイル）

mingw-w64のインストールは複数段階に分けて行われます。まずはヘッダファイル。

```shell-session:
mkdir headers-build
pushd headers-build
../src/mingw-w64/mingw-w64-headers/configure --host=x86_64-w64-mingw32 --prefix=/cygdrive/d/gcj/x86_64-w64-mingw32
make
make install
popd
ln -s /cygdrive/d/gcj/x86_64-w64-mingw32 /cygdrive/d/gcj/mingw
```

### gcc 第１段階（コンパイラ本体）

gccのインストールも複数段階に分けて行われます。まずはコンパイラ本体のインストール。

```shell-session:
export CFLAGS='-g -O2 -std=gnu99'
export CFLAGS_FOR_TARGET='-O2 -I/cygdrive/d/gcj/usr/local/include/gc'
export CXXFLAGS_FOR_TARGET='-O2 -fpermissive -I/cygdrive/d/gcj/usr/local/include/gc'
mkdir gcc-build
pushd gcc-build
../src/gcc-5.2.0/configure --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-w64-mingw32 --disable-multilib --enable-languages=c,c++,java,lto --enable-libgcj --enable-static-libjava --with-win32-nlsapi=unicode --disable-libatomic --disable-boehm-gc --enable-java-gc=boehm --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj
make all-gcc
make install-gcc
popd
```

### mingw-w64 第２段階（crt）

```shell-session:
mkdir crt-build
pushd crt-build
../src/mingw-w64/mingw-w64-crt/configure --build=x86_64-pc-cygwin --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj/x86_64-w64-mingw32 --enable-win32=no
make
make install
popd
```

### gcc 第２段階（libgcc）

```shell-session:
pushd gcc-build
make all-target-libgcc
make install-target-libgcc
popd
```

ここまでの作業で、C言語のソースからexeファイルを作るためのコンパイラとライブラリ一式がインストールされました。
目標のJavaはまだ先です。

### iconv

```shell-session:
mkdir iconv-build
pushd iconv-build
../src/libiconv-1.14/configure --host=x86_64-w64-mingw32 --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj/usr/local --enable-shared=yes --enable-static=yes
make
make install
popd
```

### libatomic_ops

```shell-session:
mkdir libatomic_ops-build
pushd libatomic_ops-build
../src/libatomic_ops-7.4.2/configure --host=x86_64-w64-mingw32 --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj/usr/local --enable-shared=yes --enable-static=yes
make
make install
popd
```

### boehm-gc

```shell-session:
export ATOMIC_OPS_CFLAGS=-I$INSTALL_PREFIX/usr/local/include
export ATOMIC_OPS_LIBS=-L$INSTALL_PREFIX/usr/local/lib
mkdir boehm-gc-build
pushd boehm-gc-build
../src/gc-7.4.2/configure --host=x86_64-w64-mingw32 --with-sysroot=/cygdrive/d/gcj --prefix=/cygdrive/d/gcj/usr/local --enable-shared=yes --enable-static=yes
make
make install
popd
unset ATOMIC_OPS_CFLAGS
unset ATOMIC_OPS_LIBS
cp -pi boehm-gc-build/include/config.h /cygdrive/d/gcj/usr/local/include/gc/gc_config.h
```

### gcc 第３段階（全体）

```shell-session:
rm -r gcc-build/x86_64-w64-mingw32/boehm-gc
mkdir -p gcc-build/x86_64-w64-mingw32/boehm-gc
ln -s /cygdrive/d/gcj/usr/local/lib/libgc.la gcc-build/x86_64-w64-mingw32/boehm-gc/libgcjgc_convenience.la
mkdir -p gcc-build/x86_64-w64-mingw32/libjava/include/
touch gcc-build/x86_64-w64-mingw32/libjava/include/gc_ext_config.h
pushd gcc-build
make
make install
popd
cp -pi /cygdrive/d/gcj/x86_64-w64-mingw32/lib/libgcj-noncore-16.dll /cygdrive/d/gcj/x86_64-w64-mingw32/lib/cyggcj-noncore-16.dll
```

以上でインストール完了です。おつかれさまでした！

# できあがったコンパイラを利用する

```java:HelloWorld.java
package test;

public class HelloWorld {

	public static void main(String[] args) {
		System.out.println("Hello, world!");
	}

}
```

これをコンパイルします。
javaからclassへのコンパイルはあらかじめ行われたものとして（javacでも使ってください）、classからexeにコンパイルします。

```shell-session:
$ x86_64-w64-mingw32-gcj --main=test.HelloWorld HelloWorld.class

$ ./a.exe
Hello, world!
```

-staticオプションをつけて静的リンクすることもできます。

```shell-session:
$ x86_64-w64-mingw32-gcj -static --main=test.HelloWorld HelloWorld.class

$ ./a.exe
Hello, world!
```

# 考察
## *.javaソースから直接コンパイルできない理由：鶏と卵
ecjというJavaコンパイラパッケージをgcjに組み込むことができます。これを使うと、gcjから直接`*.java`ファイルをコンパイルできるようになります。しかしecjはJavaで記述されており、これをgcjに組み込むにはgcjが必要で、そのgcjは今から作るのです。。。
あれ？
というわけで、今回は涙をのんでecjの組み込みを断念しました。

## cyggcj-noncore-16.dllについて：cygって何だ
`gcj -shared`でJavaをコンパイルすると、生成されたバイナリは`cyggcj-noncore-16.dll`を動的リンクするようになります。ところがインストールされているDLLファイルは`libgcj-noncore-16.dll`で、名前が微妙に違います。
この違いがどこから生じるのか不明ですが、とりあえずの対策として、コンパイル手順の中でファイルコピーを行い、差を埋めるようにしました。

## `java.io.Console.readPassword()`について
MinGW-w64は、ミニマリストなだけあって、`termios.h`がありません。
そこで、ここでは`natVMConsole.cc`にパッチをあてて必要個所を根こそぎコメントアウトすることで対応していますが、その副作用としてコンソールからパスワードを入力するとエコーバックが発生するようになってしまいます。
そんなわけで、パスワードを入力する時は後ろをよく見て、監視カメラがないかどうかチェックするようにしてください。

## multilibについて
multilibとは、`gcc -m64`や`gcc -m32`コマンドラインオプションを使って一つのコンパイラで64bitバイナリと32bitバイナリを両方作りたい場合に使う機能です。これを有効にすると、`lib`フォルダに対応する`lib32`フォルダが作られ、そこに32bit対応ライブラリがインストールされます。普通の`lib`フォルダには64bit対応ライブラリがインストールされます。こうして64bitと32bitの共存が図られるわけですが、残念ながら`include`フォルダに対応する`include32`フォルダや`bin`フォルダに対応する`bin32`フォルダは作られませんので、必要なファイル全部を64bit・32bit共存インストールできないのです。
そんなわけですので、32bitが必要な場合は、面倒ですが、64bit版コンパイラとは別に32bit版コンパイラを作ってインストールするのが正解のようです。
作り方は、この手順の`x86_64-w64-mingw32`のところを`i686-w64-mingw32`に置き換えればOKです。既にインストールした64bit版コンパイラを上書きしないよう、インストール先（`D:\gcj`）も、どこか別の適当な場所に変更しましょう。

# 参考文献
Jonathan Yong et al. (2013),
Cross Win32 and Win64 compiler (document version 1.13),
MinGW-w64 Project,
[http://sourceforge.net/p/mingw-w64/wiki2/Cross%20Win32%20and%20Win64%20compiler/](http://sourceforge.net/p/mingw-w64/wiki2/Cross%20Win32%20and%20Win64%20compiler/)