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.. ”
GCC: bzero(), snprintf(), strndup(), log2(), ..
Java: Math.sqrt(), System.arraycopy(), ..
GCC: _xbegin(), _xend(), .. // Transactional Memory
Java: Unsafe.allocateInstance(),
Thread.onSpinWait() // JEP 285
MSVC: __movsq(), __mul128()
Java: Unsafe.loadFence()
HelloWorld
” Intrinsicpublic class HelloWorld {
public static void sayHello() {
System.out.println("Hello JETConf!");
}
public static void main(String ... args) {
long start = System.nanoTime();
sayHello();
long stop = System.nanoTime();
System.out.format("%,9d%s%n", stop - start, "ns");
}
}
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)
"package org.simonis;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
public class MethodInstrumentationAgent {
static String pattern;
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new MethodInstrumentorTransformer());
pattern = args;
}
static class MethodInstrumentorTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> 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:
Manifest-VErsion: 1.0
Premain-Class: org.simonis.MethodInstrumentationAgent
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
// Build as follows:
// g++ -fPIC -shared -I <JDK>/include/ -I <JDK>/include/linux/ -o traceMethodAgent.so traceMethodAgent.cpp
#include <jvmti.h>
#include <stdio.h>
#include <string.h>
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
static double compute(int count, double d) {
for (int i = 0; i < count; i++) {
d += Math.pow(Math.sin(d), Math.sqrt(d)); // d += sin(d) ^ sqrt(d)
}
return d;
}
public static void main(String[] args) throws Exception {
double seed = args.length; // Avoid constant folding
int count = Integer.parseInt(args[0]); // Iteration count for compute()
for (int i = 0; i < 20_000; i++) {
compute(1, seed); // JIT-compile compute()
}
compute(count, seed); // Call compute() with huge count
}
public static void main(String[] args) throws Exception {
... // Same as before ...
new Thread() { // Create a new thread..
{ setDaemon(true); } // ..and make it a daemon thread.
public void run() {
while (true) {
try {
Thread.sleep(1_000); // Every second..
} catch (InterruptedException e) {}
System.gc(); // ..we trigger a GC.
}
}
}.start(); // Let it run concurrently.
compute(count, seed); // Call compute() with huge count
-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
613 1 b org.simonis.Loop::compute (29 bytes)
@ 9 java.lang.Math::sin (5 bytes) (intrinsic)
@ 13 java.lang.Math::sqrt (5 bytes) (intrinsic)
-XX:+CheckIntrinsics
(together with @HotSpotIntrinsicCandidate
)>Compiler intrinsic is defined for method [org.simonis.HelloWorld.sayHello()V],
but the method is not annotated with @HotSpotIntrinsicCandidate. Exiting.
-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
do_class( helloWorld, "org/simonis/HelloWorld") \
do_intrinsic(_sayHello, helloWorld, sayHello_name, sayHello_sign, F_S) \
do_name( sayHello_name, "sayHello") \
do_signature(sayHello_sign, "()V") \
Define in: src/share/vm/interpreter/abstractInterpreter.hpp
class AbstractInterpreter: AllStatic {
enum MethodKind {
zerolocals, // normal method (needs locals initialization)
java_lang_math_sin, // implementation of java.lang.Math.sin(x)
...
HelloWorld_sayHello, // implementation of org.simonis.HelloWorld.sayHello()
Map in: src/share/vm/interpreter/abstractInterpreter.cpp
MethodKind AbstractInterpreter::method_kind(methodHandle m) {
...
if (JETConf) {
if (m->intrinsic_id() == vmIntrinsics::_sayHello) {
return HelloWorld_sayHello;
Generate in: src/share/vm/interpreter/templateInterpreterGenerator.cpp
address TemplateInterpreterGenerator::generate_method_entry(MethodKind kind) {
switch (kind) {
case Interpreter::HelloWorld_sayHello: entry_point = generate_sayHello();
...
return entry_point;
Implement in: src/cpu/x86/vm/templateInterpreterGenerator_x86_64.cpp
#define __ _masm->
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
vmSymbols::SID Method::klass_id_for_intrinsics(const Klass* holder) {
// if loader is not the default loader (i.e., != NULL), we can't know the
// intrinsics because we are not loading from core libraries
const InstanceKlass* ik = InstanceKlass::cast(holder);
if (JETConf && ik->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()
public class Random implements java.io.Serializable {
...
@HotSpotIntrinsicCandidate
public int nextInt() {
return next(32);
}
Define in: src/share/vm/classfile/vmSymbols.hpp
do_class( java_util_Random, "java/util/Random") \
do_intrinsic(_nextInt, java_util_Random, nextInt_name, ni_sign, F_R)\
do_name( nextInt_name, "nextInt") \
do_signature(ni_sign, "()I") \
Detect rdrand
instruction in: src/cpu/x86/vm/vm_version_x86.hpp
class VM_Version {
static uint64_t feature_flags() {
if (_cpuid_info.std_cpuid1_ecx.bits.rdrand != 0)
result |= CPU_RDRAND;
static bool supports_rdrand() { return (_features & CPU_RDRAND) != 0; }
Implement rdrand
in: src/cpu/x86/vm/assembler_x86.hpp
void Assembler::rdrandl(Register dst) {
int encode = prefix_and_encode(dst->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
class RandINode : public Node {
public:
RandINode(Node *c) : Node(c) {}
const Type *bottom_type() const { return TypeInt::INT; }
virtual uint ideal_reg() const { return Op_RegI; }
And corresponding Machine-node and match rule in: src/cpu/x86/vm/x86.ad
instruct randI(rRegI dst) %{
match(Set dst (RandI));
format %{ "RANDI $dst\t# int" %}
ins_encode %{
__ rdrandl($dst$$Register);
%}
const bool Matcher::match_rule_supported(int opcode) {
if (!has_match_rule(opcode)) return false;
switch (opcode) {
case Op_RandI: if (!VM_Version::supports_rdrand()) return false;
...
return true;
Use it in: src/share/vm/opto/library_call.cpp
bool LibraryCallKit::try_to_inline(int predicate) {
...
switch (intrinsic_id()) {
case vmIntrinsics::_nextInt: return inline_random(intrinsic_id());
bool LibraryCallKit::inline_random(vmIntrinsics::ID id) {
Node* n = NULL;
switch (id) {
case vmIntrinsics::_nextInt:
if (!Matcher::match_rule_supported(Op_RandI)) return false;
n = new RandINode(control());
...
set_result(_gvn.transform(n));
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()
- DEMOpublic class Random {
static final java.util.Random sr = new SecureRandom();
static int foo() {
return sr.nextInt();
}
public static void main(String[] args) throws Exception {
int count = Integer.parseInt(args.length > 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
public class ArrayCopy {
public static boolean arraycopy(Object[] src, int length) {
try {
System.arraycopy(src, 1, new Object[8], 1, length);
return false;
} catch (IndexOutOfBoundsException e) {
return true;
}
}
public static void main(String args[]){
int count = Integer.parseInt(args[0]);
for (int x = 0; x < count; x++) {
if (arraycopy(new Object[8], -1) == false)
throw new RuntimeException("Expected IndexOutOfBoundsException...");
$ 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
// System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length);
bool LibraryCallKit::inline_arraycopy() {
...
// The following tests must be performed
// (1) src and dest are arrays.
// (2) src and dest arrays must have elements of the same BasicType
// (3) src and dest must not be null.
// (4) src_offset must not be negative.
// (5) dest_offset must not be negative.
// (6) length must not be negative.
// (7) src_offset + length must not exceed length of src.
...
// (5) dest_offset must not be negative.
generate_negative_guard(dest_offset, slow_region);
// (7) src_offset + length must not exceed length of src.
generate_limit_guard(src_offset, length, load_array_length(src), slow_reg);
...
ArrayCopyNode* ac = ArrayCopyNode::make(...)
src/share/vm/opto/macro.cpp
// Process users of eliminated allocation.
void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) {
...
if (use->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
// This routine is used from several intrinsics: System.arraycopy,
// Object.clone (the array subcase), and Arrays.copyOf[Range].
Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode*
...
// (6) length must not be negative.
generate_negative_guard(&local_ctrl, copy_length, slow_region);