Volker Simonis [Фолкер Симонис], SAP / volker.simonis@gmail.com
“ ..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.. ”
HelloWorld
” Intrinsic
HelloWorld
” Intrinsic - DEMO
$ java -Xint org.simonis.HelloWorld
Hello JETConf!
6,968,398ns
$ java -Xint -XX:+JETConf org.simonis.HelloWorld
Hello JETConf!
153,191ns
java.lang.instrument
-javaagent:jarpath[=options]
premain(String args, Instrumentation inst)
"
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
$ 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
/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;
}
}
]]>
$ 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
-XX:+UseCountedLoopSafepoints
(JDK-6869327)Defined in: src/share/vm/classfile/vmSymbols.hpp
StringBuffer::*
, boxing/unboxing methods)Compile::should_delay_string_inlining()
in doCall.cpp
InlineTree::should_inline()
in bytecodeInfo.cpp
jdk.internal.HotSpotIntrinsicCandidate
(since Java 9)
-XX:+CheckIntrinsics
Math
and CRC32
)AbstractInterpreter::MethodKind()
)Unsafe
and Math
intrinsics (see Compiler::is_intrinsic_supported()
)GraphBuilder::build_graph_for_intrinsic()
C2Compiler::is_intrinsic_supported()
for a complete listLibraryCallKit::try_to_inline()
in library_call.cpp
.ad
fileIdealKit
/GraphKit
to mimic the functionality directly in IRPhaseStringOpts
methods in stringopts.hpp
-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}
Define in: src/share/vm/classfile/vmSymbols.hpp
Define in: src/share/vm/interpreter/abstractInterpreter.hpp
Map in: src/share/vm/interpreter/abstractInterpreter.cpp
intrinsic_id() == vmIntrinsics::_sayHello) {
return HelloWorld_sayHello;
]]>
Generate in: src/share/vm/interpreter/templateInterpreterGenerator.cpp
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;
}
]]>
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);
}
]]>
Intrinsify: java.util.Random.nextInt()
Define in: src/share/vm/classfile/vmSymbols.hpp
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));
]]>
Implement new IR-node in: src/share/vm/opto/intrinsicnode.hpp
And corresponding Machine-node and match rule in: src/cpu/x86/vm/x86.ad
Use it in: src/share/vm/opto/library_call.cpp
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
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