「ふつうのコンパイラをつくろう」のcbcをUbuntu18.04(64bit)でビルドする

こちらの2つの記事を参考にさせてもらいました。ありがとうございます。以下の内容はこれらの記事で書かれていることとほぼ同じです。


ホスト側で適当な作業用ディレクトリを用意。 以下 WORK_DIR

cd {WORK_DIR}

cbc を用意:

git clone https://github.com/aamine/cbc.git

ブランチを 1.0.1 に合わせておきます:

(
  cd cbc
  git checkout 1.0.1 -b ubuntu1804_64bit
)

クリーンな環境で確認したいので Docker コンテナで作業します。

{WORK_DIR}/Dockerfile

FROM ubuntu:18.04

RUN apt update \
  && apt install -y --no-install-recommends \
       make \
       openjdk-8-jdk-headless

とりあえず最低限のものだけ。

この時点でのディレクトリ・ファイル階層はこう:

- WORK_DIR/
  - cbc/
  - Dockerfile

build と run:

# (作り直す場合)
# docker rmi cbc_ubuntu1804_64bit

docker build -t cbc_ubuntu1804_64bit .

dk run --rm -it \
  -v"$(pwd)/cbc:/root/cbc" \
  cbc_ubuntu1804_64bit

以下、コンテナ内での作業。 ただし、ファイルの編集はホスト側で行っています。

コンテナ内なので root で作業していますが、一般ユーザで実行する場合は適宜 sudo apt などに読み替えてください。


まずは GCC, JavaCC, Ant をインストール:

apt install -y --no-install-recommends gcc javacc ant

apt だと JavCC 5.0 が入るようです。

ちなみに、 openjdk の前に ant をインストールすると依存パッケージとして openjdk 11 がインストールされます。


cd /root/cbc

cbc のビルド手順に従う場合ここで build.properties を修正しないといけないのですが、 apt で JavaCC をインストールした場合 /usr/share/java/javacc.jar が配置されるので、今回はこの手順はスキップします。

ちなみに、このディレクトリ指定が間違っていると次のようなメッセージを出して失敗します。

# make
ant compile
Buildfile: /root/cbc/build.xml

init:

parser:

BUILD FAILED
/root/cbc/build.xml:9: JavaCC home must be a valid directory.

Total time: 0 seconds
Makefile:8: recipe for target 'lib/cbc.jar' failed
make: *** [lib/cbc.jar] Error 1

make を実行:

make clean
make

コンパイルエラーになります。

    [javac]   both class java.lang.reflect.Parameter in java.lang.reflect and class net.loveruby.cflat.entity.Parameter in net.loveruby.cflat.entity match

これは Parser.jj に import 文を1行追加すれば消えます。

--- a/net/loveruby/cflat/parser/Parser.jj
+++ b/net/loveruby/cflat/parser/Parser.jj
@@ -11,6 +11,7 @@ PARSER_BEGIN(Parser)
 package net.loveruby.cflat.parser;
 import net.loveruby.cflat.ast.*;
 import net.loveruby.cflat.entity.*;
+import net.loveruby.cflat.entity.Parameter;
 import net.loveruby.cflat.type.*;
 import net.loveruby.cflat.asm.Label;
 import net.loveruby.cflat.utils.ErrorHandler;

再度 make:

$ make

...snip...

compile:
    [javac] /root/cbc/build.xml:16: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 193 source files to /root/cbc/build/classes
      [jar] Building jar: /root/cbc/lib/cbc.jar

BUILD SUCCESSFUL
Total time: 2 seconds
cd lib; make libcbc.a
make[1]: Entering directory '/root/cbc/lib'
../bin/cbc -O -fPIC -c stdarg.cb -o stdarg.o
stdarg.s: Assembler messages:
stdarg.s:6: Error: invalid instruction suffix for `push'
stdarg.s:14: Error: invalid instruction suffix for `pop'
stdarg.s:20: Error: invalid instruction suffix for `push'
stdarg.s:43: Error: invalid instruction suffix for `pop'
cbc: error: as failed. (status 1)
cbc: error: compile error
Makefile:14: recipe for target 'stdarg.o' failed
make[1]: *** [stdarg.o] Error 1
make[1]: Leaving directory '/root/cbc/lib'
Makefile:11: recipe for target 'lib/libcbc.a' failed
make: *** [lib/libcbc.a] Error 2

Javaコンパイルは通って、今度は lib/ 内での make で失敗します。

lib/Makefile を修正:

--- a/lib/Makefile
+++ b/lib/Makefile
@@ -11,9 +11,9 @@ AR_CREATE = ar crs
 .SUFFIXES: .cb .s .o
 
 .cb.o:
-  $(CBC) $(CBFLAGS) -c $< -o $@
+   $(CBC) $(CBFLAGS) -Wa,"--32" -c $< -o $@
 .s.o:
-  $(CBC) -c $<
+   $(CBC) -Wa,"--32" -c $<
 
 $(TARGET): $(OBJS)
    $(AR_CREATE) $(TARGET) $(OBJS)

再度 make:

root@9aee3742173e:~/cbc# make
cd lib; make libcbc.a
make[1]: Entering directory '/root/cbc/lib'
../bin/cbc -O -fPIC -Wa,"--32" -c stdarg.cb -o stdarg.o
../bin/cbc -Wa,"--32" -c alloca.s
ar crs libcbc.a stdarg.o alloca.o
make[1]: Leaving directory '/root/cbc/lib'

root@9aee3742173e:~/cbc# echo $?
0

成功しました。


ビルドが成功したのでインストールしてみます。 ちなみにインストール先を変えたい場合は 引数で指定してやれば良いようです。

root@9aee3742173e:~/cbc# ./install.sh 
prefix=/usr/local/cbc
mkdir -p /usr/local/cbc/bin
install -m755 bin/cbc /usr/local/cbc/bin
mkdir -p /usr/local/cbc/lib
cp lib/cbc.jar lib/libcbc.a /usr/local/cbc/lib
rm -rf /usr/local/cbc/import
cp -r import /usr/local/cbc/import
cbc successfully installed as /usr/local/cbc/bin/cbc

とりあえずヘルプを表示してみます。

root@9aee3742173e:~/cbc# /usr/local/cbc/bin/cbc --help
Usage: cbc [options] file...
Global Options:
  --check-syntax   Checks syntax and quit.
  --dump-tokens    Dumps tokens and quit.

... snip ...

動かせますね。


サンプルコードを用意して、

int main (int argc, char** argv) {
  return 42;
}

インストールした cbcコンパイルしてみます。

root@9aee3742173e:~/cbc# /usr/local/cbc/bin/cbc sample.cb 
sample.s: Assembler messages:
sample.s:6: Error: invalid instruction suffix for `push'
sample.s:12: Error: invalid instruction suffix for `pop'
cbc: error: as failed. (status 1)
cbc: error: compile error

cbc のオプションで -Wa,"--32" -Wl,"-melf_i386" を指定して実行してみます。

root@9aee3742173e:~/cbc# /usr/local/cbc/bin/cbc -Wa,"--32" -Wl,"-melf_i386" \
>   sample.cb
/usr/bin/ld: cannot find /usr/lib/crt1.o: No such file or directory
/usr/bin/ld: cannot find /usr/lib/crti.o: No such file or directory
/usr/bin/ld: cannot find -lc
/usr/bin/ld: cannot find /usr/lib/crtn.o: No such file or directory
cbc: error: /usr/bin/ld failed. (status 1)
cbc: error: compile error

メッセージが変わりました。


crt1.o などが必要なので、 libc6-dev-i386 をインストールします。

これら(Cランタイム)が何かということについては p574, 575 で解説されています。

apt install -y --no-install-recommends libc6-dev-i386

/usr/lib32/ にインストールされました。

root@9aee3742173e:~/cbc# ls /usr/lib32/ | grep crt
Mcrt1.o
Scrt1.o
crt1.o
crti.o
crtn.o
gcrt1.o
grcrt1.o
rcrt1.o

これらを参照している箇所を修正:

--- a/net/loveruby/cflat/sysdep/GNULinker.java
+++ b/net/loveruby/cflat/sysdep/GNULinker.java
@@ -10,10 +10,10 @@ class GNULinker implements Linker {
     // #@@range/vars{
     static final private String LINKER = "/usr/bin/ld";
     static final private String DYNAMIC_LINKER      = "/lib/ld-linux.so.2";
-    static final private String C_RUNTIME_INIT      = "/usr/lib/crti.o";
-    static final private String C_RUNTIME_START     = "/usr/lib/crt1.o";
-    static final private String C_RUNTIME_START_PIE = "/usr/lib/Scrt1.o";
-    static final private String C_RUNTIME_FINI      = "/usr/lib/crtn.o";
+    static final private String C_RUNTIME_INIT      = "/usr/lib32/crti.o";
+    static final private String C_RUNTIME_START     = "/usr/lib32/crt1.o";
+    static final private String C_RUNTIME_START_PIE = "/usr/lib32/Scrt1.o";
+    static final private String C_RUNTIME_FINI      = "/usr/lib32/crtn.o";
     // #@@}
 
     ErrorHandler errorHandler;

再度 make + install:

make clean
make
./install.sh

サンプルコードをコンパイル:

/usr/local/cbc/bin/cbc -Wa,"--32" -Wl,"-melf_i386" sample.cb

コンパイルが成功して実行ファイル sample が作られました。 実行してみます。

root@9aee3742173e:~/cbc# ./sample
root@9aee3742173e:~/cbc# echo $?
42

正しく動いているようです。


毎回 -Wa,"--32" -Wl,"-melf_i386" を付けるのは煩雑なので bin/cbc に追加してしまいます。

--- a/bin/cbc
+++ b/bin/cbc
@@ -9,4 +9,5 @@ srcdir_root="$(dirname "$(dirname "$cmd_path")")"
         net.loveruby.cflat.compiler.Compiler \
         -I"$srcdir_root/import" \
         -L"$srcdir_root/lib" \
+        -Wa,"--32" -Wl,"-melf_i386" \
         "$@"

再度インストール:

./install.sh

次のように使えるようになりました。

root@9aee3742173e:~/cbc# /usr/local/cbc/bin/cbc sample.cb 
root@9aee3742173e:~/cbc# ./sample 
root@9aee3742173e:~/cbc# echo $?
42

cbc に加えた変更をまとめて見る場合はこちら:

https://github.com/sonota88/cbc/compare/1.0.1...sonota88:ubuntu1804_64bit

この記事を読んだ人は(たぶん)こちらも読んでいます

qiita.com

memo88.hatenablog.com

memo88.hatenablog.com

memo88.hatenablog.com