Java反射增強:程式碼模型

banq發表於2024-06-22


這是Java 程式碼反射(Code Reflection)中增強概念,是 Java 反射的一個增強功能,能夠編寫操作 Java 程式的 Java 程式。

這是“巴比倫計劃”的一部分,其使命是:“將 Java 的範圍擴充套件到 SQL、可微分程式設計、機器學習模型和 GPU 等外部程式設計模型。巴比倫將透過增強 Java 中的反射程式設計(稱為程式碼反射)來實現這一目標。”

背景上下文
現有 Java 程式碼建模兩種方法:

  1. 抽象語法樹(AST):Java 編譯器將源程式文字解析為形成 AST 的特定 Java 類的例項,遍歷和操作 AST 以驗證源程式是否正確,如果正確,則生成包含位元組碼指令的類檔案。
  2. 位元組碼模型:在上面一步基礎上,Java 編譯器會遍歷並操作 AST 以驗證源程式是否正確,如果正確,則生成包含位元組碼指令的類檔案。


程式碼反射需要一個不同的模型:能夠處理 Java 程式碼並生成派生的 Java 程式碼或外國程式碼(如不同的 Java 程式碼、GPU 程式碼或 SQL 語句)。

  • 允許開發者訪問 Java 程式碼中方法體和 lambda 體的符號表示。
  • 這些符號表示可以被看作是 Java 程式碼的模型,
  • 其中程式碼被表示為特定 Java 類的例項,並以適當的結構排列,

因此設計了第三種 Java 程式碼模型,稱為程式碼模型(code models)。

  • 這些模型由 Java 編譯器生成,
  • 儲存在類檔案中,
  • 並可以透過執行時反射 API 訪問。

程式碼模型保留了 Java 程式的含義:
  1. 但不包含 AST 中的所有語法細節,
  2. 同時保留了在位元組碼中不存在的型別資訊和結構資訊。

程式碼模型設計
程式碼模型設計深受許多現代編譯器用於表示程式碼的資料結構設計的影響。這些資料結構通常稱為中間表示 (IR)。該設計還受到 LLVM 編譯器基礎結構專案的子專案多級中間表示 (MLIR) 的影響。

程式碼反射的一個特別具有挑戰性的方面是確保程式碼模型設計和相應的 Java API 能夠被沒有程式語言理論和編譯器博士學位的合格 Java 開發人員廣泛使用。

程式碼模型包括程式碼元素、操作、體和塊,它們形成了一個樹狀結構。

程式碼模型採用靜態單賦值(SSA)形式,值只能被賦值一次。

本文介紹的所有程式碼均可在Babylon 儲存庫中的測試源中找到

@CodeReflection註釋
只有用 @CodeReflection註釋的方法才有程式碼模型:

@CodeReflection
static double sub(double a, double b) {
   return a - b;
}

註釋@CodeReflection表明該方法的程式碼模型應該由編譯器構建,並在執行時使用反射 API 實現訪問。

訪問方法:

  • 找到java.lang.reflect.Method的例項,
  • 然後透過呼叫方法 向其詢問其程式碼模型getCodeModel。

<font>// 獲取方法sub的反射物件<i>
Method m = ExpressionGraphs.class.getDeclaredMethod(
       
"sub", double.class, double.class);
// 獲取方法sub的程式碼模型<i>
Optional<CoreOp.FuncOp> oModel = m.getCodeModel();
CoreOp.FuncOp model = oModel.orElseThrow();

內部元素遍歷:
能訪問以後就可以實現資料結構的遍歷:

可以透過遍歷模型樹、並列印出所有程式碼元素來了解這一點。

<font>// 深度優先搜尋,預購報告要素<i>
model.traverse(null, (acc, codeElement) -> {
   
// 透過以下方式計算程式碼元素的深度<i>
   
//從子樹向上遍歷父樹<i>
    int depth = 0;
    CodeElement<?, ?> parent = codeElement;
    while ((parent = parent.parent()) != null) depth++;
   
// 列印程式碼元素類<i>
    System.out.println(
"  ".repeat(depth) + codeElement.getClass());
    return acc;
});

方法 traverse 呼叫模型中每個遇到的程式碼元素的 lambda 表示式,並列印出類名,字首空格與元素的樹深度成比例。

輸出結果如下所示:

class java.lang.reflect.code.op.CoreOp$FuncOp
  class java.lang.reflect.code.Body
    class java.lang.reflect.code.Block
      class java.lang.reflect.code.op.CoreOp$VarOp
      class java.lang.reflect.code.op.CoreOp$VarOp
      class java.lang.reflect.code.op.CoreOp$VarAccessOp$VarLoadOp
      class java.lang.reflect.code.op.CoreOp$VarAccessOp$VarLoadOp
      class java.lang.reflect.code.op.CoreOp$SubOp
      class java.lang.reflect.code.op.CoreOp$ReturnOp


程式碼模型 API 支援兩種程式碼元素的樹遍歷:

  1. 當我們計算程式碼元素的深度時,沿著樹向上,從子元素到父元素;
  2. 沿著樹向下,從父級到子級執行該traverse方法。

詳細點選標題

網友:
1、這類似於 C# 的表示式樹 API,因此不需要在 JVM 中進行任何更改,只需要 Java 編譯器和反射 API 之間的協作。
程式碼模型可以由編譯器計算,以特定的二進位制或文字格式插入到類檔案中,並在執行時使用反射 API 讀取。

2、這聽起來很酷,但對於 JVM 來說,這也意味著範圍擴大了。我意識到如今 Java 生態系統已經遠遠超出了簡單程度。沒有人會把 JVM 實現當作業餘專案。但令人遺憾的是,當一個開放標準平臺變得如此複雜以至於根本沒有機會在替代實現上進行創新時,就會造成如此深厚的根深蒂固的束縛。

3、Java相比其他語言,其主要特點是在”執行時“的強大,而Rust語言特點時”編譯時“的強大,這個程式碼模型增強了其強項。

相關文章