◀Table of Contents
Interoperability with Truffle Languages
Java on Truffle allows you to interface other “Truffle” languages (languages which interpreters are implemented with the Truffle framework) to create polyglot programs – programs written in more than one language.
This guide describes how to load code written in foreign languages, how to export and import objects between languages, how to use Java on Truffle objects from a foreign language, how to use foreign objects from Java on Truffle, and how to embed in host Java.
To avoid confusion, the terms host and guest are used to differentiate the different layers where Java is executed. Java on Truffle refers to the guest layer.
You pass polyglot options to the java -truffle
launcher.
If you are using the native configuration, you will need to use the --polyglot
flag to get access to other languages.
Foreign objects must “inhabit” a guest Java type when flowing into Java on Truffle. How this type is attached to foreign objects is an implementation detail.
Polyglot
Java on Truffle provides a guest Java Polyglot API, described in polyglot.jar
.
This JAR is automatically injected on guest Java contexts but can be excluded with --java.Polyglot=false
.
You can import the Polyglot
class to interact with other guest languages:
// guest java
import com.oracle.truffle.espresso.polyglot.Polyglot;
int two = Polyglot.eval(int.class, "js", "1+1");
You can determine if an object is foreign:
// 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
You can cast foreign objects to guest Java types:
// 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;
The Polyglot.cast(targetClass, obj)
method is an augmented Java cast, e.g., targetClass.cast(obj)
:
- Java cast succeeds ⇒
Polyglot.cast
succeeds. - Java cast does not succeeds,
Polyglot.cast
can “re-type” foreign objects, e.g., to cast toInteger
, the foreign object mustfitsInInt
. - If
Polyglot.cast
fails, it will throwClassCastException
similar toClass#cast
.
Polyglot.cast
supports a natural mapping from common interop “kinds” to Java types, summarized below:
Interop “kind” | Allowed types | Preserves identity |
---|---|---|
isBoolean | Boolean/boolean | Yes* (boxed type) |
fitsInByte | Byte/byte | Yes* (boxed type) |
fitsInShort | Short/short | Yes* (boxed type) |
fitsInInt | Integer/int | Yes* (boxed type) |
fitsInLong | Long/long | Yes* (boxed type) |
fitsInFloat | Float/float | Yes* (boxed type) |
fitsInDouble | Double/double | Yes* (boxed type) |
isString & 1-character | Character/char | Yes* (boxed type) |
isString | String | No (eager conversion) |
isException & Polyglot.isForeignObject | ForeignException | Yes |
hasArrayElements | Object[] | Yes |
isNull | * | Yes |
* | Object | Yes |
You can access the polyglot bindings:
// guest java
Object foreignObject = Polyglot.importObject("foreign_object");
// Also typed imports
String userName = Polyglot.importObject("user_name", String.class);
int year = Polyglot.importObject("year", int.class);
// And exports
Polyglot.exportObject("data", new double[]{56.77, 59.23, 55.67, 57.50, 64.44, 61.37);
Polyglot.exportObject("message", "Hello, Espresso!");
Interop Protocol
Java on Truffle provides an explicit guest API to access the Interop protocol. It contains methods mimicking the interop protocol messages. This API can be used on guest Java objects as well.
// 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
Embedding in Host Java
Java on Truffle is embedded via the Polyglot API, which is part of 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);
}
}
A number of useful context option can be set with contextBuilder.option(key, value)
:
- Java properties can be added by settings
java.Properties.property.name
to the desired value (in this case this would set theproperty.name
). java.Properties.java.class.path
can be used to set the classpath of the Java on Truffle context.java.Properties.java.library.path
can be used to set the native library path of the Java on Truffle context.java.EnableAssertions
can be set totrue
to enable assertions.java.EnableSystemAssertions
can be set totrue
to enable assertions in the Java standard library.java.Verify
can be set tonone
,remove
, orall
to control whether bytecode verification does not happen, only happens on user code, or happens for all classes.java.JDWPOptions
can be set to setup and enable debugging over JDWP. For example, it could be set totransport=dt_socket,server=y,address=localhost:8000,suspend=y
.java.Polyglot
can be set totrue
orfalse
to allow or deny access to the polyglot features from thecom.oracle.truffle.espresso.polyglot
package.
*Java on Truffle does not support evaluation (.eval
) of Java sources.
In Java, methods can be overloaded, e.g., several methods can share the same name, with different signatures.
To remove ambiguity, Java on Truffle allows to specify the method descriptor in the methodName/methodDescriptor
form:
// 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<?> instance vs. static class accessor (Klass):
The static class accessor allows to access (public) static fields and call (public) static methods.
// 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 viceversa.
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"));
Multithreading
Java on Truffle is designed to be a multi-threaded language and much of the ecosystem expects threads to be available.
This may be incompatible with other Truffle languages which do not support threading, so you can disable the creation of multiple threads with the option --java.MultiThreaded=false
.
When this option is enabled, finalizers will not run, neither the ReferenceQueue
notification mechanism.
Both these features would require starting new threads. Note that the garbage-collection of weakly reachable objects remains unaffected.
Instead, reference processing can be manually triggered through a special command, only available in single-threaded environments.
// Host Java
// Will trigger Reference processing and run finalizers
polyglot.eval("java", "<ProcessReferences>");
Note that this command might trigger arbitrary cleaner and finalizer code. As such, this should ideally be run with as few guest java frames on the stack as possible.