:: Enseignements :: ESIPE :: E5INFO :: 2025-2026 :: Virtual Runtime Environment (and stuff around ...) ::
![[LOGO]](http://monge.univ-eiffel.fr/ens/resources/mlv.png) | Lab 3b - JVM Interpreter / Object Optimization |
Exercice 1 - get field/set field/method call
We have not finished the implementation of the JVM interpreter, we need to add the support objects :
object allocation, get field, set field and method call.
The aim of this exercise is to implement them.
Here is the code of the bootstrap methods (we will optimize them in exercise 2).
...
private static final MethodHandle GET_MH, LOOKUP_MH;
static {
var lookup = MethodHandles.lookup();
try {
LOOKUP = lookup.findVirtual(JSObject.class, "lookup", methodType(Object.class, String.class));
REGISTER = lookup.findVirtual(JSObject.class, "register", methodType(void.class, String.class, Object.class));
...
GET_MH = lookup.findVirtual(JSObject.class, "getMethodHandle", methodType(MethodHandle.class));
METH_LOOKUP_MH = lookup.findStatic(RT.class, "lookupMethodHandle", methodType(MethodHandle.class, JSObject.class, String.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
...
public static CallSite bsm_get(Lookup lookup, String name, MethodType type, String fieldName) {
throw new UnsupportedOperationException("TODO bsm_get");
}
public static CallSite bsm_set(Lookup lookup, String name, MethodType type, String fieldName) {
throw new UnsupportedOperationException("TODO bsm_set");
}
@SuppressWarnings("unused") // used by a method handle
private static MethodHandle lookupMethodHandle(JSObject receiver, String fieldName) {
var function = (JSObject) receiver.lookupOrDefault(fieldName, null);
if (function == null) {
throw new Failure("no method " + fieldName);
}
return function.methodHandle();
}
public static CallSite bsm_methodcall(Lookup lookup, String name, MethodType type) {
var combiner = insertArguments(LOOKUP_MH, 1, name).asType(methodType(MethodHandle.class, Object.class));
var target = foldArguments(invoker(type), combiner);
return new ConstantCallSite(target);
}
...
-
We propose this code for rewriting a New AST node:
.when(New.class, (_new, env) -> {
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKESTATIC, JSOBJECT, "newObject", "(L" + JSOBJECT + ";)L" + JSOBJECT + ';', false);
_new.initMap().forEach((key, init) -> {
mv.visitInsn(DUP);
mv.visitLdcInsn(key);
visitor.visit(init, env);
mv.visitMethodInsn(INVOKEVIRTUAL, JSOBJECT, "register", "(Ljava/lang/String;Ljava/lang/Object;)V", false);
});
})
What this code does ?
Explain mv.visitInsn(ACONST_NULL); ?
Why DUP is needed here ?
Verify that tests marked Q13 and Q14 pass.
-
Implement the visit for the AST node FieldAccess
and verify that tests marked Q15 pass.
-
Implement the visit for the AST node FieldAssignment
and verify that tests marked Q16 pass.
-
Implement the visit for the AST node MethodCall
and verify that tests marked Q17 pass.
Exercice 2 - optimizing field access using speculative optimizations
While JavaScript allows dynamically adding fields to any objects,
in practice it's rare to find such occurrence after the object creation,
so it makes sense to optimize as if objects have the same constant set of fields.
If the set of fields of objects is mostly constant it makes sense to try to optimize
using an inlining cache.
The idea is to associate one unique object (the layout) for each set of fields so
if two objects have the same set the fields, then they share the same layout object.
(this is what the class ArrayMap does).
So the algorithm is the following, first check the layout,
if it's the same layout as the last time, then the index of the field is a constant for all the object
with that layout.
The skeleton of an inlining cache for field access
public static CallSite bsm_get(Lookup lookup, String name, MethodType type, String fieldName) {
//return new ConstantCallSite(insertArguments(LOOKUP, 1, fieldName).asType(type));
return new InliningFieldCache(type, fieldName);
}
private static final class InliningFieldCache extends MutableCallSite {
private static final MethodHandle SLOW_PATH, LAYOUT_CHECK, FAST_ACCESS;
static {
var lookup = MethodHandles.lookup();
try {
...
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
private final String fieldName;
public InliningFieldCache(MethodType type, String fieldName) {
super(type);
this.fieldName = fieldName;
setTarget(SLOW_PATH.bindTo(this));
}
@SuppressWarnings("unused") // called by a MH
private Object slowPath(Object receiver) {
var jsObject = (JSObject) receiver;
// classical access to the value
var value = jsObject.lookup(fieldName);
// fast access
//var layout = jsObject.layout();
//var slot = layout.slot(fieldName); // may be -1 !
//var value = jsObject.fastAccess(slot);
throw new UnsupportedOperationException("TODO");
}
}
-
Add the guardWithTest to finish the implementation of the inlining cache.
Verify that the tests marked Q15 and Q16 still work.
-
Add a depth check so the inlining cache is only bi-morphic.
Exercice 3 - speculative optimizations without a check
When calling a function from the
globalEnv (+, -, print, etc),
instead of checking if the qualifier change or not between the calls,
we can use a
push approach instead of a
pull approach.
The idea is to notify the method handles when something used by the speculative optimization doesn't hold anymore.
The package
java.lang.invoke has a class
SwitchPoint
to switch off guards created on this
SwitchPoint when the assumption of a speculative optimization is not true anymore.
The idea is that the global environment (
globalEnv) does not change often,
so instead of looking up in the global env each time a builtin function is called,
we can lookup once and create a guard from the switch point of the (
globalEnv).
If the (
globalEnv) change, then the switch point will be invalidated
so the guard will call the fallback. In that case, we know that we have to do a lookup again.
-
First we need to change the code that call a function in the BytecodeRewriter
to generate an invokedynamic different if the qualifier comes from the globalEnv
How to know in the case Call if the qualifier comes from the globalEnv ?
case Call(Expr qualifier, List<Expr> args, _) -> {
// is this a call using the global env ?
if (...) {
// load "this"
// for each argument, visit it
// generate an invokedynamic
mv.visitInvokeDynamicInsn("globalcall", desc, BSM_GLOBALCALL, local.name());
return;
}
...
Change the code to generate an invokedynamic that calls a boostrap method bsm_global_funcall
that takes the name of the field as bootstrap argument.
-
What is the code of the bootstrap method in RT ?
public static CallSite bsm_globalcall(Lookup lookup, String name, MethodType type, String variableName) {
throw new UnsupportedOperationException("TODO global call");
}
-
We know want to use an inlining cache
private static final class GlobalEnvInliningCache extends MutableCallSite {
private static final MethodHandle SLOW_PATH;
static {
var lookup = MethodHandles.lookup();
try {
SLOW_PATH = lookup.findVirtual(GlobalEnvInliningCache.class, "slowPath", methodType(MethodHandle.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
// ...
private GlobalEnvInliningCache(MethodType type, ...) {
// TODO
super(type);
setTarget(MethodHandles.foldArguments(MethodHandles.exactInvoker(type), SLOW_PATH.bindTo(this)));
}
@SuppressWarnings("unused") // called by a MH
private MethodHandle slowPath() {
throw new UnsupportedOperationException("TODO");
}
}
What are the arguments of the constructor GlobalEnvInliningCache ?
How to find the JSObject in the globalEnv ?
How to check that the number of argument is okay ?
How to use the SwitchPoint knowning there is a méthode JSObject.switchPoint() ?
How to manage the invalidation of the switch point ?
Checks that all the tests are okay.
© Université de Marne-la-Vallée