HotSpot Intrinsics

Volker Simonis [Фолкер Симонис], SAP / volker.simonis@gmail.com

Intrinsics

“ ..an intrinsic function (a.k.a. builtin function) is a function available for use in a given programming language whose implementation is handled specially by the compiler. Typically, it substitutes a sequence of automatically generated instructions for the original function call.. ”

“ ..the compiler has an intimate knowledge of the intrinsic function and can therefore better integrate it and optimize it for the situation.. ”

Intrinsics

The “HelloWorld” Intrinsic

              

              
            

The “HelloWorld” Intrinsic - DEMO

              
$ java -Xint              org.simonis.HelloWorld
Hello JETConf!
6,968,398ns

$ java -Xint -XX:+JETConf org.simonis.HelloWorld
Hello JETConf!
  153,191ns
              
            

Observability - Bytecode Instrumentation

  • Allows dynamic changes of Java Bytecodes at runtime
  • Only addition of bytecodes to Java methods
  • Used by monitoring agents, profilers, coverage tools and event loggers
  • Implemented in java.lang.instrument
  • Loaded into the VM with -javaagent:jarpath[=options]
  • The agent implements "premain(String args, Instrumentation inst)"

The Java Instrumentation Agent

              
 classBeingRedefined,
        ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
      MethodInstrumentorClassVisitor cv = new MethodInstrumentorClassVisitor(cw);
      ClassReader cr = new ClassReader(classfileBuffer);
      cr.accept(cv, 0);
      return cw.toByteArray();
    }

  }

  static class MethodInstrumentorClassVisitor extends ClassVisitor {
    private String className;

    public MethodInstrumentorClassVisitor(ClassVisitor cv) {
      super(Opcodes.ASM5, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
      cv.visit(version, access, name, signature, superName, interfaces);
      className = name;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
      MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
      if (className.startsWith(pattern)) {
        mv = new MethodInstrumentorMethodVisitor(mv, name + desc);
      }
      return mv;
    }
  }

  static class MethodInstrumentorMethodVisitor extends MethodVisitor implements Opcodes {
    private String methodName;

    public MethodInstrumentorMethodVisitor(MethodVisitor mv, String name) {
      super(Opcodes.ASM5, mv);
      methodName = name;
    }

    @Override
    public void visitCode() {
      mv.visitCode();
      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
      mv.visitLdcInsn("-> " + methodName);
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }

    @Override
    public void visitInsn(int opcode) {
      if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn("<- " + methodName);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
      }
      mv.visitInsn(opcode);
    }
  }
}
]]>
              
            

The Manifest file:

              

              
            

Compile and create with:

              
$ javac -XDignore.symbol.file=true org/simonis/MethodInstrumentationAgent.java

$ jar cvfm MethodInstrumentationAgent.jar manifest.mf \
                               org/simonis/MethodInstrumentationAgent*.class
              
            

Observability - Bytecode Instrumentation

              
$ java              -javaagent:MethodInstrumentationAgent.jar=org/simonis \
                                                   org.simonis.HelloWorld
-> main([Ljava/lang/String;)V
-> sayHello()V
Hello JETConf!
<- sayHello()V
  651,538ns
<- main([Ljava/lang/String;)V

$ java -XX:+JETConf -javaagent:MethodInstrumentationAgent.jar=org/simonis \
                                                   org.simonis.HelloWorld
-> main([Ljava/lang/String;)V
Hello JETConf!
   51,750ns
<- main([Ljava/lang/String;)V
              
            

Observability - JVMTI Agents

  • JVMTI is the Java Virtual Machine Tool Interface
  • Allows programs to inspect the state and control the execution of the VM
  • Used by debuggers and profilers
  • A native interface to the Java VM
  • JVMTI agents are loaded during VM initialization
  • They access the VM by calling JVMTI and JNI functions

A simple tracing JVMTI Agent

              
/include/ -I /include/linux/ -o traceMethodAgent.so traceMethodAgent.cpp
#include 
#include 
#include 

const char* pattern = "";

static void printMethod(jvmtiEnv* jvmti, jmethodID method, const char* prefix) {
  char *name, *sig, *cl;
  jclass javaClass;
  jvmti->GetMethodDeclaringClass(method, &javaClass);
  jvmti->GetClassSignature(javaClass, &cl, NULL);
  ++cl; // Ignore leading 'L'
  if (strstr(cl, pattern) == cl) {
    jvmti->GetMethodName(method, &name, &sig, NULL);
    cl[strlen(cl) - 1] = '\0'; // Strip trailing ';'
    fprintf(stdout, "%s %s::%s%s\n", prefix, cl, name, sig);
    fflush (NULL);
    jvmti->Deallocate((unsigned char*) name);
    jvmti->Deallocate((unsigned char*) sig);
  }
  jvmti->Deallocate((unsigned char*) --cl);
}

void JNICALL methodEntryCallback(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method) {
  printMethod(jvmti, method, "->");
}

void JNICALL methodExitCallback(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, jboolean except, jvalue ret_val) {
  printMethod(jvmti, method, "<-");
}

extern "C"
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
  jvmtiEnv* jvmti = NULL;
  jvmtiCapabilities capa;
  jvmtiError error;

  if (options) pattern = strdup(options); // Options may contain the pattern

  jint result = jvm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_1);
  if (result != JNI_OK) {
    fprintf(stderr, "Can't access JVMTI!\n");
    return JNI_ERR;
  }

  memset(&capa, 0, sizeof(jvmtiCapabilities));
  capa.can_generate_method_entry_events = 1;
  capa.can_generate_method_exit_events = 1;
  if (jvmti->AddCapabilities(&capa) != JVMTI_ERROR_NONE) {
    fprintf(stderr, "Can't set capabilities!\n");
    return JNI_ERR;
  }
  jvmtiEventCallbacks callbacks;
  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
  callbacks.MethodEntry = methodEntryCallback;
  callbacks.MethodExit = methodExitCallback;
  if (jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks)) != JVMTI_ERROR_NONE) {
    fprintf(stderr, "Can't set event callbacks!\n");
    return JNI_ERR;
  }
  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL) != JVMTI_ERROR_NONE) {
    fprintf(stderr, "Can't enable JVMTI_EVENT_METHOD_ENTRY!\n");
    return JNI_ERR;
  }
  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL) != JVMTI_ERROR_NONE) {
    fprintf(stderr, "Can't enable JVMTI_EVENT_METHOD_EXIT!\n");
    return JNI_ERR;
  }
}
]]>
              
            

Observability - JVMTI Agents

              
$ java -XX:-JETConf  -agentpath:traceMethodAgent.so=org/simonis \
                                         org.simonis.HelloWorld
-> org/simonis/HelloWorld::main([Ljava/lang/String;)V
-> org/simonis/HelloWorld::sayHello()V
Hello JETConf!
<- org/simonis/HelloWorld::sayHello()V
24,256,161ns
<- org/simonis/HelloWorld::main([Ljava/lang/String;)V

$ java -XX:+JETConf -agentpath:traceMethodAgent.so=org/simonis \
                                        org.simonis.HelloWorld
-> org/simonis/HelloWorld::main([Ljava/lang/String;)V
Hello JETConf!
  171,417ns
<- org/simonis/HelloWorld::main([Ljava/lang/String;)V
              
            

https://bugs.openjdk.java.net/browse/JDK-8013579

JDK-8013579: Intrinsics for Java methods don't take class redefinition into account

DEMO - Math Intrinsics

              

              
            

Math Intrinsics & Time To Safepoint (TTS)

              

              
            

-XX:+UseCountedLoopSafepoints (JDK-6869327)

JDK-6869327: Add new C2 flag to keep safepoints in counted loops

HotSpot Intrinsics

Defined in: src/share/vm/classfile/vmSymbols.hpp

  • Library intrinsics (~260)
    • Library methods replaced by assembly, IR or both
  • Bytecode intrinsics (~40)
    • Late/always inlining candidates (e.g. StringBuffer::*, boxing/unboxing methods)
    • See: Compile::should_delay_string_inlining() in doCall.cpp
    • See: InlineTree::should_inline() in bytecodeInfo.cpp
  • Only for system classes
  • Must be annotated with jdk.internal.HotSpotIntrinsicCandidate (since Java 9)
    • Checked by -XX:+CheckIntrinsics

Library Intrinsics

  • Interpreter:
    • Implements only few intrinsics (notably Math and CRC32)
    • Generates special entry points (see: AbstractInterpreter::MethodKind())
  • C1 (aka "client") JIT:
    • Mostly Unsafe and Math intrinsics (see Compiler::is_intrinsic_supported())
    • See: GraphBuilder::build_graph_for_intrinsic()
  • C2 (aka "server") JIT:
    • See C2Compiler::is_intrinsic_supported() for a complete list
    • See LibraryCallKit::try_to_inline() in library_call.cpp
    • Create new "Intermediate Representation" (i.e. IR) nodes..
      ..and match these nodes with corresponding "Machine Nodes" in .ad file
    • Use IdealKit/GraphKit to mimic the functionality directly in IR
      (see PhaseStringOpts methods in stringopts.hpp

Controlling Intrinsics

  • -XX:+PrintCompilation -XX:+PrintInlining
                    
    
                    
                  
  • -XX:+CheckIntrinsics (together with @HotSpotIntrinsicCandidate)>
                    
    
                    
                  
  • -XX:DisableIntrinsic={_dsqrt, ...} (use id from vmSymbols.hpp)
  • -XX:+PrintIntrinsics
  • -XX:+Use{AES,CRC32,GHASH,MathExact,...}Intrinsics
  • -XX:-InlineNatives, -XX:-Inline{Math, Class, Thread, ...}Natives
    -XX:-Inline{UnsafeOps, ArrayCopy, ObjectHash}

Native vs. Intrinsic methods

  • JVM state (safepoints)
  • Java vs. native calling conventions
  • Platform dependent (native implementation required!)
  • OK for 'expensive' calls like I/O

Implementing an Interpreter Intrinsics

Define in: src/share/vm/classfile/vmSymbols.hpp

              

              
            

Define in: src/share/vm/interpreter/abstractInterpreter.hpp

              

              
            

Implementing an Interpreter Intrinsics

Map in: src/share/vm/interpreter/abstractInterpreter.cpp

              
intrinsic_id() == vmIntrinsics::_sayHello) {                       
      return HelloWorld_sayHello;
]]>
              
            

Generate in: src/share/vm/interpreter/templateInterpreterGenerator.cpp

              

              
            

Implementing an Interpreter Intrinsics

Implement in: src/cpu/x86/vm/templateInterpreterGenerator_x86_64.cpp

              

address TemplateInterpreterGenerator::generate_sayHello() {
  // r13: sender sp
  // stack: [ ret adr ] <-- rsp
  address entry_point = __ pc();
  const char *msg = "Hello JETConf!\n";
  __ mov64(c_rarg1, (long)stdout);
  __ mov64(c_rarg0, (long)msg);
  __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, fputs)));
  __ pop(rax);
  __ mov(rsp, r13);
  __ jmp(rax);
  return entry_point;
}
]]>
              
            

Implementing an Interpreter Intrinsics

Enable in: src/share/vm/oops/method.cpp

              
name()->equals("org/simonis/HelloWorld")) {  // <----
    // check for org.simonis.HelloWorld                           // <----
    return vmSymbols::find_sid(ik->name());                       // <----
  }
  if ((ik->class_loader() != NULL)) {
    return vmSymbols::NO_SID;   // regardless of name, no intrinsics here
  }
  // see if the klass name is well-known:
  Symbol* klass_name = ik->name();
  return vmSymbols::find_sid(klass_name);
}
]]>
              
            

Implementing a C2-JIT Intrinsic

Intrinsify: java.util.Random.nextInt()

              

              
            

Define in: src/share/vm/classfile/vmSymbols.hpp

              

              
            

Implementing a C2-JIT Intrinsic

Detect rdrand instruction in: src/cpu/x86/vm/vm_version_x86.hpp

              

              
            

Implement rdrand in: src/cpu/x86/vm/assembler_x86.hpp

              
encoding());                       
  emit_int8(0x0F);
  emit_int8((unsigned char)0xC7);
  emit_int8((unsigned char)(0xF0 | encode));
]]>
              
            

Implementing a C2-JIT Intrinsic

Implement new IR-node in: src/share/vm/opto/intrinsicnode.hpp

              

              
            

Implementing a C2-JIT Intrinsic

And corresponding Machine-node and match rule in: src/cpu/x86/vm/x86.ad

              

              
            
              

              
            

Implementing a C2-JIT Intrinsic

Use it in: src/share/vm/opto/library_call.cpp

              

              
            
              

              
            

Implementing a C2-JIT Intrinsic

CompileBroker::compiler_thread_loop()
  CompileBroker::invoke_compiler_on_method(task=0x7ffff01da940)
  C2Compiler::compile_method(target=0x7fffc4299130)
  Compile::Compile(compiler=0x7ffff01a0a60, target=0x7fffc4299130)
  ParseGenerator::generate()
  Parse::Parse(caller=0x7fffc440de00, parse_method=0x7fffc4299130)
  Parse::do_all_blocks()
  Parse::do_one_block()
  Parse::do_one_bytecode()
  Parse::do_call()
  LibraryIntrinsic::generate()
  LibraryCallKit::try_to_inline(id=vmIntrinsics::_nextInt)
  LibraryCallKit::inline_random()

Random.nextInt() - DEMO

              
 0 ? args[0] : "10");
    int result = 0;
    for (int i = 0; i < count; i++) {
      result += foo();
    }
    System.out.println(result);
  }
}
]]>
              
            

Random.nextInt() - DEMO

              
$ time java org.simonis.Random 1000000
-886841403

real	0m2.228s
user	0m2.144s
sys	0m0.088s

$ time java -XX:DisableIntrinsic=_nextInt org.simonis.Random 1000000
695536079

real	0m3.187s
user	0m2.708s
sys	0m0.472s
              
            
              
$ java -XX:+PrintCompilation -XX:+PrintInlining org.simonis.Random 10000
...
 org.simonis.Random::foo (7 bytes)
   @ 3   java.util.Random::nextInt (7 bytes)   (intrinsic)

$ java -XX:+PrintCompilation -XX:+PrintInlining -XX:DisableIntrinsic=_nextInt \
                                                     org.simonis.Random 10000
...
 org.simonis.Random::foo (7 bytes)
   @ 3 java.util.Random::nextInt (7 bytes) inline (hot)
     @ 3 java.security.SecureRandom::next (61 bytes) inline (hot)
       @ 17 java.security.SecureRandom::nextBytes (9 bytes) inline (hot)
         @ 5 java.security.SecureRandomSpi::engineNextBytes (0 bytes) virtual call
...
 sun.security.provider.NativePRNG::engineNextBytes (8 bytes)
   @ 4 sun.security.provider.NativePRNG$RandomIO::access$400 (6 bytes) inline (hot)
     @ 2 sun.security.provider.NativePRNG$RandomIO::implNextBytes (162 bytes) already compiled into a big method
              
            
              
$ java -XX:CompileCommand="option org.simonis.Random::foo PrintOptoAssembly" \
                                                   org.simonis.Random 10000
000   B1: #     N1 <- BLOCK HEAD IS JUNK   Freq: 1
000     # stack bang (96 bytes)
        pushq   rbp             # Save rbp
        subq    rsp, #16        # Create frame

00c     RANDI    RAX            # int

$ java -XX:CompileCommand="option org.simonis.Random::foo PrintAssembly" \
                                                   org.simonis.Random 10000
 ;; B1: #       N1 <- BLOCK HEAD IS JUNK   Freq: 1
8c0: mov    %eax,-0x16000(%rsp)
8c7: push   %rbp
8c8: sub    $0x10,%rsp         ;*synchronization entry
                               ; - org.simonis.Random::foo@-1 (line 10)
8cc: rdrand %eax               ;*invokevirtual nextInt {reexecute=0 rethrow=0..
                               ; - org.simonis.Random::foo@3 (line 10)
              
            

System.arraycopy() - Bug

System.arraycopy() API Definition

see http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#arraycopy

System.arraycopy() - Bug

              

              
            
              
$ java org.simonis.ArrayCopy 1000

$ java org.simonis.ArrayCopy 100000
Exception in thread "main" java.lang.RuntimeException: \
  Expected IndexOutOfBoundsException for System.arracopy(.., -1)
	at org.simonis.ArrayCopy.main(ArrayCopy.java:19)

$ java -XX:+PrintEscapeAnalysis -XX:+PrintEliminateAllocations \
                                                   org.simonis.ArrayCopy 100000
======== Connection graph for  org.simonis.ArrayCopy::arraycopy
JavaObject NoEscape(NoEscape) 40 AllocateArray = ArrayCopy::arraycopy @ bci:4
++++ Eliminated: 40 AllocateArray
Exception in thread "main" java.lang.RuntimeException: \
  Expected IndexOutOfBoundsException for System.arracopy(.., -1)
	at org.simonis.ArrayCopy.main(ArrayCopy.java:19)

$ java -XX:+PrintEscapeAnalysis -XX:+PrintEliminateAllocations \
       -XX:-EliminateAllocations                   org.simonis.ArrayCopy 100000
======== Connection graph for  org.simonis.ArrayCopy::arraycopy
JavaObject NoEscape(NoEscape) 40 AllocateArray = ArrayCopy::arraycopy @ bci:4
              
            
Compile::Compile(compiler=0x7ffff01a0a60, target=0x7fffc4299130)
  ParseGenerator::generate()
  Parse::Parse(caller=0x7fffc440de00, parse_method=0x7fffc4299130)
  Parse::do_all_blocks()
  Parse::do_one_block()
  Parse::do_one_bytecode()
  Parse::do_call()
  LibraryIntrinsic::generate()
  LibraryCallKit::try_to_inline(id=vmIntrinsics::_arraycopy)
  LibraryCallKit::inline_arraycopy()
Compile::optimze()
  PhaseMacroExpand::eliminate_macro_nodes()
  PhaseMacroExpand::eliminate_allocate_node(AllocateNode*, ...)
  PhaseMacroExpand::process_users_of_allocation()
PhaseMacroExpand::expand_macro_nodes()
  PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode*)
  PhaseMacroExpand::generate_arraycopy(ArrayCopyNode*, AllocateNode*, ...)

src/share/vm/opto/library_call.cpp

              

              
            

src/share/vm/opto/macro.cpp

              
is_ArrayCopy()) {
    // Disconnect ArrayCopy node
    ArrayCopyNode* ac = use->as_ArrayCopy();
    ...
    // Disconnect src right away: it can help find new opportunities ...
    Node* src = ac->in(ArrayCopyNode::Src);
]]>
              
            

src/share/vm/opto/macroArrayCopy.cpp

              

              
            

https://bugs.openjdk.java.net/browse/JDK-8159611

JDK-8159611: C2: ArrayCopy elimination skips required parameter checks

https://github.com/simonis/JBreak2016

https://rawgit.com/simonis/JBreak2016/master/jbreak2016.xhtml