diff options
author | 2017-11-27 15:39:04 -0800 | |
---|---|---|
committer | 2017-12-01 10:53:46 -0800 | |
commit | 6b1aebe3612a6e87d7d1847ccca0d7a213cd22a2 (patch) | |
tree | 8348a6b921579a33e5c1fc07ac55e6e730dda670 | |
parent | 45d3efbc433e321d0fdb3de54b01cf056c3d85ba (diff) |
Allow devirtualized method to be intrinsified.
For a invocation that's devirtualized to a different method, try
to give intrinsics matching an opportunity before trying to inline it.
Test: run-test on host. 638-checker-inline-cache-intrinsic.
Change-Id: I51f70835db4c07575c58872a64a603a38dbcb89c
-rw-r--r-- | compiler/optimizing/inliner.cc | 57 | ||||
-rw-r--r-- | compiler/optimizing/instruction_simplifier.cc | 4 | ||||
-rw-r--r-- | compiler/optimizing/intrinsics.cc | 12 | ||||
-rw-r--r-- | compiler/optimizing/intrinsics.h | 2 | ||||
-rw-r--r-- | test/638-checker-inline-cache-intrinsic/expected.txt | 1 | ||||
-rw-r--r-- | test/638-checker-inline-cache-intrinsic/info.txt | 1 | ||||
-rw-r--r-- | test/638-checker-inline-cache-intrinsic/run | 17 | ||||
-rw-r--r-- | test/638-checker-inline-cache-intrinsic/src/Main.java | 95 |
8 files changed, 174 insertions, 15 deletions
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 2444e43d64..560372e22e 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -1211,11 +1211,49 @@ bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction, ReferenceTypeInfo receiver_type, bool do_rtp, bool cha_devirtualize) { + DCHECK(!invoke_instruction->IsIntrinsic()); HInstruction* return_replacement = nullptr; uint32_t dex_pc = invoke_instruction->GetDexPc(); HInstruction* cursor = invoke_instruction->GetPrevious(); HBasicBlock* bb_cursor = invoke_instruction->GetBlock(); - if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) { + bool should_remove_invoke_instruction = false; + + // If invoke_instruction is devirtualized to a different method, give intrinsics + // another chance before we try to inline it. + bool wrong_invoke_type = false; + if (invoke_instruction->GetResolvedMethod() != method && + IntrinsicsRecognizer::Recognize(invoke_instruction, method, &wrong_invoke_type)) { + MaybeRecordStat(stats_, MethodCompilationStat::kIntrinsicRecognized); + if (invoke_instruction->IsInvokeInterface()) { + // We don't intrinsify an invoke-interface directly. + // Replace the invoke-interface with an invoke-virtual. + HInvokeVirtual* new_invoke = new (graph_->GetAllocator()) HInvokeVirtual( + graph_->GetAllocator(), + invoke_instruction->GetNumberOfArguments(), + invoke_instruction->GetType(), + invoke_instruction->GetDexPc(), + invoke_instruction->GetDexMethodIndex(), // Use interface method's dex method index. + method, + method->GetMethodIndex()); + HInputsRef inputs = invoke_instruction->GetInputs(); + for (size_t index = 0; index != inputs.size(); ++index) { + new_invoke->SetArgumentAt(index, inputs[index]); + } + invoke_instruction->GetBlock()->InsertInstructionBefore(new_invoke, invoke_instruction); + new_invoke->CopyEnvironmentFrom(invoke_instruction->GetEnvironment()); + if (invoke_instruction->GetType() == DataType::Type::kReference) { + new_invoke->SetReferenceTypeInfo(invoke_instruction->GetReferenceTypeInfo()); + } + // Run intrinsic recognizer again to set new_invoke's intrinsic. + IntrinsicsRecognizer::Recognize(new_invoke, method, &wrong_invoke_type); + DCHECK_NE(new_invoke->GetIntrinsic(), Intrinsics::kNone); + return_replacement = new_invoke; + // invoke_instruction is replaced with new_invoke. + should_remove_invoke_instruction = true; + } else { + // invoke_instruction is intrinsified and stays. + } + } else if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) { if (invoke_instruction->IsInvokeInterface()) { DCHECK(!method->IsProxyMethod()); // Turn an invoke-interface into an invoke-virtual. An invoke-virtual is always @@ -1258,26 +1296,27 @@ bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction, new_invoke->SetReferenceTypeInfo(invoke_instruction->GetReferenceTypeInfo()); } return_replacement = new_invoke; - // Directly check if the new virtual can be recognized as an intrinsic. - // This way, we avoid running a full recognition pass just to detect - // these relative rare cases. - bool wrong_invoke_type = false; - if (IntrinsicsRecognizer::Recognize(new_invoke, &wrong_invoke_type)) { - MaybeRecordStat(stats_, MethodCompilationStat::kIntrinsicRecognized); - } + // invoke_instruction is replaced with new_invoke. + should_remove_invoke_instruction = true; } else { // TODO: Consider sharpening an invoke virtual once it is not dependent on the // compiler driver. return false; } + } else { + // invoke_instruction is inlined. + should_remove_invoke_instruction = true; } + if (cha_devirtualize) { AddCHAGuard(invoke_instruction, dex_pc, cursor, bb_cursor); } if (return_replacement != nullptr) { invoke_instruction->ReplaceWith(return_replacement); } - invoke_instruction->GetBlock()->RemoveInstruction(invoke_instruction); + if (should_remove_invoke_instruction) { + invoke_instruction->GetBlock()->RemoveInstruction(invoke_instruction); + } FixUpReturnReferenceType(method, return_replacement); if (do_rtp && ReturnTypeMoreSpecific(invoke_instruction, return_replacement)) { // Actual return value has a more specific type than the method's declared diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 7fa0c2be3d..089e41b4f4 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -2035,7 +2035,9 @@ void InstructionSimplifierVisitor::SimplifyStringEquals(HInvoke* instruction) { optimizations.SetArgumentIsString(); } else if (kUseReadBarrier) { DCHECK(instruction->GetResolvedMethod() != nullptr); - DCHECK(instruction->GetResolvedMethod()->GetDeclaringClass()->IsStringClass()); + DCHECK(instruction->GetResolvedMethod()->GetDeclaringClass()->IsStringClass() || + // Object.equals() can be devirtualized to String.equals(). + instruction->GetResolvedMethod()->GetDeclaringClass()->IsObjectClass()); Runtime* runtime = Runtime::Current(); // For AOT, we always assume that the boot image shall contain the String.class and // we do not need a read barrier for boot image classes as they are non-moveable. diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index 77199242f5..6928b70df7 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -137,7 +137,7 @@ static bool CheckInvokeType(Intrinsics intrinsic, HInvoke* invoke) case kVirtual: // Call might be devirtualized. - return (invoke_type == kVirtual || invoke_type == kDirect); + return (invoke_type == kVirtual || invoke_type == kDirect || invoke_type == kInterface); case kSuper: case kInterface: @@ -148,8 +148,12 @@ static bool CheckInvokeType(Intrinsics intrinsic, HInvoke* invoke) UNREACHABLE(); } -bool IntrinsicsRecognizer::Recognize(HInvoke* invoke, /*out*/ bool* wrong_invoke_type) { - ArtMethod* art_method = invoke->GetResolvedMethod(); +bool IntrinsicsRecognizer::Recognize(HInvoke* invoke, + ArtMethod* art_method, + /*out*/ bool* wrong_invoke_type) { + if (art_method == nullptr) { + art_method = invoke->GetResolvedMethod(); + } *wrong_invoke_type = false; if (art_method == nullptr || !art_method->IsIntrinsic()) { return false; @@ -182,7 +186,7 @@ void IntrinsicsRecognizer::Run() { HInstruction* inst = inst_it.Current(); if (inst->IsInvoke()) { bool wrong_invoke_type = false; - if (Recognize(inst->AsInvoke(), &wrong_invoke_type)) { + if (Recognize(inst->AsInvoke(), /* art_method */ nullptr, &wrong_invoke_type)) { MaybeRecordStat(stats_, MethodCompilationStat::kIntrinsicRecognized); } else if (wrong_invoke_type) { LOG(WARNING) diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h index c07a99032a..62991435c7 100644 --- a/compiler/optimizing/intrinsics.h +++ b/compiler/optimizing/intrinsics.h @@ -47,7 +47,7 @@ class IntrinsicsRecognizer : public HOptimization { // Static helper that recognizes intrinsic call. Returns true on success. // If it fails due to invoke type mismatch, wrong_invoke_type is set. // Useful to recognize intrinsics on individual calls outside this full pass. - static bool Recognize(HInvoke* invoke, /*out*/ bool* wrong_invoke_type) + static bool Recognize(HInvoke* invoke, ArtMethod* method, /*out*/ bool* wrong_invoke_type) REQUIRES_SHARED(Locks::mutator_lock_); static constexpr const char* kIntrinsicsRecognizerPassName = "intrinsics_recognition"; diff --git a/test/638-checker-inline-cache-intrinsic/expected.txt b/test/638-checker-inline-cache-intrinsic/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/638-checker-inline-cache-intrinsic/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/638-checker-inline-cache-intrinsic/info.txt b/test/638-checker-inline-cache-intrinsic/info.txt new file mode 100644 index 0000000000..764577be54 --- /dev/null +++ b/test/638-checker-inline-cache-intrinsic/info.txt @@ -0,0 +1 @@ +Verify the devirtualization of a method that should be intrinsified. diff --git a/test/638-checker-inline-cache-intrinsic/run b/test/638-checker-inline-cache-intrinsic/run new file mode 100644 index 0000000000..f43681dd56 --- /dev/null +++ b/test/638-checker-inline-cache-intrinsic/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +exec ${RUN} --jit --runtime-option -Xjitthreshold:100 -Xcompiler-option --verbose-methods=inlineMonomorphic,knownReceiverType,stringEquals $@ diff --git a/test/638-checker-inline-cache-intrinsic/src/Main.java b/test/638-checker-inline-cache-intrinsic/src/Main.java new file mode 100644 index 0000000000..472cbf68bc --- /dev/null +++ b/test/638-checker-inline-cache-intrinsic/src/Main.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + + /// CHECK-START: char Main.$noinline$inlineMonomorphic(java.lang.CharSequence) inliner (before) + /// CHECK: InvokeInterface method_name:java.lang.CharSequence.charAt + + /// CHECK-START: char Main.$noinline$inlineMonomorphic(java.lang.CharSequence) inliner (after) + /// CHECK: Deoptimize + /// CHECK: InvokeVirtual method_name:java.lang.String.charAt intrinsic:StringCharAt + + /// CHECK-START: char Main.$noinline$inlineMonomorphic(java.lang.CharSequence) instruction_simplifier$after_inlining (after) + /// CHECK: Deoptimize + /// CHECK-NOT: InvokeInterface + /// CHECK-NOT: InvokeVirtual + + public static char $noinline$inlineMonomorphic(CharSequence cs) { + return cs.charAt(0); + } + + /// CHECK-START: char Main.$noinline$knownReceiverType() inliner (before) + /// CHECK: InvokeInterface method_name:java.lang.CharSequence.charAt + + /// CHECK-START: char Main.$noinline$knownReceiverType() inliner (after) + /// CHECK: InvokeVirtual method_name:java.lang.String.charAt intrinsic:StringCharAt + + /// CHECK-START: char Main.$noinline$knownReceiverType() instruction_simplifier$after_inlining (after) + /// CHECK-NOT: InvokeInterface + /// CHECK-NOT: InvokeVirtual + + public static char $noinline$knownReceiverType() { + CharSequence cs = "abc"; + return cs.charAt(1); + } + + /// CHECK-START: boolean Main.$noinline$stringEquals(java.lang.Object) inliner (before) + /// CHECK: InvokeVirtual method_name:java.lang.Object.equals intrinsic:None + + /// CHECK-START: boolean Main.$noinline$stringEquals(java.lang.Object) inliner (after) + /// CHECK: Deoptimize + /// CHECK: InvokeVirtual method_name:java.lang.Object.equals intrinsic:StringEquals + + /// CHECK-START: boolean Main.$noinline$stringEquals(java.lang.Object) instruction_simplifier$after_inlining (after) + /// CHECK: Deoptimize + /// CHECK: InvokeVirtual method_name:java.lang.Object.equals intrinsic:StringEquals + + public static boolean $noinline$stringEquals(Object obj) { + return obj.equals("def"); + } + + public static void test() { + // Warm up inline cache. + for (int i = 0; i < 45; i++) { + $noinline$inlineMonomorphic(str); + } + for (int i = 0; i < 60; i++) { + $noinline$stringEquals(str); + } + ensureJitCompiled(Main.class, "$noinline$stringEquals"); + ensureJitCompiled(Main.class, "$noinline$inlineMonomorphic"); + ensureJitCompiled(Main.class, "$noinline$knownReceiverType"); + if ($noinline$inlineMonomorphic(str) != 'x') { + throw new Error("Expected x"); + } + if ($noinline$knownReceiverType() != 'b') { + throw new Error("Expected b"); + } + if ($noinline$stringEquals("abc")) { + throw new Error("Expected false"); + } + } + + public static void main(String[] args) { + System.loadLibrary(args[0]); + test(); + } + + static String str = "xyz"; + + private static native void ensureJitCompiled(Class<?> itf, String method_name); +} |