Inliner: Always try code pattern recognition. am: aa02410a2d

Original change: https://android-review.googlesource.com/c/platform/art/+/2971991

Change-Id: I4cba83d09b74edb0024eba9e47f5d63a03f74a61
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc
index 381db3d..43e984b 100644
--- a/compiler/dex/inline_method_analyser.cc
+++ b/compiler/dex/inline_method_analyser.cc
@@ -152,8 +152,7 @@
   if (kIsDebugBuild && target_method != nullptr) {
     CHECK(!target_method->IsStatic());
     CHECK(target_method->IsConstructor());
-    CHECK(target_method->GetDeclaringClass() == method->GetDeclaringClass() ||
-          target_method->GetDeclaringClass() == method->GetDeclaringClass()->GetSuperClass());
+    CHECK(method->GetDeclaringClass()->IsSubClass(target_method->GetDeclaringClass()));
   }
   return target_method;
 }
@@ -256,11 +255,11 @@
                           /*inout*/ ConstructorIPutData (&iputs)[kMaxConstructorIPuts])
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // On entry we should not have any IPUTs yet.
-  DCHECK_EQ(0, std::count_if(
+  DCHECK(std::all_of(
       iputs,
       iputs + arraysize(iputs),
       [](const ConstructorIPutData& iput_data) {
-        return iput_data.field_index != DexFile::kDexNoIndex16;
+        return iput_data.field_index == DexFile::kDexNoIndex16;
       }));
 
   // Limit the maximum number of code units we're willing to match.
@@ -396,56 +395,36 @@
   return true;
 }
 
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET), "iget type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_WIDE), "iget_wide type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_OBJECT),
-              "iget_object type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BOOLEAN),
-              "iget_boolean type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BYTE), "iget_byte type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_CHAR), "iget_char type");
-static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_SHORT), "iget_short type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT), "iput type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_WIDE), "iput_wide type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_OBJECT),
-              "iput_object type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BOOLEAN),
-              "iput_boolean type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BYTE), "iput_byte type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_CHAR), "iput_char type");
-static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_SHORT), "iput_short type");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT), "iget/iput variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_WIDE) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_WIDE), "iget/iput_wide variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_OBJECT) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_OBJECT), "iget/iput_object variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BOOLEAN) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BOOLEAN), "iget/iput_boolean variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BYTE) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BYTE), "iget/iput_byte variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_CHAR) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_CHAR), "iget/iput_char variant");
-static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) ==
-    InlineMethodAnalyser::IPutVariant(Instruction::IPUT_SHORT), "iget/iput_short variant");
+static_assert(IsInstructionIGet(Instruction::IGET));
+static_assert(IsInstructionIGet(Instruction::IGET_WIDE));
+static_assert(IsInstructionIGet(Instruction::IGET_OBJECT));
+static_assert(IsInstructionIGet(Instruction::IGET_BOOLEAN));
+static_assert(IsInstructionIGet(Instruction::IGET_BYTE));
+static_assert(IsInstructionIGet(Instruction::IGET_CHAR));
+static_assert(IsInstructionIGet(Instruction::IGET_SHORT));
+static_assert(IsInstructionIPut(Instruction::IPUT));
+static_assert(IsInstructionIPut(Instruction::IPUT_WIDE));
+static_assert(IsInstructionIPut(Instruction::IPUT_OBJECT));
+static_assert(IsInstructionIPut(Instruction::IPUT_BOOLEAN));
+static_assert(IsInstructionIPut(Instruction::IPUT_BYTE));
+static_assert(IsInstructionIPut(Instruction::IPUT_CHAR));
+static_assert(IsInstructionIPut(Instruction::IPUT_SHORT));
+static_assert(IGetMemAccessType(Instruction::IGET) == IPutMemAccessType(Instruction::IPUT));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_WIDE) == IPutMemAccessType(Instruction::IPUT_WIDE));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_OBJECT) == IPutMemAccessType(Instruction::IPUT_OBJECT));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_BOOLEAN) == IPutMemAccessType(Instruction::IPUT_BOOLEAN));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_BYTE) == IPutMemAccessType(Instruction::IPUT_BYTE));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_CHAR) == IPutMemAccessType(Instruction::IPUT_CHAR));
+static_assert(
+    IGetMemAccessType(Instruction::IGET_SHORT) == IPutMemAccessType(Instruction::IPUT_SHORT));
 
-bool InlineMethodAnalyser::AnalyseMethodCode(ArtMethod* method, InlineMethod* result) {
-  CodeItemDataAccessor code_item(method->DexInstructionData());
-  if (!code_item.HasCodeItem()) {
-    // Native or abstract.
-    return false;
-  }
-  return AnalyseMethodCode(&code_item,
-                           MethodReference(method->GetDexFile(), method->GetDexMethodIndex()),
-                           method->IsStatic(),
-                           method,
-                           result);
-}
-
-bool InlineMethodAnalyser::AnalyseMethodCode(const CodeItemDataAccessor* code_item,
-                                             const MethodReference& method_ref,
-                                             bool is_static,
-                                             ArtMethod* method,
+bool InlineMethodAnalyser::AnalyseMethodCode(ArtMethod* method,
+                                             const CodeItemDataAccessor* code_item,
                                              InlineMethod* result) {
   // We currently support only plain return or 2-instruction methods.
 
@@ -492,7 +471,7 @@
     // case Instruction::IGET_QUICK:
     // case Instruction::IGET_WIDE_QUICK:
     // case Instruction::IGET_OBJECT_QUICK:
-      return AnalyseIGetMethod(code_item, method_ref, is_static, method, result);
+      return AnalyseIGetMethod(method, code_item, result);
     case Instruction::IPUT:
     case Instruction::IPUT_OBJECT:
     case Instruction::IPUT_BOOLEAN:
@@ -504,15 +483,16 @@
     // case Instruction::IPUT_QUICK:
     // case Instruction::IPUT_WIDE_QUICK:
     // case Instruction::IPUT_OBJECT_QUICK:
-      return AnalyseIPutMethod(code_item, method_ref, is_static, method, result);
+      return AnalyseIPutMethod(method, code_item, result);
     default:
       return false;
   }
 }
 
-bool InlineMethodAnalyser::IsSyntheticAccessor(MethodReference ref) {
-  const dex::MethodId& method_id = ref.dex_file->GetMethodId(ref.index);
-  const char* method_name = ref.dex_file->GetMethodName(method_id);
+bool InlineMethodAnalyser::IsSyntheticAccessor(ArtMethod* method) {
+  const DexFile* dex_file = method->GetDexFile();
+  const dex::MethodId& method_id = dex_file->GetMethodId(method->GetDexMethodIndex());
+  const char* method_name = dex_file->GetMethodName(method_id);
   // javac names synthetic accessors "access$nnn",
   // jack names them "-getN", "-putN", "-wrapN".
   return strncmp(method_name, "access$", strlen("access$")) == 0 ||
@@ -572,10 +552,8 @@
   return true;
 }
 
-bool InlineMethodAnalyser::AnalyseIGetMethod(const CodeItemDataAccessor* code_item,
-                                             const MethodReference& method_ref,
-                                             bool is_static,
-                                             ArtMethod* method,
+bool InlineMethodAnalyser::AnalyseIGetMethod(ArtMethod* method,
+                                             const CodeItemDataAccessor* code_item,
                                              InlineMethod* result) {
   DexInstructionIterator instruction = code_item->begin();
   Instruction::Code opcode = instruction->Opcode();
@@ -607,39 +585,37 @@
     return false;  // Not returning the value retrieved by IGET?
   }
 
-  if (is_static || object_arg != 0u) {
-    // TODO: Implement inlining of IGET on non-"this" registers (needs correct stack trace for NPE).
-    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
-    if (!IsSyntheticAccessor(method_ref)) {
-      return false;
-    }
-  }
-
   // InlineIGetIPutData::object_arg is only 4 bits wide.
   static constexpr uint16_t kMaxObjectArg = 15u;
   if (object_arg > kMaxObjectArg) {
     return false;
   }
 
-  if (result != nullptr) {
-    InlineIGetIPutData* data = &result->d.ifield_data;
-    if (!ComputeSpecialAccessorInfo(method, field_idx, false, data)) {
+  bool is_static = method->IsStatic();
+  if (is_static || object_arg != 0u) {
+    // TODO: Implement inlining of IGET on non-"this" registers (needs correct stack trace for NPE).
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!IsSyntheticAccessor(method)) {
       return false;
     }
-    result->opcode = kInlineOpIGet;
-    data->op_variant = IGetVariant(opcode);
-    data->method_is_static = is_static ? 1u : 0u;
-    data->object_arg = object_arg;  // Allow IGET on any register, not just "this".
-    data->src_arg = 0u;
-    data->return_arg_plus1 = 0u;
   }
+
+  DCHECK(result != nullptr);
+  InlineIGetIPutData* data = &result->d.ifield_data;
+  if (!ComputeSpecialAccessorInfo(method, field_idx, false, data)) {
+    return false;
+  }
+  result->opcode = kInlineOpIGet;
+  data->op_variant = enum_cast<uint16_t>(IGetMemAccessType(opcode));
+  data->method_is_static = is_static ? 1u : 0u;
+  data->object_arg = object_arg;  // Allow IGET on any register, not just "this".
+  data->src_arg = 0u;
+  data->return_arg_plus1 = 0u;
   return true;
 }
 
-bool InlineMethodAnalyser::AnalyseIPutMethod(const CodeItemDataAccessor* code_item,
-                                             const MethodReference& method_ref,
-                                             bool is_static,
-                                             ArtMethod* method,
+bool InlineMethodAnalyser::AnalyseIPutMethod(ArtMethod* method,
+                                             const CodeItemDataAccessor* code_item,
                                              InlineMethod* result) {
   DexInstructionIterator instruction = code_item->begin();
   Instruction::Code opcode = instruction->Opcode();
@@ -673,14 +649,6 @@
   uint32_t object_arg = object_reg - arg_start;
   uint32_t src_arg = src_reg - arg_start;
 
-  if (is_static || object_arg != 0u) {
-    // TODO: Implement inlining of IPUT on non-"this" registers (needs correct stack trace for NPE).
-    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
-    if (!IsSyntheticAccessor(method_ref)) {
-      return false;
-    }
-  }
-
   // InlineIGetIPutData::object_arg/src_arg/return_arg_plus1 are each only 4 bits wide.
   static constexpr uint16_t kMaxObjectArg = 15u;
   static constexpr uint16_t kMaxSrcArg = 15u;
@@ -689,18 +657,26 @@
     return false;
   }
 
-  if (result != nullptr) {
-    InlineIGetIPutData* data = &result->d.ifield_data;
-    if (!ComputeSpecialAccessorInfo(method, field_idx, true, data)) {
+  bool is_static = method->IsStatic();
+  if (is_static || object_arg != 0u) {
+    // TODO: Implement inlining of IPUT on non-"this" registers (needs correct stack trace for NPE).
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!IsSyntheticAccessor(method)) {
       return false;
     }
-    result->opcode = kInlineOpIPut;
-    data->op_variant = IPutVariant(opcode);
-    data->method_is_static = is_static ? 1u : 0u;
-    data->object_arg = object_arg;  // Allow IPUT on any register, not just "this".
-    data->src_arg = src_arg;
-    data->return_arg_plus1 = return_arg_plus1;
   }
+
+  DCHECK(result != nullptr);
+  InlineIGetIPutData* data = &result->d.ifield_data;
+  if (!ComputeSpecialAccessorInfo(method, field_idx, true, data)) {
+    return false;
+  }
+  result->opcode = kInlineOpIPut;
+  data->op_variant = enum_cast<uint16_t>(IPutMemAccessType(opcode));
+  data->method_is_static = is_static ? 1u : 0u;
+  data->object_arg = object_arg;  // Allow IPUT on any register, not just "this".
+  data->src_arg = src_arg;
+  data->return_arg_plus1 = return_arg_plus1;
   return true;
 }
 
diff --git a/compiler/dex/inline_method_analyser.h b/compiler/dex/inline_method_analyser.h
index 99d07c6..4cd5b82 100644
--- a/compiler/dex/inline_method_analyser.h
+++ b/compiler/dex/inline_method_analyser.h
@@ -21,7 +21,6 @@
 #include "base/mutex.h"
 #include "dex/dex_file.h"
 #include "dex/dex_instruction.h"
-#include "dex/method_reference.h"
 
 /*
  * NOTE: This code is part of the quick compiler. It lives in the runtime
@@ -100,47 +99,23 @@
    *
    * @return true if the method is a candidate for inlining, false otherwise.
    */
-  static bool AnalyseMethodCode(ArtMethod* method, InlineMethod* result)
+  static bool AnalyseMethodCode(ArtMethod* method,
+                                const CodeItemDataAccessor* code_item,
+                                InlineMethod* result)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static constexpr bool IsInstructionIGet(Instruction::Code opcode) {
-    return Instruction::IGET <= opcode && opcode <= Instruction::IGET_SHORT;
-  }
-
-  static constexpr bool IsInstructionIPut(Instruction::Code opcode) {
-    return Instruction::IPUT <= opcode && opcode <= Instruction::IPUT_SHORT;
-  }
-
-  static constexpr uint16_t IGetVariant(Instruction::Code opcode) {
-    return opcode - Instruction::IGET;
-  }
-
-  static constexpr uint16_t IPutVariant(Instruction::Code opcode) {
-    return opcode - Instruction::IPUT;
-  }
-
   // Determines whether the method is a synthetic accessor (method name starts with "access$").
-  static bool IsSyntheticAccessor(MethodReference ref);
+  static bool IsSyntheticAccessor(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
-  static bool AnalyseMethodCode(const CodeItemDataAccessor* code_item,
-                                const MethodReference& method_ref,
-                                bool is_static,
-                                ArtMethod* method,
-                                InlineMethod* result)
-      REQUIRES_SHARED(Locks::mutator_lock_);
   static bool AnalyseReturnMethod(const CodeItemDataAccessor* code_item, InlineMethod* result);
   static bool AnalyseConstMethod(const CodeItemDataAccessor* code_item, InlineMethod* result);
-  static bool AnalyseIGetMethod(const CodeItemDataAccessor* code_item,
-                                const MethodReference& method_ref,
-                                bool is_static,
-                                ArtMethod* method,
+  static bool AnalyseIGetMethod(ArtMethod* method,
+                                const CodeItemDataAccessor* code_item,
                                 InlineMethod* result)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  static bool AnalyseIPutMethod(const CodeItemDataAccessor* code_item,
-                                const MethodReference& method_ref,
-                                bool is_static,
-                                ArtMethod* method,
+  static bool AnalyseIPutMethod(ArtMethod* method,
+                                const CodeItemDataAccessor* code_item,
                                 InlineMethod* result)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index d7ca17b..f1e2733 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1619,17 +1619,28 @@
     return true;
   }
 
+  CodeItemDataAccessor accessor(method->DexInstructionData());
+
+  if (!IsInliningAllowed(method, accessor)) {
+    return false;
+  }
+
+  // We have checked above that inlining is "allowed" to make sure that the method has bytecode
+  // (is not native), is compilable and verified and to enforce the @NeverInline annotation.
+  // However, the pattern substitution is always preferable, so we do it before the check if
+  // inlining is "encouraged". It also has an exception to the `MayInline()` restriction.
+  if (TryPatternSubstitution(invoke_instruction, method, accessor, return_replacement)) {
+    LOG_SUCCESS() << "Successfully replaced pattern of invoke "
+                  << method->PrettyMethod();
+    MaybeRecordStat(stats_, MethodCompilationStat::kReplacedInvokeWithSimplePattern);
+    return true;
+  }
+
   // Check whether we're allowed to inline. The outermost compilation unit is the relevant
   // dex file here (though the transitivity of an inline chain would allow checking the caller).
   if (!MayInline(codegen_->GetCompilerOptions(),
                  *method->GetDexFile(),
                  *outer_compilation_unit_.GetDexFile())) {
-    if (TryPatternSubstitution(invoke_instruction, method, return_replacement)) {
-      LOG_SUCCESS() << "Successfully replaced pattern of invoke "
-                    << method->PrettyMethod();
-      MaybeRecordStat(stats_, MethodCompilationStat::kReplacedInvokeWithSimplePattern);
-      return true;
-    }
     LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedWont)
         << "Won't inline " << method->PrettyMethod() << " in "
         << outer_compilation_unit_.GetDexFile()->GetLocation() << " ("
@@ -1638,12 +1649,6 @@
     return false;
   }
 
-  CodeItemDataAccessor accessor(method->DexInstructionData());
-
-  if (!IsInliningAllowed(method, accessor)) {
-    return false;
-  }
-
   if (!IsInliningSupported(invoke_instruction, method, accessor)) {
     return false;
   }
@@ -1683,9 +1688,10 @@
 // Try to recognize known simple patterns and replace invoke call with appropriate instructions.
 bool HInliner::TryPatternSubstitution(HInvoke* invoke_instruction,
                                       ArtMethod* method,
+                                      const CodeItemDataAccessor& accessor,
                                       HInstruction** return_replacement) {
   InlineMethod inline_method;
-  if (!InlineMethodAnalyser::AnalyseMethodCode(method, &inline_method)) {
+  if (!InlineMethodAnalyser::AnalyseMethodCode(method, &accessor, &inline_method)) {
     return false;
   }
 
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index 4860054..57d3364 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -126,6 +126,7 @@
   // Try to recognize known simple patterns and replace invoke call with appropriate instructions.
   bool TryPatternSubstitution(HInvoke* invoke_instruction,
                               ArtMethod* method,
+                              const CodeItemDataAccessor& accessor,
                               HInstruction** return_replacement)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/test/2243-checker-not-inline-into-throw/src/Main.java b/test/2243-checker-not-inline-into-throw/src/Main.java
index 6f1280c..f1d60a1 100644
--- a/test/2243-checker-not-inline-into-throw/src/Main.java
+++ b/test/2243-checker-not-inline-into-throw/src/Main.java
@@ -32,14 +32,18 @@
   // Empty methods are easy to inline anywhere.
   private static void easyToInline() {}
   private static void $inline$easyToInline() {}
+  private static void twoLevelEasyToInline() { easyToInline(); }
 
   /// CHECK-START: int Main.$noinline$testEndsWithThrow() inliner (before)
-  /// CHECK: InvokeStaticOrDirect method_name:Main.easyToInline
+  /// CHECK: InvokeStaticOrDirect method_name:Main.twoLevelEasyToInline
 
   /// CHECK-START: int Main.$noinline$testEndsWithThrow() inliner (after)
-  /// CHECK: InvokeStaticOrDirect method_name:Main.easyToInline
+  /// CHECK: InvokeStaticOrDirect method_name:Main.twoLevelEasyToInline
   static int $noinline$testEndsWithThrow() {
-    easyToInline();
+    // Use two level inlining to avoid a pattern match in the inliner.
+    // The pattern matching is deliberately done before we check if inlining is "encouraged"
+    // which includes checking if the block ends with a `throw`.
+    twoLevelEasyToInline();
     throw new Error("");
   }
 
diff --git a/test/476-checker-ctor-fence-redun-elim/src/Main.java b/test/476-checker-ctor-fence-redun-elim/src/Main.java
index 05f2f7c..b065b13 100644
--- a/test/476-checker-ctor-fence-redun-elim/src/Main.java
+++ b/test/476-checker-ctor-fence-redun-elim/src/Main.java
@@ -32,6 +32,13 @@
   int w2;
   int w3;
 
+  Base() {
+    // Prevent inliner from matching the code pattern when calling this constructor
+    // to test the normal inlining that builds and inserts the callee graph.
+    // (Pattern matching can merge or eliminate constructor barriers.)
+    $inline$nop();
+  }
+
   @Override
   public String toString() {
     return getClass().getName() + "(" + baseString() + ")";
@@ -40,6 +47,8 @@
   protected String baseString() {
     return String.format("w0: %d, w1: %d, w2: %d, w3: %d", w0, w1, w2, w3);
   }
+
+  private void $inline$nop() {}
 }
 
 // This has a final field in its constructor, so there must be a field freeze
diff --git a/test/476-checker-ctor-memory-barrier/src/Main.java b/test/476-checker-ctor-memory-barrier/src/Main.java
index e887cd3..f4ae3a9 100644
--- a/test/476-checker-ctor-memory-barrier/src/Main.java
+++ b/test/476-checker-ctor-memory-barrier/src/Main.java
@@ -61,7 +61,9 @@
   /// CHECK-NOT:  {{[slm]}}fence
   public ClassWithFinals() {
     // Exactly one constructor barrier.
-    x = 0;
+    // Note: Do not store 0 as that can be eliminated together with the constructor
+    // barrier by the code pattern substitution in the inliner.
+    x = 1;
   }
 
   /// CHECK-START: void ClassWithFinals.<init>(int) inliner (after)
diff --git a/test/569-checker-pattern-replacement/build.py b/test/569-checker-pattern-replacement/build.py
new file mode 100644
index 0000000..abe80d8
--- /dev/null
+++ b/test/569-checker-pattern-replacement/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2024 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/569-checker-pattern-replacement/src-multidex/Second.java b/test/569-checker-pattern-replacement/src-multidex/Second.java
index 89835c6..6528806 100644
--- a/test/569-checker-pattern-replacement/src-multidex/Second.java
+++ b/test/569-checker-pattern-replacement/src-multidex/Second.java
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
+import dalvik.annotation.optimization.NeverInline;
+
 public final class Second {
   public static void staticNop(int unused) { }
 
+  @NeverInline
+  public static void staticNopNeverInline(int unused) { }
+
   public void nop() { }
 
   public static Object staticReturnArg2(int unused1, String arg2) {
diff --git a/test/569-checker-pattern-replacement/src/Main.java b/test/569-checker-pattern-replacement/src/Main.java
index 8951d30..9d0f772 100644
--- a/test/569-checker-pattern-replacement/src/Main.java
+++ b/test/569-checker-pattern-replacement/src/Main.java
@@ -15,6 +15,32 @@
  */
 
 public class Main {
+  static class ExpectedError extends Error {}
+
+  /// CHECK-START: void Main.localStaticNopAndThrow() inliner (before)
+  /// CHECK:                          InvokeStaticOrDirect method_name:Main.localStaticNop
+
+  /// CHECK-START: void Main.localStaticNopAndThrow() inliner (after)
+  /// CHECK-NOT:                      InvokeStaticOrDirect method_name:Main.localStaticNop
+
+  public static void localStaticNopAndThrow() {
+    // Pattern matching replaces the invoke even in a block that ends with a `throw`.
+    localStaticNop();
+    throw new ExpectedError();
+  }
+
+  public static void localStaticNop() {}
+
+  /// CHECK-START: void Main.staticNopNeverInline() inliner (before)
+  /// CHECK:                          InvokeStaticOrDirect
+
+  /// CHECK-START: void Main.staticNopNeverInline() inliner (after)
+  /// CHECK:                          InvokeStaticOrDirect
+
+  public static void staticNopNeverInline() {
+    Second.staticNopNeverInline(11);
+  }
+
   /// CHECK-START: void Main.staticNop() inliner (before)
   /// CHECK:                          InvokeStaticOrDirect
 
@@ -1177,6 +1203,8 @@
     // Replaced NOP pattern.
     staticNop();
     nop(s);
+    // Not replaced NOP pattern.
+    staticNopNeverInline();
     // Replaced "return arg" pattern.
     assertEquals("arbitrary string", staticReturnArg2("arbitrary string"));
     assertEquals(4321L, returnArg1(s, 4321L));
@@ -1259,6 +1287,11 @@
     assertEquals(123, constructDerivedInSecondDex(123));
     assertEquals(0, constructDerivedInSecondDexWith0());
     assertEquals(0, constructDerivedInSecondDex(7L));
+
+    try {
+      localStaticNopAndThrow();
+      throw new Error("Unreachable");
+    } catch (ExpectedError expected) {}
   }
 
   private static void assertEquals(int expected, int actual) {
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index f8c1d9d..6d1cded 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -17,8 +17,14 @@
 public class Main {
   static class ValueHolder {
     int getValue() {
+      // Prevent inliner from matching the code pattern when calling this method to test
+      // the normal inlining path that does not inline in blocks that end with a `throw`.
+      $inline$nop();
+
       return 1;
     }
+
+    private void $inline$nop() {}
   }
 
   public static void main(String[] args) throws Exception {
diff --git a/test/knownfailures.json b/test/knownfailures.json
index b47a1a1..5323fd5 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -58,12 +58,6 @@
                         "loaded systems."]
     },
     {
-        "tests": "569-checker-pattern-replacement",
-        "variant": "target",
-        "description": ["569-checker-pattern-replacement tests behaviour",
-                        "present only on host."]
-    },
-    {
         "tests": ["116-nodex2oat",
                   "118-noimage-dex2oat"],
         "variant": "prebuild",