Inliner: Always try code pattern recognition.

Try code pattern recognition whenever we're allowed to
inline. The pattern recognition is fast and when we find a
match, we avoid costly processing, such as building the
callee graph. The pattern replacement is always better than
the invoke, so do it even when inlining is not "encouraged",
for example in blocks that end with a `throw`.

Reorder the code so that the pattern recognition respects
the @NeverInline annotation. Enable run-test 569 for target
and add a test for the @NeverInline annotation.

Also remove some duplicated helper functions and reduce the
number of arguments we pass around.

Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: testrunner.py --jvm
Test: run-gtests.sh
Test: testrunner.py --target --optimizing
Bug: 181943478
Change-Id: I863fe1190eb38c7decf0c5e34a00c103e8e559f1
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",