:: Enseignements :: Master :: M2 :: 2024-2025 :: Virtual Runtime Environment (and stuff around ...) ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | Lab 3 - JVM Interpreter |
We now want to implement an interpreter based on the JVM.
Here are two videos of Charles Nutter, one of the creators of JRuby that
explains how the Java bytecode works and how to use method handles
Java Bytecode for Dummies
The method handle API
Exercice 1 - JVM interpreter
The aim of this exercise is to write a simple interpreter/compiler that
will generate a Java Class with one static method for each function of
smalljs.
One goal is to make the interpreter lazy and to only compile a method when
needed (i.e. at the first execution) so the interpreter will work mostly
like a JIT.
Once a function is translated to bytecode, the interpreter will transfer
the execution to the JVM
-
What is the purpose of the classes
-
ByteCodeRewriter,
-
FunClassLoader,
-
FunDictionary,
-
RT and
-
JVMInterpreter ?
How the method Rewriter.createFunction works in details ?
-
In the Rewriter visitor, implement the support of Literal.
Warning: ldc in bytecode only load primitive and String, to load a constant which
is an Integer you have use a ldc constant dynamic with the boostrap method bsm_const.
Verify that the test marked "Q2" pass.
-
Verify that the string literal are also supported by executing the test marked "Q3".
-
Add the support of the function call by implementing the visit of FunCall.
For that, you need to generate an instruction invokedynamic on the bootstrap method bsm_funcall.
Verify that the test marked "Q4" pass.
Note: When an identifier is not a local variable, a ldc constant dynamic is emitted.
Implement the visit of LocalVarAccess to add that behavior.
Note2: in case the local variable exists, throw an exception, we will change that later.
-
Verify that the test marked "Q5" pass.
-
Verify that the return value are propagated properly so the test marked "Q6" pass.
-
Also Verify that the test marked "Q7" pass.
-
Add the support to rewrite the declaration of a variable and the loading of that variable
by implementing respectively the visit of LocalVarAssignment and LocalVarAccess.
Verify that the tests marked "Q8" pass.
-
Verify that the test marked "Q9" pass.
-
We now want to support the declaration of user defined function.
The idea is to pass Fun as an argument of a ldc constant dynamic,
but arguments can only be constant, so we reuse the dictionary trick.
Verify that the tests marked "Q10" pass.
-
In order to implement the if ... else, implement the visit of If
ast node and verify that the tests marked "Q11" pass.
-
Verify that the tests marked "Q12" pass.
Exercice 2 - speculative optimizations
In JavaScript, the qualifier of a function call can come from two ways,
either from a value on the stack (often a local variable) or from the global object.
If the qualifier value comes from the global object, we can try to use
an optimization techniques known as speculative optimization.
The idea is that for a call site of a function call, the qualifier may stay the same
like by example when the function print is called in a look,
in that case, it's better to organize the generated code with a fast path
and a slow path.
The fast path can quickly check that the function is print so print can be directly called.
The slow path will do the normal slow call.
The api java.lang.invoke have been designed especially to solve this issue
by enabling a tree of method handles to be generated directly in assembler by the JIT
if the method handles are constant.
Here the idea is to implement a speculative optimization i.e. to install a
guard in front of the call that will check that the function value taken
as first argument (the value of the qualifier) will not changed. If the
function value doesn't change, the fast patch can call it directly. If the
function value change at some point in time, the function can be called in
the slow path and the guard removed so all next calls will never check if
the function is constant or not.
Because we need to be able to mutate the code if the speculative optimization doesn't work
(here if the qualifier is not constant), instead of using a
ConstantCallSite,
we will use a
MutableCallSite.
Here is an example of how to use a
MutableCallSite:
...
public static CallSite bsm_funcall(Lookup lookup, String name, MethodType type) {
return new InliningCache(type);
}
private static class InliningCache extends MutableCallSite {
private static final MethodHandle SLOW_PATH;
static {
var lookup = MethodHandles.lookup();
try {
SLOW_PATH = lookup.findVirtual(InliningCache.class, "slowPath", methodType(MethodHandle.class, Object.class, Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public InliningCache(MethodType type) {
super(type);
setTarget(MethodHandles.foldArguments(MethodHandles.exactInvoker(type), SLOW_PATH.bindTo(this)));
}
private MethodHandle slowPath(Object qualifier, Object receiver) {
var jsObject = (JSObject)qualifier;
return jsObject.invoke(receiver, args);
// var jsObject = (JSObject)qualifier;
// var mh = jsObject.getMethodHandle();
throw new UnsupportedOperationException("TODO");
}
}
...
-
What the code above do ?
Does it implement the speculative optimization ?
-
Modify the code to install a speculative guard that check that the
function value doesn't not change. If it doesn't work, it will try to install a new guard
with the new qualifier.
Note: builtin methods like print are specials because they
accept an illimited number of arguments.
They are varargs collectors) and can adapt themselves to several signatures.
The adaptation is done by calling asType() before any other methods.
-
Verify that the test callAUserDefinedFunctionWithTheWrongNumberOfArguments pass.
-
How to create a bi-morphic inlining cache, i.e. a cache that will install
at most 2 guard with two different qualifier before giving up and us
foldArguments + jsObject::getMethodHandle() for all calls ?
Modify the implementation to implement such bi-morphic inlining cache.
© Université de Marne-la-Vallée