GraalVM, a polyglot virtual machine

Introduction

This lecture introduces the GraalVM ecosystem, a runtime able to develop applications not only with JVM-based languages such as Java, Scala, and Kotlin, but also with other programming languages such as JavaScript, Ruby, Python, and R. Additionally, it enables the execution of native code via an LLVM front-end, and WebAssembly programs on the JVM. Recall that the LLVM project is a compiler infrastructure and here an intermediate representation of programs. C and C++ programs can be translated into LLVM, then they can be used by the GraalVM virtual machine. WebAssembly is a technology for cross-browser collaboration on a new, portable, size- and load-time-efficient format suitable for compilation. Again, this is an intermediate representation for "programs" that can be executed by the browser without the need of a JavaScript interpreter, more exactly in cooperation with JavaScript.

Summarizing, GraalVM aims at building applications composed according to multiple programming languages. The following diagram illustrates the architecture of GraalVM as an open ecosystem.

GraalVM consists of core and optional components, and is distributed as an archive. You can download the archives from the project home page. After unarchiving, I get the following tree structure:

  cerin@ordinateur-cerin:~/Bureau$ ls
  Documents   Dropbox   graalvm-ce-java11-20.0.0  'Old Firefox Data'
  cerin@ordinateur-cerin:~/Bureau$ cd graalvm-ce-java11-20.0.0/
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0$ ls
  3rd_party_license_llvm-toolchain.txt  include    lib                      tools
  bin                                   jmods      LICENSE.txt
  conf                                  languages  release
  GRAALVM-README.md                     legal      THIRD_PARTY_LICENSE.txt
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0$ ls bin
  gu         jcmd       jimage  jrunscript  keytool            polyglot
  jar        jconsole   jinfo   js          lli                rmic
  jarsigner  jdb        jjs     jshell      node               rmid
  java       jdeprscan  jlink   jstack      node_modules       rmiregistry
  javac      jdeps      jmap    jstat       npm                serialver
  javadoc    jfr        jmod    jstatd      pack200            unpack200
  javap      jhsdb      jps     jvisualvm   package-lock.json
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0$

The bin directory contains, among others, the gu tool which is a package installer that can be used to install language packs for Python, R, and Ruby, js that runs a JavaScript console with GraalVM, node which is a drop-in replacement for Node.js, using GraalVM’s JavaScript engine and lli which is a high-performance LLVM bitcode interpreter integrated with GraalVM.

Before starting with examples, check the versions of some installed tools, and if necessary, modify the PATH environment variable to set it to the highest priority with the GraalVM bin directory. Otherwise, "normal" Java, NodeJS... binaries will disturbing the compiling and running steps.

  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0/bin$ ./java -version
  openjdk version "11.0.6" 2020-01-14
  OpenJDK Runtime Environment GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02)
  OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02, mixed mode, sharing)
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0/bin$ ./node -v
  v12.15.0
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0/bin$ ./lli --version
  LLVM (GraalVM CE Native 20.0.0)
  cerin@ordinateur-cerin:~/Bureau/graalvm-ce-java11-20.0.0/bin$ 

Ready for the first examples!

Main stream

You are now invited to compile and run many small examples from this page. Please, go ahead! More elaborated examples follow, in order to use the polyglot facilities of GraalVM. Stop before the Docker containers section.

If you want to learn what GraalVM offers to different types of teams, read the Why GraalVM page. Some of the diverse features of GraalVM are disclosed and supported with examples in Top 10 Things To Do With GraalVM article. Or, you can examine different supported languages in action by looking at example applications. If you want to learn about the common tools GraalVM enables for the supported languages, proceed to the tools section of the reference manual. And if you are mostly interested in a specific language, more extensive documentation is available in the reference manual as well.

Main issues

The incompatibilities between GraalVM and some libraries are the main issue for developing programs. In the following fragment of code, I tried to install the treemap package of R, but I received some message of failures. It is difficult to say if it is a problem due to an incompatibility of the package with my system or with any system or a problem related to the integration of the package with the R/GraalVM software itself. I got:

:~/Bureau/graalvm-ce-java11-20.0.0/graalvm-demos/polyglot-javascript-java-r$ R
R version 3.6.1 (FastR)
Copyright (c) 2013-19, Oracle and/or its affiliates
Copyright (c) 1995-2018, The R Core Team
Copyright (c) 2018 The R Foundation for Statistical Computing
Copyright (c) 2012-4 Purdue University
Copyright (c) 1997-2002, Makoto Matsumoto and Takuji Nishimura
All rights reserved.

FastR is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

R is a collaborative project with many contributors.
Type 'contributors()' for more information.

Type 'q()' to quit R.
> install.packages("treemap")
Warning: dependency ‘data.table’ is not available
also installing the dependencies ‘glue’, ‘stringi’, ‘assertthat’, ‘utf8’, ‘Rcpp’, ‘stringr’, ‘labeling’, ‘munsell’, ‘cli’, ‘fansi’, ‘pillar’, ‘BH’, ‘digest’, ‘gtable’, ‘lazyeval’, ‘mgcv’, ‘plyr’, ‘reshape2’, ‘rlang’, ‘scales’, ‘tibble’, ‘viridisLite’, ‘withr’, ‘magrittr’, ‘pkgconfig’, ‘httpuv’, ‘mime’, ‘jsonlite’, ‘xtable’, ‘htmltools’, ‘R6’, ‘sourcetools’, ‘later’, ‘promises’, ‘crayon’, ‘colorspace’, ‘ggplot2’, ‘gridBase’, ‘igraph’, ‘RColorBrewer’, ‘shiny’

Content type 'application/octet-stream' length 56368 bytes (55 KB)
Content type 'application/octet-stream' length 17916239 bytes (17,1 MB)
Content type 'application/octet-stream' length 11612 bytes (11 KB)
Content type 'application/octet-stream' length 218882 bytes (213 KB)
Content type 'application/octet-stream' length 3635277 bytes (3,5 MB)
Content type 'application/octet-stream' length 135777 bytes (132 KB)
Content type 'application/octet-stream' length 10722 bytes (10 KB)
Content type 'application/octet-stream' length 182653 bytes (178 KB)
Content type 'application/octet-stream' length 2186755 bytes (2,1 MB)
Content type 'application/octet-stream' length 266123 bytes (259 KB)
Content type 'application/octet-stream' length 103972 bytes (101 KB)
Content type 'application/octet-stream' length 12378154 bytes (11,8 MB)
Content type 'application/octet-stream' length 128553 bytes (125 KB)
Content type 'application/octet-stream' length 24157 bytes (23 KB)
Content type 'application/octet-stream' length 80150 bytes (78 KB)
Content type 'application/octet-stream' length 915013 bytes (893 KB)
Content type 'application/octet-stream' length 392451 bytes (383 KB)
Content type 'application/octet-stream' length 36405 bytes (35 KB)
Content type 'application/octet-stream' length 857682 bytes (837 KB)
Content type 'application/octet-stream' length 299262 bytes (292 KB)
Content type 'application/octet-stream' length 729338 bytes (712 KB)
Content type 'application/octet-stream' length 44019 bytes (42 KB)
Content type 'application/octet-stream' length 53578 bytes (52 KB)
Content type 'application/octet-stream' length 200504 bytes (195 KB)
Content type 'application/octet-stream' length 6024 bytes
Content type 'application/octet-stream' length 1675978 bytes (1,6 MB)
Content type 'application/octet-stream' length 12960 bytes (12 KB)
Content type 'application/octet-stream' length 1052728 bytes (1,0 MB)
Content type 'application/octet-stream' length 610140 bytes (595 KB)
Content type 'application/octet-stream' length 45408 bytes (44 KB)
Content type 'application/octet-stream' length 30968 bytes (30 KB)
Content type 'application/octet-stream' length 24155 bytes (23 KB)
Content type 'application/octet-stream' length 40293 bytes (39 KB)
Content type 'application/octet-stream' length 106866 bytes (104 KB)
Content type 'application/octet-stream' length 658694 bytes (643 KB)
Content type 'application/octet-stream' length 2146163 bytes (2,0 MB)
Content type 'application/octet-stream' length 2863109 bytes (2,7 MB)
Content type 'application/octet-stream' length 153373 bytes (149 KB)
Content type 'application/octet-stream' length 2703891 bytes (2,6 MB)
Content type 'application/octet-stream' length 11532 bytes (11 KB)
Content type 'application/octet-stream' length 2987723 bytes (2,8 MB)
Content type 'application/octet-stream' length 121814 bytes (118 KB)
* installing *source* package ‘glue’ ...
** package ‘glue’ successfully unpacked and MD5 sums checked
** using staged installation
** libs
.....
.....
.....
.....
** R
** demo
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** checking absolute paths in shared objects and dynamic libraries
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (httpuv)
ERROR: dependency ‘mgcv’ is not available for package ‘ggplot2’
* removing ‘/home/agathe/Bureau/graalvm-ce-java11-20.0.0/languages/R/library/ggplot2’
* installing *source* package ‘shiny’ ...
** package ‘shiny’ successfully unpacked and MD5 sums checked
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (shiny)
ERROR: dependencies ‘data.table’, ‘ggplot2’, ‘igraph’ are not available for package ‘treemap’
* removing ‘/home/agathe/Bureau/graalvm-ce-java11-20.0.0/languages/R/library/treemap’

The downloaded source packages are in
‘/tmp/RtmpJxnzkm/downloaded_packages’
Updating HTML index of packages in '.Library'
Making 'packages.html' ... done
Warning messages:
1: In install.packages("treemap") :
installation of package ‘mgcv’ had non-zero exit status
2: In install.packages("treemap") :
installation of package ‘igraph’ had non-zero exit status
3: In install.packages("treemap") :
installation of package ‘ggplot2’ had non-zero exit status
4: In install.packages("treemap") :
installation of package ‘treemap’ had non-zero exit status
>

Another issue is to guess, for a language, the polyglot interface to make a dialog with yet another language. For R, the manual/documentation says that the interface is as follows:

GraalVM execution of R provides the following interoperability primitives:
    
   eval.polyglot('languageId', 'code') evaluates code in some other
   language, the languageId can be, e.g., js.

   eval.polyglot(path = '/path/to/file.extension') evaluates code
   loaded from a file. The language is recognized from the extension.

   export('polyglot-value-name', rObject) exports an R object so that
   it can be imported by other languages.

   import('exported-polyglot-value-name') imports a polyglot value
   exported by some other language.
 

Then the documentation gives the following example of interoperability:

# use R vector in JavaScript
export('robj', c(1,2,3))
eval.polyglot('js', paste0(
'rvalue = Polyglot.import("robj"); ',
'console.log("JavaScript: " + rvalue.length);'))
# JavaScript: 3
# NULL -- the return value of eval.polyglot

Modifying an existing example

First step: reusing a code

You are invited to play with this source code and restart the application to see what else you can do with the mix of JavaScript, Java, and R. From the server side, you will get:

    $ node --polyglot --jvm server.js
    Example app listening on port 3000!
    Loading required package: lattice

Second step: adapting the code to a new situation

We now put in place a Node (JavaScript) program that fetch an online weather station to get some information about the weather in Paris, then illustrates the interoperability of JavaScript with R. For that purpose, the JavaScript program exports to the R program an integer, a vector of integer and a vector of strings. Then the R program do some computation on the objects and export the results to the JavaScript program. The JavaScript program explicitly imports the results and aggregate them to the Web page. The JavaScript program also explores the JSON object returned by the POST method, in search of the name of the city. This name is also aggregated to the Web page. Note also that we communicate between the two languages through the foo.r program which is a R program.

The JavaScript program is:

const express = require('express')
const app = express()

const BigInteger = Java.type('java.math.BigInteger')


app.get('/', function (req, res1) {
  var text = '<h1>Weather Station</h1> '

  // Using Java standard library classes
  text += '<p>Version: ' + BigInteger.valueOf(2).pow(12).toString() + '</p>'

  // Using R methods to return arrays
  //text += Polyglot.eval('R',
  //    'ifelse(1 > 2, "no", paste(1:42,  c="|"))') + '<br>'
  text += '<p>Arithmetic with R: ' +Polyglot.eval('R',
      '3+4') + '</p>'

  // use JavaScript objects in R
  var fruits = [1, 2, 3, 4, 5, 6, 7];
  Polyglot.export('robj', fruits);
  var mystr = ["Bonjour ","la ","ville ","de ","Paris"];
  Polyglot.export('mystr', mystr);
  var myint = 1;
  Polyglot.export('intobj', myint);
  Polyglot.evalFile("R", "./foo.r");
  var lvalue = Polyglot.import('lvalue');
  text += '<p>Arithmetic with R (calling the foo.r file): '+lvalue+'</p>'
  var myint = Polyglot.import('myint');
  text += '<p>Sum of Vector fruits (calling the foo.r file): '+myint+'</p>'
  var mystr = Polyglot.import('mystrres');
  text += '<p>Concatenate strings (calling the foo.r file): '+mystr+'</p>'
  
  const https = require('https')
  const options = {
    hostname: 'worldweather.wmo.int',
    port: 443,
    path: '/fr/json/194_fr.xml',
    method: 'GET'
  }

  const req1 = https.request(options, res => {
    console.log(`statusCode: ${res.statusCode}`)

    res.on('data', d => {
        process.stdout.write(d)
	var obj=JSON.parse(d)
	res1.send(text+'<h2>City name: '+obj.city.cityName+'</h2>')
	//console.log(Polyglot.eval('R', 'library(treemap)')[10]);
	//text += Polyglot.eval('python',"3+4");
        //import org.graalvm.polyglot.Context;
	//try (Context context = Context.create()) {
	//   Value function = context.eval("python", "lambda x: x + 1");
	//   assert function.canExecute();
	//   int x = function.execute(41).asInt();
	//   assert x == 42;
	//   text += '<p>'+x.toTring()+'</p>';
        //    res1.send(text)
	//}
    })
  })

  req1.on('error', error => {
    console.error(error)
  })

  req1.end()
    
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

The R program is:

print("Start script R");

intvalue <- import("intobj");
print(typeof(intvalue));
lvalue=intvalue+3;
export('lvalue',lvalue);

rvalue <- import("robj");
print(typeof(rvalue));
print(rvalue)
print(rvalue[1:1])
myint <- 0;
for(i in 1:(length(rvalue))) {
      myint=myint+rvalue[i:1][1];
      print(rvalue[i:1][1]);
}
export('myint',myint);

rsrt <- import("mystr");
#paste(rsrt, collapse = '')
# It works with an explicit iteration
mystrres <- "";
for(i in 1:(length(rsrt))) {
      mystrres=paste(mystrres,rsrt[i:1][1]);
      print(rsrt[i:1][1]);
}
print(mystrres)
#print(rsrt)
export('mystrres',mystrres);
#export('mystrres',rsrt);

print("End script R")

The JavaScript program is started with the node --experimental-options --js.nashorn-compat=true --jvm --polyglot server_weather.js command, and we get the following result, with on the left part of the Figure the message sent to the console by the JavaScript and the R programs, and on the right part of the Figure the browser's layout.

Putting it all together!

We are now ready to improve the weather station program written in JavaScript and R. The key idea is to ask the user to enter a city code, then to draw a barchart for the temperatures. The drawing function is implemented with a R program. The JavaScript program explores the JSON object returned by the POST request, parametrized by the city code.

The code architecture is through a POST method to send the HTML code to the page when the button is pressed. Inside the POST method we also have a GET method to download the weather information from the Web site. This GET method is done with the Javacript fetch() method. This analysis of the obtained JSON file is done in the makeCsvFile(dico) function. This function do a synchronous write to the cerin.csv. Then we call the plotFunction(json.city.cityName) function for the drawing. The reader should also notice the way we realize the interoperability between Javacript and R.

The server.js is:

var express = require('express');
var bodyParser = require("body-parser");
var app = express();
var APP_PORT = 8088;
const fetch = require('node-fetch');
const fs = require('fs');

Polyglot.evalFile("ruby", "validator.rb");
var Validator = Polyglot.import('Validator');

Polyglot.evalFile("R", "functionGraph.r");
var plotFunction = Polyglot.import('plotFunction');

app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(bodyParser.json());

function makeCsvFile(dico) {
    var res = 'Id,Date,Temp' + '\n'
    var i = 0;
    dico.forEach((value, index, self) => {
        //console.log(value, index, self)
        res += (1 + i).toString() + ',' + '(min) ' + value.forecastDate + ',' + value.minTemp.toString() + '\n';
        res += (2 + i).toString() + ',' + '(max) ' + value.forecastDate + ',' + value.maxTemp.toString() + '\n';
        i += 2;
    })
    console.log(res);
    // Asynchronous write
    //fs.writeFile('cerin.csv', res, (err) => {
    //   if(err) {
    //      return console.log(err);
    //}
    //console.log("The file was saved!");
    //});

    // Synchronous write
    // Write a string to another file and set the file mode to 0755
    try {
        fs.writeFileSync('cerin.csv', res, {
            mode: 0o755
        });
    } catch (err) {
        // An error occurred
        console.error(err);
    }
}

app.post('/graph', function(req, res) {
    let expr = req.body.expr;
    let errorMsg = Validator.validate(expr);
    if (errorMsg.length() == 0) {
        fetch('https://worldweather.wmo.int/fr/json/' + expr + '_fr.xml')
            .then(res => res.json())
            .then(json => {
                console.log(json);
                //console.log(json.city.forecast.forecastDay);
                makeCsvFile(json.city.forecast.forecastDay);
                res.send(plotFunction(json.city.cityName));
            });
    } else {
        res.send(errorMsg);
    }
});

app.use(express.static(__dirname + "/public"));
app.listen(APP_PORT, function() {
    console.log("Server listening on port " + APP_PORT);
});

The functionGraph.r is:

library(stats)
library(lattice)

plotFunction <- function(expr) {
  svg()
  #x <- seq(from = x1, to = x2, by = 0.01)
  #y <- eval(parse(text=expr))
  #print(xyplot(y ~ x, type="l"))
  #data <- read.csv("./barley.csv", header = TRUE, sep = ',')
  #print(barchart(gen ~ yield,  data=data,
  data <- read.csv("./cerin.csv", header = TRUE, sep = ',')
  print(barchart(Date ~ Temp,  data=data,
         main = expr,
         xlab = "Temperature",
         ylab = "Date",
         col = c("chocolate", "green", "grey", "blue", "yellow")))
  grDevices:::svg.off()
}

export('plotFunction', plotFunction)

The full package is available by clicking on this TAR archive. Check also th public directory for the index.html file.

An example of the execution of the code is given in the Figure below. On the left side you get the messages on the console, on the right side of the Figure you get the bar chart.


Christophe Cérin
christophe.cerin@univ-paris13.fr
May 10, 2020