Create an abstraction for dexopt tasks am: c887816493

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

Change-Id: I7efe565f90a9125d845ea6a6ea17349d52acf37c
diff --git a/artd/Android.bp b/artd/Android.bp
index 6338fa5..fa0804a 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -45,6 +45,73 @@
     ],
 }
 
+cc_defaults {
+    name: "libdexopt_defaults",
+    defaults: ["art_defaults"],
+    srcs: [
+        "libdexopt.cc",
+    ],
+    shared_libs: [
+        "artd-private-aidl-ndk",
+        "libbase",
+        "liblog",
+    ],
+    export_include_dirs: [
+        "include/libdexopt",
+    ],
+    visibility: [
+        "//art:__subpackages__",
+    ],
+}
+
+art_cc_library {
+    name: "libdexopt",
+    defaults: ["libdexopt_defaults"],
+    shared_libs: [
+        "libart",
+        "libartbase",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+art_cc_library {
+    name: "libdexoptd",
+    defaults: ["libdexopt_defaults"],
+    shared_libs: [
+        "libartd",
+        "libartbased",
+    ],
+    apex_available: [
+        "com.android.art.debug",
+    ],
+}
+
+art_cc_defaults {
+    name: "art_artd_tests_defaults",
+    srcs: [
+        "libdexopt_test.cc",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+}
+
+art_cc_test {
+    name: "art_standalone_artd_tests",
+    defaults: [
+        "art_standalone_gtest_defaults",
+        "art_artd_tests_defaults",
+    ],
+    shared_libs: [
+        "artd-private-aidl-ndk",
+        "libdexopt",
+    ],
+    test_config_template: "art_artd_tests.xml",
+}
+
 prebuilt_etc {
     name: "com.android.art.artd.init.rc",
     src: "artd.rc",
diff --git a/artd/art_artd_tests.xml b/artd/art_artd_tests.xml
new file mode 100644
index 0000000..9129d98
--- /dev/null
+++ b/artd/art_artd_tests.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+
+        <option name="gtest-env" value="DEX2OATBOOTCLASSPATH=/apex/com.android.art/javalib/core-foo.jar:/system/framework/framework.jar" />
+        <option name="gtest-env" value="BOOTCLASSPATH=/apex/com.android.art/javalib/core-bar.jar:/system/framework/framework.jar:/apex/com.android.bar/javalib/bar.jar" />
+    </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+</configuration>
diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp
index 94f3f17..17678af 100644
--- a/artd/binder/Android.bp
+++ b/artd/binder/Android.bp
@@ -40,6 +40,32 @@
             apex_available: [
                 "com.android.art",
                 "com.android.art.debug",
+                "com.android.compos",
+            ],
+            // TODO(b/177273468): Increment to next version when possible
+            min_sdk_version: "S",
+        },
+    },
+    unstable: true,
+    visibility: [
+        "//system/tools/aidl/build",
+        "//art:__subpackages__",
+    ],
+}
+
+aidl_interface {
+    name: "artd-private-aidl",
+    srcs: [
+        "private/**/*.aidl",
+    ],
+    host_supported: true,
+    local_include_dir: "private",
+    backend: {
+        ndk: {
+            enabled: true,
+            apex_available: [
+                "com.android.art",
+                "com.android.art.debug",
             ],
             // TODO(b/177273468): Increment to next version when possible
             min_sdk_version: "S",
diff --git a/artd/binder/private/com/android/art/CompilerFilter.aidl b/artd/binder/private/com/android/art/CompilerFilter.aidl
new file mode 100644
index 0000000..1e2902a
--- /dev/null
+++ b/artd/binder/private/com/android/art/CompilerFilter.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.art;
+
+@Backing(type="int")
+enum CompilerFilter {
+    UNSUPPORTED = 0,
+    VERIFY,
+    SPEED,
+    SPEED_PROFILE,
+}
+
diff --git a/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl b/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl
new file mode 100644
index 0000000..43fd922
--- /dev/null
+++ b/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.art;
+
+import com.android.art.Isa;
+
+/**
+ * Arguments for dexopt of BCP extension.
+ *
+ * <h1>Security Considerations:</h1>
+ *
+ * <p>On Android, both the data provider and consumer are currently assumed in the same trusting
+ * domain * (e.g. since they are in the same process and the boundary is function call).
+ *
+ * <p>Compilation OS, on the contrary, plays role of data consumer and can't trust the data provided
+ * by * the potentially malicious clients. The data must be validated before use.
+ *
+ * <p>When adding a new field, make sure the value space can be validated. See "SECURITY:" below for
+ * examples.
+ *
+ * {@hide}
+ */
+parcelable DexoptBcpExtArgs {
+    // SECURITY: File descriptor are currently integers. They are assumed trusted in Android right
+    // now. For CompOS, they are verified transparently to the compiler, thus can also be assumed
+    // trusted.
+    int[] dexFds;
+    // Note: It's more ideal to put bootClasspaths and bootClasspathFds in a Map<path, fd>, but Map
+    // is only supported by the Java backend of AIDL. As a result, we need to maintain both arrays
+    // manually.
+    String[] bootClasspaths;
+    int[] bootClasspathFds;
+    int profileFd = -1;
+    int dirtyImageObjectsFd = -1;
+    // Output file descriptors
+    int oatFd = -1;
+    int vdexFd = -1;
+    int imageFd = -1;
+
+    // TODO(victorhsieh): Try to reconstruct behind the API, otherwise reasonable the security.
+    String[] dexPaths;
+    String oatLocation;
+
+    // SECURITY: The server may accept the request to produce code for the specified architecture,
+    // if supported.
+    Isa isa = Isa.UNSUPPORTED;
+
+    // SECURITY: Computational resource should not affect the compilation results.
+    int[] cpuSet;
+    int threads;
+}
diff --git a/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
new file mode 100644
index 0000000..b351c67
--- /dev/null
+++ b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.art;
+
+import com.android.art.CompilerFilter;
+import com.android.art.Isa;
+
+/**
+ * Arguments for dexopt of a system server JAR.
+ *
+ * <h1>Security Considerations:</h1>
+ *
+ * <p>On Android, both the data provider and consumer are currently assumed in the same trusting
+ * domain * (e.g. since they are in the same process and the boundary is function call).
+ *
+ * <p>Compilation OS, on the contrary, plays role of data consumer and can't trust the data provided
+ * by * the potentially malicious clients. The data must be validated before use.
+ *
+ * <p>When adding a new field, make sure the value space can be validated. See "SECURITY:" below for
+ * examples.
+ *
+ * {@hide}
+ */
+parcelable DexoptSystemServerArgs {
+    // SECURITY: File descriptor are currently integers. They are assumed trusted in Android right
+    // now. For CompOS, they are verified transparently to the compiler, thus can also be assumed
+    // trusted.
+    int dexFd = -1;
+    int profileFd = -1;
+    // Note: It's more ideal to put bootClasspaths and bootClasspathFds in a Map<path, fd>
+    // (similarly to the other bootClasspath*Fds), but Map is only supported by the Java backend of
+    // AIDL. As a result, we need to maintain both arrays manually. Note that this array is not used
+    // as -Xbootclasspath but as a container of the mapping keys for the arrays below.
+    String[] bootClasspaths;
+    int[] bootClasspathFds;
+    int[] bootClasspathImageFds;
+    int[] bootClasspathVdexFds;
+    int[] bootClasspathOatFds;
+    int[] classloaderFds;
+    // Output file descriptors
+    int imageFd = -1;
+    int vdexFd = -1;
+    int oatFd = -1;
+
+    // TODO(victorhsieh): Try to reconstruct behind the API, otherwise reasonable the security.
+    String dexPath;
+    String oatLocation;
+    String[] classloaderContext;
+    boolean isBootImageOnSystem;
+
+    // SECURITY: The server may accept the request to produce code for the specified architecture,
+    // if supported.
+    Isa isa = Isa.UNSUPPORTED;
+
+    // SECURITY: The server may apply compiler filter as long as the compilation can be truthful.
+    // For example, given that the profile is valid, the produced code should still be translated
+    // correctly. Effectively, what a malicious attacker can do is to produce less/more efficient
+    // code.
+    CompilerFilter compilerFilter = CompilerFilter.UNSUPPORTED;
+
+    // SECURITY: Computational resource should not affect the compilation results.
+    int[] cpuSet;
+    int threads;
+}
diff --git a/artd/binder/private/com/android/art/Isa.aidl b/artd/binder/private/com/android/art/Isa.aidl
new file mode 100644
index 0000000..3a2bd36
--- /dev/null
+++ b/artd/binder/private/com/android/art/Isa.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.art;
+
+@Backing(type="int")
+enum Isa {
+    UNSUPPORTED = 0,
+    ARM,
+    ARM64,
+    THUMB2,
+    X86,
+    X86_64,
+}
diff --git a/artd/include/libdexopt/libdexopt.h b/artd/include/libdexopt/libdexopt.h
new file mode 100644
index 0000000..2e958be
--- /dev/null
+++ b/artd/include/libdexopt/libdexopt.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef ART_ARTD_INCLUDE_LIBDEXOPT_LIBDEXOPT_H_
+#define ART_ARTD_INCLUDE_LIBDEXOPT_LIBDEXOPT_H_
+
+#include <string>
+#include <vector>
+
+#include "android-base/result.h"
+
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+
+namespace art {
+
+android::base::Result<void> AddDex2oatArgsFromBcpExtensionArgs(
+    const aidl::com::android::art::DexoptBcpExtArgs& dexopt_args,
+    std::vector<std::string>& dex2oat_args);
+
+android::base::Result<void> AddDex2oatArgsFromSystemServerArgs(
+    const aidl::com::android::art::DexoptSystemServerArgs& dexopt_args,
+    std::vector<std::string>& dex2oat_args);
+
+}  // namespace art
+
+#endif  // ART_ARTD_INCLUDE_LIBDEXOPT_LIBDEXOPT_H_
diff --git a/artd/libdexopt.cc b/artd/libdexopt.cc
new file mode 100644
index 0000000..c064c2e
--- /dev/null
+++ b/artd/libdexopt.cc
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2021 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 "libdexopt.h"
+#define LOG_TAG "libdexopt"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android-base/result.h"
+#include "base/file_utils.h"
+#include "log/log.h"
+
+#include "aidl/com/android/art/CompilerFilter.h"
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+#include "aidl/com/android/art/Isa.h"
+
+namespace art {
+
+namespace {
+
+using aidl::com::android::art::CompilerFilter;
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+using aidl::com::android::art::Isa;
+using android::base::Error;
+using android::base::Result;
+
+std::string GetBootImage() {
+  // Typically "/apex/com.android.art/javalib/boot.art".
+  return art::GetArtRoot() + "/javalib/boot.art";
+}
+
+std::string GetEnvironmentVariableOrDie(const char* name) {
+  const char* value = getenv(name);
+  LOG_ALWAYS_FATAL_IF(value == nullptr, "%s is not defined.", name);
+  return value;
+}
+
+std::string GetDex2oatBootClasspath() {
+  return GetEnvironmentVariableOrDie("DEX2OATBOOTCLASSPATH");
+}
+
+std::string GetBootClasspath() {
+  return GetEnvironmentVariableOrDie("BOOTCLASSPATH");
+}
+
+std::string ToInstructionSetString(Isa isa) {
+  switch (isa) {
+    case Isa::ARM:
+    case Isa::THUMB2:
+      return "arm";
+    case Isa::ARM64:
+      return "arm64";
+    case Isa::X86:
+      return "x86";
+    case Isa::X86_64:
+      return "x86_64";
+    default:
+      UNREACHABLE();
+  }
+}
+
+const char* CompilerFilterAidlToString(CompilerFilter compiler_filter) {
+  switch (compiler_filter) {
+    case CompilerFilter::SPEED_PROFILE:
+      return "speed-profile";
+    case CompilerFilter::SPEED:
+      return"speed";
+    case CompilerFilter::VERIFY:
+      return "verify";
+    default:
+      UNREACHABLE();
+  }
+}
+
+Result<void> AddBootClasspath(/*inout*/ std::vector<std::string>& cmdline,
+                              const std::string& bootclasspath_env,
+                              const std::vector<std::string>& boot_classpaths,
+                              const std::vector<int>& boot_classpath_fds) {
+  if (boot_classpaths.empty()) {
+    return Errorf("Missing BCP files");
+  }
+
+  if (boot_classpaths.size() != boot_classpath_fds.size()) {
+    return Errorf("Number of BCP paths (%d) != number of FDs (%d)", boot_classpaths.size(),
+                  boot_classpath_fds.size());
+  }
+
+  cmdline.emplace_back("--runtime-arg");
+  cmdline.emplace_back("-Xbootclasspath:" + bootclasspath_env);
+
+  // Construct a path->fd map from both arrays. If the client provides duplicated paths, only one
+  // will be used. This is fine since the client may not be trusted any way.
+  std::map<std::string, int> bcp_map;
+  auto zip = [](const std::string &path, int fd) { return std::make_pair(path, fd); };
+  std::transform(boot_classpaths.begin(), boot_classpaths.end(), boot_classpath_fds.begin(),
+                 std::inserter(bcp_map, bcp_map.end()), zip);
+
+  std::vector<std::string> jar_paths = android::base::Split(bootclasspath_env, ":");
+  std::stringstream ss;
+  ss << "-Xbootclasspathfds";
+  for (auto& jar_path : jar_paths) {
+    auto iter = bcp_map.find(jar_path);
+    if (iter == bcp_map.end()) {
+      ss << ":-1";
+    } else {
+      ss << ":" << std::to_string(iter->second);
+      bcp_map.erase(iter);
+    }
+  }
+  cmdline.emplace_back("--runtime-arg");
+  cmdline.emplace_back(ss.str());
+
+  if (!bcp_map.empty()) {
+    std::stringstream error_ss;
+    for (const auto &[key, _] : bcp_map) {
+      error_ss << key << ":";
+    }
+    return Error() << "Residual BCP paths: " << error_ss.str();
+  }
+  return {};
+}
+
+Result<void> AddCompiledBootClasspathFdsIfAny(/*inout*/ std::vector<std::string>& cmdline,
+                                              const DexoptSystemServerArgs& args) {
+  // Result
+  if ((args.bootClasspathImageFds.size() != args.bootClasspathOatFds.size()) ||
+      (args.bootClasspathImageFds.size() != args.bootClasspathVdexFds.size()) ||
+      (args.bootClasspathImageFds.size() != args.bootClasspaths.size())) {
+    return Errorf("Inconsistent FD numbers of BCP artifacts: jar/image/vdex/oat: %d/%d/%d/%d",
+                  args.bootClasspaths.size(), args.bootClasspathImageFds.size(),
+                  args.bootClasspathVdexFds.size(), args.bootClasspathOatFds.size());
+  }
+
+  if (!args.bootClasspathImageFds.empty()) {
+    cmdline.emplace_back("--runtime-arg");
+    cmdline.emplace_back(
+        "-Xbootclasspathimagefds:" + android::base::Join(args.bootClasspathImageFds, ':'));
+    cmdline.emplace_back("--runtime-arg");
+    cmdline.emplace_back(
+        "-Xbootclasspathoatfds:" + android::base::Join(args.bootClasspathOatFds, ':'));
+    cmdline.emplace_back("--runtime-arg");
+    cmdline.emplace_back(
+        "-Xbootclasspathvdexfds:" + android::base::Join(args.bootClasspathVdexFds, ':'));
+  }
+  return {};
+}
+
+void AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& cmdline,
+                                    int threads,
+                                    const std::vector<int>& cpu_set) {
+  if (threads > 0) {
+      cmdline.emplace_back(android::base::StringPrintf("-j%d", threads));
+  }
+  if (!cpu_set.empty()) {
+      cmdline.emplace_back("--cpu-set=" + android::base::Join(cpu_set, ','));
+  }
+}
+
+void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>& cmdline) {
+  cmdline.emplace_back("--android-root=out/empty");
+  cmdline.emplace_back("--abort-on-hard-verifier-error");
+  cmdline.emplace_back("--no-abort-on-soft-verifier-error");
+  cmdline.emplace_back("--compilation-reason=boot");
+  cmdline.emplace_back("--image-format=lz4");
+  cmdline.emplace_back("--force-determinism");
+  cmdline.emplace_back("--resolve-startup-const-strings=true");
+}
+
+void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& cmdline) {
+  cmdline.emplace_back("--generate-mini-debug-info");
+  cmdline.emplace_back("--strip");
+}
+
+}  // namespace
+
+Result<void> AddDex2oatArgsFromBcpExtensionArgs(const DexoptBcpExtArgs& args,
+                                                /*out*/ std::vector<std::string>& cmdline) {
+  // Common dex2oat flags
+  AddDex2OatCommonOptions(cmdline);
+  AddDex2OatDebugInfo(cmdline);
+
+  cmdline.emplace_back("--instruction-set=" + ToInstructionSetString(args.isa));
+
+  if (args.profileFd >= 0) {
+    cmdline.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", args.profileFd));
+    cmdline.emplace_back("--compiler-filter=speed-profile");
+  } else {
+    cmdline.emplace_back("--compiler-filter=speed");
+  }
+
+  // Compile as a single image for fewer files and slightly less memory overhead.
+  cmdline.emplace_back("--single-image");
+
+  // Set boot-image and expectation of compiling boot classpath extensions.
+  cmdline.emplace_back("--boot-image=" + GetBootImage());
+
+  if (args.dirtyImageObjectsFd >= 0) {
+    cmdline.emplace_back(android::base::StringPrintf("--dirty-image-objects-fd=%d",
+                                                     args.dirtyImageObjectsFd));
+  }
+
+  if (args.dexPaths.size() != args.dexFds.size()) {
+    return Errorf("Mismatched number of dexPaths (%d) and dexFds (%d)",
+                  args.dexPaths.size(),
+                  args.dexFds.size());
+  }
+  for (unsigned int i = 0; i < args.dexPaths.size(); ++i) {
+    cmdline.emplace_back("--dex-file=" + args.dexPaths[i]);
+    cmdline.emplace_back(android::base::StringPrintf("--dex-fd=%d", args.dexFds[i]));
+  }
+
+  std::string bcp_env = GetDex2oatBootClasspath();
+  auto result = AddBootClasspath(cmdline, bcp_env, args.bootClasspaths, args.bootClasspathFds);
+  if (!result.ok()) {
+    return result.error();
+  }
+
+  cmdline.emplace_back("--oat-location=" + args.oatLocation);
+
+  // Output files
+  if (args.imageFd < 0) {
+    return Error() << "imageFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--image-fd=%d", args.imageFd));
+  if (args.vdexFd < 0) {
+    return Error() << "vdexFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--output-vdex-fd=%d", args.vdexFd));
+  if (args.oatFd < 0) {
+    return Error() << "oatFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--oat-fd=%d", args.oatFd));
+
+  AddDex2OatConcurrencyArguments(cmdline, args.threads, args.cpuSet);
+
+  return {};
+}
+
+Result<void> AddDex2oatArgsFromSystemServerArgs(const DexoptSystemServerArgs& args,
+                                                /*out*/ std::vector<std::string>& cmdline) {
+  cmdline.emplace_back("--dex-file=" + args.dexPath);
+  cmdline.emplace_back(android::base::StringPrintf("--dex-fd=%d", args.dexFd));
+
+  // Common dex2oat flags
+  AddDex2OatCommonOptions(cmdline);
+  AddDex2OatDebugInfo(cmdline);
+
+  cmdline.emplace_back("--instruction-set=" + ToInstructionSetString(args.isa));
+
+  if (args.compilerFilter == CompilerFilter::SPEED_PROFILE) {
+    if (args.profileFd < 0) {
+      return Error() << "profileFd is missing";
+    }
+    cmdline.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", args.profileFd));
+    cmdline.emplace_back("--compiler-filter=speed-profile");
+  } else {
+    cmdline.emplace_back("--compiler-filter=" +
+                         std::string(CompilerFilterAidlToString(args.compilerFilter)));
+  }
+
+  // Output files
+  if (args.imageFd < 0) {
+    return Error() << "imageFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--app-image-fd=%d", args.imageFd));
+  if (args.vdexFd < 0) {
+    return Error() << "vdexFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--output-vdex-fd=%d", args.vdexFd));
+  if (args.oatFd < 0) {
+    return Error() << "oatFd is missing";
+  }
+  cmdline.emplace_back(android::base::StringPrintf("--oat-fd=%d", args.oatFd));
+  cmdline.emplace_back("--oat-location=" + args.oatLocation);
+
+  std::string bcp_env = GetBootClasspath();
+  auto result = AddBootClasspath(cmdline, bcp_env, args.bootClasspaths, args.bootClasspathFds);
+  if (!result.ok()) {
+    return result.error();
+  }
+  result = AddCompiledBootClasspathFdsIfAny(cmdline, args);
+  if (!result.ok()) {
+    return result.error();
+  }
+
+  if (args.classloaderFds.empty()) {
+    cmdline.emplace_back("--class-loader-context=PCL[]");
+  } else {
+    const std::string context_path = android::base::Join(args.classloaderContext, ':');
+    cmdline.emplace_back("--class-loader-context=PCL[" + context_path + "]");
+    cmdline.emplace_back("--class-loader-context-fds=" +
+                         android::base::Join(args.classloaderFds, ':'));
+  }
+
+  // Derive boot image
+  // b/197176583
+  // If the boot extension artifacts are not on /data, then boot extensions are not re-compiled
+  // and the artifacts must exist on /system.
+  std::vector<std::string> jar_paths = android::base::Split(GetDex2oatBootClasspath(), ":");
+  auto iter = std::find_if_not(jar_paths.begin(), jar_paths.end(), &LocationIsOnArtModule);
+  if (iter == jar_paths.end()) {
+    return Error() << "Missing BCP extension compatible JAR";
+  }
+  const std::string& first_boot_extension_compatible_jars = *iter;
+  // TODO(197176583): Support compiling against BCP extension in /system.
+  const std::string extension_image = GetBootImagePath(args.isBootImageOnSystem,
+                                                       first_boot_extension_compatible_jars);
+  if (extension_image.empty()) {
+    return Error() << "Can't identify the first boot extension compatible jar";
+  }
+  cmdline.emplace_back("--boot-image=" + GetBootImage() + ":" + extension_image);
+
+  AddDex2OatConcurrencyArguments(cmdline, args.threads, args.cpuSet);
+
+  return {};
+}
+
+}  // namespace art
diff --git a/artd/libdexopt_test.cc b/artd/libdexopt_test.cc
new file mode 100644
index 0000000..7536dcd
--- /dev/null
+++ b/artd/libdexopt_test.cc
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2021 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 "libdexopt.h"
+
+#include <string>
+#include <vector>
+
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "aidl/com/android/art/CompilerFilter.h"
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+#include "aidl/com/android/art/Isa.h"
+
+namespace art {
+
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using aidl::com::android::art::CompilerFilter;
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+using aidl::com::android::art::Isa;
+using android::base::Result;
+
+std::string GetEnvironmentVariableOrDie(const char* name) {
+  const char* value = getenv(name);
+  EXPECT_NE(value, nullptr);
+  return value;
+}
+
+// See art_artd_tests.xml for *CLASSPATH setup.
+class LibDexoptTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+
+    default_bcp_ext_args_.dexFds = {10, 11};
+    default_bcp_ext_args_.bootClasspaths = android::base::Split(
+        GetEnvironmentVariableOrDie("DEX2OATBOOTCLASSPATH"), ":");  // from art_artd_tests.xml
+    default_bcp_ext_args_.bootClasspathFds = {21, 22};
+    default_bcp_ext_args_.profileFd = 30;
+    default_bcp_ext_args_.dirtyImageObjectsFd = 31;
+    default_bcp_ext_args_.imageFd = 90;
+    default_bcp_ext_args_.vdexFd = 91;
+    default_bcp_ext_args_.oatFd = 92;
+    default_bcp_ext_args_.dexPaths = {"/path/to/foo.jar", "/path/to/bar.jar"};
+    default_bcp_ext_args_.oatLocation = "/oat/location/bar.odex";
+    default_bcp_ext_args_.isa = Isa::X86_64;
+    default_bcp_ext_args_.cpuSet = {0, 1};
+    default_bcp_ext_args_.threads = 42;
+    ASSERT_EQ(default_bcp_ext_args_.bootClasspaths.size(),
+              default_bcp_ext_args_.bootClasspathFds.size());
+
+    default_system_server_args_.dexFd = 10;
+    default_system_server_args_.profileFd = 11;
+    default_system_server_args_.bootClasspaths = android::base::Split(
+        GetEnvironmentVariableOrDie("BOOTCLASSPATH"), ":");  // from art_artd_tests.xml
+    default_system_server_args_.bootClasspathFds = {21, 22, 23};
+    default_system_server_args_.bootClasspathImageFds = {-1, 31, -1};
+    default_system_server_args_.bootClasspathVdexFds = {-1, 32, -1};
+    default_system_server_args_.bootClasspathOatFds = {-1, 33, -1};
+    default_system_server_args_.classloaderFds = {40, 41};
+    default_system_server_args_.classloaderContext = {"/cl/abc.jar", "/cl/def.jar"};
+    default_system_server_args_.imageFd = 90;
+    default_system_server_args_.vdexFd = 91;
+    default_system_server_args_.oatFd = 92;
+    default_system_server_args_.dexPath = "/path/to/foo.jar";
+    default_system_server_args_.oatLocation = "/oat/location/bar.odex";
+    default_system_server_args_.isa = Isa::X86_64;
+    default_system_server_args_.compilerFilter = CompilerFilter::SPEED_PROFILE;
+    default_system_server_args_.cpuSet = {0, 1};
+    default_system_server_args_.threads = 42;
+    default_system_server_args_.isBootImageOnSystem = true;
+    ASSERT_EQ(default_system_server_args_.bootClasspaths.size(),
+              default_system_server_args_.bootClasspathFds.size());
+  }
+
+  void TearDown() override {
+    CommonArtTest::TearDown();
+  }
+
+  std::vector<std::string> Dex2oatArgsFromBcpExtensionArgs(const DexoptBcpExtArgs& args) {
+    std::vector<std::string> cmdline;
+    Result<void> result = AddDex2oatArgsFromBcpExtensionArgs(args, cmdline);
+    EXPECT_TRUE(result.ok()) << "Failed expectation: " << result.error().message();
+    return cmdline;
+  }
+
+  std::vector<std::string> Dex2oatArgsFromSystemServerArgs(const DexoptSystemServerArgs& args) {
+    std::vector<std::string> cmdline;
+    Result<void> result = AddDex2oatArgsFromSystemServerArgs(args, cmdline);
+    EXPECT_TRUE(result.ok()) << "Failed expectation: " << result.error().message();
+    return cmdline;
+  }
+
+  bool DexoptBcpExtArgsIsInvalid(const DexoptBcpExtArgs& args) {
+    std::vector<std::string> cmdline;
+    Result<void> result = AddDex2oatArgsFromBcpExtensionArgs(args, cmdline);
+    return result.ok();
+  }
+
+  bool DexoptSystemServerArgsIsInvalid(const DexoptSystemServerArgs& args) {
+    std::vector<std::string> cmdline;
+    Result<void> result = AddDex2oatArgsFromSystemServerArgs(args, cmdline);
+    return result.ok();
+  }
+
+  DexoptBcpExtArgs default_bcp_ext_args_;
+  DexoptSystemServerArgs default_system_server_args_;
+};
+
+TEST_F(LibDexoptTest, AddDex2oatArgsFromBcpExtensionArgs) {
+  // Test basics with default args
+  {
+    std::vector<std::string> cmdline = Dex2oatArgsFromBcpExtensionArgs(default_bcp_ext_args_);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Contains("--dex-fd=10"),
+        Contains("--dex-fd=11"),
+        Contains("--dex-file=/path/to/foo.jar"),
+        Contains("--dex-file=/path/to/bar.jar"),
+        Contains(HasSubstr("-Xbootclasspath:")),
+        Contains("-Xbootclasspathfds:21:22"),
+
+        Contains("--profile-file-fd=30"),
+        Contains("--compiler-filter=speed-profile"),
+
+        Contains("--image-fd=90"),
+        Contains("--output-vdex-fd=91"),
+        Contains("--oat-fd=92"),
+        Contains("--oat-location=/oat/location/bar.odex"),
+
+        Contains("--dirty-image-objects-fd=31"),
+        Contains("--instruction-set=x86_64"),
+        Contains("--cpu-set=0,1"),
+        Contains("-j42")));
+  }
+
+  // No profile
+  {
+    auto args = default_bcp_ext_args_;
+    args.profileFd = -1;
+    std::vector<std::string> cmdline = Dex2oatArgsFromBcpExtensionArgs(args);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Not(Contains(HasSubstr("--profile-file-fd="))),
+        Contains("--compiler-filter=speed")));
+  }
+
+  // No dirty image objects fd
+  {
+    auto args = default_bcp_ext_args_;
+    args.dirtyImageObjectsFd = -1;
+    std::vector<std::string> cmdline = Dex2oatArgsFromBcpExtensionArgs(args);
+
+    EXPECT_THAT(cmdline, Not(Contains(HasSubstr("--dirty-image-objects-fd"))));
+  }
+}
+
+TEST_F(LibDexoptTest, AddDex2oatArgsFromBcpExtensionArgs_InvalidArguments) {
+  // Mismatched dex number
+  {
+    auto args = default_bcp_ext_args_;
+    args.dexPaths = {"/path/to/foo.jar", "/path/to/bar.jar"};
+    args.dexFds = {100, 101, 102};
+
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+  }
+
+  // Mismatched classpath arguments
+  {
+    auto args = default_bcp_ext_args_;
+    args.bootClasspathFds.emplace_back(200);
+
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+  }
+
+  // Mismatched classpath arguments
+  {
+    auto args = default_bcp_ext_args_;
+    args.bootClasspaths.emplace_back("/unrecognized/jar");
+
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+  }
+
+  // Missing output fds
+  {
+    auto args = default_bcp_ext_args_;
+    args.imageFd = -1;
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+
+    args = default_bcp_ext_args_;
+    args.vdexFd = -1;
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+
+    args = default_bcp_ext_args_;
+    args.oatFd = -1;
+    EXPECT_FALSE(DexoptBcpExtArgsIsInvalid(args));
+  }
+}
+
+TEST_F(LibDexoptTest, AddDex2oatArgsFromSystemServerArgs) {
+  // Test basics with default args
+  {
+    std::vector<std::string> cmdline = Dex2oatArgsFromSystemServerArgs(default_system_server_args_);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Contains("--dex-fd=10"),
+        Contains("--dex-file=/path/to/foo.jar"),
+        Contains(HasSubstr("-Xbootclasspath:")),
+        Contains("-Xbootclasspathfds:21:22:23"),
+        Contains("-Xbootclasspathimagefds:-1:31:-1"),
+        Contains("-Xbootclasspathvdexfds:-1:32:-1"),
+        Contains("-Xbootclasspathoatfds:-1:33:-1"),
+
+        Contains("--profile-file-fd=11"),
+        Contains("--compiler-filter=speed-profile"),
+
+        Contains("--app-image-fd=90"),
+        Contains("--output-vdex-fd=91"),
+        Contains("--oat-fd=92"),
+        Contains("--oat-location=/oat/location/bar.odex"),
+
+        Contains("--class-loader-context-fds=40:41"),
+        Contains("--class-loader-context=PCL[/cl/abc.jar:/cl/def.jar]"),
+
+        Contains("--instruction-set=x86_64"),
+        Contains("--cpu-set=0,1"),
+        Contains("-j42")));
+  }
+
+  // Test different compiler filters
+  {
+    // speed
+    auto args = default_system_server_args_;
+    args.compilerFilter = CompilerFilter::SPEED;
+    std::vector<std::string> cmdline = Dex2oatArgsFromSystemServerArgs(args);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Not(Contains(HasSubstr("--profile-file-fd="))),
+        Contains("--compiler-filter=speed")));
+
+    // verify
+    args = default_system_server_args_;
+    args.compilerFilter = CompilerFilter::VERIFY;
+    cmdline = Dex2oatArgsFromSystemServerArgs(args);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Not(Contains(HasSubstr("--profile-file-fd="))),
+        Contains("--compiler-filter=verify")));
+  }
+
+  // Test classloader context
+  {
+    auto args = default_system_server_args_;
+    args.classloaderFds = {};
+    args.classloaderContext = {};
+    std::vector<std::string> cmdline = Dex2oatArgsFromSystemServerArgs(args);
+
+    EXPECT_THAT(cmdline, AllOf(
+        Not(Contains(HasSubstr("--class-loader-context-fds"))),
+        Contains("--class-loader-context=PCL[]")));
+  }
+}
+
+TEST_F(LibDexoptTest, AddDex2oatArgsFromSystemServerArgs_InvalidArguments) {
+  // Mismatched classpath arguments
+  {
+    auto args = default_system_server_args_;
+    args.bootClasspathFds.emplace_back(200);
+
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+  }
+
+  // Unrecognized jar path
+  {
+    auto args = default_system_server_args_;
+    args.bootClasspaths.emplace_back("/unrecognized/jar");
+
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+  }
+
+  // speed-profile without profile fd
+  {
+    auto args = default_system_server_args_;
+    args.compilerFilter = CompilerFilter::SPEED_PROFILE;
+    args.profileFd = -1;
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+  }
+
+  // Missing output fds
+  {
+    auto args = default_system_server_args_;
+    args.imageFd = -1;
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+
+    args = default_system_server_args_;
+    args.vdexFd = -1;
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+
+    args = default_system_server_args_;
+    args.oatFd = -1;
+    EXPECT_FALSE(DexoptSystemServerArgsIsInvalid(args));
+  }
+}
+
+}  // namespace art
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 5c10695..b074a41 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -359,12 +359,16 @@
     "art_libdexfile_support_tests",
     "art_libprofile_tests",
     "art_oatdump_tests",
-    "art_odrefresh_tests",
     "art_profman_tests",
     "art_runtime_compiler_tests",
     "art_runtime_tests",
     "art_sigchain_tests",
 ]
+art_gtests_first_only = [
+    // We only ship "first" for odrefresh, so don't bother test the "second". This also avoids a
+    // problem where some dependencies may only build "first" and doesn't have "second".
+    "art_odrefresh_tests",
+]
 
 // "Testing" version of the ART APEX module (containing both release
 // and debug variants, additional tools, and ART gtests), for testing
@@ -374,7 +378,14 @@
     defaults: ["com.android.art-devel-defaults"],
     file_contexts: ":com.android.art.debug-file_contexts",
     certificate: ":com.android.art.certificate",
-    tests: art_gtests,
+    multilib: {
+        both: {
+            tests: art_gtests,
+        },
+        first: {
+            tests: art_gtests_first_only,
+        },
+    },
     binaries: ["signal_dumper"], // Need signal_dumper for run-tests.
     updatable: false,
 }
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 0f87901..1ac85ca 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -44,6 +44,7 @@
 
 # Architectures supported by APEX packages.
 ARCHS = ["arm", "arm64", "x86", "x86_64"]
+ARCHS_64 = ["arm64", "x86_64"]
 
 # Directory containing ART tests within an ART APEX (if the package includes
 # any). ART test executables are installed in `bin/art/<arch>`. Segregating
@@ -247,7 +248,7 @@
       self.fail('%s is not a symlink', path)
     self._expected_file_globs.add(path)
 
-  def arch_dirs_for_path(self, path):
+  def arch_dirs_for_path(self, path, prefer64=False):
     # Look for target-specific subdirectories for the given directory path.
     # This is needed because the list of build targets is not propagated
     # to this script.
@@ -255,15 +256,15 @@
     # TODO(b/123602136): Pass build target information to this script and fix
     # all places where this function in used (or similar workarounds).
     dirs = []
-    for arch in ARCHS:
+    archs = ARCHS_64 if prefer64 else ARCHS
+    for arch in archs:
       dir = '%s/%s' % (path, arch)
       found, _ = self.is_dir(dir)
       if found:
         dirs.append(dir)
     return dirs
 
-  def check_art_test_executable(self, filename):
-    dirs = self.arch_dirs_for_path(ART_TEST_DIR)
+  def check_art_test_executable_in_dirs(self, filename, dirs):
     if not dirs:
       self.fail('ART test binary missing: %s', filename)
     for dir in dirs:
@@ -272,6 +273,14 @@
       if not self._provider.get(test_path).is_exec:
         self.fail('%s is not executable', test_path)
 
+  def check_art_test_executable(self, filename):
+    self.check_art_test_executable_in_dirs(
+        filename, self.arch_dirs_for_path(ART_TEST_DIR))
+
+  def check_prefer64_art_test_executable(self, filename):
+    self.check_art_test_executable_in_dirs(
+        filename, self.arch_dirs_for_path(ART_TEST_DIR, prefer64=True))
+
   def check_art_test_data(self, filename):
     dirs = self.arch_dirs_for_path(ART_TEST_DIR)
     if not dirs:
@@ -469,6 +478,8 @@
 
     # Check internal libraries for ART.
     self._checker.check_prefer64_library('artd-aidl-ndk')
+    self._checker.check_prefer64_library('artd-private-aidl-ndk')
+    self._checker.check_prefer64_library('libdexopt')
     self._checker.check_native_library('libadbconnection')
     self._checker.check_native_library('libart')
     self._checker.check_native_library('libart-compiler')
@@ -669,6 +680,7 @@
 
   def run(self):
     # Check ART test binaries.
+    self._checker.check_prefer64_art_test_executable('art_odrefresh_tests')
     self._checker.check_art_test_executable('art_cmdline_tests')
     self._checker.check_art_test_executable('art_compiler_tests')
     self._checker.check_art_test_executable('art_dex2oat_tests')
@@ -687,13 +699,13 @@
     self._checker.check_art_test_executable('art_libdexfile_tests')
     self._checker.check_art_test_executable('art_libprofile_tests')
     self._checker.check_art_test_executable('art_oatdump_tests')
-    self._checker.check_art_test_executable('art_odrefresh_tests')
     self._checker.check_art_test_executable('art_profman_tests')
     self._checker.check_art_test_executable('art_runtime_compiler_tests')
     self._checker.check_art_test_executable('art_runtime_tests')
     self._checker.check_art_test_executable('art_sigchain_tests')
 
     # Check ART test (internal) libraries.
+    self._checker.check_prefer64_library('libdexoptd')
     self._checker.check_native_library('libartd-gtest')
     self._checker.check_native_library('libartd-simulator-container')
 
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index 884bca1..0780fe0 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -358,6 +358,18 @@
   return GetDefaultBootImageLocation(android_root, /*deny_art_apex_data_files=*/false);
 }
 
+std::string GetBootImagePath(bool on_system, const std::string& jar_path) {
+  if (on_system) {
+    const std::string jar_name = android::base::Basename(jar_path);
+    const std::string image_name = ReplaceFileExtension(jar_name, "art");
+    // Typically "/system/framework/boot-framework.art".
+    return StringPrintf("%s/framework/boot-%s", GetAndroidRoot().c_str(), image_name.c_str());
+  } else {
+    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework.art".
+    return GetApexDataBootImage(jar_path);
+  }
+}
+
 static std::string GetDalvikCacheDirectory(std::string_view root_directory,
                                            std::string_view sub_directory = {}) {
   static constexpr std::string_view kDalvikCache = "dalvik-cache";
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index 3c11ac3..6fc1caa 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -77,6 +77,9 @@
 std::string GetDefaultBootImageLocation(const std::string& android_root,
                                         bool deny_art_apex_data_files);
 
+// Returns the boot image path of the provided jar, on /system or /data.
+std::string GetBootImagePath(bool on_system, const std::string& jar_path);
+
 // Return true if we found the dalvik cache and stored it in the dalvik_cache argument.
 // `have_android_data` will be set to true if we have an ANDROID_DATA that exists,
 // `dalvik_cache_exists` will be true if there is a dalvik-cache directory that is present.
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index 9016719..a8ec76c 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -29,6 +29,7 @@
         "odrefresh.cc",
         "odr_common.cc",
         "odr_compilation_log.cc",
+        "odr_dexopt.cc",
         "odr_fs_utils.cc",
         "odr_metrics.cc",
         "odr_metrics_record.cc",
@@ -41,6 +42,7 @@
         "art-odrefresh-operator-srcs",
     ],
     shared_libs: [
+        "artd-private-aidl-ndk",
         "libartpalette",
         "libbase",
         "liblog",
@@ -104,6 +106,7 @@
     shared_libs: [
         "libart",
         "libartbase",
+        "libdexopt",
     ],
     apex_available: [
         "com.android.art",
@@ -124,6 +127,7 @@
     shared_libs: [
         "libartd",
         "libartbased",
+        "libdexoptd",
     ],
     apex_available: [
         "com.android.art.debug",
@@ -176,6 +180,11 @@
     static_libs: [
         "libgmock",
     ],
+    target: {
+        android: {
+            compile_multilib: "first",
+        },
+    },
 }
 
 // Version of ART gtest `art_odrefresh_tests` bundled with the ART APEX on target.
@@ -189,6 +198,7 @@
     host_supported: false,
     shared_libs: [
         "libdexfiled",
+        "libdexoptd",
     ],
     test_config_template: "art_odrefresh_tests.xml",
 }
@@ -202,6 +212,7 @@
     ],
     shared_libs: [
         "libdexfile",
+        "libdexopt",
     ],
     test_config_template: "art_odrefresh_tests.xml",
 }
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 48327f6..1fd1900 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -53,7 +53,7 @@
   std::string program_name_;
   std::string system_server_classpath_;
   ZygoteKind zygote_kind_;
-  std::string compilation_os_address_;
+  int compilation_os_address_ = -1;
   std::string boot_classpath_;
 
   // Staging directory for artifacts. The directory must exist and will be automatically removed
@@ -123,10 +123,8 @@
   const std::string& GetSystemServerClasspath() const {
     return system_server_classpath_;
   }
-  bool UseCompilationOs() const { return !compilation_os_address_.empty(); }
-  const std::string& GetCompilationOsAddress() const {
-    return compilation_os_address_;
-  }
+  bool UseCompilationOs() const { return compilation_os_address_ >= 0; }
+  int GetCompilationOsAddress() const { return compilation_os_address_; }
   const std::string& GetStagingDir() const {
     return staging_dir_;
   }
@@ -140,7 +138,7 @@
 
   void SetDryRun() { dry_run_ = true; }
   void SetIsa(const InstructionSet isa) { isa_ = isa; }
-  void SetCompilationOsAddress(const std::string& address) { compilation_os_address_ = address; }
+  void SetCompilationOsAddress(int address) { compilation_os_address_ = address; }
 
   void SetSystemServerClasspath(const std::string& classpath) {
     system_server_classpath_ = classpath;
diff --git a/odrefresh/odr_dexopt.cc b/odrefresh/odr_dexopt.cc
new file mode 100644
index 0000000..99e0814
--- /dev/null
+++ b/odrefresh/odr_dexopt.cc
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 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 "odr_dexopt.h"
+
+#include <vector>
+
+#include "android-base/logging.h"
+#include <android-base/result.h>
+#include "android-base/strings.h"
+#include "exec_utils.h"
+#include "log/log.h"
+#include "odr_config.h"
+#include "libdexopt.h"
+
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+
+namespace art {
+namespace odrefresh {
+
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+using android::base::Result;
+
+namespace {
+
+int ExecAndReturnCode(ExecUtils* exec_utils,
+                      std::vector<std::string>& cmdline,
+                      time_t timeout_secs,
+                      /*out*/ bool* timed_out,
+                      /*out*/ std::string* error_msg) {
+  LOG(DEBUG) << "odr_dexopt cmdline: " << android::base::Join(cmdline, ' ') << " [timeout "
+             << timeout_secs << "s]";
+  return exec_utils->ExecAndReturnCode(cmdline, timeout_secs, timed_out, error_msg);
+}
+
+class OdrDexoptLocal final : public OdrDexopt {
+ public:
+  static OdrDexoptLocal* Create(const std::string& dex2oat_path,
+                                std::unique_ptr<ExecUtils> exec_utils) {
+    return new OdrDexoptLocal(dex2oat_path, std::move(exec_utils));
+  }
+
+  int DexoptBcpExtension(const DexoptBcpExtArgs& args,
+                         time_t timeout_secs,
+                         /*out*/ bool* timed_out,
+                         /*out*/ std::string* error_msg) override {
+    std::vector<std::string> cmdline = { dex2oat_path_ };
+    auto result = art::AddDex2oatArgsFromBcpExtensionArgs(args, cmdline);
+    if (!result.ok()) {
+      LOG(ERROR) << "Dexopt (local) failed: " << result.error().message() << ", cmdline: "
+                 << android::base::Join(cmdline, ' ');
+      return -1;
+    }
+    return ExecAndReturnCode(exec_utils_.get(), cmdline, timeout_secs, timed_out, error_msg);
+  }
+
+  int DexoptSystemServer(const DexoptSystemServerArgs& args,
+                         time_t timeout_secs,
+                         /*out*/ bool* timed_out,
+                         /*out*/ std::string* error_msg) override {
+    std::vector<std::string> cmdline = { dex2oat_path_ };
+    auto result = art::AddDex2oatArgsFromSystemServerArgs(args, cmdline);
+    if (!result.ok()) {
+      LOG(ERROR) << "Dexopt (local) failed: " << result.error().message() << ", cmdline: "
+                 << android::base::Join(cmdline, ' ');
+      return -1;
+    }
+    return ExecAndReturnCode(exec_utils_.get(), cmdline, timeout_secs, timed_out, error_msg);
+  }
+
+ private:
+  OdrDexoptLocal(const std::string& dex2oat_path, std::unique_ptr<ExecUtils> exec_utils)
+      : dex2oat_path_(dex2oat_path), exec_utils_(std::move(exec_utils)) {}
+
+  std::string dex2oat_path_;
+  std::unique_ptr<ExecUtils> exec_utils_;
+};
+
+class OdrDexoptCompilationOS final : public OdrDexopt {
+ public:
+  static OdrDexoptCompilationOS* Create(int cid, std::unique_ptr<ExecUtils> exec_utils) {
+    return new OdrDexoptCompilationOS(cid, std::move(exec_utils));
+  }
+
+  int DexoptBcpExtension(const DexoptBcpExtArgs& args,
+                         time_t timeout_secs,
+                         /*out*/ bool* timed_out,
+                         /*out*/ std::string* error_msg) override {
+    std::vector<int> input_fds, output_fds;
+    collectFdsFromDexoptBcpExtensionArgs(input_fds, output_fds, args);
+
+    std::vector<std::string> cmdline;
+    AppendPvmExecArgs(cmdline, input_fds, output_fds);
+
+    // Original dex2oat flags
+    cmdline.push_back("/apex/com.android.art/bin/dex2oat64");
+    auto result = AddDex2oatArgsFromBcpExtensionArgs(args, cmdline);
+    if (!result.ok()) {
+      LOG(ERROR) << "Dexopt (CompOS) failed: " << result.error().message() << ", cmdline: "
+                 << android::base::Join(cmdline, ' ');
+      return -1;
+    }
+
+    return ExecAndReturnCode(exec_utils_.get(), cmdline, timeout_secs, timed_out, error_msg);
+  }
+
+  int DexoptSystemServer(const DexoptSystemServerArgs& args,
+                         time_t timeout_secs,
+                         /*out*/ bool* timed_out,
+                         /*out*/ std::string* error_msg) override {
+    std::vector<int> input_fds, output_fds;
+    collectFdsFromDexoptSystemServerArgs(input_fds, output_fds, args);
+
+    std::vector<std::string> cmdline;
+    AppendPvmExecArgs(cmdline, input_fds, output_fds);
+
+    // Original dex2oat flags
+    cmdline.push_back("/apex/com.android.art/bin/dex2oat64");
+    auto result = AddDex2oatArgsFromSystemServerArgs(args, cmdline);
+    if (!result.ok()) {
+      LOG(ERROR) << "Dexopt (CompOS) failed: " << result.error().message() << ", cmdline: "
+                 << android::base::Join(cmdline, ' ');
+      return -1;
+    }
+
+    LOG(DEBUG) << "DexoptSystemServer cmdline: " << android::base::Join(cmdline, ' ')
+               << " [timeout " << timeout_secs << "s]";
+    return ExecAndReturnCode(exec_utils_.get(), cmdline, timeout_secs, timed_out, error_msg);
+  }
+
+ private:
+  OdrDexoptCompilationOS(int cid, std::unique_ptr<ExecUtils> exec_utils)
+      : cid_(cid), exec_utils_(std::move(exec_utils)) {}
+
+  void AppendPvmExecArgs(/*inout*/ std::vector<std::string>& cmdline,
+                         const std::vector<int>& input_fds,
+                         const std::vector<int>& output_fds) {
+    cmdline.emplace_back("/apex/com.android.compos/bin/pvm_exec");
+    cmdline.emplace_back("--cid=" + std::to_string(cid_));
+    cmdline.emplace_back("--in-fd=" + android::base::Join(input_fds, ','));
+    cmdline.emplace_back("--out-fd=" + android::base::Join(output_fds, ','));
+    cmdline.emplace_back("--");
+  }
+
+  void collectFdsFromDexoptBcpExtensionArgs(/*inout*/ std::vector<int>& input_fds,
+                                            /*inout*/ std::vector<int>& output_fds,
+                                            const DexoptBcpExtArgs& args) {
+    // input
+    insertOnlyNonNegative(input_fds, args.dexFds);
+    insertIfNonNegative(input_fds, args.profileFd);
+    insertIfNonNegative(input_fds, args.dirtyImageObjectsFd);
+    insertOnlyNonNegative(input_fds, args.bootClasspathFds);
+    // output
+    insertIfNonNegative(output_fds, args.imageFd);
+    insertIfNonNegative(output_fds, args.vdexFd);
+    insertIfNonNegative(output_fds, args.oatFd);
+  }
+
+  void collectFdsFromDexoptSystemServerArgs(/*inout*/ std::vector<int>& input_fds,
+                                            /*inout*/ std::vector<int>& output_fds,
+                                            const DexoptSystemServerArgs& args) {
+    // input
+    insertIfNonNegative(input_fds, args.dexFd);
+    insertIfNonNegative(input_fds, args.profileFd);
+    insertOnlyNonNegative(input_fds, args.bootClasspathFds);
+    insertOnlyNonNegative(input_fds, args.bootClasspathImageFds);
+    insertOnlyNonNegative(input_fds, args.bootClasspathVdexFds);
+    insertOnlyNonNegative(input_fds, args.bootClasspathOatFds);
+    insertOnlyNonNegative(input_fds, args.classloaderFds);
+    // output
+    insertIfNonNegative(output_fds, args.imageFd);
+    insertIfNonNegative(output_fds, args.vdexFd);
+    insertIfNonNegative(output_fds, args.oatFd);
+  }
+
+  void insertIfNonNegative(/*inout*/ std::vector<int>& vec, int n) {
+    if (n < 0) {
+      return;
+    }
+    vec.emplace_back(n);
+  }
+
+  void insertOnlyNonNegative(/*inout*/ std::vector<int>& vec, const std::vector<int>& ns) {
+    std::copy_if(ns.begin(), ns.end(), std::back_inserter(vec), [](int n) { return n >= 0; });
+  }
+
+  int cid_;
+  std::unique_ptr<ExecUtils> exec_utils_;
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<OdrDexopt> OdrDexopt::Create(const OdrConfig& config,
+                                             std::unique_ptr<ExecUtils> exec_utils) {
+  if (config.UseCompilationOs()) {
+    int cid = config.GetCompilationOsAddress();
+    return std::unique_ptr<OdrDexopt>(OdrDexoptCompilationOS::Create(cid, std::move(exec_utils)));
+  } else {
+    return std::unique_ptr<OdrDexopt>(OdrDexoptLocal::Create(config.GetDex2Oat(),
+                                                             std::move(exec_utils)));
+  }
+}
+
+}  // namespace odrefresh
+}  // namespace art
diff --git a/odrefresh/odr_dexopt.h b/odrefresh/odr_dexopt.h
new file mode 100644
index 0000000..2db3b4a
--- /dev/null
+++ b/odrefresh/odr_dexopt.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef ART_ODREFRESH_ODR_DEXOPT_H_
+#define ART_ODREFRESH_ODR_DEXOPT_H_
+
+#include <memory>
+#include <string>
+#include <time.h>
+
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+
+namespace art {
+
+class ExecUtils;
+
+namespace odrefresh {
+
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+
+class OdrConfig;
+
+class OdrDexopt {
+ public:
+  static std::unique_ptr<OdrDexopt> Create(const OdrConfig& confg,
+                                           std::unique_ptr<ExecUtils> exec_utils);
+
+  virtual ~OdrDexopt() {}
+
+  virtual int DexoptBcpExtension(const DexoptBcpExtArgs& args,
+                                 time_t timeout_secs,
+                                 /*out*/ bool* timed_out,
+                                 /*out*/ std::string* error_msg) = 0;
+  virtual int DexoptSystemServer(const DexoptSystemServerArgs& args,
+                                 time_t timeout_secs,
+                                 /*out*/ bool* timed_out,
+                                 /*out*/ std::string* error_msg) = 0;
+};
+
+}  // namespace odrefresh
+}  // namespace art
+
+#endif  // ART_ODREFRESH_ODR_DEXOPT_H_
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index a984413..36fbf75 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -50,7 +50,9 @@
 #include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
+#include "android-base/parseint.h"
 #include "android-base/properties.h"
+#include "android-base/result.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "android/log.h"
@@ -66,23 +68,36 @@
 #include "dex/art_dex_file_loader.h"
 #include "dexoptanalyzer.h"
 #include "exec_utils.h"
+#include "libdexopt.h"
 #include "log/log.h"
 #include "odr_artifacts.h"
 #include "odr_common.h"
 #include "odr_compilation_log.h"
 #include "odr_config.h"
+#include "odr_dexopt.h"
 #include "odr_fs_utils.h"
 #include "odr_metrics.h"
 #include "odrefresh/odrefresh.h"
 #include "palette/palette.h"
 #include "palette/palette_types.h"
 
+#include "aidl/com/android/art/CompilerFilter.h"
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+#include "aidl/com/android/art/Isa.h"
+
 namespace art {
 namespace odrefresh {
 
 namespace apex = com::android::apex;
 namespace art_apex = com::android::art;
 
+using aidl::com::android::art::CompilerFilter;
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+using aidl::com::android::art::Isa;
+using android::base::Result;
+
 namespace {
 
 // Name of cache info file in the ART Apex artifact cache.
@@ -283,93 +298,65 @@
   return true;
 }
 
-void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>* args) {
-  args->emplace_back("--android-root=out/empty");
-  args->emplace_back("--abort-on-hard-verifier-error");
-  args->emplace_back("--no-abort-on-soft-verifier-error");
-  args->emplace_back("--compilation-reason=boot");
-  args->emplace_back("--image-format=lz4");
-  args->emplace_back("--force-determinism");
-  args->emplace_back("--resolve-startup-const-strings=true");
-}
+bool PrepareDex2OatConcurrencyArguments(/*out*/ int* threads, /*out*/ std::vector<int>* cpu_set) {
+  DCHECK(threads);
+  DCHECK(cpu_set && cpu_set->empty());
+  *threads = android::base::GetIntProperty("dalvik.vm.boot-dex2oat-threads",
+                                           /*default_value=*/ 0,
+                                           /*min=*/ 1);
 
-void AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>* args) {
-  static constexpr std::pair<const char*, const char*> kPropertyArgPairs[] = {
-      std::make_pair("dalvik.vm.boot-dex2oat-cpu-set", "--cpu-set="),
-      std::make_pair("dalvik.vm.boot-dex2oat-threads", "-j"),
-  };
-  for (auto property_arg_pair : kPropertyArgPairs) {
-    auto [property, arg] = property_arg_pair;
-    std::string value = android::base::GetProperty(property, {});
-    if (!value.empty()) {
-      args->push_back(arg + value);
-    }
+  std::string cpu_set_spec = android::base::GetProperty("dalvik.vm.boot-dex2oat-cpu-set", "");
+  if (cpu_set_spec.empty()) {
+    return true;
   }
+  for (auto& str : android::base::Split(cpu_set_spec, ",")) {
+    int id;
+    if (!android::base::ParseInt(str, &id, 0)) {
+      LOG(ERROR) << "Invalid CPU set spec: " << cpu_set_spec;
+      return false;
+    }
+    cpu_set->push_back(id);
+  }
+  return true;
 }
 
-void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>* args) {
-  args->emplace_back("--generate-mini-debug-info");
-  args->emplace_back("--strip");
-}
-
-void AddDex2OatInstructionSet(/*inout*/ std::vector<std::string>* args, InstructionSet isa) {
-  const char* isa_str = GetInstructionSetString(isa);
-  args->emplace_back(Concatenate({"--instruction-set=", isa_str}));
-}
-
-void AddDex2OatProfileAndCompilerFilter(
-    /*inout*/ std::vector<std::string>* args,
-    /*inout*/ std::vector<std::unique_ptr<File>>* output_files,
-    const std::string& profile_path) {
+bool PrepareDex2OatProfileIfExists(/*inout*/ int* profile_fd,
+                                   /*inout*/ std::vector<std::unique_ptr<File>>* output_files,
+                                   const std::string& profile_path) {
   std::unique_ptr<File> profile_file(OS::OpenFileForReading(profile_path.c_str()));
   if (profile_file && profile_file->IsOpened()) {
-    args->emplace_back(android::base::StringPrintf("--profile-file-fd=%d", profile_file->Fd()));
-    args->emplace_back("--compiler-filter=speed-profile");
+    *profile_fd = profile_file->Fd();
     output_files->push_back(std::move(profile_file));
+    return true;
   } else {
-    args->emplace_back("--compiler-filter=speed");
+    return false;
   }
 }
 
-bool AddBootClasspathFds(/*inout*/ std::vector<std::string>& args,
-                         /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
-                         const std::vector<std::string>& bcp_jars) {
-  auto bcp_fds = std::vector<std::string>();
+bool PrepareBootClasspathFds(/*inout*/ std::vector<int>& boot_classpath_fds,
+                             /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
+                             const std::vector<std::string>& bcp_jars) {
   for (const std::string& jar : bcp_jars) {
     std::unique_ptr<File> jar_file(OS::OpenFileForReading(jar.c_str()));
     if (!jar_file || !jar_file->IsValid()) {
       LOG(ERROR) << "Failed to open a BCP jar " << jar;
       return false;
     }
-    bcp_fds.push_back(std::to_string(jar_file->Fd()));
+    boot_classpath_fds.emplace_back(jar_file->Fd());
     output_files.push_back(std::move(jar_file));
   }
-  args.emplace_back("--runtime-arg");
-  args.emplace_back(Concatenate({"-Xbootclasspathfds:", android::base::Join(bcp_fds, ':')}));
   return true;
 }
 
-std::string GetBootImagePath(bool on_system, const std::string& jar_path) {
-  if (on_system) {
-    const std::string jar_name = android::base::Basename(jar_path);
-    const std::string image_name = ReplaceFileExtension(jar_name, "art");
-    // Typically "/system/framework/boot-framework.art".
-    return Concatenate({GetAndroidRoot(), "/framework/boot-", image_name});
-  } else {
-    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework.art".
-    return GetApexDataBootImage(jar_path);
-  }
-}
-
-void AddCompiledBootClasspathFdsIfAny(
-    /*inout*/ std::vector<std::string>& args,
+void PrepareCompiledBootClasspathFdsIfAny(
+    /*inout*/ DexoptSystemServerArgs& dexopt_args,
     /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
     const std::vector<std::string>& bcp_jars,
     const InstructionSet isa,
     bool on_system) {
-  std::vector<std::string> bcp_image_fds;
-  std::vector<std::string> bcp_oat_fds;
-  std::vector<std::string> bcp_vdex_fds;
+  std::vector<int> bcp_image_fds;
+  std::vector<int> bcp_oat_fds;
+  std::vector<int> bcp_vdex_fds;
   std::vector<std::unique_ptr<File>> opened_files;
   bool added_any = false;
   for (const std::string& jar : bcp_jars) {
@@ -377,46 +364,40 @@
     image_path = image_path.empty() ? "" : GetSystemImageFilename(image_path.c_str(), isa);
     std::unique_ptr<File> image_file(OS::OpenFileForReading(image_path.c_str()));
     if (image_file && image_file->IsValid()) {
-      bcp_image_fds.push_back(std::to_string(image_file->Fd()));
+      bcp_image_fds.push_back(image_file->Fd());
       opened_files.push_back(std::move(image_file));
       added_any = true;
     } else {
-      bcp_image_fds.push_back("-1");
+      bcp_image_fds.push_back(-1);
     }
 
     std::string oat_path = ReplaceFileExtension(image_path, "oat");
     std::unique_ptr<File> oat_file(OS::OpenFileForReading(oat_path.c_str()));
     if (oat_file && oat_file->IsValid()) {
-      bcp_oat_fds.push_back(std::to_string(oat_file->Fd()));
+      bcp_oat_fds.push_back(oat_file->Fd());
       opened_files.push_back(std::move(oat_file));
       added_any = true;
     } else {
-      bcp_oat_fds.push_back("-1");
+      bcp_oat_fds.push_back(-1);
     }
 
     std::string vdex_path = ReplaceFileExtension(image_path, "vdex");
     std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex_path.c_str()));
     if (vdex_file && vdex_file->IsValid()) {
-      bcp_vdex_fds.push_back(std::to_string(vdex_file->Fd()));
+      bcp_vdex_fds.push_back(vdex_file->Fd());
       opened_files.push_back(std::move(vdex_file));
       added_any = true;
     } else {
-      bcp_vdex_fds.push_back("-1");
+      bcp_vdex_fds.push_back(-1);
     }
   }
   // Add same amount of FDs as BCP JARs, or none.
   if (added_any) {
     std::move(opened_files.begin(), opened_files.end(), std::back_inserter(output_files));
 
-    args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathimagefds:", android::base::Join(bcp_image_fds, ':')}));
-    args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathoatfds:", android::base::Join(bcp_oat_fds, ':')}));
-    args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathvdexfds:", android::base::Join(bcp_vdex_fds, ':')}));
+    dexopt_args.bootClasspathImageFds = bcp_image_fds;
+    dexopt_args.bootClasspathVdexFds = bcp_vdex_fds;
+    dexopt_args.bootClasspathOatFds = bcp_oat_fds;
   }
 }
 
@@ -424,20 +405,6 @@
   return Concatenate({staging_dir, "/", android::base::Basename(path)});
 }
 
-std::string JoinFilesAsFDs(const std::vector<std::unique_ptr<File>>& files, char delimiter) {
-  std::stringstream output;
-  bool is_first = true;
-  for (const auto& f : files) {
-    if (is_first) {
-      is_first = false;
-    } else {
-      output << delimiter;
-    }
-    output << std::to_string(f->Fd());
-  }
-  return output.str();
-}
-
 WARN_UNUSED bool CheckCompilationSpace() {
   // Check the available storage space against an arbitrary threshold because dex2oat does not
   // report when it runs out of storage space and we do not want to completely fill
@@ -469,20 +436,52 @@
   return GetArtRoot() + "/javalib/boot.art";
 }
 
+Isa InstructionSetToAidlIsa(InstructionSet isa) {
+  switch (isa) {
+    case InstructionSet::kArm:
+      return Isa::ARM;
+    case InstructionSet::kThumb2:
+      return Isa::THUMB2;
+    case InstructionSet::kArm64:
+      return Isa::ARM64;
+    case InstructionSet::kX86:
+      return Isa::X86;
+    case InstructionSet::kX86_64:
+      return Isa::X86_64;
+    default:
+      UNREACHABLE();
+  }
+}
+
+CompilerFilter CompilerFilterStringToAidl(const std::string& compiler_filter) {
+  if (compiler_filter == "speed-profile") {
+    return CompilerFilter::SPEED_PROFILE;
+  } else if (compiler_filter == "speed") {
+    return CompilerFilter::SPEED;
+  } else if (compiler_filter == "verify") {
+    return CompilerFilter::VERIFY;
+  } else {
+    return CompilerFilter::UNSUPPORTED;
+  }
+}
+
 }  // namespace
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config)
     : OnDeviceRefresh(config,
                       Concatenate({kOdrefreshArtifactDirectory, "/", kCacheInfoFile}),
-                      std::make_unique<ExecUtils>()) {}
+                      std::make_unique<ExecUtils>(),
+                      std::move(OdrDexopt::Create(config, std::make_unique<ExecUtils>()))) {}
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config,
                                  const std::string& cache_info_filename,
-                                 std::unique_ptr<ExecUtils> exec_utils)
+                                 std::unique_ptr<ExecUtils> exec_utils,
+                                 std::unique_ptr<OdrDexopt> odr_dexopt)
     : config_{config},
       cache_info_filename_{cache_info_filename},
       start_time_{time(nullptr)},
-      exec_utils_{std::move(exec_utils)} {
+      exec_utils_{std::move(exec_utils)},
+      odr_dexopt_{std::move(odr_dexopt)} {
   for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
     // Boot class path extensions are those not in the ART APEX. Updatable APEXes should not
     // have DEX files in the DEX2OATBOOTCLASSPATH. At the time of writing i18n is a non-updatable
@@ -1274,28 +1273,22 @@
                                                                 uint32_t* dex2oat_invocation_count,
                                                                 std::string* error_msg) const {
   ScopedOdrCompilationTimer compilation_timer(metrics);
-  std::vector<std::string> args;
-  args.push_back(config_.GetDex2Oat());
 
-  AddDex2OatCommonOptions(&args);
-  AddDex2OatConcurrencyArguments(&args);
-  AddDex2OatDebugInfo(&args);
-  AddDex2OatInstructionSet(&args, isa);
+  DexoptBcpExtArgs dexopt_args;
+  dexopt_args.isa = InstructionSetToAidlIsa(isa);
 
   std::vector<std::unique_ptr<File>> readonly_files_raii;
   const std::string boot_profile_file(GetAndroidRoot() + "/etc/boot-image.prof");
-  AddDex2OatProfileAndCompilerFilter(&args, &readonly_files_raii, boot_profile_file);
-
-  // Compile as a single image for fewer files and slightly less memory overhead.
-  args.emplace_back("--single-image");
-
-  // Set boot-image and expectation of compiling boot classpath extensions.
-  args.emplace_back("--boot-image=" + GetBootImage());
+  if (!PrepareDex2OatProfileIfExists(&dexopt_args.profileFd, &readonly_files_raii,
+                                     boot_profile_file)) {
+    LOG(ERROR) << "Missing expected profile for boot extension: " << boot_profile_file;
+    return false;
+  }
 
   const std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects");
   if (OS::FileExists(dirty_image_objects_file.c_str())) {
     std::unique_ptr<File> file(OS::OpenFileForReading(dirty_image_objects_file.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dirty-image-objects-fd=%d", file->Fd()));
+    dexopt_args.dirtyImageObjectsFd = file->Fd();
     readonly_files_raii.push_back(std::move(file));
   } else {
     LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file);
@@ -1303,16 +1296,17 @@
 
   // Add boot extensions to compile.
   for (const std::string& component : boot_extension_compilable_jars_) {
-    args.emplace_back("--dex-file=" + component);
     std::unique_ptr<File> file(OS::OpenFileForReading(component.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dex-fd=%d", file->Fd()));
+    dexopt_args.dexPaths.emplace_back(component);
+    dexopt_args.dexFds.emplace_back(file->Fd());
     readonly_files_raii.push_back(std::move(file));
   }
 
-  args.emplace_back("--runtime-arg");
-  args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
   auto bcp_jars = android::base::Split(config_.GetDex2oatBootClasspath(), ":");
-  if (!AddBootClasspathFds(args, readonly_files_raii, bcp_jars)) {
+  dexopt_args.bootClasspaths = bcp_jars;
+  if (!PrepareBootClasspathFds(dexopt_args.bootClasspathFds,
+                               readonly_files_raii,
+                               bcp_jars)) {
     return false;
   }
 
@@ -1321,19 +1315,18 @@
   CHECK_EQ(GetApexDataOatFilename(boot_extension_compilable_jars_.front().c_str(), isa),
            artifacts.OatPath());
 
-  args.emplace_back("--oat-location=" + artifacts.OatPath());
-  const std::pair<const std::string, const char*> location_kind_pairs[] = {
-      std::make_pair(artifacts.ImagePath(), "image"),
-      std::make_pair(artifacts.OatPath(), "oat"),
-      std::make_pair(artifacts.VdexPath(), "output-vdex")};
-
+  dexopt_args.oatLocation = artifacts.OatPath();
+  const std::pair<const std::string, int*> location_kind_pairs[] = {
+      std::make_pair(artifacts.ImagePath(), &dexopt_args.imageFd),
+      std::make_pair(artifacts.OatPath(), &dexopt_args.oatFd),
+      std::make_pair(artifacts.VdexPath(), &dexopt_args.vdexFd)};
   std::vector<std::unique_ptr<File>> staging_files;
   for (const auto& location_kind_pair : location_kind_pairs) {
-    auto& [location, kind] = location_kind_pair;
+    auto& [location, out_ptr] = location_kind_pair;
     const std::string staging_location = GetStagingLocation(staging_dir, location);
     std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
     if (staging_file == nullptr) {
-      PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+      PLOG(ERROR) << "Failed to create file: " << staging_location;
       metrics.SetStatus(OdrMetrics::Status::kIoError);
       EraseFiles(staging_files);
       return false;
@@ -1346,7 +1339,7 @@
       return false;
     }
 
-    args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+    *out_ptr = staging_file->Fd();
     staging_files.emplace_back(std::move(staging_file));
   }
 
@@ -1356,28 +1349,22 @@
     return false;
   }
 
-  if (config_.UseCompilationOs()) {
-    std::vector<std::string> prefix_args = {
-        "/apex/com.android.compos/bin/pvm_exec",
-        "--cid=" + config_.GetCompilationOsAddress(),
-        "--in-fd=" + JoinFilesAsFDs(readonly_files_raii, ','),
-        "--out-fd=" + JoinFilesAsFDs(staging_files, ','),
-        "--",
-    };
-    args.insert(args.begin(), prefix_args.begin(), prefix_args.end());
+  if (!PrepareDex2OatConcurrencyArguments(&dexopt_args.threads, &dexopt_args.cpuSet)) {
+    return false;
   }
 
   const time_t timeout = GetSubprocessTimeout();
-  const std::string cmd_line = android::base::Join(args, ' ');
-  LOG(INFO) << "Compiling boot extensions (" << isa << "): " << cmd_line << " [timeout " << timeout
-            << "s]";
+  LOG(INFO) << "Compiling boot extensions (" << isa << "): " << dexopt_args.toString()
+            << " [timeout " << timeout << "s]";
   if (config_.GetDryRun()) {
     LOG(INFO) << "Compilation skipped (dry-run).";
     return true;
   }
 
   bool timed_out = false;
-  int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
+  int dex2oat_exit_code = odr_dexopt_->DexoptBcpExtension(
+      dexopt_args, timeout, &timed_out, error_msg);
+
   if (dex2oat_exit_code != 0) {
     if (timed_out) {
       metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
@@ -1410,26 +1397,29 @@
   const InstructionSet isa = config_.GetSystemServerIsa();
   for (const std::string& jar : systemserver_compilable_jars_) {
     std::vector<std::unique_ptr<File>> readonly_files_raii;
-    std::vector<std::string> args;
-    args.emplace_back(dex2oat);
-    args.emplace_back("--dex-file=" + jar);
+    DexoptSystemServerArgs dexopt_args;
+    dexopt_args.isa = InstructionSetToAidlIsa(isa);
 
     std::unique_ptr<File> dex_file(OS::OpenFileForReading(jar.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dex-fd=%d", dex_file->Fd()));
+
+    dexopt_args.dexPath = jar;
+    dexopt_args.dexFd = dex_file->Fd();
     readonly_files_raii.push_back(std::move(dex_file));
 
-    AddDex2OatCommonOptions(&args);
-    AddDex2OatConcurrencyArguments(&args);
-    AddDex2OatDebugInfo(&args);
-    AddDex2OatInstructionSet(&args, isa);
+    dexopt_args.isa = InstructionSetToAidlIsa(isa);
     const std::string jar_name(android::base::Basename(jar));
     const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"});
     std::string compiler_filter =
         android::base::GetProperty("dalvik.vm.systemservercompilerfilter", "speed");
     if (compiler_filter == "speed-profile") {
-      AddDex2OatProfileAndCompilerFilter(&args, &readonly_files_raii, profile);
+      // Use speed-profile only if profile is provided, otherwise fallback to speed.
+      if (PrepareDex2OatProfileIfExists(&dexopt_args.profileFd, &readonly_files_raii, profile)) {
+        dexopt_args.compilerFilter = CompilerFilter::SPEED_PROFILE;
+      } else {
+        dexopt_args.compilerFilter = CompilerFilter::SPEED;
+      }
     } else {
-      args.emplace_back("--compiler-filter=" + compiler_filter);
+      dexopt_args.compilerFilter = CompilerFilterStringToAidl(compiler_filter);
     }
 
     const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar);
@@ -1445,31 +1435,32 @@
     OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
     CHECK_EQ(artifacts.OatPath(), GetApexDataOdexFilename(jar.c_str(), isa));
 
-    const std::pair<const std::string, const char*> location_kind_pairs[] = {
-        std::make_pair(artifacts.ImagePath(), "app-image"),
-        std::make_pair(artifacts.OatPath(), "oat"),
-        std::make_pair(artifacts.VdexPath(), "output-vdex")};
+    const std::pair<const std::string, int*> location_kind_pairs[] = {
+        std::make_pair(artifacts.ImagePath(), &dexopt_args.imageFd),
+        std::make_pair(artifacts.OatPath(), &dexopt_args.oatFd),
+        std::make_pair(artifacts.VdexPath(), &dexopt_args.vdexFd)};
 
     std::vector<std::unique_ptr<File>> staging_files;
     for (const auto& location_kind_pair : location_kind_pairs) {
-      auto& [location, kind] = location_kind_pair;
+      auto& [location, out_ptr] = location_kind_pair;
       const std::string staging_location = GetStagingLocation(staging_dir, location);
       std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
       if (staging_file == nullptr) {
-        PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+        PLOG(ERROR) << "Failed to create file: " << staging_location;
         metrics.SetStatus(OdrMetrics::Status::kIoError);
         EraseFiles(staging_files);
         return false;
       }
-      args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+      *out_ptr = staging_file->Fd();
       staging_files.emplace_back(std::move(staging_file));
     }
-    args.emplace_back("--oat-location=" + artifacts.OatPath());
+    dexopt_args.oatLocation = artifacts.OatPath();
 
-    args.emplace_back("--runtime-arg");
-    args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetBootClasspath()}));
     auto bcp_jars = android::base::Split(config_.GetBootClasspath(), ":");
-    if (!AddBootClasspathFds(args, readonly_files_raii, bcp_jars)) {
+    dexopt_args.bootClasspaths = bcp_jars;
+    if (!PrepareBootClasspathFds(dexopt_args.bootClasspathFds,
+                                 readonly_files_raii,
+                                 bcp_jars)) {
       return false;
     }
     std::string unused_error_msg;
@@ -1477,11 +1468,11 @@
     // and the artifacts must exist on /system.
     bool boot_image_on_system =
         !BootExtensionArtifactsExist(/*on_system=*/false, isa, &unused_error_msg);
-    AddCompiledBootClasspathFdsIfAny(
-        args, readonly_files_raii, bcp_jars, isa, boot_image_on_system);
+    PrepareCompiledBootClasspathFdsIfAny(
+        dexopt_args, readonly_files_raii, bcp_jars, isa, boot_image_on_system);
+    dexopt_args.isBootImageOnSystem = boot_image_on_system;
 
-    const std::string context_path = android::base::Join(classloader_context, ':');
-    args.emplace_back(Concatenate({"--class-loader-context=PCL[", context_path, "]"}));
+    dexopt_args.classloaderContext = classloader_context;
     if (!classloader_context.empty()) {
       std::vector<int> fds;
       for (const std::string& path : classloader_context) {
@@ -1494,33 +1485,25 @@
         fds.emplace_back(file->Fd());
         readonly_files_raii.emplace_back(std::move(file));
       }
-      const std::string context_fds = android::base::Join(fds, ':');
-      args.emplace_back(Concatenate({"--class-loader-context-fds=", context_fds}));
+      dexopt_args.classloaderFds = fds;
     }
-    std::string extension_image = GetBootImageExtensionImage(boot_image_on_system);
-    args.emplace_back(Concatenate({"--boot-image=", GetBootImage(), ":", extension_image}));
 
-    if (config_.UseCompilationOs()) {
-      std::vector<std::string> prefix_args = {
-          "/apex/com.android.compos/bin/pvm_exec",
-          "--cid=" + config_.GetCompilationOsAddress(),
-          "--in-fd=" + JoinFilesAsFDs(readonly_files_raii, ','),
-          "--out-fd=" + JoinFilesAsFDs(staging_files, ','),
-          "--",
-      };
-      args.insert(args.begin(), prefix_args.begin(), prefix_args.end());
+    if (!PrepareDex2OatConcurrencyArguments(&dexopt_args.threads, &dexopt_args.cpuSet)) {
+      return false;
     }
 
     const time_t timeout = GetSubprocessTimeout();
-    const std::string cmd_line = android::base::Join(args, ' ');
-    LOG(INFO) << "Compiling " << jar << ": " << cmd_line << " [timeout " << timeout << "s]";
+    LOG(INFO) << "Compiling " << jar << ": " << dexopt_args.toString() << " [timeout " << timeout
+              << "s]";
     if (config_.GetDryRun()) {
       LOG(INFO) << "Compilation skipped (dry-run).";
       return true;
     }
 
     bool timed_out = false;
-    int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
+    int dex2oat_exit_code = odr_dexopt_->DexoptSystemServer(
+        dexopt_args, timeout, &timed_out, error_msg);
+
     if (dex2oat_exit_code != 0) {
       if (timed_out) {
         metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index 2a7164b..d842688 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -28,6 +28,7 @@
 #include "exec_utils.h"
 #include "odr_artifacts.h"
 #include "odr_config.h"
+#include "odr_dexopt.h"
 #include "odr_metrics.h"
 #include "odrefresh/odrefresh.h"
 
@@ -41,7 +42,8 @@
   // Constructor with injections. For testing and internal use only.
   OnDeviceRefresh(const OdrConfig& config,
                   const std::string& cache_info_filename,
-                  std::unique_ptr<ExecUtils> exec_utils);
+                  std::unique_ptr<ExecUtils> exec_utils,
+                  std::unique_ptr<OdrDexopt> odr_dexopt);
 
   // Returns the exit code, a list of ISAs that boot extensions should be compiled for, and a
   // boolean indicating whether the system server should be compiled.
@@ -180,6 +182,8 @@
 
   std::unique_ptr<ExecUtils> exec_utils_;
 
+  std::unique_ptr<OdrDexopt> odr_dexopt_;
+
   DISALLOW_COPY_AND_ASSIGN(OnDeviceRefresh);
 };
 
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 93ce5fb..09614b9 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -19,6 +19,7 @@
 #include <string>
 #include <string_view>
 
+#include "android-base/parseint.h"
 #include "android-base/properties.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -200,7 +201,11 @@
     const char* arg = argv[n];
     std::string value;
     if (ArgumentMatches(arg, "--use-compilation-os=", &value)) {
-      config->SetCompilationOsAddress(value);
+      int cid;
+      if (!android::base::ParseInt(value, &cid)) {
+        ArgumentError("Failed to parse CID: %s", value.c_str());
+      }
+      config->SetCompilationOsAddress(cid);
     } else if (!InitializeCommonConfig(arg, config)) {
       UsageError("Unrecognized argument: '%s'", arg);
     }
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 99b8d03..909800d 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -21,17 +21,14 @@
 #include <functional>
 #include <memory>
 #include <string_view>
+#include <utility>
 #include <vector>
 
-#include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 #include "base/stl_util.h"
 #include "odr_artifacts.h"
 
-#ifdef __ANDROID__
 #include <android/api-level.h>
-#endif
-
 #include "android-base/properties.h"
 #include "android-base/scopeguard.h"
 #include "android-base/strings.h"
@@ -43,24 +40,35 @@
 #include "gtest/gtest.h"
 #include "odr_common.h"
 #include "odr_config.h"
+#include "odr_dexopt.h"
 #include "odr_fs_utils.h"
 #include "odr_metrics.h"
 #include "odrefresh/odrefresh.h"
 
+#include "aidl/com/android/art/CompilerFilter.h"
+#include "aidl/com/android/art/DexoptBcpExtArgs.h"
+#include "aidl/com/android/art/DexoptSystemServerArgs.h"
+#include "aidl/com/android/art/Isa.h"
+
 namespace art {
 namespace odrefresh {
 
 using ::testing::AllOf;
 using ::testing::Contains;
-using ::testing::HasSubstr;
-using ::testing::Not;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Ge;
 using ::testing::Return;
+using aidl::com::android::art::CompilerFilter;
+using aidl::com::android::art::DexoptBcpExtArgs;
+using aidl::com::android::art::DexoptSystemServerArgs;
+using aidl::com::android::art::Isa;
 
 constexpr int kReplace = 1;
 
 void CreateEmptyFile(const std::string& name) {
   File* file = OS::CreateEmptyFile(name.c_str());
-  ASSERT_TRUE(file != nullptr);
+  ASSERT_TRUE(file != nullptr) << "Cannot create file " << name;
   file->Release();
   delete file;
 }
@@ -77,42 +85,32 @@
   return android::base::ScopeGuard([=]() { android::base::SetProperty(key, old_value); });
 }
 
-class MockExecUtils : public ExecUtils {
+class MockOdrDexopt : public OdrDexopt {
  public:
   // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
   // to a conflict between gmock and android-base/logging.h (b/132668253).
-  int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                        time_t,
-                        bool*,
-                        std::string*) const override {
-    return DoExecAndReturnCode(arg_vector);
+  int DexoptBcpExtension(const DexoptBcpExtArgs& args,
+                         time_t,
+                         bool*,
+                         std::string*) override {
+    return DoDexoptBcpExtension(args);
   }
 
-  MOCK_METHOD(int, DoExecAndReturnCode, (std::vector<std::string> & arg_vector), (const));
+  int DexoptSystemServer(const DexoptSystemServerArgs& args,
+                         time_t,
+                         bool*,
+                         std::string*) override {
+    return DoDexoptSystemServer(args);
+  }
+
+  MOCK_METHOD(int, DoDexoptBcpExtension, (const DexoptBcpExtArgs&));
+  MOCK_METHOD(int, DoDexoptSystemServer, (const DexoptSystemServerArgs&));
 };
 
-// Matches a flag that starts with `flag` and is a colon-separated list that contains an element
-// that matches `matcher`.
-MATCHER_P2(FlagContains, flag, matcher, "") {
-  std::string_view value = arg;
-  if (!android::base::ConsumePrefix(&value, flag)) {
-    return false;
-  }
-  for (std::string_view s : SplitString(value, ':')) {
-    if (ExplainMatchResult(matcher, s, result_listener)) {
-      return true;
-    }
-  }
-  return false;
-}
-
 // Matches an FD of a file whose path matches `matcher`.
 MATCHER_P(FdOf, matcher, "") {
   char path[PATH_MAX];
-  int fd;
-  if (!android::base::ParseInt(std::string{arg}, &fd)) {
-    return false;
-  }
+  int fd = arg;
   std::string proc_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
   ssize_t len = readlink(proc_path.c_str(), path, sizeof(path));
   if (len < 0) {
@@ -152,6 +150,11 @@
     dalvik_cache_dir_ = art_apex_data_path + "/dalvik-cache";
     ASSERT_TRUE(EnsureDirectoryExists(dalvik_cache_dir_ + "/x86_64"));
 
+    std::string system_etc_dir = Concatenate({android_root_path, "/etc"});
+    ASSERT_TRUE(EnsureDirectoryExists(system_etc_dir));
+    boot_profile_file_ = system_etc_dir + "/boot-image.prof";
+    CreateEmptyFile(boot_profile_file_);
+
     framework_dir_ = android_root_path + "/framework";
     framework_jar_ = framework_dir_ + "/framework.jar";
     location_provider_jar_ = framework_dir_ + "/com.android.location.provider.jar";
@@ -181,12 +184,7 @@
     ASSERT_TRUE(EnsureDirectoryExists(staging_dir));
     config_.SetStagingDir(staging_dir);
 
-    auto mock_exec_utils = std::make_unique<MockExecUtils>();
-    mock_exec_utils_ = mock_exec_utils.get();
-
     metrics_ = std::make_unique<OdrMetrics>(dalvik_cache_dir_);
-    odrefresh_ = std::make_unique<OnDeviceRefresh>(
-        config_, dalvik_cache_dir_ + "/cache-info.xml", std::move(mock_exec_utils));
   }
 
   void TearDown() override {
@@ -199,19 +197,27 @@
     CommonArtTest::TearDown();
   }
 
+  std::pair<std::unique_ptr<OnDeviceRefresh>, MockOdrDexopt*> CreateOdRefresh() {
+    auto mock_odr_dexopt = std::make_unique<MockOdrDexopt>();
+    MockOdrDexopt* mock_odr_dexopt_ptr = mock_odr_dexopt.get();
+    auto odrefresh = std::make_unique<OnDeviceRefresh>(
+        config_, dalvik_cache_dir_ + "/cache-info.xml", std::make_unique<ExecUtils>(),
+        std::move(mock_odr_dexopt));
+    return std::make_pair(std::move(odrefresh), mock_odr_dexopt_ptr);
+  }
+
   std::unique_ptr<ScratchDir> temp_dir_;
   std::unique_ptr<ScopedUnsetEnvironmentVariable> android_root_env_;
   std::unique_ptr<ScopedUnsetEnvironmentVariable> android_art_root_env_;
   std::unique_ptr<ScopedUnsetEnvironmentVariable> art_apex_data_env_;
   OdrConfig config_;
-  MockExecUtils* mock_exec_utils_;
   std::unique_ptr<OdrMetrics> metrics_;
-  std::unique_ptr<OnDeviceRefresh> odrefresh_;
   std::string framework_jar_;
   std::string location_provider_jar_;
   std::string services_jar_;
   std::string dalvik_cache_dir_;
   std::string framework_dir_;
+  std::string boot_profile_file_;
 };
 
 TEST_F(OdRefreshTest, OdrefreshArtifactDirectory) {
@@ -221,73 +227,124 @@
   EXPECT_EQ(kOdrefreshArtifactDirectory, GetArtApexData() + "/dalvik-cache");
 }
 
-TEST_F(OdRefreshTest, CompileSetsCompilerFilter) {
-#ifdef __ANDROID__
+TEST_F(OdRefreshTest, CompileSetsCompilerFilterForSystemServer) {
   // This test depends on a system property introduced in S. Since the whole odrefresh program is
   // for S and later, we don't need to run the test on older platforms.
   if (android_get_device_api_level() < __ANDROID_API_S__) {
     return;
   }
-#endif
 
   {
-    // Defaults to "speed".
+    auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
+    // Test setup: default compiler filter should be "speed".
+    auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "");
+
     EXPECT_CALL(
-        *mock_exec_utils_,
-        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                  Not(Contains(HasSubstr("--profile-file-fd="))),
-                                  Contains("--compiler-filter=speed"))))
-        .WillOnce(Return(0));
-    EXPECT_CALL(*mock_exec_utils_,
-                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                          Not(Contains(HasSubstr("--profile-file-fd="))),
-                                          Contains("--compiler-filter=speed"))))
-        .WillOnce(Return(0));
-    EXPECT_EQ(odrefresh_->Compile(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_CALL(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_EQ(odrefresh->Compile(
                   *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
               ExitCode::kCompilationSuccess);
   }
 
   {
+    auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
+    // Test setup: with "speed-profile" compiler filter in the request, only apply if there is a
+    // profile, otherwise fallback to speed.
     auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "speed-profile");
+
     // services.jar has a profile, while location.provider.jar does not.
     EXPECT_CALL(
-        *mock_exec_utils_,
-        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                  Not(Contains(HasSubstr("--profile-file-fd="))),
-                                  Contains("--compiler-filter=speed"))))
-        .WillOnce(Return(0));
-    EXPECT_CALL(*mock_exec_utils_,
-                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                          Contains(HasSubstr("--profile-file-fd=")),
-                                          Contains("--compiler-filter=speed-profile"))))
-        .WillOnce(Return(0));
-    EXPECT_EQ(odrefresh_->Compile(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+            Field(&DexoptSystemServerArgs::profileFd, Ge(0)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED_PROFILE)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_CALL(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_EQ(odrefresh->Compile(
                   *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
               ExitCode::kCompilationSuccess);
   }
 
   {
+    auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
+    // Test setup: "verify" compiler filter should be simply applied.
     auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "verify");
+
     EXPECT_CALL(
-        *mock_exec_utils_,
-        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                  Not(Contains(HasSubstr("--profile-file-fd="))),
-                                  Contains("--compiler-filter=verify"))))
-        .WillOnce(Return(0));
-    EXPECT_CALL(*mock_exec_utils_,
-                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                          Not(Contains(HasSubstr("--profile-file-fd="))),
-                                          Contains("--compiler-filter=verify"))))
-        .WillOnce(Return(0));
-    EXPECT_EQ(odrefresh_->Compile(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_CALL(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
+      .WillOnce(Return(0))
+      .RetiresOnSaturation();
+    EXPECT_EQ(odrefresh->Compile(
                   *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
               ExitCode::kCompilationSuccess);
   }
 }
 
+TEST_F(OdRefreshTest, OutputFilesAndIsa) {
+  auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
+  EXPECT_CALL(
+      *mock_odr_dexopt,
+      DoDexoptBcpExtension(AllOf(
+          Field(&DexoptBcpExtArgs::isa, Eq(Isa::X86_64)),
+          Field(&DexoptBcpExtArgs::imageFd, Ge(0)),
+          Field(&DexoptBcpExtArgs::vdexFd, Ge(0)),
+          Field(&DexoptBcpExtArgs::oatFd, Ge(0)))))
+      .WillOnce(Return(0));
+
+  EXPECT_CALL(
+      *mock_odr_dexopt,
+      DoDexoptSystemServer(AllOf(
+          Field(&DexoptSystemServerArgs::isa, Eq(Isa::X86_64)),
+          Field(&DexoptSystemServerArgs::imageFd, Ge(0)),
+          Field(&DexoptSystemServerArgs::vdexFd, Ge(0)),
+          Field(&DexoptSystemServerArgs::oatFd, Ge(0)))))
+      .Times(2)
+      .WillRepeatedly(Return(0));
+
+  EXPECT_EQ(odrefresh->Compile(*metrics_,
+                                /*compile_boot_extensions=*/{InstructionSet::kX86_64},
+                                /*compile_system_server=*/true),
+            ExitCode::kCompilationSuccess);
+}
+
 TEST_F(OdRefreshTest, CompileChoosesBootImage) {
   {
+    auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
     // Boot image is on /data.
     OdrArtifacts artifacts =
         OdrArtifacts::ForBootImageExtension(dalvik_cache_dir_ + "/x86_64/boot-framework.art");
@@ -296,20 +353,25 @@
     auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
 
     EXPECT_CALL(
-        *mock_exec_utils_,
-        DoExecAndReturnCode(AllOf(
-            Contains(FlagContains("--boot-image=", dalvik_cache_dir_ + "/boot-framework.art")),
-            Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))),
-            Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))),
-            Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath()))))))
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::isBootImageOnSystem, Eq(false)),
+            Field(&DexoptSystemServerArgs::bootClasspathImageFds,
+                  Contains(FdOf(artifacts.ImagePath()))),
+            Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
+                  Contains(FdOf(artifacts.VdexPath()))),
+            Field(&DexoptSystemServerArgs::bootClasspathOatFds,
+                  Contains(FdOf(artifacts.OatPath()))))))
         .Times(2)
         .WillRepeatedly(Return(0));
-    EXPECT_EQ(odrefresh_->Compile(
+    EXPECT_EQ(odrefresh->Compile(
                   *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
               ExitCode::kCompilationSuccess);
   }
 
   {
+    auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
     // Boot image is on /system.
     OdrArtifacts artifacts =
         OdrArtifacts::ForBootImageExtension(framework_dir_ + "/x86_64/boot-framework.art");
@@ -317,15 +379,19 @@
     auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath());
     auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
 
-    EXPECT_CALL(*mock_exec_utils_,
-                DoExecAndReturnCode(AllOf(
-                    Contains(FlagContains("--boot-image=", framework_dir_ + "/boot-framework.art")),
-                    Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))),
-                    Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))),
-                    Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath()))))))
+    EXPECT_CALL(
+        *mock_odr_dexopt,
+        DoDexoptSystemServer(AllOf(
+            Field(&DexoptSystemServerArgs::isBootImageOnSystem, Eq(true)),
+            Field(&DexoptSystemServerArgs::bootClasspathImageFds,
+                  Contains(FdOf(artifacts.ImagePath()))),
+            Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
+                  Contains(FdOf(artifacts.VdexPath()))),
+            Field(&DexoptSystemServerArgs::bootClasspathOatFds,
+                  Contains(FdOf(artifacts.OatPath()))))))
         .Times(2)
         .WillRepeatedly(Return(0));
-    EXPECT_EQ(odrefresh_->Compile(
+    EXPECT_EQ(odrefresh->Compile(
                   *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
               ExitCode::kCompilationSuccess);
   }