サマリー
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というフォルダを作成し、そこに展開します。
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を削除します。
$ rm -r src/gcc-5.3.0/boehm-gc/
次にパッチをあてます。
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コマンドでパッチをあてます。
$ patch -p0 < diff.txt
PATHの設定
インストール先にPATHを通します。
「システムのプロパティ」→「詳細設定」→「環境変数」で、環境変数PATHに以下のパスを追加します。
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
以下、実行するコマンドを羅列していきます。
ここはひとつ愛をこめて一行一行丹念に手打ちするのが清く美しいコンパイル道。。。かどうかは知りませんが、シェルスクリプトにして一気に実行しても構いません。
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 第1段階(ヘッダファイル)
mingw-w64のインストールは複数段階に分けて行われます。まずはヘッダファイル。
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 第1段階(コンパイラ本体)
gccのインストールも複数段階に分けて行われます。まずはコンパイラ本体のインストール。
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 第2段階(crt)
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 第2段階(libgcc)
pushd gcc-build
make all-target-libgcc
make install-target-libgcc
popd
ここまでの作業で、C言語のソースからexeファイルを作るためのコンパイラとライブラリ一式がインストールされました。
目標のJavaはまだ先です。
iconv
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
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
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 第3段階(全体)
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
以上でインストール完了です。おつかれさまでした!
できあがったコンパイラを利用する
package test;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
これをコンパイルします。
javaからclassへのコンパイルはあらかじめ行われたものとして(javacでも使ってください)、classからexeにコンパイルします。
$ x86_64-w64-mingw32-gcj --main=test.HelloWorld HelloWorld.class
$ ./a.exe
Hello, world!
-staticオプションをつけて静的リンクすることもできます。
$ 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/