日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

从零开始开发JVM语言(十三)代码生成与ASM

發布時間:2025/6/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零开始开发JVM语言(十三)代码生成与ASM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

目錄戳這里

如果能夠做完語義分析,得到帶類型的AST,或者更接近于虛擬機字節碼的結構,那么你離整個編譯器的“落成”就不遠了!

在這個步驟,你可以直接操作byte數組,也可以使用第三方的中間結構,也可以使用字節碼類庫。

當然,最直接的當然是字節碼庫了,例如大名鼎鼎的ASM。

#ASM ASM庫很小,但是功能非常完善。所有的屬性都有一套封裝來支持,棧的大小,StackMapTable的計算做的都很好。當然也有不好的地方,如果你給出的指令有問題它也不會報錯,有時候計算StackMapTable時會拋出IndexOutOfBoundsException,讓人摸不著頭腦。而且,mvn中下載的是壓縮版本,報錯不帶行數,也沒有源碼參考。用maven之類的進行管理時還可能出現依賴沖突。

所以正確的做法是像Spring一樣,對它做一個repackaging,其實就是保留版權信息,然后把源代碼直接往項目里放,包名調整一下。比如我這么做。

字節碼的生成本質上幾個循環就可以搞定了。 對類型需要類型名稱,修飾符,父類,接口,注解。 各字段,需要名稱,descriptor,注解。 構造方法(<init>),普通方法,static塊方法(<clinit>),它們需要名稱,descriptor,注解。相比較語法語義分析來說這一步實在是太簡單了。

這里介紹一下ASM庫的組成吧。

ASM庫主要由各個Visitor構成。而Visitor都是abstract的類型,我們實際需要用到的是Writer。

  • 類 ClassWriter
  • 注解 AnnotationWriter
  • 字段 FieldWriter
  • 方法 MethodWriter

其中,只有ClassWriter是需要手動構造的,其他幾個都是可以通過方法調用來獲取的。

new ClassWriter(ClassWriter.COMPUTE_FRAMES)

建議參數中的COMPUTE_MAXS不要加上。因為棧的彈出 我們需要在字節碼生成步驟手動完成(當然,這取決于你的語義分析輸出,我的輸出是基本不帶POP的,因為POP可以很自然的由當前棧深度分析出來,只有明確需要POP的地方才在語義分析中加上)。
比方說這樣一條語句(Integer.valueOf(1)),它將返回一個Integer類型的值。但是這個值沒有被變量或者字段接收,所以需要將其pop掉。這時可以分析出當前棧深度為1,需要pop一次。于是在此加入一個pop,并將當前棧深度減1。
實際上,要考慮的不僅僅是棧深度,還要考慮棧占用一個位置還是兩個位置。比如double和long就會占用兩個棧的位置,pop時不能用pop指令,而需要使用pop2。比如這里的實現,定義了一個結構來指定占用多少的棧深度,并合理的pop出去。
由于有了這個自動pop的機制,棧最大深度也可以順便做出來,不必使用自動計算的最大深度了。

#生成器 在字節碼生成中,我定義了這樣幾個工具方法,來使代碼邏輯更明確

  • int acc(List<SModifier> modifiers) 用來獲取Modifier
  • String typeToDesc(STypeDef type) 用來獲取類型的descriptor
  • String typeToInternalName(STypeDef type) 用來獲取internal name
  • String methodDesc(STypeDef returnType, List<STypeDef> parameters) 用來獲取方法的descriptor

這些東西經常需要獲取,所以單獨拎出來實現一下。

對于字段、方法、注解等結構自然也是各管各的分開實現:

  • void buildStatic(ClassWriter classWriter, List<Instruction> staticIns, List<ExceptionTable> exceptionTable) static塊
  • void buildConstructor(ClassWriter classWriter, List<SConstructorDef> constructors) 構造函數
  • void buildField(ClassWriter classWriter, List<SFieldDef> fields) 字段
  • void buildMethod(ClassWriter classWriter, List<SMethodDef> methods) 方法
  • void buildParameter(MethodVisitor methodVisitor, List<SParameter> params) 參數
  • void buildAnnotation(AnnotationVisitor annotationVisitor, SAnno anno) 注解

其中buildAnnotation經常被調用,因為不管是類,字段還是方法,都可能會有注解,甚至注解中還可以包含注解。所以上述這幾個方法內部都有buildAnnotation的調用。

#指令 我實現的語義分析輸出的指令與字節碼非常接近,所以對每一個指令加一個if分支,并調用對應的方法構造指令即可。

ASM將指令依據調用所需操作數(不是棧內操作數)進行了分類。

  • void visitInsn(int opcode) 不帶任何操作數的指令
  • void visitIntInsn(int opcode, int operand) 只帶一個整數作為操作數的指令
  • void visitVarInsn(int opcode, int var) 與局部變量相關的指令
  • void visitTypeInsn(int opcode, String type) 接收一個internal name的指令
  • void visitFieldInsn(int opcode, String owner, String name, String desc) 接收一個字段的指令。字段由類型/名稱/descriptor表示
  • void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) 接收一個方法的指令。方法由類型/名稱/descriptor/是否為接口方法 表示
  • void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) InvokeDynamic特有指令
  • void visitJumpInsn(int opcode, Label label) 跳轉指令
  • void visitLdcInsn(Object cst) 取常量池指令。這里的參數會自動加到常量池中去。

到此,整個編譯器主體已經完成(因為是JVM語言,所以編譯目標到字節碼即可)。然而,接下來要做的工作還有很多,Evaluator,REPL,編譯器交互,語法高亮,ide支持等。不過至少最困難的部分終于結束了!

下一篇說說Evaluator和REPL怎么實現~

最后,希望看官能夠關注我的編譯器哦~Latte

轉載于:https://my.oschina.net/wkgcass/blog/704503

總結

以上是生活随笔為你收集整理的从零开始开发JVM语言(十三)代码生成与ASM的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。