clojureが*.cljをコンパイルしたときに,どのようなバイトコードが
生成されるのかを調べてみる.
まずは,単純な例: (ns example.hello)
から始めてみる.
気をつけること
- コンパイル単位は名前空間
- 名前空間ごとにクラスローダ"namespace__init.class"が作られる
- http://clojure.org/compilationに詳細あり
- コンパイル時は(コンパイル対象のclojureコード)ソースディレクトリをクラスパスに含める
- 実行時はコンパイルされたクラスファイルの他にclojure.jarをクラスパスに含める
コンパイル
leiningenを使わず,生のclojureでコンパイルしていく. 以下のように3つのクラスファイルが作られる
- hello__init.class
- hello$fn__4.class
- hello$loading__4958__auto__.class
$ mkdir clojure-work ; cd clojure-work
$ wget http://central.maven.org/maven2/org/clojure/clojure/1.6.0/clojure-1.6.0.jar
$ mkdir -p src/example
$ vim src/example/hello.clj
(ns example.hello)
$ mkdir classes
$ tree
.
├── classes
├── clojure-1.6.0.jar
└── src
└── example
└── hello.clj
$ java -cp ./src:./classes:clojure-1.6.0.jar clojure.main
user=> (compile 'example.hello)
$ tree classes
classes
└── example
├── hello$fn__4.class
├── hello__init.class
└── hello$loading__4958__auto__.class
## もし,hello.cljにmain関数を定義したならば
## hello.classが作られるように:gen-classをつけコンパイルする.
# $ java -cp clojure-1.6.0.jar:./classes example.hello
マクロ展開
名前空間を作る(ns ’example.hello)は,マクロなので展開しておく. まず,macroexpandの結果.
(do (clojure.core/in-ns (quote example.hello))
(clojure.core/with-loading-context (clojure.core/refer (quote clojure.core)))
(if (.equals (quote example.hello) (quote clojure.core))
nil
(do (clojure.core/dosync
(clojure.core/commute
(clojure.core/deref (var clojure.core/*loaded-libs*))
clojure.core/conj
(quote example.hello)))
nil)))
さらに展開すると以下のようになるよう. ただmacroexpandでは展開しきれないなので,参考資料から予想した. 後々詳細を書くが,全体はhello__init.classに書かれている.
(do
(clojure.core/in-ns (quote example.hello))
((fn* loading__4958__auto__
([]
(. clojure.lang.Var (clojure.core/pushThreadBindings {clojure.lang.Compiler/LOADER (. (. loading__4958__auto__ getClass) getClassLoader)}))
(try
(clojure.core/refer (quote clojure.core))
(finally
(. clojure.lang.Var (clojure.core/popThreadBindings)))))))
(if (. (quote example.hello) equals (quote clojure.core))
nil
(do
(. clojure.lang.LockingTransaction (clojure.core/runInTransaction (fn*
([]
(clojure.core/commute (clojure.core/deref (var clojure.core/*loaded-libs*))
clojure.core/conj
(quote example.hello))))))
nil)))
これ以降は生成されたクラスファイルを見ていく.
hello__init.class
名前空間をロードする場合は,AOTの__init.classファイルを探し,ロードする. static initializerはクラスの読み込み時に走り, 名前空間以下の関数などのロードはこのタイミングで行われる. 以下は,javapでデコンパイルしたものをjava擬似コードで表現したもの. pushNSandLoaderやpopthreadbindingsは*ns*,*fn-loader*,*read-eval*などの clojureのトップレベルの束縛を変更している. (in-ns … )や(with-loading-context … )はIFnを使って呼んでいる. 特に,(with-loading-context … )の方はhello$loading__4958__auto__.classに 実体がある.
package example;
import clojure.lang.*;
public class hello__init {
public static {}; // staitc initializer
public static final Var const__0;
public static final AFn const__1;
public static final AFn const__2;
public static void load();
public static void __init0();
}
static {
__init0();
ClassLoader loader = Class.forName("example.hello__init").getClassLoader();
clojure.lang.Compiler.pushNSandLoader(loader);
try {
load();
} finally {
clojure.lang.Var.popThreadBindings();
}
}
static void __init0() {
const__0 = (Var)RT.var("clojure.core", "in-ns");
const__1 = (AFn)Symbol.intern(null, "example.hello");
const__2 = (AFn)Symbol.intern(null, "clojure.core");
}
public static void load() {
// (in-ns 'example.hello)
IFn inNs = (IFn)const__0.getRawRoot();
inNs.invoke(const__1);
// (with-loading-context (refer 'clojure.core))
IFn loading4958auto = (IFn)new example.hello$loading__4958__auto();
loading4958auto.invoke();
// (if (.equals 'example.hello 'clojure.core)
// nil
// (do
// (LockingTransaction/runIntransaction (fn* …))
// nil))
Symbol exampleHello = (Symbol)const__1;
if (exampleHello.equals(const__2)) {
return null;
} else {
Callable callable = (Callable)new example.hello$fn__4();
LockingTransaction.runInTransaction(callable);
return null;
}
}
hello$loading__4958__auto__.class
(with-loading-context (refer 'clojure-core))
に対応する.
AFunctionクラスは,clojureの1関数に対応するようで,
- ロード時に,static initializer
- 関数利用時に,constructor
を使う.
mapUniqueKeysあたりで引数を取ってきているが詳細は未調査. なんとなくそれっぽいことはわかる.
package example;
import clojure.lang.*;
public final class hello$loading__4958__auto__ extends AFunction {
public static final Var const__0;
public static final AFn const__1;
public static {};
public hello$loading__4958__auto__();
public Object invoke();
}
public static {
const__0 = (Var)Rt.var("clojure.core", "refer"); const__1 = (AFn)Symbol.intern(null, "clojure.core");
}
public hello$loading__4958__auto__() {
super();
}
public Object invoke() {
// (Var/pushThreadBindings {Compiler/LOADER (.getClassLoader (.getClass loading__4958__auto__))})
Object[] bindings = new Object[2];
bindings[0] = Compiler.LOADER;
bindings[1] = this.getClass().getClassLoader();
Var.pushThreadBindings((Associative)RT.mapUniqueKeys(bindings));
try {
// (refer 'clojure.core)
IFn refer = (IFn)const__0.getRawRoot();
return refer.invoke(const__1);
} finally {
Var.popThreadBindings();
}
}
hello$fn__4.class
まず,展開したものを書いておく. (fn* ([] …))あたりに対応している. *loaded-libs*にexample.helloを追加していくのはわかる.
(dosync (commute (deref #'clojure.core/*loaded-libs*)
conj
'example.hello))
=>
(LockingTransaction/runInTransaction
(fn* ([] (commute (deref #'clojure.core/*loaded-libs*)
conj
'example.hello))))
package example;
import clojure.lang.*;
public final class hello$fn__4 extends AFunction {
public static final Var const__0;
public static final Var const__1;
public static final Var const__2;
public static final Var const__3;
public static final AFn const__4;
public static {};
public hello$fn__4();
public Object invoke();
}
public static {
const__0 = (Var)RT.var("clojure.core", "commute");
const__1 = (Var)RT.var("clojure.core", "deref");
const__2 = (Var)RT.var("clojure.core", "*loaded-libs*");
const__3 = (Var)RT.var("clojure.core", "conj");
const__4 = (AFn)Symbol.intern(null, "example.hello");
}
public hello$fn__4() {
super();
}
public Object invoke() {
IFn commute = (IFn)const__0.getRawRoot();
IFn deref = (IFn)const__1.getRawRoot();
// (deref #'clojure.core/*loaded-libs*)
Object loadedLibs = deref.invoke(const__2);
Object conj = const__3.getRawRoot();
// (commute loadedLibs conj 'example.hello)
return commute.invoke(loadedLibs, conj, const__4);
}