Clojure Hacking Guide
Posted on 2010-07-11 12:07 dennis 閱讀(3605) 評論(1) 編輯 收藏 所屬分類: 動態語言 、java 、Clojure 這題目起的嘩眾取寵,其實只是想介紹下怎么查看Clojure動態生成的字節碼,這對分析Clojure的內部實現很重要。
第一步,下載最新的Clojure 1.1.0源碼并解壓,并導入到你喜歡的IDE。
其次,下載asm 3.0的源碼并解壓。
第三,刪除Clojure 1.1.0源碼中的clojure.asm包。clojure并不是引用asm的jar包,而是將asm的源碼合并到clojure中,并且刪除一些只會在調試階段用到的package和class,保留使用asm的最小源碼集合,這可能是處于防止asm不同版本的jar包沖突以及縮小clojure大小的考慮。
第四,將asm 3.0源碼拷入clojure的源碼中,并將包org.objectweb.asm包括子包整體重名名為clojure.asm。
第五步,修改Clojure源碼,加入TraceClassVisitor的適配器,用于跟蹤字節碼生成,這需要修改clojure.lang.Compiler類中的兩個compile方法,找到類似
這樣的代碼,將cv修改為TraceClassVisitor:
TraceClassVisitor的第二個參數指定將跟蹤到的字節碼輸出到哪里,這里簡單地輸出到標準輸出方便查看。
第六步,接下來可以嘗試下我們修改過的clojure怎么動態生成字節碼,啟動REPL,
啟動階段就會輸出一些字節碼信息,主要預先加載的一些標準庫函數,如clojure.core中的函數等,REPL啟動完畢,隨便輸入一個表達式都將看到生成的字節碼
輸出類似
3就是表達式的結果。可以看到,一個表達式生成了一個class。其中<clinit>是靜態初始化塊,主要是初始化表達式中的字面常量;<init>不用說,默認的構造函數;invoke是核心方法,表達式生成的class,new一個實例后調用的就是invoke方法,執行實際的代碼,高亮部分加載了兩個常量,并執行Number.add方法。
最后,請Happy hacking!。
第一步,下載最新的Clojure 1.1.0源碼并解壓,并導入到你喜歡的IDE。
其次,下載asm 3.0的源碼并解壓。
第三,刪除Clojure 1.1.0源碼中的clojure.asm包。clojure并不是引用asm的jar包,而是將asm的源碼合并到clojure中,并且刪除一些只會在調試階段用到的package和class,保留使用asm的最小源碼集合,這可能是處于防止asm不同版本的jar包沖突以及縮小clojure大小的考慮。
第四,將asm 3.0源碼拷入clojure的源碼中,并將包org.objectweb.asm包括子包整體重名名為clojure.asm。
第五步,修改Clojure源碼,加入TraceClassVisitor的適配器,用于跟蹤字節碼生成,這需要修改clojure.lang.Compiler類中的兩個compile方法,找到類似
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = cw;
// ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = cw;
這樣的代碼,將cv修改為TraceClassVisitor:
ClassVisitor cv = new TraceClassVisitor(new CheckClassAdapter(cw), new PrintWriter(System.out));
TraceClassVisitor的第二個參數指定將跟蹤到的字節碼輸出到哪里,這里簡單地輸出到標準輸出方便查看。
第六步,接下來可以嘗試下我們修改過的clojure怎么動態生成字節碼,啟動REPL,
java clojure.main
啟動階段就會輸出一些字節碼信息,主要預先加載的一些標準庫函數,如clojure.core中的函數等,REPL啟動完畢,隨便輸入一個表達式都將看到生成的字節碼
user=> (+ 1 2)
輸出類似
compile 1
// class version 49.0 (49)
// access flags 33
public class user$eval__4346 extends clojure/lang/AFunction {
// compiled from: NO_SOURCE_FILE
// debug info: SMAP
eval__4346.java
Clojure
*S Clojure
*F
+ 1 NO_SOURCE_FILE
NO_SOURCE_PATH
*L
0#1,1:0
*E
// access flags 25
public final static Lclojure/lang/Var; const__0
// access flags 25
public final static Ljava/lang/Object; const__1
// access flags 25
public final static Ljava/lang/Object; const__2
// access flags 9
public static <clinit>()V
L0
LINENUMBER 2 L0
LDC "clojure.core"
LDC "+"
INVOKESTATIC clojure/lang/RT.var (Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
CHECKCAST clojure/lang/Var
PUTSTATIC user$eval__4346.const__0 : Lclojure/lang/Var;
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTSTATIC user$eval__4346.const__1 : Ljava/lang/Object;
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTSTATIC user$eval__4346.const__2 : Ljava/lang/Object;
RETURN
MAXSTACK = 0
MAXLOCALS = 0
// access flags 1
public <init>()V
L0
LINENUMBER 2 L0
L1
ALOAD 0
INVOKESPECIAL clojure/lang/AFunction.<init> ()V
L2
RETURN
MAXSTACK = 0
MAXLOCALS = 0
// access flags 1
public invoke()Ljava/lang/Object; throws java/lang/Exception
L0
LINENUMBER 2 L0
L1
LINENUMBER 2 L1
GETSTATIC user$eval__4346.const__1 : Ljava/lang/Object;
GETSTATIC user$eval__4346.const__2 : Ljava/lang/Object;
INVOKESTATIC clojure/lang/Numbers.add (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;
L2
LOCALVARIABLE this Ljava/lang/Object; L0 L2 0
ARETURN
MAXSTACK = 0
MAXLOCALS = 0
}
3
// class version 49.0 (49)
// access flags 33
public class user$eval__4346 extends clojure/lang/AFunction {
// compiled from: NO_SOURCE_FILE
// debug info: SMAP
eval__4346.java
Clojure
*S Clojure
*F
+ 1 NO_SOURCE_FILE
NO_SOURCE_PATH
*L
0#1,1:0
*E
// access flags 25
public final static Lclojure/lang/Var; const__0
// access flags 25
public final static Ljava/lang/Object; const__1
// access flags 25
public final static Ljava/lang/Object; const__2
// access flags 9
public static <clinit>()V
L0
LINENUMBER 2 L0
LDC "clojure.core"
LDC "+"
INVOKESTATIC clojure/lang/RT.var (Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
CHECKCAST clojure/lang/Var
PUTSTATIC user$eval__4346.const__0 : Lclojure/lang/Var;
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTSTATIC user$eval__4346.const__1 : Ljava/lang/Object;
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTSTATIC user$eval__4346.const__2 : Ljava/lang/Object;
RETURN
MAXSTACK = 0
MAXLOCALS = 0
// access flags 1
public <init>()V
L0
LINENUMBER 2 L0
L1
ALOAD 0
INVOKESPECIAL clojure/lang/AFunction.<init> ()V
L2
RETURN
MAXSTACK = 0
MAXLOCALS = 0
// access flags 1
public invoke()Ljava/lang/Object; throws java/lang/Exception
L0
LINENUMBER 2 L0
L1
LINENUMBER 2 L1
GETSTATIC user$eval__4346.const__1 : Ljava/lang/Object;
GETSTATIC user$eval__4346.const__2 : Ljava/lang/Object;
INVOKESTATIC clojure/lang/Numbers.add (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;
L2
LOCALVARIABLE this Ljava/lang/Object; L0 L2 0
ARETURN
MAXSTACK = 0
MAXLOCALS = 0
}
3
3就是表達式的結果。可以看到,一個表達式生成了一個class。其中<clinit>是靜態初始化塊,主要是初始化表達式中的字面常量;<init>不用說,默認的構造函數;invoke是核心方法,表達式生成的class,new一個實例后調用的就是invoke方法,執行實際的代碼,高亮部分加載了兩個常量,并執行Number.add方法。
最后,請Happy hacking!。