Java反射增强:代码模型


这是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。

// 获取方法sub的反射对象
Method m = ExpressionGraphs.class.getDeclaredMethod(
       
"sub", double.class, double.class);
// 获取方法sub的代码模型
Optional<CoreOp.FuncOp> oModel = m.getCodeModel();
CoreOp.FuncOp model = oModel.orElseThrow();

内部元素遍历:
能访问以后就可以实现数据结构的遍历:

可以通过遍历模型树、并打印出所有代码元素来了解这一点。

// 深度优先搜索,预购报告要素
model.traverse(null, (acc, codeElement) -> {
   
// 通过以下方式计算代码元素的深度
   
//从子树向上遍历父树
    int depth = 0;
    CodeElement<?, ?> parent = codeElement;
    while ((parent = parent.parent()) != null) depth++;
   
// 打印代码元素类
    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语言特点时”编译时“的强大,这个代码模型增强了其强项。