Add an optimizing compiler fuzzer

Bug: 438934944
Flag: EXEMPT testing only
Test: atest FuzzerCorpusTest
Test: m test-art-host-gtest-art_runtime_tests64
Test: Build/Run optimized compiler fuzzer
Test: Build/Run optimized classes verifier fuzzer
Change-Id: I6a39d3849d476a228b6837d093a5e8c664e71a2a
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index d790660..f89d9a4 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -671,6 +671,7 @@
     # Fuzzer cases
     self._checker.check_art_test_data('dex_verification_fuzzer_corpus.zip')
     self._checker.check_art_test_data('class_verification_fuzzer_corpus.zip')
+    self._checker.check_art_test_data('optimized_compiler_fuzzer_corpus.zip')
 
 
 class NoSuperfluousFilesChecker:
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index ae1ab5d..6c2a87f 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -33,6 +33,13 @@
 
 namespace art HIDDEN {
 
+// Forward declare CompilerOptions so that the CreateCompilerOptions forward declare works.
+class CompilerOptions;
+
+namespace fuzzer {
+std::unique_ptr<CompilerOptions> CreateCompilerOptions();
+}  // namespace fuzzer
+
 namespace jit {
 class JitCompiler;
 }  // namespace jit
@@ -414,7 +421,6 @@
   // Classes listed in the preloaded-classes file, used for boot image and
   // boot image extension compilation.
   HashSet<std::string> preloaded_classes_;
-
   CompilerType compiler_type_;
   ImageType image_type_;
   bool multi_image_;
@@ -505,6 +511,8 @@
   friend class linker::Arm64RelativePatcherTest;
   friend class linker::Thumb2RelativePatcherTest;
 
+  friend std::unique_ptr<CompilerOptions> fuzzer::CreateCompilerOptions();
+
   template <class Base>
   friend bool ReadCompilerOptions(Base& map, CompilerOptions* options, std::string* error_msg);
 
diff --git a/runtime/Android.bp b/runtime/Android.bp
index e0a6dc5..8f5f238 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -1064,6 +1064,7 @@
         ":art-gtest-jars-AllFields",
         ":art-gtest-jars-DexVerificationFuzzerFolder",
         ":art-gtest-jars-ClassVerificationFuzzerFolder",
+        ":art-gtest-jars-OptimizedCompilerFuzzerFolder",
         ":art-gtest-jars-ErroneousA",
         ":art-gtest-jars-ErroneousB",
         ":art-gtest-jars-ErroneousInit",
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index d71173a..93f7056 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -65,6 +65,7 @@
         <option name="push" value="art-gtest-jars-XandY.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-XandY.jar" />
         <option name="push" value="dex_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/dex_verification_fuzzer_corpus.zip" />
         <option name="push" value="class_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/class_verification_fuzzer_corpus.zip" />
+        <option name="push" value="optimized_compiler_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/optimized_compiler_fuzzer_corpus.zip" />
     </target_preparer>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/runtime/fuzzer_corpus_test.cc b/runtime/fuzzer_corpus_test.cc
index f360901..88d9218 100644
--- a/runtime/fuzzer_corpus_test.cc
+++ b/runtime/fuzzer_corpus_test.cc
@@ -59,9 +59,35 @@
 
     jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
     const bool passed_class_verification = fuzzer::VerifyClasses(class_loader, dex_file.get());
+    fuzzer::IterationCleanup(class_loader, dex_file.get());
     ASSERT_EQ(passed_class_verification, expected_success) << " Failed for " << name;
   }
 
+  static void OptimizedCompilation(const uint8_t* data,
+                                   size_t size,
+                                   const std::string& name,
+                                   bool expected_success) {
+    std::unique_ptr<StandardDexFile> dex_file = fuzzer::VerifyDexFile(data, size, name);
+    ASSERT_EQ(dex_file != nullptr, true) << " Failed for " << name;
+
+    Runtime* runtime = Runtime::Current();
+    CHECK(runtime != nullptr);
+
+    fuzzer::FuzzerCompiledMethodStorage storage;
+    std::unique_ptr<fuzzer::FuzzerCompilerCallbacks> callbacks(
+        new fuzzer::FuzzerCompilerCallbacks());
+    std::unique_ptr<CompilerOptions> compiler_options = fuzzer::CreateCompilerOptions();
+    std::unique_ptr<Compiler> compiler(fuzzer::CreateCompiler(*compiler_options, &storage));
+
+    jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
+    fuzzer::VerifyClasses(class_loader, dex_file.get());
+    const bool at_least_one_method_called_the_compiler = fuzzer::CompileClasses(
+        class_loader, dex_file.get(), compiler.get(), callbacks.get(), /*kDebugPrints=*/false);
+    // Note: no need to reset callbacks as they will get destroyed
+    fuzzer::IterationCleanup(class_loader, dex_file.get());
+    ASSERT_EQ(at_least_one_method_called_the_compiler, expected_success) << " Failed for " << name;
+  }
+
   void TestFuzzerHelper(
       const std::string& archive_filename,
       const std::unordered_set<std::string>& valid_dex_files,
@@ -132,4 +158,13 @@
   TestFuzzerHelper(archive_filename, valid_dex_files, ClassVerification);
 }
 
+// Tests that we can compile classes with kOptimizing from dex files without crashing.
+TEST_F(FuzzerCorpusTest, OptimizeCompileDexFiles) {
+  // These dex files are expected to pass verification. The others are regressions tests.
+  const std::unordered_set<std::string> valid_dex_files = {"Main.dex", "hello_world.dex"};
+  const std::string archive_filename = "optimized_compiler_fuzzer_corpus.zip";
+
+  TestFuzzerHelper(archive_filename, valid_dex_files, OptimizedCompilation);
+}
+
 }  // namespace art
diff --git a/runtime/noop_compiler_callbacks.h b/runtime/noop_compiler_callbacks.h
index 9432c53..64b9cf0 100644
--- a/runtime/noop_compiler_callbacks.h
+++ b/runtime/noop_compiler_callbacks.h
@@ -24,7 +24,7 @@
 namespace art HIDDEN {
 
 // Used for tests and some tools that pretend to be a compiler (say, oatdump).
-class NoopCompilerCallbacks final : public CompilerCallbacks {
+class NoopCompilerCallbacks : public CompilerCallbacks {
  public:
   NoopCompilerCallbacks() : CompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp) {}
   ~NoopCompilerCallbacks() {}
diff --git a/tools/fuzzer/Android.bp b/tools/fuzzer/Android.bp
index cd06f55..e73e5f8 100644
--- a/tools/fuzzer/Android.bp
+++ b/tools/fuzzer/Android.bp
@@ -200,6 +200,35 @@
     ],
 }
 
+// Optimizing compiler
+cc_fuzz {
+    name: "libart_optimizing_compiler_fuzzer",
+    srcs: ["libart_optimizing_compiler_fuzzer.cc"],
+    defaults: [
+        "libart_fuzzer-defaults",
+        "fuzzer-creating-runtime-defaults",
+    ],
+    // Can not be in defaults due to soong limitations.
+    device_common_corpus: [
+        ":art_runtest_corpus",
+        "class-verifier-corpus/*",
+    ],
+}
+
+cc_fuzz {
+    name: "libart_optimizing_compiler_fuzzerd",
+    srcs: ["libart_optimizing_compiler_fuzzer.cc"],
+    defaults: [
+        "libart_fuzzerd-defaults",
+        "fuzzer-creating-runtime-defaults",
+    ],
+    // Can not be in defaults due to soong limitations.
+    device_common_corpus: [
+        ":art_runtest_corpus",
+        "class-verifier-corpus/*",
+    ],
+}
+
 // Build an ART apex and extract the .jar from them. In particular, the core-oj and
 // core-libart jars will have hiddenapi data, which is needed for the debug+target
 // version of the fuzzer.
@@ -1457,3 +1486,14 @@
     out: ["class_verification_fuzzer_corpus.zip"],
     tools: ["soong_zip"],
 }
+
+genrule {
+    name: "art-gtest-jars-OptimizedCompilerFuzzerFolder",
+    // Zip the optimized-compiler-corpus folder. To get the folder, we grab the first file
+    // from `in` and use its directory.
+    cmd: "FILES=($(in)) &&" +
+        "$(location soong_zip) -j -L 0 -o $(out) -D $$(dirname $${FILES[0]})",
+    srcs: ["optimized-compiler-corpus/*"],
+    out: ["optimized_compiler_fuzzer_corpus.zip"],
+    tools: ["soong_zip"],
+}
diff --git a/tools/fuzzer/fuzzer_common.cc b/tools/fuzzer/fuzzer_common.cc
index b1b75f0..2a0f136 100644
--- a/tools/fuzzer/fuzzer_common.cc
+++ b/tools/fuzzer/fuzzer_common.cc
@@ -21,7 +21,6 @@
 #include "dex/class_accessor-inl.h"
 #include "handle_scope-inl.h"
 #include "interpreter/unstarted_runtime.h"
-#include "noop_compiler_callbacks.h"
 #include "runtime_intrinsics.h"
 #include "scoped_thread_state_change-inl.h"
 
@@ -64,16 +63,13 @@
   return option + android::base::Join(class_path, ':');
 }
 
-void FuzzerInitialize() {
+void FuzzerInitialize(CompilerCallbacks* callbacks) {
   // Set logging to error and above to avoid warnings about unexpected checksums.
   android::base::SetMinimumLogSeverity(android::base::ERROR);
 
   // Create runtime.
   RuntimeOptions options;
-  {
-    static NoopCompilerCallbacks callbacks;
-    options.push_back(std::make_pair("compilercallbacks", &callbacks));
-  }
+  options.push_back(std::make_pair("compilercallbacks", callbacks));
 
   std::string boot_class_path_string =
       GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames());
@@ -147,67 +143,68 @@
   ClassLinker* class_linker = runtime->GetClassLinker();
   ScopedObjectAccess soa(Thread::Current());
 
-  // Scope for the handles
-  {
-    StackHandleScope<4> scope(soa.Self());
-    Handle<mirror::ClassLoader> h_loader =
-        scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
-    MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
-    MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
-    MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
+  StackHandleScope<4> scope(soa.Self());
+  Handle<mirror::ClassLoader> h_loader =
+      scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+  MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
+  MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
+  MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
 
-    for (const ClassAccessor accessor : dex_file->GetClasses()) {
-      h_klass.Assign(
-          class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
-      // Ignore classes that couldn't be loaded since we are looking for crashes during
-      // class/method verification.
-      if (h_klass == nullptr || h_klass->IsErroneous()) {
-        // Treat as failure to pass verification
-        passed_class_verification = false;
-        soa.Self()->ClearException();
-        continue;
-      }
-      if (&h_klass->GetDexFile() != dex_file) {
-        // Skip a duplicate class (as the resolved class is from another dex file). This can happen
-        // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
-        continue;
-      }
-
-      h_dex_cache.Assign(h_klass->GetDexCache());
-
-      // The class loader from the class's dex cache is different from the dex file's class loader
-      // for boot image classes e.g. java.util.AbstractCollection.
-      h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
-
-      CHECK(h_klass->IsResolved()) << h_klass->PrettyClass();
-      verifier::FailureKind failure_kind =
-          class_linker->VerifyClass(soa.Self(),
-                                    /* verifier_deps= */ nullptr,
-                                    h_klass,
-                                    // Don't abort on verification errors.
-                                    verifier::HardFailLogMode::kLogWarning);
-
-      DCHECK_EQ(h_klass->IsErroneous(), failure_kind == verifier::FailureKind::kHardFailure);
-      if (failure_kind == verifier::FailureKind::kHardFailure) {
-        passed_class_verification = false;
-        // ClassLinker::VerifyClass throws, so we clear it before we continue.
-        CHECK(soa.Self()->IsExceptionPending());
-        soa.Self()->ClearException();
-      }
-
-      CHECK(h_klass->ShouldVerifyAtRuntime() ||
-            h_klass->IsVerifiedNeedsAccessChecks() ||
-            h_klass->IsVerified() ||
-            h_klass->IsErroneous())
-          << h_klass->PrettyDescriptor() << ": state=" << h_klass->GetStatus();
-
-      soa.Self()->AssertNoPendingException();
+  for (ClassAccessor accessor : dex_file->GetClasses()) {
+    h_klass.Assign(
+        class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
+    // Ignore classes that couldn't be loaded since we are looking for crashes during
+    // class/method verification.
+    if (h_klass == nullptr || h_klass->IsErroneous()) {
+      // Treat as failure to pass verification
+      passed_class_verification = false;
+      soa.Self()->ClearException();
+      continue;
     }
+    if (&h_klass->GetDexFile() != dex_file) {
+      // Skip a duplicate class (as the resolved class is from another dex file). This can happen
+      // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
+      continue;
+    }
+
+    h_dex_cache.Assign(h_klass->GetDexCache());
+
+    // The class loader from the class's dex cache is different from the dex file's class loader
+    // for boot image classes e.g. java.util.AbstractCollection.
+    h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
+
+    CHECK(h_klass->IsResolved()) << h_klass->PrettyClass();
+    verifier::FailureKind failure_kind =
+        class_linker->VerifyClass(soa.Self(),
+                                  /* verifier_deps= */ nullptr,
+                                  h_klass,
+                                  // Don't abort on verification errors.
+                                  verifier::HardFailLogMode::kLogWarning);
+
+    DCHECK_EQ(h_klass->IsErroneous(), failure_kind == verifier::FailureKind::kHardFailure);
+    if (failure_kind == verifier::FailureKind::kHardFailure) {
+      passed_class_verification = false;
+      // ClassLinker::VerifyClass throws, so we clear it before we continue.
+      CHECK(soa.Self()->IsExceptionPending());
+      soa.Self()->ClearException();
+    }
+
+    CHECK(h_klass->ShouldVerifyAtRuntime() || h_klass->IsVerifiedNeedsAccessChecks() ||
+          h_klass->IsVerified() || h_klass->IsErroneous())
+        << h_klass->PrettyDescriptor() << ": state=" << h_klass->GetStatus();
+
+    soa.Self()->AssertNoPendingException();
   }
 
+  return passed_class_verification;
+}
+
+ALWAYS_INLINE void IterationCleanup(jobject class_loader, const StandardDexFile* dex_file) {
+  ScopedObjectAccess soa(Thread::Current());
+  Runtime* runtime = Runtime::Current();
   // Clear the arena pool to free RAM. The next iteration won't be referencing the pool we just
   // used.
-  Runtime::Current()->ReclaimArenaPoolMemory();
+  runtime->ReclaimArenaPoolMemory();
 
   // Delete weak root to the DexCache before removing a DEX file from the cache. This is usually
   // handled by the GC, but since we are not calling it every iteration, we need to delete them
@@ -220,12 +217,10 @@
 
   // Mimic DexFile_closeDexFile.
   RemoveNativeDebugInfoForDex(soa.Self(), dex_file);
-  class_linker->RemoveDexFromCaches(*dex_file);
+  runtime->GetClassLinker()->RemoveDexFromCaches(*dex_file);
 
   // Delete global ref and unload class loader to free RAM.
   soa.Env()->GetVm()->DeleteGlobalRef(soa.Self(), class_loader);
-
-  return passed_class_verification;
 }
 
 jobject RegisterDexFileAndGetClassLoader(Runtime* runtime, const StandardDexFile* dex_file) {
@@ -239,5 +234,134 @@
   return class_loader;
 }
 
+std::unique_ptr<CompilerOptions> CreateCompilerOptions() {
+  std::unique_ptr<CompilerOptions> opt = std::make_unique<CompilerOptions>();
+  opt->emit_read_barrier_ = gUseReadBarrier;
+  opt->instruction_set_ =
+      (kRuntimeISA == InstructionSet::kArm) ? InstructionSet::kThumb2 : kRuntimeISA;
+  std::unique_ptr<const InstructionSetFeatures> kISAFeatures =
+      InstructionSetFeatures::FromCppDefines();
+  CHECK(kISAFeatures != nullptr);
+  CHECK_EQ(kRuntimeISA, kISAFeatures->GetInstructionSet());
+  opt->instruction_set_features_ =
+      InstructionSetFeatures::FromBitmap(kRuntimeISA, kISAFeatures->AsBitmap());
+  CHECK(opt->instruction_set_features_ != nullptr);
+  opt->implicit_null_checks_ = true;
+  opt->implicit_so_checks_ = true;
+  opt->implicit_suspend_checks_ = kRuntimeISA == InstructionSet::kArm64;
+  return opt;
+}
+
+Compiler* CreateCompiler(const CompilerOptions& compiler_options,
+                         CompiledCodeStorage* storage) {
+  // Consistency checks
+  CHECK(!compiler_options.GetDebuggable());
+  CHECK(!kUseTableLookupReadBarrier);
+  CHECK(kReserveMarkingRegister);
+  CHECK(!kPoisonHeapReferences);
+  // TODO(solanes): parametrize the compilation kind and get kBaseline for free.
+  CHECK(!compiler_options.IsBaseline());
+
+  // Testing AOT compiler.
+  CHECK_EQ(Runtime::Current()->GetJit(), nullptr);
+
+  return Compiler::Create(compiler_options, storage);
+}
+
+ALWAYS_INLINE bool CompileClasses(jobject class_loader,
+                                  const StandardDexFile* dex_file,
+                                  Compiler* compiler,
+                                  FuzzerCompilerCallbacks* callbacks,
+                                  bool debug_prints) {
+  bool at_least_one_method_called_the_compiler = false;
+  Runtime* runtime = Runtime::Current();
+  ClassLinker* class_linker = runtime->GetClassLinker();
+
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<4> scope(soa.Self());
+  Handle<mirror::ClassLoader> h_loader =
+      scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+  MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
+  MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
+  MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
+
+  // We finished verification and we can move to compilation, using the verification results
+  for (ClassAccessor accessor : dex_file->GetClasses()) {
+    h_klass.Assign(
+        class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
+    // Ignore classes that couldn't be loaded since we are looking for crashes during
+    // compilation.
+    if (h_klass == nullptr || h_klass->IsErroneous()) {
+      soa.Self()->ClearException();
+      continue;
+    }
+    if (&h_klass->GetDexFile() != dex_file) {
+      // Skip a duplicate class (as the resolved class is from another dex file). This can happen
+      // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
+      continue;
+    }
+
+    ClassReference ref(dex_file, h_klass->GetDexClassDefIndex());
+    if (callbacks->IsClassRejected(ref)) {
+      continue;
+    }
+
+    h_dex_cache.Assign(h_klass->GetDexCache());
+
+    // The class loader from the class's dex cache is different from the dex file's class loader
+    // for boot image classes e.g. java.util.AbstractCollection.
+    h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
+
+    int64_t previous_method_idx = -1;
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      const uint32_t method_idx = method.GetIndex();
+      if (method_idx == previous_method_idx) {
+        // Duplicate method
+        continue;
+      }
+      previous_method_idx = method_idx;
+
+      const uint32_t access_flags = method.GetAccessFlags();
+
+      if (ArtMethod::IsNative(access_flags)) {
+        // TODO(solanes): Support JNI?
+        continue;
+      }
+
+      if (ArtMethod::IsAbstract(access_flags)) {
+        // Abstract methods don't have code.
+        continue;
+      }
+
+      // TODO(solanes): If we want to support the fast compiler, add `!method.IsInvokable() ||`.
+      if (!ArtMethod::IsCompilable(access_flags) || ArtMethod::IsIntrinsic(access_flags)) {
+        // This method will never be compiled.
+        continue;
+      }
+
+      MethodReference method_ref(dex_file, method.GetIndex());
+      if (debug_prints) {
+        LOG(ERROR) << "Going to compile: " << dex_file->PrettyMethod(method.GetIndex())
+                   << ". IsUncompilableMethod: " << std::boolalpha
+                   << callbacks->IsUncompilableMethod(method_ref) << std::noboolalpha
+                   << " using klass " << h_klass->PrettyClass();
+      }
+
+      if (callbacks->IsUncompilableMethod(method_ref)) {
+        continue;
+      }
+      at_least_one_method_called_the_compiler = true;
+      compiler->Compile(method.GetCodeItem(),
+                        access_flags,
+                        accessor.GetClassDefIndex(),
+                        method.GetIndex(),
+                        h_dex_cache_class_loader,
+                        *dex_file,
+                        h_dex_cache);
+    }
+  }
+  return at_least_one_method_called_the_compiler;
+}
+
 }  // namespace fuzzer
 }  // namespace art
diff --git a/tools/fuzzer/fuzzer_common.h b/tools/fuzzer/fuzzer_common.h
index 2bb6793..3dc3600 100644
--- a/tools/fuzzer/fuzzer_common.h
+++ b/tools/fuzzer/fuzzer_common.h
@@ -25,11 +25,18 @@
 #include "base/file_utils.h"
 #include "base/mem_map.h"
 #include "class_linker.h"
+#include "compiler.h"
+#include "dex/class_accessor-inl.h"
+#include "dex/class_reference.h"
 #include "dex/dex_file_verifier.h"
+#include "dex/method_reference.h"
 #include "dex/standard_dex_file.h"
+#include "driver/compiled_code_storage.h"
+#include "driver/compiler_options.h"
 #include "interpreter/unstarted_runtime.h"
 #include "jit/debugger_interface.h"
 #include "jni/java_vm_ext.h"
+#include "noop_compiler_callbacks.h"
 #include "runtime.h"
 #include "verifier/class_verifier.h"
 #include "well_known_classes.h"
@@ -37,12 +44,16 @@
 namespace art {
 namespace fuzzer {
 
+// Dex file verification
+
 // Returns the DEX file created using the parameters, if it could be verified. Returns nullptr if
 // the DEX file couldn't be verified.
 ALWAYS_INLINE std::unique_ptr<StandardDexFile> VerifyDexFile(const uint8_t* data,
                                                              size_t size,
                                                              const std::string& location);
 
+// Class verification
+
 // A class to be friends with ClassLinker and access the internal FindDexCacheDataLocked method.
 class FuzzerCommonHelper {
  public:
@@ -58,7 +69,89 @@
 jobject RegisterDexFileAndGetClassLoader(Runtime* runtime, const StandardDexFile* dex_file);
 
 // Initialization for all fuzzers that need a Runtime.
-void FuzzerInitialize();
+void FuzzerInitialize(CompilerCallbacks* callbacks);
+
+// Cleans up memory after the iteration ran.
+ALWAYS_INLINE void IterationCleanup(jobject class_loader, const StandardDexFile* dex_file);
+
+// Compilation
+
+// Storage that will fake setting the thunk code so that the fuzzer can call Emit and test that code
+// path.
+class FuzzerCompiledMethodStorage final : public CompiledCodeStorage {
+ public:
+  FuzzerCompiledMethodStorage() {}
+  ~FuzzerCompiledMethodStorage() {}
+
+  CompiledMethod* CreateCompiledMethod(InstructionSet instruction_set,
+                                       ArrayRef<const uint8_t> code,
+                                       ArrayRef<const uint8_t> stack_map,
+                                       [[maybe_unused]] ArrayRef<const uint8_t> cfi,
+                                       [[maybe_unused]] ArrayRef<const linker::LinkerPatch> patches,
+                                       [[maybe_unused]] bool is_intrinsic) override {
+    DCHECK_NE(instruction_set, InstructionSet::kNone);
+    DCHECK(!code.empty());
+    DCHECK(!stack_map.empty());
+    return reinterpret_cast<CompiledMethod*>(this);
+  }
+
+  ArrayRef<const uint8_t> GetThunkCode([[maybe_unused]] const linker::LinkerPatch& patch,
+                                       [[maybe_unused]] /*out*/ std::string* debug_name) override {
+    return ArrayRef<const uint8_t>();
+  }
+
+  void SetThunkCode([[maybe_unused]] const linker::LinkerPatch& patch,
+                    [[maybe_unused]] ArrayRef<const uint8_t> code,
+                    [[maybe_unused]] const std::string& debug_name) override {}
+};
+
+class FuzzerCompilerCallbacks final : public NoopCompilerCallbacks {
+ public:
+  FuzzerCompilerCallbacks() {}
+
+  void AddUncompilableMethod(MethodReference ref) override { uncompilable_methods_.insert(ref); }
+
+  void AddUncompilableClass(ClassReference ref) override {
+    const DexFile& dex_file = *ref.dex_file;
+    const dex::ClassDef& class_def = dex_file.GetClassDef(ref.ClassDefIdx());
+    ClassAccessor accessor(dex_file, class_def);
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      MethodReference method_ref(&dex_file, method.GetIndex());
+      AddUncompilableMethod(method_ref);
+    }
+  }
+
+  void ClassRejected(ClassReference ref) override { rejected_classes_.insert(ref); }
+
+  bool IsUncompilableMethod(MethodReference ref) override {
+    return uncompilable_methods_.find(ref) != uncompilable_methods_.end();
+  }
+
+  bool IsClassRejected(ClassReference ref) {
+    return rejected_classes_.find(ref) != rejected_classes_.end();
+  }
+
+  void Reset() {
+    uncompilable_methods_.clear();
+    rejected_classes_.clear();
+  }
+
+ private:
+  std::set<MethodReference> uncompilable_methods_;
+  std::set<ClassReference> rejected_classes_;
+};
+
+// Compiles all classes within a DEX file. Returns true iff at least one method called the compiler.
+ALWAYS_INLINE bool CompileClasses(jobject class_loader,
+                                  const StandardDexFile* dex_file,
+                                  Compiler* compiler,
+                                  FuzzerCompilerCallbacks* callbacks,
+                                  bool debug_prints);
+
+// Creates a compiler. As part of this, creates and sets `compiler_options`.
+Compiler* CreateCompiler(const CompilerOptions& compiler_options,
+                         CompiledCodeStorage* storage);
+std::unique_ptr<CompilerOptions> CreateCompilerOptions();
 
 }  // namespace fuzzer
 }  // namespace art
diff --git a/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc b/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc
new file mode 100644
index 0000000..a6d1f0b
--- /dev/null
+++ b/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#include <cstdint>
+#include <vector>
+
+#include "compiler.h"
+#include "dex/standard_dex_file.h"
+#include "driver/compiler_options.h"
+#include "fuzzer_common.h"
+#include "gc/heap.h"
+#include "runtime.h"
+
+namespace art {
+namespace fuzzer {
+
+// Global variable to count how many DEX files passed DEX file verification and they were
+// registered, since these are the cases for which we would be running the GC. In case of
+// scheduling multiple fuzzer jobs, using the ‘-jobs’ flag, this is not shared among the threads.
+int skipped_gc_iterations = 0;
+// Global variable to call the GC once every maximum number of iterations.
+// TODO: These values were obtained from local experimenting. They can be changed after
+// further investigation.
+static constexpr int kMaxSkipGCIterations = 3000;
+
+// Extra debugging information.
+static constexpr bool kDebugPrints = false;
+
+std::vector<std::unique_ptr<uint8_t[]>> data_to_delete;
+std::vector<std::unique_ptr<StandardDexFile>> dex_files_to_delete;
+
+std::unique_ptr<Compiler> compiler;
+std::unique_ptr<CompilerOptions> compiler_options;
+std::unique_ptr<FuzzerCompilerCallbacks> callbacks;
+// No need for unique_ptr as it is just a fake storage.
+FuzzerCompiledMethodStorage storage;
+
+extern "C" int LLVMFuzzerInitialize([[maybe_unused]] int* argc, [[maybe_unused]] char*** argv) {
+  callbacks.reset(new FuzzerCompilerCallbacks());
+  FuzzerInitialize(callbacks.get());
+  compiler_options = CreateCompilerOptions();
+  compiler.reset(CreateCompiler(*compiler_options, &storage));
+  return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  // Note that we ignore the resulting DEX file from `VerifyDexFile` since we want to copy `data` to
+  // manage it ourselves.
+  if (VerifyDexFile(data, size, "fuzz.dex") == nullptr) {
+    // DEX file couldn't be verified, don't save it in the corpus.
+    return -1;
+  }
+
+  // Copy data to keep it alive. Use unique_ptr so that we don't leak.
+  data_to_delete.emplace_back(new uint8_t[size]);
+  uint8_t* new_data = data_to_delete.back().get();
+  memcpy(new_data, data, size);
+
+  dex_files_to_delete.emplace_back(
+      new StandardDexFile(new_data,
+                          /*location=*/"fuzz.dex",
+                          /*location_checksum=*/0,
+                          /*oat_dex_file=*/nullptr,
+                          std::make_shared<MemoryDexFileContainer>(new_data, size)));
+  StandardDexFile* dex_file = dex_files_to_delete.back().get();
+
+  Runtime* runtime = Runtime::Current();
+  CHECK(runtime != nullptr);
+
+  jobject class_loader = RegisterDexFileAndGetClassLoader(runtime, dex_file);
+
+  VerifyClasses(class_loader, dex_file);
+  const bool at_least_one_method_called_the_compiler =
+      CompileClasses(class_loader, dex_file, compiler.get(), callbacks.get(), kDebugPrints);
+  callbacks->Reset();
+  IterationCleanup(class_loader, dex_file);
+
+  if (skipped_gc_iterations == kMaxSkipGCIterations) {
+    runtime->GetHeap()->CollectGarbage(/* clear_soft_references */ true);
+    skipped_gc_iterations = 0;
+    data_to_delete.clear();
+    dex_files_to_delete.clear();
+  } else {
+    skipped_gc_iterations++;
+  }
+
+  // Save only if at least one method compiled
+  return at_least_one_method_called_the_compiler ? 0 : -1;
+}
+
+}  // namespace fuzzer
+}  // namespace art
diff --git a/tools/fuzzer/libart_verify_classes_fuzzer.cc b/tools/fuzzer/libart_verify_classes_fuzzer.cc
index 5e2e336..0406e25 100644
--- a/tools/fuzzer/libart_verify_classes_fuzzer.cc
+++ b/tools/fuzzer/libart_verify_classes_fuzzer.cc
@@ -15,14 +15,12 @@
  */
 
 #include <cstdint>
-#include <iostream>
 #include <vector>
 
-#include "base/mem_map.h"
+#include "dex/standard_dex_file.h"
 #include "fuzzer_common.h"
-#include "handle_scope-inl.h"
+#include "gc/heap.h"
 #include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
 
 namespace art {
 namespace fuzzer {
@@ -37,10 +35,11 @@
 static constexpr int kMaxSkipGCIterations = 3000;
 
 std::vector<std::unique_ptr<uint8_t[]>> data_to_delete;
-std::vector<std::unique_ptr<art::StandardDexFile>> dex_files_to_delete;
+std::vector<std::unique_ptr<StandardDexFile>> dex_files_to_delete;
 
 extern "C" int LLVMFuzzerInitialize([[maybe_unused]] int* argc, [[maybe_unused]] char*** argv) {
-  FuzzerInitialize();
+  static NoopCompilerCallbacks callbacks;
+  FuzzerInitialize(&callbacks);
   return 0;
 }
 
@@ -58,19 +57,20 @@
   memcpy(new_data, data, size);
 
   dex_files_to_delete.emplace_back(
-      new art::StandardDexFile(new_data,
-                               /*location=*/"fuzz.dex",
-                               /*location_checksum=*/0,
-                               /*oat_dex_file=*/nullptr,
-                               std::make_shared<art::MemoryDexFileContainer>(new_data, size)));
-  art::StandardDexFile* dex_file = dex_files_to_delete.back().get();
+      new StandardDexFile(new_data,
+                          /*location=*/"fuzz.dex",
+                          /*location_checksum=*/0,
+                          /*oat_dex_file=*/nullptr,
+                          std::make_shared<MemoryDexFileContainer>(new_data, size)));
+  StandardDexFile* dex_file = dex_files_to_delete.back().get();
 
-  art::Runtime* runtime = art::Runtime::Current();
+  Runtime* runtime = Runtime::Current();
   CHECK(runtime != nullptr);
 
   jobject class_loader = RegisterDexFileAndGetClassLoader(runtime, dex_file);
 
   VerifyClasses(class_loader, dex_file);
+  IterationCleanup(class_loader, dex_file);
 
   if (skipped_gc_iterations == kMaxSkipGCIterations) {
     runtime->GetHeap()->CollectGarbage(/* clear_soft_references */ true);
diff --git a/tools/fuzzer/optimized-compiler-corpus/Main.dex b/tools/fuzzer/optimized-compiler-corpus/Main.dex
new file mode 100644
index 0000000..ec29035
--- /dev/null
+++ b/tools/fuzzer/optimized-compiler-corpus/Main.dex
Binary files differ
diff --git a/tools/fuzzer/optimized-compiler-corpus/hello_world.dex b/tools/fuzzer/optimized-compiler-corpus/hello_world.dex
new file mode 100644
index 0000000..6e59dd7
--- /dev/null
+++ b/tools/fuzzer/optimized-compiler-corpus/hello_world.dex
Binary files differ