Selective C2 optimizations using annotations

In this post I will be talking about how to make C2 selectively optimize methods based on a newly created annotation.  I will also describe the parts of the code that must be modified to implement such feature.

Do you need annotations to optimize parts of your code?

Annotations are a useful feature to provide metadata for a program. Developers can create new annotations and have them consumed by libraries, frameworks, java agents and source code analysis tools. Some specific annotations are also recognized by at least one of the Hostpot’s compilers. For example,  the @ForceInline annotation is used to indicate that a given method must be inlined.

At Diverse, we wanted to modify the C2 compiler to include a new experimental, approximate optimization that will trade-off accuracy for speed. Approximate optimizations does not maintain the logic of the program, meaning that such optimizations can only be applied in certain cases. This forces us to manually annotate the parts of a program that can be optimized. Since we wanted to implement the optimization in the C2 compiler, we also had to modify it to let it recognize this new annotation, in a similar way it recognizes @ForceInline.

Adding the annotation

The first step is to include the annotation interface in the Hotspot sources. In our case, we created the fr.inria.approximated.Approximated annotation and we stored it in the jdk/src/java.base/share/classes folder of the Hotspot sources, as the following image shows:

anotation

How the annotation is consumed by C2 (The Patch)

In fact, the C2 compiler has little knowledge of the concept of annotation. The compiler is even decoupled from the rest of the VM by a layer of indirection, the Compiler Interface (ci). What actually happens is that the classFileParser class detect the few annotations that are meaningful for the VM and stores them in properties of the Java metamodel classes, such as Method and Field. These classes are indirected to the compiler by their corresponding indirection class, like ciMethod and ciField which are ultimately meaningful data for C2.

So, in order to have an annotation that influences the optimizations performed by C2 we modified several classes. The files that we modified where:

  • /hotspot/src/share/vm/classfile/vmSymbols.hpp
  • /hotspot/src/share/vm/classfile/classFileParser.cpp
  • /hotspot/src/share/vm/oops/method.hpp
  • /hotspot/src/share/vm/ci/ciMethod.hpp
  • /hotspot/src/share/vm/opto/compile.cpp

What we did was to add our annotation to the symbols known to the VM. This is achieved modifying the VM_SYMBOLS_DO macro in vmSymbols.hpp. Afterwards, we add the property approximated to both classes ciMethod and Method. The class Method holds metadata for a given instance of a method, while the ciMethod class belong to the ci layer between the compiler and the rest of the VM,  decoupling both and reducing the necessary knowledge by the compiler of the rest of the VM. Basically ciMethod wraps the Method for the compiler, it also include properties that are specific to compilation. Then, we modified the classFileParser to set the approximated property to TRUE for every parsed method annotated with our @Approximated annotation. The classFileParser uses the Method class, while the compiler C2 uses ciMethod. Finally the C2 compiler can use this property to determine whether to launch the optimization or not.

Adding the annotation’s signature to known symbols to the VM

The symbols known to the VM are defined in the /hotspot/src/share/vm/classfile/vmSymbols.hpp file. Class vmSymbols is a name space for fast lookup of symbols used by the VM. To add the signature of our annotation, we added a line to the end of a lengthy macro known as VM_SYMBOLS_DO:

template(fr_inria_approximated_Approximated_signature, "Lfr/inria/approximated/Approximated;" )

Modify the Method classes

To add the approximated property to the classes representing method of the VM, we first modified the flags set of the Method class:

enum Flags {
_jfr_towrite = 1 << 0,
_caller_sensitive = 1 << 1,
_force_inline = 1 << 2,
_dont_inline = 1 << 3,
_hidden = 1 << 4,
_has_injected_profile = 1 << 5,
_running_emcp = 1 << 6,
_intrinsic_candidate = 1 << 7,
_reserved_stack_access = 1 << 8,
_approximated = 1 << 9 //<-- OUR FLAG HERE!!
};

And then added the property:

bool approximated() {
return (_flags & _approximated) != 0;
}
void set_approximated(bool x) {
_flags = x ? (_flags | _approximated) : (_flags & ~_approximated);
}

The ciMethod wraps the properties of the Method class to the compiler, therefore we added its approximated property like this:

bool approximated() const { return get_Method()->approdximated(); }

Modifying the classFileParser

Now that the compiler can identify the symbol and the metamodel is ready to store which methods can be approximated, we  we will modify the annotation’s collector of the class file parser /hotspot/src/share/vm/classfile/classFileParser.cpp to recognize the annotation in the class file. The AnnotationCollector is the class in charge of collecting all annotations of the class elements such as methods and fields. We modify the AnnotationCollector::annotation_index list of ID adding our annotation just before the _annotation_LIMIT :

class AnnotationCollector : public ResourceObj{
public:
enum ID {
_unknown = 0,
_method_CallerSensitive,
_method_ForceInline,
_method_DontInline,
(. . .)
_method_Approximated, //<-- Our annotation
_annotation_LIMIT
};
(. . .)

And now modify the AnnotationCollector::annotation_index method like so:

switch (sid) {
case vmSymbols::VM_SYMBOL_ENUM_NAME(fr_inria_approximated_Approximated_signature): {
if (_location != _in_method) break; // only allow for methods
return _method_Approximated;
}
case vmSymbols::VM_SYMBOL_ENUM_NAME(sun_reflect_CallerSensitive_signature): {
if (_location != _in_method) break; // only allow for methods
if (!privileged) break; // only allow in privileged code
return _method_CallerSensitive;
}
(. . . )
}

This converts the symbols found in the class file into a flag in AnnotationsCollector. Afterwards, this flag is set to an instance of the Method metadata class in MethodAnnotationsCollector:

void MethodAnnotationCollector::apply_to(methodHandle m) {
if (has_annotation(_method_Approximated) )
m->set_approximated(true);
(. . .)
}

Selectively optimize

With all this modifications in place, is simply a matter  of reading the property of the ciMethod instance passed to the C2 to know wether to launch the optimization or not:

void Compile::Optimize() {
if ( target->approximated() ) {
//launch optimization
}

Conclusions

In this post we have shown the modifications needed in the source code of the compiler to add an annotation that can be used by the VM. We also saw that the C2 compiler has little knowledge of the concept of annotation and that the meaningful metadata influencing in the compilation is passed in the metadata classes and finally we got a peak at the code that had to be modified to implement the feature.

Leave a Reply

Your email address will not be published. Required fields are marked *