与 Truffle 语言的互操作性

Espresso 使您能够将其他“Truffle”语言(其解释器使用 Truffle 框架 实现)相互连接,以创建多语言程序——即使用多种语言编写的程序。

本指南介绍如何加载其他语言编写的代码、如何在语言之间导出和导入对象、如何在其他语言中使用 Espresso 对象、如何在 Espresso 中使用其他语言的对象以及如何在 Java 应用程序中嵌入 Espresso。

为避免混淆,术语主机客体用于区分 Java 执行的不同层。Espresso 指的是客体层。

您将多语言选项传递给 java -truffle 启动器。如果您使用的是本机配置,则需要使用 --polyglot 标志才能访问其他语言。

进入 Espresso 的其他语言对象必须“驻留”在客体 Java 类型中。如何将这种类型附加到其他语言对象是实现细节。

多语言 #

Espresso 提供一个客体 Java 多语言 API,在 polyglot.jar 中进行描述。此 JAR 文件会自动注入客体 Java 上下文,但可以使用 --java.Polyglot=false 排除它。

您可以导入 Polyglot 类来与其他客体语言进行交互

// guest java
import com.oracle.truffle.espresso.polyglot.Polyglot;

int two = Polyglot.eval(int.class, "js", "1+1");

您可以确定一个对象是否为其他语言对象

// guest java
Object foreign = Polyglot.eval("js", "[2, 0, 2, 1]");
Object local = new int[]{2, 0, 2, 1};
System.out.println(Polyglot.isForeignObject(foreign)); // prints true
System.out.println(Polyglot.isForeignObject(local));   // prints false

您可以将其他语言对象强制转换为客体 Java 类型

// guest java
Object foreignArray = Polyglot.eval("js", "['a string', 42, 3.14159, null]");
Object[] objects = Polyglot.cast(Object[].class, foreignArray);

assert objects.length == 4;
String elem0 = Polyglot.cast(String.class, objects[0]);   // eager conversion
Integer elem1 = Polyglot.cast(Integer.class, objects[1]); // preserves identity
int elem1_ = Polyglot.cast(int.class, objects[1]);        // eager conversion

double elem2 = Polyglot.cast(double.class, objects[2]);   // eager conversion
Object elem3 = objects[3];
assert elem3 == null;

Polyglot.cast(targetClass, obj) 方法是一种增强的 Java 强制转换,例如 targetClass.cast(obj)

  • Java 强制转换成功 ⇒ Polyglot.cast 成功。
  • Java 强制转换不成功,Polyglot.cast 可以“重新类型化”其他语言对象,例如,要强制转换为 Integer,其他语言对象必须 fitsInInt
  • 如果 Polyglot.cast 失败,它将抛出 ClassCastException,类似于 Class#cast

Polyglot.cast 支持从常见的互操作“种类”到 Java 类型的自然映射,总结如下

互操作“种类” 允许的类型 保留身份
isBoolean Boolean/boolean 是*(包装类型)
fitsInByte Byte/byte 是*(包装类型)
fitsInShort Short/short 是*(包装类型)
fitsInInt Integer/int 是*(包装类型)
fitsInLong Long/long 是*(包装类型)
fitsInFloat Float/float 是*(包装类型)
fitsInDouble Double/double 是*(包装类型)
isString & 1 个字符 Character/char 是*(包装类型)
isString String 否(提前转换)
isException & Polyglot.isForeignObject ForeignException
hasArrayElements Object[]
isNull *
* Object

您可以访问多语言绑定

// guest java
Object foreignObject = Polyglot.importObject("foreign_object");

// typed imports
String userName = Polyglot.importObject("user_name", String.class);
int year = Polyglot.importObject("year", int.class);

// exports
Polyglot.exportObject("data", new double[]{56.77, 59.23, 55.67, 57.50, 64.44, 61.37);
Polyglot.exportObject("message", "Hello, Espresso!");

互操作协议 #

Espresso 提供一个显式的客体 API 来访问 互操作协议。它包含模拟互操作协议消息的方法。此 API 也可用于客体 Java 对象。

// guest java
import com.oracle.truffle.espresso.polyglot.Interop;

Object foreignArray = Polyglot.eval("js", "[2, 0, 2, 1]");
System.out.println(Interop.hasArrayElements(foreignArray)); // prints true
System.out.println(Interop.getArraySize(foreignArray));     // prints 4

Object elem0 = Interop.readArrayElement(foreignArray, 0);
System.out.println(Interop.fitsInInt(elem0)); // prints true
System.out.println(Interop.asInt(elem0));     // prints 2

在主机 Java 中嵌入 #

Espresso 通过 多语言 API 嵌入,该 API 是 GraalVM 的一部分。

// host java
import org.graalvm.polyglot.*;

class Embedding {
    public static void main(String[] args) {
        Context polyglot = Context.newBuilder().allowAllAccess(true).build();

        // Class loading is exposed through language bindings, with class
        // names using the same format as Class#forName(String).
        Value intArray = polyglot.getBindings("java").getMember("[I");
        Value objectArray = polyglot.getBindings("java").getMember("[Ljava.lang.Object;")

        Value java_lang_Math = polyglot.getBindings("java").getMember("java.lang.Math");
        double sqrt2 = java_lang_Math.invokeMember("sqrt", 2).asDouble();
        double pi = java_lang_Math.getMember("PI").asDouble();
        System.out.println(sqrt2);
        System.out.println(pi);
    }
}

可以使用 contextBuilder.option(key, value) 设置许多有用的上下文选项

  • 可以通过将 java.Properties.property.name 设置为所需值来添加 Java 属性(在这种情况下,这将设置 property.name)。
  • java.Properties.java.class.path 可用于设置 Java on Truffle 上下文的类路径。
  • java.Properties.java.library.path 可用于设置 Java on Truffle 上下文的本机库路径。
  • java.EnableAssertions 可以设置为 true 以启用断言。
  • java.EnableSystemAssertions 可以设置为 true 以启用 Java 标准库中的断言。
  • java.Verify 可以设置为 noneremoveall 来控制是否不进行字节码验证、仅对用户代码进行验证,还是对所有类进行验证。
  • java.JDWPOptions 可以设置为设置和启用 JDWP 上的调试。例如,它可以设置为 transport=dt_socket,server=y,address=localhost:8000,suspend=y
  • java.Polyglot 可以设置为 truefalse 来允许或拒绝从 com.oracle.truffle.espresso.polyglot 包中访问多语言功能。
  • java.PolyglotTypeConverters 可以设置为声明一个类型转换函数,该函数将元限定名称映射到类型转换器类。有关更多详细信息,请参阅下面的专用部分。
  • java.PolyglotInterfaceMappings 可以设置为以分号分隔的 1:1 接口类型映射列表,以自动构建对实现列表中声明接口的主机对象的客体代理。有关更多详细信息,请参阅下面的专用部分。

*Espresso 不支持 Java 源代码的评估(.eval)。

在 Java 中,方法可以重载,例如,多个方法可以共享同一个名称,但具有不同的签名。为了消除歧义,Espresso 允许在 methodName/methodDescriptor 形式中指定 方法描述符

// host java
Value java_lang_String = polyglot.getBindings("java").getMember("java.lang.String");
// String#valueOf(int)
String valueOf = String.format("%s/%s", "valueOf", "(I)Ljava/lang/String;");
Value fortyTwo = java_lang_String.invokeMember(valueOf, 42);
assert "42".equals(fortyTwo.asString());

Class<?> 实例与静态类访问器(Klass)

静态类访问器允许访问(公共)静态字段和调用(公共)静态方法。

// Class loading through language bindings return the static class accessor.
Value java_lang_Number = polyglot.getBindings("java").getMember("java.lang.Number");
Value java_lang_Class = polyglot.getBindings("java").getMember("java.lang.Class");

// Class#forName(String) returns the Class<Integer> instance.
Value integer_class = java_lang_Class.invokeMember("forName", "java.lang.Integer");

// Static class accessor to Class<?> instance and vice versa.
assert integer_class.equals(java_lang_Integer.getMember("class"));
assert java_lang_Integer.equals(integer_class.getMember("static"));

// Get Integer super class.
assert java_lang_Number.equals(java_lang_Integer.getMember("super"));

使用类型转换器将主机对象转换为客体类型 #

Espresso 内置支持将主机对象声明为类型转换为适当的客体类型对象。这可以通过上述上下文构建器选项完成。主要的想法是允许对象从主机透明地流向客体,而无需在主机对象进入嵌入式 Espresso 上下文时执行客体类型检查。具体来说,以下选项可以设置为控制嵌入式上下文的类型转换

java.PolyglotTypeConverters

此选项优先于 java.PolyglotInterfaceMappings,因此,如果定义了专用类型转换函数,则不会生成其他自动接口映射代理 Espresso。

注意:声明的类型转换器必须实现 GuestTypeConversion 接口,该接口位于 polyglor.jar 中的 com.oracle.truffle.espresso.polyglot 包中。

package com.oracle.truffle.espresso.polyglot;

public interface GuestTypeConversion<T> {
    T toGuest(Object polyglotInstance);
}

对于每个声明的类型转换器,请使用以下一种选项调用

// host java
Context polyglot = Context.newBuilder().allowAllAccess(true).
        option("java.PolyglotTypeConverters.java.math.BigDecimal", "guest.context.path.BigDecimalConverter").
        build();
...

// guest java
package guest.context.path;

import com.oracle.truffle.espresso.polyglot.GuestTypeConversion;
import com.oracle.truffle.espresso.polyglot.Interop;
import com.oracle.truffle.espresso.polyglot.InteropException;

import java.math.BigDecimal;

public class BigDecimalConverter implements GuestTypeConversion<BigDecimal> {

    @Override
    @SuppressWarnings("unchecked")
    public BigDecimal toGuest(Object polyglotInstance) {
        try {
            return new BigDecimal(Interop.asString(Interop.invokeMember(polyglotInstance, "toString")));
        } catch (InteropException e) {
            throw new ClassCastException("polyglot instance cannot be cast to java.math.BigDecimal");
        }
    }
}

选项的 java.math.Bigdecimal 部分声明进入 Espresso 的主机对象的完全限定元名称。

java.PolyglotInterfaceMappings

如果流入嵌入式 Espresso 上下文的主机对象没有专用的 java.PolyglotTypeConverters,则会启动自动接口类型映射。java.PolyglotInterfaceMappings 使主机和嵌入式上下文之间的无缝接口类型共享成为可能。

以下示例演示了如何使用此选项通过接口将常见的 JDK 集合类型传递到嵌入式 Espresso 上下文

// host java
builder.option("java.PolyglotInterfaceMappings", getInterfaceMappings());


private static String getInterfaceMappings(){
    return "java.lang.Iterable;"+
    "java.util.Collection;"+
    "java.util.List;"+
    "java.util.Set;"+
    "java.util.Map;"+
    "java.util.Iterator;"+
    "java.util.Spliterator;";
}

多线程 #

Espresso 被设计为一种多线程语言,生态系统的大部分内容都期望线程可用。这可能与不支持线程的其他 Truffle 语言不兼容,因此您可以使用选项 --java.MultiThreaded=false 禁用创建多个线程。

启用此选项后,终结器将不会运行,ReferenceQueue 通知机制也不会运行。这两个功能都需要启动新线程。请注意,弱可达对象的垃圾回收不受影响。

相反,可以通过一个特殊命令手动触发引用处理,该命令仅在单线程环境中可用。

// Host Java
// Will trigger Reference processing and run finalizers
polyglot.eval("java", "<ProcessReferences>");

请注意,此命令可能会触发任意清理器和终结器代码。因此,理想情况下,应在堆栈上的客体 Java 帧尽可能少的情况下运行此命令。

与我们联系