Polyglot Programming

GraalVM allows to write polyglot applications with a seamless way to pass values from one language to another by means of the Language Implementation Framework. It is a Java library for building programming languages implementations as interpreters for self-modifying Abstract Syntax Trees. When writing a language interpreter with this Language Implementation Framework, it will automatically use the GraalVM compiler as a just-in-time compiler for the language. By having access to this Language Implementation Framework, a Ruby application, for example, can run on the same JVM as a Java application. Also, a host JVM-based language and a guest language can directly interoperate with each other and pass data back and forth in the same memory space.

In order to provide foreign polyglot values in the languages implemented with the Language Implementation Framework, the so-called polyglot interoperability protocol has been developed. This interoperability protocol consists of a set of standardized messages that every language implements and uses for foreign polyglot values. The protocol allows GraalVM to support interoperability between any combination of languages without requiring them to know of each other. For more details, proceed to the High-Performance Cross-Language Interoperability in a Multi-Language Runtime paper.

Throughout this section you learn how to combine multiple languages using GraalVM Polyglot APIs.

Running Polyglot Applications #

The following examples are designed to get you started with a basic polyglot application. Select a section for your Start Language and then select a tab for the Target Language.

Ensure you set up GraalVM before you begin.

The below examples work:

  • on a JVM, by passing --polyglot --jvm.
  • on native launchers with --polyglot (e.g., js --polyglot). It might be required to rebuild images to access languages installed with gu.
  • with native image executables (e.g., native-image --language:js).

For native launchers and native image executables using Java as a Target Language and accessing other classes than Java arrays, it is required to recompile the image and provide a reflection configuration file.

Note: to start an application with LLVM as a Target Language, make sure to precompile polyglot.c file provided below.

Start from JavaScript / Node.js

Create a file polyglot.js:

  // BEGIN-SNIPPET
var array = Polyglot.eval("R", "c(1,2,42,4)")
console.log(array[2]);
// END-SNIPPET

  
  // BEGIN-SNIPPET
var array = Polyglot.eval("ruby", "[1,2,42,4]")
console.log(array[2]);
// END-SNIPPET

  
  // BEGIN-SNIPPET
var array = Polyglot.eval("python", "[1,2,42,4]")
console.log(array[2]);
// END-SNIPPET

  
  // BEGIN-SNIPPET
var array = new (Java.type("int[]"))(4);
array[2] = 42;
console.log(array[2])
// END-SNIPPET
  
  // BEGIN-SNIPPET
var cpart = Polyglot.evalFile("llvm", "polyglot");
cpart.main()
// END-SNIPPET

  

Run:

js --polyglot --jvm polyglot.js
42
node --polyglot --jvm polyglot.js
42

Start Language R

Create a file polyglot.R:

 
  # BEGIN-SNIPPET
array <- eval.polyglot("js", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET


  
 
  # BEGIN-SNIPPET
array <- eval.polyglot("ruby", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET


  
 
  # BEGIN-SNIPPET
array <- eval.polyglot("python", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET


  
 
  # BEGIN-SNIPPET
array <- new("int[]", 4)
array[3L] <- 42
print(array[3L])
# END-SNIPPET
  
 
  # BEGIN-SNIPPET
cpart <- eval.polyglot("llvm", path="polyglot")
cpart$main()
# END-SNIPPET

  

Run:

Rscript --polyglot --jvm polyglot.R
[1] 42

Start Language Ruby:

Create a file polyglot.rb:

  # BEGIN-SNIPPET
array = Polyglot.eval('js', '[1,2,42,4]')
puts array[2]
# END-SNIPPET

  
  # BEGIN-SNIPPET
array = Polyglot.eval('R', 'c(1L,2L,42L,4L)')
puts array[2]
# END-SNIPPET

  
  # BEGIN-SNIPPET
array = Polyglot.eval('python', '[1,2,42,4]')
puts array[2]
# END-SNIPPET

  
  # BEGIN-SNIPPET
array = Java.type('int[]').new(4)
array[2] = 42
print(array[2])
# END-SNIPPET

  
  # BEGIN-SNIPPET
cpart = Polyglot.eval_file('llvm', 'polyglot')
cpart.main()
# END-SNIPPET

  

Run:

ruby --polyglot --jvm polyglot.rb
42

Start Language Python:

Create a file polyglot.py:

  # BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="js", string="[1,2,42,4]")
print(array[2])
# END-SNIPPET
  
  # BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="R", string="c(1L,2L,42L,4L)")
print(array[2])
# END-SNIPPET


  
  # BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="ruby", string="[1,2,42,4]")
print(array[2])
# END-SNIPPET


  
  # BEGIN-SNIPPET
import java
array = java.type("int[]")(4)
array[2] = 42
print(array[2])
# END-SNIPPET
  
  # BEGIN-SNIPPET
import polyglot
cpart = polyglot.eval(language="llvm", path="polyglot")
cpart.main()
# END-SNIPPET

  

Run:

graalpython --polyglot --jvm polyglot.py
42

Start Language Java:

Create a file Polyglot.java:

  // BEGIN-SNIPPET
import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) {
        Context polyglot = Context.create();
        Value array = polyglot.eval("js", "[1,2,42,4]");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}
// END-SNIPPET
  
  // BEGIN-SNIPPET
import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) {
        Context polyglot = Context.newBuilder().
    	    		               allowAllAccess(true).build();
        Value array = polyglot.eval("R", "c(1,2,42,4)");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}
// END-SNIPPET

  
  // BEGIN-SNIPPET
import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) {
        Context polyglot = Context.newBuilder().
        		               allowAllAccess(true).build();
        Value array = polyglot.eval("ruby", "[1,2,42,4]");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}
// END-SNIPPET

  
  // BEGIN-SNIPPET
import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) {
        Context context = Context.newBuilder().allowIO(true).build();
        Value array = context.eval("python", "[1,2,42,4]");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}
// END-SNIPPET

  
  // BEGIN-SNIPPET
import java.io.*;
import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) throws IOException {
        Context polyglot = Context.newBuilder().
        		               allowAllAccess(true).build();
        File file = new File("polyglot");
        Source source = Source.newBuilder("llvm", file).build();
        Value cpart = polyglot.eval(source);
        cpart.execute();
    }
}
// END-SNIPPET

  

Run:

javac Polyglot.java
java Polyglot
42

Start Language C:

Create a file polyglot.c:

 
  // BEGIN-SNIPPET
#include <stdio.h>
#include <polyglot.h>

int main() {
    void *array = polyglot_eval("js", "[1,2,42,4]");
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;
}
// END-SNIPPET

  
 
  // BEGIN-SNIPPET
#include <stdio.h>
#include <polyglot.h>

int main() {
    void *array = polyglot_eval("R", "c(1,2,42,4)");
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;
}
// END-SNIPPET
  
 
  // BEGIN-SNIPPET
#include <stdio.h>
#include <polyglot.h>

int main() {
    void *array = polyglot_eval("ruby", "[1,2,42,4]");
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;
}
// END-SNIPPET
  
 
  // BEGIN-SNIPPET
#include <stdio.h>
#include <polyglot.h>

int main() {
    void *array = polyglot_eval("python", "[1,2,42,4]");
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;
}
// END-SNIPPET
  
 
  // BEGIN-SNIPPET
#include <stdio.h>
#include <polyglot.h>

int main() {
    void *arrayType = polyglot_java_type("int[]");
    void *array = polyglot_new_instance(arrayType, 4);
    polyglot_set_array_element(array, 2, 42);
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;
}
// END-SNIPPET

  

The example C code has to be compiled to LLVM bitcode using the LLVM frontend such as clang. A user can use clang shipped with GraalVM by installing a pre-built LLVM toolchain support:

gu install llvm-toolchain
export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)

Run:

$LLVM_TOOLCHAIN/clang polyglot.c -lpolyglot-mock -o polyglot
lli --polyglot polyglot
42

Polyglot Launcher #

With polyglot applications it is often impossible to decide what the primary language of an application is. Therefore, an experimental new launcher, called polyglot, has been added to GraalVM. For the moment, this launcher runs code for JavaScript, Ruby, and R without requiring the selection of a primary language. The polyglot launcher does not require the --polyglot option, it is enabled by default.

This is how you can run a polyglot application by using the examples from above:

polyglot --jvm polyglot.js polyglot.R polyglot.rb

We have also included a basic experimental shell for multiple languages called the Polyglot Shell. It is useful to quickly test to test the interactivity of languages implemented with the Language Implementation framework. This is how you can start it:

polyglot --jvm --shell

GraalVM MultiLanguage Shell 20.2.1
  JavaScript version 20.2.1
  Python version 3.8.2
  R version 3.6.1
  Ruby version 2.6.6
Usage:
  Use Ctrl+L to switch language and Ctrl+D to exit.
  Enter -usage to get a list of available commands.
js>

Note: The polyglot launcher and the Polyglot Shell are experimental features in GraalVM.

Polyglot Options #

You can configure language engine for better throughput or startup.

  • --engine.Mode=default: Configures the execution mode of the engine. The execution mode automatically tunes the polyglot engine towards latency or throughput.
    • throughput: To collect the maximum amount of profiling information and compile using the maximum number of optimizations. This mode results in slower application startup but better throughput. This mode uses the compiler configuration community or enterprise if not specified otherwise.
    • default: To use a balanced engine configuration. This mode uses the compiler configuration community or enterprise if not specified otherwise.
    • latency: To collect only minimal profiling information and compile as fast as possible with less optimal generated code. This mode results in faster application startup but less optimal throughput. This mode uses the compiler configuration economy if not specified otherwise.

Passing Options for Language Launchers #

Every language launcher has been extended with a set of so called polyglot options. Polyglot options allow users of any language launcher to access the options of other languages supported by GraalVM (implemented with the Language Implementation Framework). The format is: --<languageID>.<property>=<value>. For example the R launcher also supports the --js.atomics=true JavaScript option.

Allowed values for the languageID are:

  • js options for JavaScript
  • python options for Python
  • r options for R
  • ruby options for Ruby
  • llvm options for LLVM

Use --help:languages to find out which options are available.

Options for polyglot tools work in the same way with the following format: --<toolID>.<property>=<value>.

Allowed values for <toolID> are:

  • inspect allows debugging with Chrome DevTools
  • cpusampler collects data about CPU usage
  • cputracer captures trace information about CPU usage
  • memtracer captures trace information about memory usage

Use --help:tools to find out which options are available.

Passing Options Programmatically #

Options can also be passed programmatically using the Java polyglot API.

Create a file called OptionsTest.java:

import org.graalvm.polyglot.*;

class OptionsTest {

    public static void main(String[] args) {
        Context polyglot = Context.newBuilder()
            .allowExperimentalOptions(true)
            .option("js.shared-array-buffer", "true")
            .build();
        // the use of shared array buffer requires the 'js.shared-array-buffer' option to be 'true'
        polyglot.eval("js", "new SharedArrayBuffer(1024)");
    }
}

Run:

javac OptionsTest.java
java OptionsTest

Note that tools options can be passed in the same way. Options cannot be modified after the context was created.

Passing Options using JVM Arguments #

Every polyglot option can also be passed as a Java system property. Each available option translates to a system property with the polyglot. prefix. For example: -Dpolyglot.js.strict=true sets the default value for a strict interpretation for all JavaScript code that runs in the JVM. Options that were set programmatically take precedence over Java system properties. For languages the following format can be used: -Dpolyglot.<languageID>.<property>=<value> and for tools it is: -Dpolyglot.<toolID>.<property>=<value>.

Create a file called SystemPropertiesTest.java:

import org.graalvm.polyglot.*;

class SystemPropertiesTest {

    public static void main(String[] args) {
        Context polyglot = Context.newBuilder()
        .allowExperimentalOptions(true)
        .build();
        // the use of shared array buffer requires the 'js.shared-array-buffer' option to be 'true'
        polyglot.eval("js", "new SharedArrayBuffer(1024)");
    }
}

Run:

javac SystemPropertiesTest.java
java -Dpolyglot.js.strict=true SystemPropertiesTest

Note that system properties are read once when the polyglot context is created. Subsequent changes have no effect.