[clang] [llvm] [DTLTO] Serialize of LTO Config (PR #219894)

Konstantin Belochapka via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 7 22:38:45 PDT 2026


https://github.com/kbelochapka updated https://github.com/llvm/llvm-project/pull/219894

>From 53cddec45315f9d3329f8f89eb78eacefab66046 Mon Sep 17 00:00:00 2001
From: Konstantin Belochapka <konstantin.belochapka at sony.com>
Date: Mon, 17 Aug 2026 03:10:47 -0700
Subject: [PATCH] [LTO] Preserve unsigned option values in bitcode

[LTO] Generate option handling from definition files

[DTLTO] Serialize of LTO Config

Serialize the serializable fields of lto::Config, TargetOptions,
MCTargetOptions, and PassBuilder options as versioned module metadata.
Preserve structured state such as optional values, string lists, and the
basic-block sections profile buffer while omitting runtime-only
callbacks, plugin pointers, and stream handles.

Add APIs to round-trip the configuration through modules, standalone
bitcode files, and ThinLTO summary indexes. Extend the bitcode writer to
carry self-contained module metadata in summary-only output, embed the
configuration in DTLTO index shards, and restore it in Clang's
distributed ThinLTO backend while retaining the legacy fallback for
indexes without metadata.

Add unit and DTLTO integration coverage for module, file, and
summary-index round trips. Add compile-time synchronization tests so new
Config and TargetOptions fields require an explicit serialization update
or omission.
---
 clang/lib/CodeGen/BackendUtil.cpp             | 105 ++++--
 .../dtlto/config-serialization-sync.cpp       |  21 ++
 .../target-options-serialization-sync.cpp     |  21 ++
 cross-project-tests/lit.cfg.py                |   1 +
 cross-project-tests/lit.site.cfg.py.in        |   1 +
 llvm/include/llvm/Bitcode/BitcodeWriter.h     |   9 +-
 llvm/include/llvm/LTO/Config.def              | 128 +++++++
 llvm/include/llvm/LTO/Config.h                | 248 +-----------
 llvm/include/llvm/LTO/LTOConfigBitcode.h      |  63 ++++
 llvm/include/llvm/LTO/TargetOptionsBitcode.h  |  49 +++
 llvm/include/llvm/MC/MCTargetOptions.def      | 101 +++++
 llvm/include/llvm/MC/MCTargetOptions.h        | 122 ++----
 llvm/include/llvm/Passes/PassBuilder.h        |  71 +---
 .../llvm/Passes/PipelineTuningOptions.def     |  70 ++++
 llvm/include/llvm/Target/TargetOptions.def    | 199 ++++++++++
 llvm/include/llvm/Target/TargetOptions.h      | 305 +++------------
 llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp   |   1 -
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     |  38 +-
 llvm/lib/LTO/BitcodeMetadataUtils.h           | 247 ++++++++++++
 llvm/lib/LTO/CMakeLists.txt                   |   2 +
 llvm/lib/LTO/LTO.cpp                          |  12 +-
 llvm/lib/LTO/LTOConfigBitcode.cpp             | 355 ++++++++++++++++++
 llvm/lib/LTO/TargetOptionsBitcode.cpp         | 265 +++++++++++++
 llvm/lib/MC/MCTargetOptions.cpp               |  36 +-
 llvm/lib/Passes/PassBuilderPipelines.cpp      |  23 +-
 llvm/test/ThinLTO/X86/dtlto/summary.ll        |  23 +-
 llvm/unittests/CMakeLists.txt                 |   1 +
 llvm/unittests/LTO/CMakeLists.txt             |  13 +
 llvm/unittests/LTO/LTOConfigBitcodeTest.cpp   | 181 +++++++++
 29 files changed, 1978 insertions(+), 733 deletions(-)
 create mode 100644 cross-project-tests/dtlto/config-serialization-sync.cpp
 create mode 100644 cross-project-tests/dtlto/target-options-serialization-sync.cpp
 create mode 100644 llvm/include/llvm/LTO/Config.def
 create mode 100644 llvm/include/llvm/LTO/LTOConfigBitcode.h
 create mode 100644 llvm/include/llvm/LTO/TargetOptionsBitcode.h
 create mode 100644 llvm/include/llvm/MC/MCTargetOptions.def
 create mode 100644 llvm/include/llvm/Passes/PipelineTuningOptions.def
 create mode 100644 llvm/include/llvm/Target/TargetOptions.def
 create mode 100644 llvm/lib/LTO/BitcodeMetadataUtils.h
 create mode 100644 llvm/lib/LTO/LTOConfigBitcode.cpp
 create mode 100644 llvm/lib/LTO/TargetOptionsBitcode.cpp
 create mode 100644 llvm/unittests/LTO/CMakeLists.txt
 create mode 100644 llvm/unittests/LTO/LTOConfigBitcodeTest.cpp

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c09a8f7c0d6795..0f1ec0f8b56d75 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -40,6 +40,7 @@
 #include "llvm/IR/Verifier.h"
 #include "llvm/IRPrinter/IRPrintingPasses.h"
 #include "llvm/LTO/LTOBackend.h"
+#include "llvm/LTO/LTOConfigBitcode.h"
 #include "llvm/MC/MCTargetOptions.h"
 #include "llvm/MC/TargetRegistry.h"
 #include "llvm/Object/OffloadBinary.h"
@@ -1315,7 +1316,28 @@ runThinLTOBackend(CompilerInstance &CI, ModuleSummaryIndex *CombinedIndex,
     return std::make_unique<CachedFileStream>(std::move(OS),
                                               CGOpts.ObjectFilenameForDebug);
   };
-  lto::Config Conf;
+
+  ErrorOr<std::unique_ptr<MemoryBuffer>> IndexBuffer =
+      CI.getVirtualFileSystem().getBufferForFile(CGOpts.ThinLTOIndexFile);
+  if (!IndexBuffer) {
+    errs() << "Error loading LTO config from index file '"
+           << CGOpts.ThinLTOIndexFile
+           << "': " << IndexBuffer.getError().message() << '\n';
+    return;
+  }
+  Expected<std::optional<lto::Config>> SerializedConf =
+      lto::readLTOConfigFromSummaryIndexIfPresent(
+          (*IndexBuffer)->getMemBufferRef());
+  if (!SerializedConf) {
+    logAllUnhandledErrors(SerializedConf.takeError(), errs(),
+                          "Error loading LTO config from index file '" +
+                              CGOpts.ThinLTOIndexFile + "': ");
+    return;
+  }
+
+  bool HasSerializedConf = SerializedConf->has_value();
+  lto::Config Conf =
+      HasSerializedConf ? std::move(**SerializedConf) : lto::Config();
   if (CGOpts.SaveTempsFilePrefix != "") {
     if (Error E = Conf.addSaveTemps(CGOpts.SaveTempsFilePrefix + ".",
                                     /* UseInputModulePath */ false)) {
@@ -1325,47 +1347,48 @@ runThinLTOBackend(CompilerInstance &CI, ModuleSummaryIndex *CombinedIndex,
       });
     }
   }
-  Conf.CPU = TOpts.CPU;
-  Conf.CodeModel = getCodeModel(CGOpts);
-  Conf.MAttrs = TOpts.Features;
-  Conf.RelocModel = CGOpts.RelocationModel;
-  std::optional<CodeGenOptLevel> OptLevelOrNone =
-      CodeGenOpt::getLevel(CGOpts.OptimizationLevel);
-  assert(OptLevelOrNone && "Invalid optimization level!");
-  Conf.CGOptLevel = *OptLevelOrNone;
-  Conf.OptLevel = CGOpts.OptimizationLevel;
-  initTargetOptions(CI, Diags, Conf.Options);
-  Conf.SampleProfile = std::move(SampleProfile);
-  Conf.PTO.LoopUnrolling = CGOpts.UnrollLoops;
-  Conf.PTO.LoopInterchange = CGOpts.InterchangeLoops;
-  Conf.PTO.LoopFusion = CGOpts.FuseLoops;
-  // For historical reasons, loop interleaving is set to mirror setting for loop
-  // unrolling.
-  Conf.PTO.LoopInterleaving = CGOpts.UnrollLoops;
-  Conf.PTO.LoopVectorization = CGOpts.VectorizeLoop;
-  Conf.PTO.SLPVectorization = CGOpts.VectorizeSLP;
-  // Only enable CGProfilePass when using integrated assembler, since
-  // non-integrated assemblers don't recognize .cgprofile section.
-  Conf.PTO.CallGraphProfile = !CGOpts.DisableIntegratedAS;
-
-  // Context sensitive profile.
-  if (CGOpts.hasProfileCSIRInstr()) {
-    Conf.RunCSIRInstr = true;
-    Conf.CSIRProfile = getProfileGenName(CGOpts);
-  } else if (CGOpts.hasProfileCSIRUse()) {
-    Conf.RunCSIRInstr = false;
-    Conf.CSIRProfile = std::move(CGOpts.ProfileInstrumentUsePath);
-  }
+  if (!HasSerializedConf) {
+    Conf.CPU = TOpts.CPU;
+    Conf.CodeModel = getCodeModel(CGOpts);
+    Conf.MAttrs = TOpts.Features;
+    Conf.RelocModel = CGOpts.RelocationModel;
+    std::optional<CodeGenOptLevel> OptLevelOrNone =
+        CodeGenOpt::getLevel(CGOpts.OptimizationLevel);
+    assert(OptLevelOrNone && "Invalid optimization level!");
+    Conf.CGOptLevel = *OptLevelOrNone;
+    Conf.OptLevel = CGOpts.OptimizationLevel;
+    initTargetOptions(CI, Diags, Conf.Options);
+    Conf.SampleProfile = std::move(SampleProfile);
+    Conf.PTO.LoopUnrolling = CGOpts.UnrollLoops;
+    Conf.PTO.LoopInterchange = CGOpts.InterchangeLoops;
+    Conf.PTO.LoopFusion = CGOpts.FuseLoops;
+    // For historical reasons, loop interleaving mirrors loop unrolling.
+    Conf.PTO.LoopInterleaving = CGOpts.UnrollLoops;
+    Conf.PTO.LoopVectorization = CGOpts.VectorizeLoop;
+    Conf.PTO.SLPVectorization = CGOpts.VectorizeSLP;
+    // Only enable CGProfilePass when using integrated assembler, since
+    // non-integrated assemblers don't recognize .cgprofile section.
+    Conf.PTO.CallGraphProfile = !CGOpts.DisableIntegratedAS;
+
+    // Context sensitive profile.
+    if (CGOpts.hasProfileCSIRInstr()) {
+      Conf.RunCSIRInstr = true;
+      Conf.CSIRProfile = getProfileGenName(CGOpts);
+    } else if (CGOpts.hasProfileCSIRUse()) {
+      Conf.RunCSIRInstr = false;
+      Conf.CSIRProfile = std::move(CGOpts.ProfileInstrumentUsePath);
+    }
 
-  Conf.ProfileRemapping = std::move(ProfileRemapping);
-  Conf.DebugPassManager = CGOpts.DebugPassManager;
-  Conf.VerifyEach = CGOpts.VerifyEach;
-  Conf.RemarksWithHotness = CGOpts.DiagnosticsWithHotness;
-  Conf.RemarksFilename = CGOpts.OptRecordFile;
-  Conf.RemarksPasses = CGOpts.OptRecordPasses;
-  Conf.RemarksFormat = CGOpts.OptRecordFormat;
-  Conf.SplitDwarfFile = CGOpts.SplitDwarfFile;
-  Conf.SplitDwarfOutput = CGOpts.SplitDwarfOutput;
+    Conf.ProfileRemapping = std::move(ProfileRemapping);
+    Conf.DebugPassManager = CGOpts.DebugPassManager;
+    Conf.VerifyEach = CGOpts.VerifyEach;
+    Conf.RemarksWithHotness = CGOpts.DiagnosticsWithHotness;
+    Conf.RemarksFilename = CGOpts.OptRecordFile;
+    Conf.RemarksPasses = CGOpts.OptRecordPasses;
+    Conf.RemarksFormat = CGOpts.OptRecordFormat;
+    Conf.SplitDwarfFile = CGOpts.SplitDwarfFile;
+    Conf.SplitDwarfOutput = CGOpts.SplitDwarfOutput;
+  }
   for (auto &Plugin : CI.getPassPlugins())
     Conf.LoadedPassPlugins.push_back(Plugin.get());
   switch (Action) {
diff --git a/cross-project-tests/dtlto/config-serialization-sync.cpp b/cross-project-tests/dtlto/config-serialization-sync.cpp
new file mode 100644
index 00000000000000..5e7323e195e640
--- /dev/null
+++ b/cross-project-tests/dtlto/config-serialization-sync.cpp
@@ -0,0 +1,21 @@
+// Verify that adding an lto::Config field makes the real serialization guard
+// fail to compile until the field is handled.
+//
+// REQUIRES: clang
+// RUN: not %clangxx -std=c++17 -fsyntax-only \
+// RUN:   -I%llvm_src_root/include -I%llvm_obj_root/include \
+// RUN:   -I%llvm_src_root/lib/LTO %s 2>&1 | FileCheck %s
+
+// Inject an extra field at the final Config field declaration. Undefine the
+// macro before including the implementation so its structured binding still
+// contains the production field list.
+#define GetCacheKeyOutputString                                               \
+  GetCacheKeyOutputString;                                                    \
+  bool SerializationTestExtraField
+#include "llvm/LTO/LTOConfigBitcode.h"
+#undef GetCacheKeyOutputString
+
+#include "LTOConfigBitcode.cpp"
+
+// CHECK: type 'const Config' {{binds to|decomposes into}} 61 elements,
+// CHECK-SAME: but only 60 names were provided
diff --git a/cross-project-tests/dtlto/target-options-serialization-sync.cpp b/cross-project-tests/dtlto/target-options-serialization-sync.cpp
new file mode 100644
index 00000000000000..6e2a798662415f
--- /dev/null
+++ b/cross-project-tests/dtlto/target-options-serialization-sync.cpp
@@ -0,0 +1,21 @@
+// Verify that adding a TargetOptions field makes the real serialization guard
+// fail to compile until the field is handled.
+//
+// REQUIRES: clang
+// RUN: not %clangxx -std=c++17 -fsyntax-only \
+// RUN:   -I%llvm_src_root/include -I%llvm_obj_root/include \
+// RUN:   -I%llvm_src_root/lib/LTO %s 2>&1 | FileCheck %s
+
+// Inject an extra field at the final TargetOptions field declaration. Undefine
+// the macro before including the implementation so its structured binding
+// still contains the production field list.
+#define ObjectFilenameForDebug                                                \
+  ObjectFilenameForDebug;                                                     \
+  bool SerializationTestExtraField
+#include "llvm/LTO/TargetOptionsBitcode.h"
+#undef ObjectFilenameForDebug
+
+#include "TargetOptionsBitcode.cpp"
+
+// CHECK: type 'const TargetOptions' {{binds to|decomposes into}} 63 elements,
+// CHECK-SAME: but only 62 names were provided
diff --git a/cross-project-tests/lit.cfg.py b/cross-project-tests/lit.cfg.py
index ae4647d33672e4..94019275f5809e 100644
--- a/cross-project-tests/lit.cfg.py
+++ b/cross-project-tests/lit.cfg.py
@@ -56,6 +56,7 @@
         ),
     ),
     ToolSubst("%llvm_src_root", config.llvm_src_root),
+    ToolSubst("%llvm_obj_root", config.llvm_obj_root),
     ToolSubst("%llvm_tools_dir", config.llvm_tools_dir),
 ]
 
diff --git a/cross-project-tests/lit.site.cfg.py.in b/cross-project-tests/lit.site.cfg.py.in
index b8992b6dca45ec..0f57041e6d7522 100644
--- a/cross-project-tests/lit.site.cfg.py.in
+++ b/cross-project-tests/lit.site.cfg.py.in
@@ -6,6 +6,7 @@ from pathlib import Path
 
 config.targets_to_build = "@TARGETS_TO_BUILD@".split()
 config.llvm_src_root = "@LLVM_SOURCE_DIR@"
+config.llvm_obj_root = "@LLVM_BINARY_DIR@"
 config.llvm_tools_dir = lit_config.substitute("@LLVM_TOOLS_DIR@")
 config.llvm_libs_dir = "@LLVM_LIBS_DIR@"
 config.llvm_shlib_dir = lit_config.substitute("@SHLIBDIR@")
diff --git a/llvm/include/llvm/Bitcode/BitcodeWriter.h b/llvm/include/llvm/Bitcode/BitcodeWriter.h
index d88e261f8c6844..9ce577f4ca9ca0 100644
--- a/llvm/include/llvm/Bitcode/BitcodeWriter.h
+++ b/llvm/include/llvm/Bitcode/BitcodeWriter.h
@@ -105,7 +105,8 @@ class BitcodeWriter {
   LLVM_ABI void
   writeIndex(const ModuleSummaryIndex *Index,
              const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex,
-             const GVSummaryPtrSet *DecSummaries);
+             const GVSummaryPtrSet *DecSummaries,
+             const Module *ModuleMetadata = nullptr);
 };
 
 /// Write the specified module to the specified raw output stream.
@@ -152,10 +153,14 @@ LLVM_ABI void writeThinLinkBitcodeToFile(const Module &M, raw_ostream &Out,
 /// index for a distributed backend, provide the \p ModuleToSummariesForIndex
 /// map. \p DecSummaries specifies the set of summaries for which the
 /// corresponding value should be imported as a declaration (prototype).
+/// If \p ModuleMetadata is provided, its module-level metadata is emitted into
+/// the index module. The metadata must be self-contained and must not reference
+/// globals or functions from \p ModuleMetadata.
 LLVM_ABI void writeIndexToFile(
     const ModuleSummaryIndex &Index, raw_ostream &Out,
     const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex = nullptr,
-    const GVSummaryPtrSet *DecSummaries = nullptr);
+    const GVSummaryPtrSet *DecSummaries = nullptr,
+    const Module *ModuleMetadata = nullptr);
 
 /// If EmbedBitcode is set, save a copy of the llvm IR as data in the
 ///  __LLVM,__bitcode section (.llvmbc on non-MacOS).
diff --git a/llvm/include/llvm/LTO/Config.def b/llvm/include/llvm/LTO/Config.def
new file mode 100644
index 00000000000000..c3bbf6805ed3f7
--- /dev/null
+++ b/llvm/include/llvm/LTO/Config.def
@@ -0,0 +1,128 @@
+//===-- Config.def - LTO configuration database ---------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the fields stored in llvm::lto::Config. The Kind argument
+// identifies how a field is serialized; NONE denotes process-local state that
+// is intentionally omitted from serialized configurations.
+//
+// Users must define LTO_CONFIG_OPTION and LTO_CONFIG_MUTABLE_OPTION before
+// including this file.
+//
+// NOTE: NO INCLUDE GUARD DESIRED!
+//
+//===----------------------------------------------------------------------===//
+
+#if !(defined(LTO_CONFIG_OPTION) && defined(LTO_CONFIG_MUTABLE_OPTION))
+#error "Define the LTO_CONFIG_* macros before including Config.def"
+#endif
+
+LTO_CONFIG_OPTION(std::string, CPU, {}, STRING)
+LTO_CONFIG_OPTION(TargetOptions, Options, {}, TARGET_OPTIONS)
+LTO_CONFIG_OPTION(std::vector<std::string>, MAttrs, {}, STRING_LIST)
+LTO_CONFIG_OPTION(std::vector<std::string>, MllvmArgs, {}, STRING_LIST)
+
+/// Process-local pass plugins, intentionally not serialized.
+LTO_CONFIG_OPTION(std::vector<llvm::PassPlugin *>, LoadedPassPlugins, {}, NONE)
+
+LTO_CONFIG_OPTION(std::vector<std::string>, PassPluginFilenames, {},
+                  STRING_LIST)
+
+/// Process-local callback, intentionally not serialized.
+LTO_CONFIG_OPTION(std::function<void(legacy::PassManager &)>,
+                  PreCodeGenPassesHook, {}, NONE)
+
+LTO_CONFIG_OPTION(std::optional<Reloc::Model>, RelocModel, Reloc::PIC_,
+                  OPTIONAL_RELOC_MODEL)
+LTO_CONFIG_OPTION(std::optional<CodeModel::Model>, CodeModel, std::nullopt,
+                  OPTIONAL_ENUM)
+LTO_CONFIG_OPTION(CodeGenOptLevel, CGOptLevel, CodeGenOptLevel::Default, ENUM)
+LTO_CONFIG_OPTION(CodeGenFileType, CGFileType, CodeGenFileType::ObjectFile,
+                  ENUM)
+LTO_CONFIG_OPTION(unsigned, OptLevel, 2, U32)
+LTO_CONFIG_OPTION(bool, VerifyEach, false, BOOL)
+LTO_CONFIG_OPTION(bool, DisableVerify, false, BOOL)
+
+/// Do not assume that target builtins are present.
+LTO_CONFIG_OPTION(bool, Freestanding, false, BOOL)
+
+/// Disable the optimizer, including importing for ThinLTO.
+LTO_CONFIG_OPTION(bool, CodeGenOnly, false, BOOL)
+
+/// Run context-sensitive PGO IR instrumentation.
+LTO_CONFIG_OPTION(bool, RunCSIRInstr, false, BOOL)
+
+/// Warn about hash mismatches in PGO profile data.
+LTO_CONFIG_OPTION(bool, PGOWarnMismatch, true, BOOL)
+
+LTO_CONFIG_OPTION(bool, HasWholeProgramVisibility, false, BOOL)
+LTO_CONFIG_OPTION(bool, ValidateAllVtablesHaveTypeInfos, false, BOOL)
+LTO_CONFIG_OPTION(bool, AllVtablesHaveTypeInfos, false, BOOL)
+LTO_CONFIG_OPTION(bool, AlwaysEmitRegularLTOObj, false, BOOL)
+LTO_CONFIG_OPTION(bool, KeepSymbolNameCopies, true, BOOL)
+
+/// Distinguish in-process and out-of-process DTLTO cache entries.
+LTO_CONFIG_MUTABLE_OPTION(bool, Dtlto, 0, BOOL)
+
+LTO_CONFIG_OPTION(Config::VisScheme, VisibilityScheme, FromPrevailing, ENUM)
+
+LTO_CONFIG_OPTION(std::string, OptPipeline, {}, STRING)
+LTO_CONFIG_OPTION(std::string, AAPipeline, {}, STRING)
+LTO_CONFIG_OPTION(std::string, OverrideTriple, {}, STRING)
+LTO_CONFIG_OPTION(std::string, DefaultTriple, {}, STRING)
+LTO_CONFIG_OPTION(std::string, CSIRProfile, {}, STRING)
+LTO_CONFIG_OPTION(std::string, SampleProfile, {}, STRING)
+LTO_CONFIG_OPTION(std::string, ProfileRemapping, {}, STRING)
+LTO_CONFIG_OPTION(std::string, DwoDir, {}, STRING)
+LTO_CONFIG_OPTION(std::string, SplitDwarfFile, {}, STRING)
+LTO_CONFIG_OPTION(std::string, SplitDwarfOutput, {}, STRING)
+LTO_CONFIG_OPTION(std::string, RemarksFilename, {}, STRING)
+LTO_CONFIG_OPTION(std::string, RemarksPasses, {}, STRING)
+LTO_CONFIG_OPTION(bool, RemarksWithHotness, false, BOOL)
+
+/// Optional remarks threshold with disabled, manual, and automatic modes.
+LTO_CONFIG_OPTION(std::optional<uint64_t>, RemarksHotnessThreshold, 0,
+                  REMARKS_HOTNESS)
+
+LTO_CONFIG_OPTION(std::string, RemarksFormat, {}, STRING)
+LTO_CONFIG_OPTION(bool, DebugPassManager, false, BOOL)
+LTO_CONFIG_OPTION(std::string, StatsFile, {}, STRING)
+LTO_CONFIG_OPTION(std::vector<std::string>, ThinLTOModulesToCompile, {},
+                  STRING_LIST)
+LTO_CONFIG_OPTION(bool, TimeTraceEnabled, false, BOOL)
+LTO_CONFIG_OPTION(unsigned, TimeTraceGranularity, 500, U32)
+LTO_CONFIG_OPTION(bool, ShouldDiscardValueNames, true, BOOL)
+
+/// Process-local diagnostic callback, intentionally not serialized.
+LTO_CONFIG_OPTION(DiagnosticHandlerFunction, DiagHandler, {}, NONE)
+
+LTO_CONFIG_OPTION(bool, AddFSDiscriminator, false, BOOL)
+
+/// Process-local output stream, intentionally not serialized.
+LTO_CONFIG_OPTION(std::unique_ptr<raw_ostream>, ResolutionFile, {}, NONE)
+
+LTO_CONFIG_OPTION(PipelineTuningOptions, PTO, {}, PIPELINE_TUNING_OPTIONS)
+
+/// Process-local module hooks, intentionally not serialized.
+LTO_CONFIG_OPTION(ModuleHookFn, PreOptModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(ModuleHookFn, PostPromoteModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(ModuleHookFn, PostInternalizeModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(ModuleHookFn, PostImportModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(ModuleHookFn, PostOptModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(ModuleHookFn, PreCodeGenModuleHook, {}, NONE)
+LTO_CONFIG_OPTION(CombinedIndexHookFn, CombinedIndexHook, {}, NONE)
+
+/// Process-local backend callbacks, intentionally not serialized.
+LTO_CONFIG_OPTION(std::function<std::unique_ptr<raw_pwrite_stream>(size_t)>,
+                  GetSummaryIndexOutputStream, {}, NONE)
+LTO_CONFIG_OPTION(std::function<std::vector<std::string> &(size_t)>,
+                  GetImportsListOutputArray, {}, NONE)
+LTO_CONFIG_OPTION(std::function<std::string &(size_t)>, GetCacheKeyOutputString,
+                  {}, NONE)
+
+#undef LTO_CONFIG_OPTION
+#undef LTO_CONFIG_MUTABLE_OPTION
diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h
index f322f753813ff4..876e15b27ca0bf 100644
--- a/llvm/include/llvm/LTO/Config.h
+++ b/llvm/include/llvm/LTO/Config.h
@@ -45,231 +45,15 @@ struct Config {
     FromPrevailing,
     ELF,
   };
-  // Note: when adding fields here, consider whether they need to be added to
-  // computeLTOCacheKey in LTO.cpp.
-  std::string CPU;
-  TargetOptions Options;
-  std::vector<std::string> MAttrs;
-  std::vector<std::string> MllvmArgs;
-  // LTO will register both lists of plugins, but
-  // if an LTO client has already loaded a set of plugins,
-  // they should register them via LoadedPassPlugins.
-  // LoadedPassPlugins is currently used by distributed thin-lto.
-  std::vector<llvm::PassPlugin *> LoadedPassPlugins;
-  std::vector<std::string> PassPluginFilenames;
-  /// For adding passes that run right before codegen.
-  std::function<void(legacy::PassManager &)> PreCodeGenPassesHook;
-  std::optional<Reloc::Model> RelocModel = Reloc::PIC_;
-  std::optional<CodeModel::Model> CodeModel;
-  CodeGenOptLevel CGOptLevel = CodeGenOptLevel::Default;
-  CodeGenFileType CGFileType = CodeGenFileType::ObjectFile;
-  unsigned OptLevel = 2;
-  bool VerifyEach = false;
-  bool DisableVerify = false;
-
-  /// Flag to indicate that the optimizer should not assume builtins are present
-  /// on the target.
-  bool Freestanding = false;
-
-  /// Disable entirely the optimizer, including importing for ThinLTO
-  bool CodeGenOnly = false;
-
-  /// Run PGO context sensitive IR instrumentation.
-  bool RunCSIRInstr = false;
-
-  /// Turn on/off the warning about a hash mismatch in the PGO profile data.
-  bool PGOWarnMismatch = true;
-
-  /// Asserts whether we can assume whole program visibility during the LTO
-  /// link.
-  bool HasWholeProgramVisibility = false;
-
-  /// We're validating that all native vtables have corresponding type infos.
-  bool ValidateAllVtablesHaveTypeInfos = false;
-  /// If all native vtables have corresponding type infos, allow
-  /// usage of RTTI to block devirtualization on types used in native files.
-  bool AllVtablesHaveTypeInfos = false;
-
-  /// Always emit a Regular LTO object even when it is empty because no Regular
-  /// LTO modules were linked. This option is useful for some build system which
-  /// want to know a priori all possible output files.
-  bool AlwaysEmitRegularLTOObj = false;
-
-  /// If true, the LTO instance creates copies of the symbol names for LTO::run.
-  /// The lld linker uses string saver to keep symbol names alive and doesn't
-  /// need to create copies, so it can set this field to false.
-  bool KeepSymbolNameCopies = true;
-
-  /// This flag is used as one of parameters to calculate cache entries and to
-  /// ensure that in-process cache and out-of-process (DTLTO) cache are
-  /// distinguished.
-  mutable bool Dtlto = 0;
-
-  /// Allows non-imported definitions to get the potentially more constraining
-  /// visibility from the prevailing definition. FromPrevailing is the default
-  /// because it works for many binary formats. ELF can use the more optimized
-  /// 'ELF' scheme.
-  VisScheme VisibilityScheme = FromPrevailing;
-
-  /// If this field is set, the set of passes run in the middle-end optimizer
-  /// will be the one specified by the string. Only works with the new pass
-  /// manager as the old one doesn't have this ability.
-  std::string OptPipeline;
-
-  // If this field is set, it has the same effect of specifying an AA pipeline
-  // identified by the string. Only works with the new pass manager, in
-  // conjunction OptPipeline.
-  std::string AAPipeline;
-
-  /// Setting this field will replace target triples in input files with this
-  /// triple.
-  std::string OverrideTriple;
-
-  /// Setting this field will replace unspecified target triples in input files
-  /// with this triple.
-  std::string DefaultTriple;
-
-  /// Context Sensitive PGO profile path.
-  std::string CSIRProfile;
-
-  /// Sample PGO profile path.
-  std::string SampleProfile;
-
-  /// Name remapping file for profile data.
-  std::string ProfileRemapping;
-
-  /// The directory to store .dwo files.
-  std::string DwoDir;
-
-  /// The name for the split debug info file used for the DW_AT_[GNU_]dwo_name
-  /// attribute in the skeleton CU. This should generally only be used when
-  /// running an individual backend directly via thinBackend(), as otherwise
-  /// all objects would use the same .dwo file. Not used as output path.
-  std::string SplitDwarfFile;
-
-  /// The path to write a .dwo file to. This should generally only be used when
-  /// running an individual backend directly via thinBackend(), as otherwise
-  /// all .dwo files will be written to the same path. Not used in skeleton CU.
-  std::string SplitDwarfOutput;
-
-  /// Optimization remarks file path.
-  std::string RemarksFilename;
-
-  /// Optimization remarks pass filter.
-  std::string RemarksPasses;
-
-  /// Whether to emit optimization remarks with hotness informations.
-  bool RemarksWithHotness = false;
-
-  /// The minimum hotness value a diagnostic needs in order to be included in
-  /// optimization diagnostics.
-  ///
-  /// The threshold is an Optional value, which maps to one of the 3 states:
-  /// 1. 0            => threshold disabled. All emarks will be printed.
-  /// 2. positive int => manual threshold by user. Remarks with hotness exceed
-  ///                    threshold will be printed.
-  /// 3. None         => 'auto' threshold by user. The actual value is not
-  ///                    available at command line, but will be synced with
-  ///                    hotness threhold from profile summary during
-  ///                    compilation.
-  ///
-  /// If threshold option is not specified, it is disabled by default.
-  std::optional<uint64_t> RemarksHotnessThreshold = 0;
-
-  /// The format used for serializing remarks (default: YAML).
-  std::string RemarksFormat;
-
-  /// Whether to emit the pass manager debuggging informations.
-  bool DebugPassManager = false;
-
-  /// Statistics output file path.
-  std::string StatsFile;
-
-  /// Specific thinLTO modules to compile.
-  std::vector<std::string> ThinLTOModulesToCompile;
-
-  /// Time trace enabled.
-  bool TimeTraceEnabled = false;
-
-  /// Time trace granularity.
-  unsigned TimeTraceGranularity = 500;
-
-  bool ShouldDiscardValueNames = true;
-  DiagnosticHandlerFunction DiagHandler;
-
-  /// Add FSAFDO discriminators.
-  bool AddFSDiscriminator = false;
-
-  /// If this field is set, LTO will write input file paths and symbol
-  /// resolutions here in llvm-lto2 command line flag format. This can be
-  /// used for testing and for running the LTO pipeline outside of the linker
-  /// with llvm-lto2.
-  std::unique_ptr<raw_ostream> ResolutionFile;
-
-  /// Tunable parameters for passes in the default pipelines.
-  PipelineTuningOptions PTO;
-
-  /// The following callbacks deal with tasks, which normally represent the
-  /// entire optimization and code generation pipeline for what will become a
-  /// single native object file. Each task has a unique identifier between 0 and
-  /// getMaxTasks()-1, which is supplied to the callback via the Task parameter.
-  /// A task represents the entire pipeline for ThinLTO and regular
-  /// (non-parallel) LTO, but a parallel code generation task will be split into
-  /// N tasks before code generation, where N is the parallelism level.
-  ///
-  /// LTO may decide to stop processing a task at any time, for example if the
-  /// module is empty or if a module hook (see below) returns false. For this
-  /// reason, the client should not expect to receive exactly getMaxTasks()
-  /// native object files.
-
-  /// A module hook may be used by a linker to perform actions during the LTO
-  /// pipeline. For example, a linker may use this function to implement
-  /// -save-temps. If this function returns false, any further processing for
-  /// that task is aborted.
-  ///
-  /// Module hooks must be thread safe with respect to the linker's internal
-  /// data structures. A module hook will never be called concurrently from
-  /// multiple threads with the same task ID, or the same module.
-  ///
-  /// Note that in out-of-process backend scenarios, none of the hooks will be
-  /// called for ThinLTO tasks.
   using ModuleHookFn = std::function<bool(unsigned Task, const Module &)>;
-
-  /// This module hook is called after linking (regular LTO) or loading
-  /// (ThinLTO) the module, before modifying it.
-  ModuleHookFn PreOptModuleHook;
-
-  /// This hook is called after promoting any internal functions
-  /// (ThinLTO-specific).
-  ModuleHookFn PostPromoteModuleHook;
-
-  /// This hook is called after internalizing the module.
-  ModuleHookFn PostInternalizeModuleHook;
-
-  /// This hook is called after importing from other modules (ThinLTO-specific).
-  ModuleHookFn PostImportModuleHook;
-
-  /// This module hook is called after optimization is complete.
-  ModuleHookFn PostOptModuleHook;
-
-  /// This module hook is called before code generation. It is similar to the
-  /// PostOptModuleHook, but for parallel code generation it is called after
-  /// splitting the module.
-  ModuleHookFn PreCodeGenModuleHook;
-
-  /// A combined index hook is called after all per-module indexes have been
-  /// combined (ThinLTO-specific). It can be used to implement -save-temps for
-  /// the combined index.
-  ///
-  /// If this function returns false, any further processing for ThinLTO tasks
-  /// is aborted.
-  ///
-  /// It is called regardless of whether the backend is in-process, although it
-  /// is not called from individual backend processes.
   using CombinedIndexHookFn = std::function<bool(
       const ModuleSummaryIndex &Index,
       const DenseSet<GlobalValue::GUID> &GUIDPreservedSymbols)>;
-  CombinedIndexHookFn CombinedIndexHook;
+
+#define LTO_CONFIG_OPTION(Type, Name, Default, Kind) Type Name = Default;
+#define LTO_CONFIG_MUTABLE_OPTION(Type, Name, Default, Kind)                   \
+  mutable Type Name = Default;
+#include "llvm/LTO/Config.def"
 
   /// This is a convenience function that configures this Config object to write
   /// temporary files named after the given OutputFileName for each of the LTO
@@ -292,24 +76,6 @@ struct Config {
   LLVM_ABI Error addSaveTemps(std::string OutputFileName,
                               bool UseInputModulePath = false,
                               const DenseSet<StringRef> &SaveTempsArgs = {});
-
-  /// Called by WriteIndexesThinBackend when it needs to write a bitcode
-  /// module's summary index. The callback should return a stream to write
-  /// the index into. If not set, the backend falls back
-  /// to writing the summary index to a file.
-  std::function<std::unique_ptr<raw_pwrite_stream>(size_t Task)>
-      GetSummaryIndexOutputStream;
-  /// Called by WriteIndexesThinBackend when it needs to store a bitcode
-  /// module's imports list. The callback should return a vector that the
-  /// backend will populate with the imported module paths. If not set, the
-  /// backend writes the imports list to a file instead.
-  std::function<std::vector<std::string> &(size_t Task)>
-      GetImportsListOutputArray;
-  /// Called by WriteIndexesThinBackend when it needs to store a bitcode
-  /// module's cache key. The callback should return a string that the backend
-  /// will fill with the computed cache key. If not set, the cache key is
-  /// discarded.
-  std::function<std::string &(size_t Task)> GetCacheKeyOutputString;
 };
 
 struct LTOLLVMDiagnosticHandler : public DiagnosticHandler {
@@ -337,7 +103,7 @@ struct LTOLLVMContext : LLVMContext {
   DiagnosticHandlerFunction DiagHandler;
 };
 
-}
-}
+} // namespace lto
+} // namespace llvm
 
 #endif
diff --git a/llvm/include/llvm/LTO/LTOConfigBitcode.h b/llvm/include/llvm/LTO/LTOConfigBitcode.h
new file mode 100644
index 00000000000000..6cbd68be1a94a4
--- /dev/null
+++ b/llvm/include/llvm/LTO/LTOConfigBitcode.h
@@ -0,0 +1,63 @@
+//===- LTOConfigBitcode.h - lto::Config in bitcode ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// Utility for embedding serializable fields of lto::Config in LLVM IR bitcode
+// via module metadata. Intended for LTO / DTLTO configuration transport.
+//
+// Non-serializable fields (callbacks, loaded plugin pointers, stream handles)
+// are omitted. See encodeLTOConfigToModule() documentation in the .cpp file.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LTO_LTOCONFIG_BITCODE_H
+#define LLVM_LTO_LTOCONFIG_BITCODE_H
+
+#include "llvm/IR/Module.h"
+#include "llvm/IR/ModuleSummaryIndex.h"
+#include "llvm/LTO/Config.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBufferRef.h"
+
+#include <optional>
+
+namespace llvm {
+namespace lto {
+
+inline constexpr StringLiteral LTOConfigMetadataName = "llvm.lto.config";
+
+/// Serialize all serializable fields of \p Config into \p M.
+LLVM_ABI Error encodeLTOConfigToModule(Module &M, const Config &Config);
+
+/// Deserialize lto::Config previously stored by encodeLTOConfigToModule.
+LLVM_ABI Expected<Config> decodeLTOConfigFromModule(const Module &M);
+
+/// Serialize \p Config into a standalone LLVM bitcode file at \p Path.
+LLVM_ABI Error writeLTOConfigToFile(StringRef Path, const Config &Config);
+
+/// Read a Config from a file written by writeLTOConfigToFile().
+LLVM_ABI Expected<Config> readLTOConfigFromFile(StringRef Path);
+
+/// Write a ThinLTO summary index containing serialized Config metadata.
+LLVM_ABI Error writeIndexWithLTOConfigToFile(
+    const ModuleSummaryIndex &Index, const Config &Config, raw_ostream &Out,
+    const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex = nullptr,
+    const GVSummaryPtrSet *DecSummaries = nullptr);
+
+/// Read Config metadata from a ThinLTO summary index.
+LLVM_ABI Expected<Config> readLTOConfigFromSummaryIndex(MemoryBufferRef Buffer);
+
+/// Read Config metadata from a ThinLTO summary index, or return std::nullopt if
+/// the index has no Config metadata.
+LLVM_ABI Expected<std::optional<Config>>
+readLTOConfigFromSummaryIndexIfPresent(MemoryBufferRef Buffer);
+
+/// Returns true if \p M contains serialized lto::Config metadata.
+LLVM_ABI bool hasEncodedLTOConfig(const Module &M);
+
+} // namespace lto
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/LTO/TargetOptionsBitcode.h b/llvm/include/llvm/LTO/TargetOptionsBitcode.h
new file mode 100644
index 00000000000000..4f15c908ca4779
--- /dev/null
+++ b/llvm/include/llvm/LTO/TargetOptionsBitcode.h
@@ -0,0 +1,49 @@
+//===- TargetOptionsBitcode.h - TargetOptions in bitcode --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// Utility for embedding llvm::TargetOptions in LLVM IR bitcode via module
+// metadata. Intended for LTO / DTLTO configuration transport.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LTO_TARGETOPTIONS_BITCODE_H
+#define LLVM_LTO_TARGETOPTIONS_BITCODE_H
+
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Target/TargetOptions.h"
+
+namespace llvm {
+namespace lto {
+
+/// Metadata name written into the module and persisted in bitcode.
+inline constexpr StringLiteral TargetOptionsMetadataName =
+    "llvm.lto.target_options";
+
+/// Serialize \p Options into \p M as named module metadata.
+/// Non-serializable fields are skipped.
+LLVM_ABI Error encodeTargetOptionsToModule(Module &M,
+                                           const TargetOptions &Options);
+
+/// Deserialize TargetOptions previously stored by encodeTargetOptionsToModule.
+/// Returns an error if metadata is missing or malformed.
+LLVM_ABI Expected<TargetOptions> decodeTargetOptionsFromModule(const Module &M);
+
+/// Returns true if \p M contains serialized TargetOptions metadata.
+LLVM_ABI bool hasEncodedTargetOptions(const Module &M);
+
+/// Encode TargetOptions as a standalone metadata node (for nesting).
+LLVM_ABI MDNode *encodeTargetOptionsAsNode(LLVMContext &Ctx,
+                                           const TargetOptions &Options);
+
+/// Decode TargetOptions from a node produced by encodeTargetOptionsAsNode.
+LLVM_ABI Expected<TargetOptions>
+decodeTargetOptionsFromNode(const MDNode *Root);
+
+} // namespace lto
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/MC/MCTargetOptions.def b/llvm/include/llvm/MC/MCTargetOptions.def
new file mode 100644
index 00000000000000..e17a149a77e15b
--- /dev/null
+++ b/llvm/include/llvm/MC/MCTargetOptions.def
@@ -0,0 +1,101 @@
+//===-- MCTargetOptions.def - MC target option database --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the options stored in llvm::MCTargetOptions. Users of this
+// file must define MC_TARGET_OPTION before including it.
+//
+// NOTE: NO INCLUDE GUARD DESIRED!
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MC_TARGET_OPTION
+#error "Define MC_TARGET_OPTION before including MCTargetOptions.def"
+#endif
+
+MC_TARGET_OPTION((bool), MCRelaxAll, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCNoExecStack, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCFatalWarnings, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCNoWarn, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCNoDeprecatedWarn, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCNoTypeCheck, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCSaveTempLabels, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), MCIncrementalLinkerCompatible, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), FDPIC, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), ShowMCEncoding, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), ShowMCInst, 1, false, BITFIELD)
+MC_TARGET_OPTION((bool), AsmVerbose, 1, false, BITFIELD)
+
+/// Preserve comments in assembly.
+MC_TARGET_OPTION((bool), PreserveAsmComments, 1, true, BITFIELD)
+
+MC_TARGET_OPTION((bool), Dwarf64, 1, false, BITFIELD)
+
+/// Use the CREL relocation format for ELF.
+MC_TARGET_OPTION((bool), Crel, 0, false, BOOL)
+
+MC_TARGET_OPTION((bool), ImplicitMapSyms, 0, false, BOOL)
+
+/// Prefer R_X86_64_[REX_]GOTPCRELX to R_X86_64_GOTPCREL on x86-64 ELF.
+MC_TARGET_OPTION((bool), X86RelaxRelocations, 0, true, BOOL)
+
+MC_TARGET_OPTION((bool), X86Sse2Avx, 0, false, BOOL)
+
+/// Disable the integrated assembler.
+MC_TARGET_OPTION((bool), DisableIntegratedAS, 0, false, BOOL)
+
+/// Control section-symbol conversion for ELF relocations.
+MC_TARGET_OPTION((RelocSectionSymType), RelocSectionSym, 0,
+                 RelocSectionSymType::All, ENUM)
+
+MC_TARGET_OPTION((std::optional<unsigned>), OutputAsmVariant, 0, {},
+                 OPTIONAL_UINT)
+
+MC_TARGET_OPTION((EmitDwarfUnwindType), EmitDwarfUnwind, 0,
+                 EmitDwarfUnwindType::Default, ENUM)
+
+MC_TARGET_OPTION((int), DwarfVersion, 0, 0, INT)
+
+/// If greater than 0, override the default MCAsmInfo binutils version.
+MC_TARGET_OPTION((std::pair<int, int>), BinutilsVersion, 0, {}, PAIR)
+
+MC_TARGET_OPTION((MCTargetOptions::DwarfDirectory), MCUseDwarfDirectory, 0,
+                 MCTargetOptions::DefaultDwarfDirectory, ENUM)
+
+/// Whether to compress DWARF debug sections.
+MC_TARGET_OPTION((DebugCompressionType), CompressDebugSections, 0,
+                 DebugCompressionType::None, ENUM)
+
+MC_TARGET_OPTION((std::string), ABIName, 0, {}, STRING)
+MC_TARGET_OPTION((std::string), AssemblyLanguage, 0, {}, STRING)
+MC_TARGET_OPTION((std::string), SplitDwarfFile, 0, {}, STRING)
+MC_TARGET_OPTION((std::string), AsSecureLogFile, 0, {}, STRING)
+
+/// Compiler path and command-line arguments recorded in CodeView LF_BUILDINFO.
+MC_TARGET_OPTION((std::string), Argv0, 0, {}, STRING)
+MC_TARGET_OPTION((std::string), CommandlineArgs, 0, {}, STRING)
+
+/// Additional paths searched for integrated-assembler .include directives.
+MC_TARGET_OPTION((std::vector<std::string>), IASSearchPaths, 0, {}, STRING_LIST)
+
+/// Instruction-printer options.
+MC_TARGET_OPTION((std::vector<std::string>), InstPrinterOptions, 0, {},
+                 STRING_LIST)
+
+/// Emit compact unwind for non-canonical personality functions on Darwin.
+MC_TARGET_OPTION((bool), EmitCompactUnwindNonCanonical, 1, false, BITFIELD)
+
+/// Emit SFrame unwind sections.
+MC_TARGET_OPTION((bool), EmitSFrameUnwind, 1, false, BITFIELD)
+
+/// Use full register names on PowerPC.
+MC_TARGET_OPTION((bool), PPCUseFullRegisterNames, 1, false, BITFIELD)
+
+/// Force 8-byte pointer encodings for ELF exception handling.
+MC_TARGET_OPTION((bool), LargeEHEncoding, 0, false, BOOL)
+
+#undef MC_TARGET_OPTION
diff --git a/llvm/include/llvm/MC/MCTargetOptions.h b/llvm/include/llvm/MC/MCTargetOptions.h
index 6bda34904a5c56..37a76a0ba7e024 100644
--- a/llvm/include/llvm/MC/MCTargetOptions.h
+++ b/llvm/include/llvm/MC/MCTargetOptions.h
@@ -13,6 +13,7 @@
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Compression.h"
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -38,100 +39,47 @@ class StringRef;
 
 class MCTargetOptions {
 public:
-  enum AsmInstrumentation {
-    AsmInstrumentationNone,
-    AsmInstrumentationAddress
-  };
-
-  bool MCRelaxAll : 1;
-  bool MCNoExecStack : 1;
-  bool MCFatalWarnings : 1;
-  bool MCNoWarn : 1;
-  bool MCNoDeprecatedWarn : 1;
-  bool MCNoTypeCheck : 1;
-  bool MCSaveTempLabels : 1;
-  bool MCIncrementalLinkerCompatible : 1;
-  bool FDPIC : 1;
-  bool ShowMCEncoding : 1;
-  bool ShowMCInst : 1;
-  bool AsmVerbose : 1;
-
-  /// Preserve Comments in Assembly.
-  bool PreserveAsmComments : 1;
-
-  bool Dwarf64 : 1;
-
-  // Use CREL relocation format for ELF.
-  bool Crel = false;
-
-  bool ImplicitMapSyms = false;
-
-  // If true, prefer R_X86_64_[REX_]GOTPCRELX to R_X86_64_GOTPCREL on x86-64
-  // ELF.
-  bool X86RelaxRelocations = true;
-
-  bool X86Sse2Avx = false;
-
-  // Disable the integrated assembler.
-  bool DisableIntegratedAS = false;
-
-  // For ELF relocations, controls section symbol conversion.
-  RelocSectionSymType RelocSectionSym = RelocSectionSymType::All;
-
-  std::optional<unsigned> OutputAsmVariant;
-
-  EmitDwarfUnwindType EmitDwarfUnwind;
-
-  int DwarfVersion = 0;
-
-  /// If greater than 0, overrides the default MCAsmInfo binutils version.
-  std::pair<int, int> BinutilsVersion = {0, 0};
+  enum AsmInstrumentation { AsmInstrumentationNone, AsmInstrumentationAddress };
 
   enum DwarfDirectory {
-    // Force disable
+    // Force disable.
     DisableDwarfDirectory,
-    // Force enable, for assemblers that support
-    // `.file fileno directory filename' syntax
+    // Force enable for assemblers that support the
+    // `.file fileno directory filename' syntax.
     EnableDwarfDirectory,
-    // Default is based on the target
+    // Default is based on the target.
     DefaultDwarfDirectory
   };
-  DwarfDirectory MCUseDwarfDirectory;
-
-  // Whether to compress DWARF debug sections.
-  DebugCompressionType CompressDebugSections = DebugCompressionType::None;
-
-  std::string ABIName;
-  std::string AssemblyLanguage;
-  std::string SplitDwarfFile;
-  std::string AsSecureLogFile;
-
-  // Used for codeview debug info. These will be set as compiler path and commandline arguments in LF_BUILDINFO
-  std::string Argv0;
-  std::string CommandlineArgs;
-
-  /// Additional paths to search for `.include` directives when using the
-  /// integrated assembler.
-  std::vector<std::string> IASSearchPaths;
-
-  // InstPrinter options.
-  std::vector<std::string> InstPrinterOptions;
-
-  // Whether to emit compact-unwind for non-canonical personality
-  // functions on Darwins.
-  bool EmitCompactUnwindNonCanonical : 1;
-
-  // Whether to emit SFrame unwind sections.
-  bool EmitSFrameUnwind : 1;
-
-  // Whether or not to use full register names on PowerPC.
-  bool PPCUseFullRegisterNames : 1;
 
-  // Force 8-byte (sdata8) pointer encodings for ELF exception-handling.
-  // On x86_64 this affects the .eh_frame FDE CFI plus the personality, LSDA,
-  // and TType encodings; on AArch64/PPC64 only the FDE CFI encoding changes
-  // (personality/LSDA/TType already default to sdata8).
-  bool LargeEHEncoding = false;
+#define MC_TARGET_OPTION_TYPE(...) __VA_ARGS__
+#define MC_TARGET_OPTION_DECLARE_BITFIELD(Type, Name, Bits, Default)           \
+  MC_TARGET_OPTION_TYPE Type Name : Bits;
+#define MC_TARGET_OPTION_DECLARE_BOOL(Type, Name, Bits, Default)               \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_ENUM(Type, Name, Bits, Default)               \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_OPTIONAL_UINT(Type, Name, Bits, Default)      \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_INT(Type, Name, Bits, Default)                \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_PAIR(Type, Name, Bits, Default)               \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_STRING(Type, Name, Bits, Default)             \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION_DECLARE_STRING_LIST(Type, Name, Bits, Default)        \
+  MC_TARGET_OPTION_TYPE Type Name = Default;
+#define MC_TARGET_OPTION(Type, Name, Bits, Default, Kind)                      \
+  MC_TARGET_OPTION_DECLARE_##Kind(Type, Name, Bits, Default)
+#include "llvm/MC/MCTargetOptions.def"
+#undef MC_TARGET_OPTION_TYPE
+#undef MC_TARGET_OPTION_DECLARE_BITFIELD
+#undef MC_TARGET_OPTION_DECLARE_BOOL
+#undef MC_TARGET_OPTION_DECLARE_ENUM
+#undef MC_TARGET_OPTION_DECLARE_OPTIONAL_UINT
+#undef MC_TARGET_OPTION_DECLARE_INT
+#undef MC_TARGET_OPTION_DECLARE_PAIR
+#undef MC_TARGET_OPTION_DECLARE_STRING
+#undef MC_TARGET_OPTION_DECLARE_STRING_LIST
 
   LLVM_ABI MCTargetOptions();
 
diff --git a/llvm/include/llvm/Passes/PassBuilder.h b/llvm/include/llvm/Passes/PassBuilder.h
index 0221cda0c308d7..c90e0b6b1cf714 100644
--- a/llvm/include/llvm/Passes/PassBuilder.h
+++ b/llvm/include/llvm/Passes/PassBuilder.h
@@ -44,65 +44,8 @@ class PipelineTuningOptions {
   /// can be set in the PassBuilder when using a LLVM as a library.
   LLVM_ABI PipelineTuningOptions();
 
-  /// Tuning option to set loop interleaving on/off, set based on opt level.
-  bool LoopInterleaving;
-
-  /// Tuning option to enable/disable loop vectorization, set based on opt
-  /// level.
-  bool LoopVectorization;
-
-  /// Tuning option to enable/disable slp loop vectorization, set based on opt
-  /// level.
-  bool SLPVectorization;
-
-  /// Tuning option to enable/disable loop unrolling. Its default value is true.
-  bool LoopUnrolling;
-
-  /// Tuning option to enable/disable loop interchange. Its default value is
-  /// false.
-  bool LoopInterchange;
-
-  /// Tuning option to enable/disable loop fusion. Its default value is false.
-  bool LoopFusion;
-
-  /// Tuning option to forget all SCEV loops in LoopUnroll. Its default value
-  /// is that of the flag: `-forget-scev-loop-unroll`.
-  bool ForgetAllSCEVInLoopUnroll;
-
-  /// Tuning option to cap the number of calls to retrive clobbering accesses in
-  /// MemorySSA, in LICM.
-  unsigned LicmMssaOptCap;
-
-  /// Tuning option to disable promotion to scalars in LICM with MemorySSA, if
-  /// the number of access is too large.
-  unsigned LicmMssaNoAccForPromotionCap;
-
-  /// Tuning option to enable/disable call graph profile. Its default value is
-  /// that of the flag: `-enable-npm-call-graph-profile`.
-  bool CallGraphProfile;
-
-  // Add LTO pipeline tuning option to enable the unified LTO pipeline.
-  bool UnifiedLTO;
-
-  /// Tuning option to enable/disable function merging. Its default value is
-  /// false.
-  bool MergeFunctions;
-
-  /// Tuning option to override the default inliner threshold.
-  int InlinerThreshold;
-
-  // Experimental option to eagerly invalidate more analyses. This has the
-  // potential to decrease max memory usage in exchange for more compile time.
-  // This may affect codegen due to either passes using analyses only when
-  // cached, or invalidating and recalculating an analysis that was
-  // stale/imprecise but still valid. Currently this invalidates all function
-  // analyses after various module->function or cgscc->function adaptors in the
-  // default pipelines.
-  bool EagerlyInvalidateAnalyses;
-
-  // Tuning option to enable/disable speculative devirtualization.
-  // Its default value is false.
-  bool DevirtualizeSpeculatively;
+#define PIPELINE_TUNING_OPTION(Type, Name, Default, Kind) Type Name;
+#include "llvm/Passes/PipelineTuningOptions.def"
 };
 
 /// This class provides access to building LLVM's passes.
@@ -158,10 +101,10 @@ class PassBuilder {
 
   /// Registers all available CGSCC analysis passes.
   ///
-  /// This is an interface that can be used to populate a \c CGSCCAnalysisManager
-  /// with all registered CGSCC analyses. Callers can still manually register any
-  /// additional analyses. Callers can also pre-register analyses and this will
-  /// not override those.
+  /// This is an interface that can be used to populate a \c
+  /// CGSCCAnalysisManager with all registered CGSCC analyses. Callers can still
+  /// manually register any additional analyses. Callers can also pre-register
+  /// analyses and this will not override those.
   LLVM_ABI void registerCGSCCAnalyses(CGSCCAnalysisManager &CGAM);
 
   /// Registers all available function analysis passes.
@@ -1020,6 +963,6 @@ LLVM_ABI extern cl::opt<std::optional<PrintPipelinePassesFormat>, false,
 LLVM_ABI void printFormattedPipelinePasses(
     raw_ostream &OS, StringRef Pipeline,
     PrintPipelinePassesFormat Format = PrintPipelinePassesFormat::Text);
-}
+} // namespace llvm
 
 #endif
diff --git a/llvm/include/llvm/Passes/PipelineTuningOptions.def b/llvm/include/llvm/Passes/PipelineTuningOptions.def
new file mode 100644
index 00000000000000..1bcbec54f84911
--- /dev/null
+++ b/llvm/include/llvm/Passes/PipelineTuningOptions.def
@@ -0,0 +1,70 @@
+//===-- PipelineTuningOptions.def - Pipeline tuning database ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the options stored in llvm::PipelineTuningOptions. Users
+// must define the PIPELINE_TUNING_OPTION macro before including this file.
+//
+// NOTE: NO INCLUDE GUARD DESIRED!
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef PIPELINE_TUNING_OPTION
+#error                                                                         \
+    "Define PIPELINE_TUNING_OPTION before including PipelineTuningOptions.def"
+#endif
+
+/// Enable loop interleaving, subject to the optimization level.
+PIPELINE_TUNING_OPTION(bool, LoopInterleaving, true, BOOL)
+
+/// Enable loop vectorization, subject to the optimization level.
+PIPELINE_TUNING_OPTION(bool, LoopVectorization, true, BOOL)
+
+/// Enable SLP vectorization, subject to the optimization level.
+PIPELINE_TUNING_OPTION(bool, SLPVectorization, false, BOOL)
+
+/// Enable loop unrolling.
+PIPELINE_TUNING_OPTION(bool, LoopUnrolling, true, BOOL)
+
+/// Enable loop interchange.
+PIPELINE_TUNING_OPTION(bool, LoopInterchange, EnableLoopInterchange, BOOL)
+
+/// Enable loop fusion.
+PIPELINE_TUNING_OPTION(bool, LoopFusion, false, BOOL)
+
+/// Forget all SCEV loops during loop unrolling.
+PIPELINE_TUNING_OPTION(bool, ForgetAllSCEVInLoopUnroll, ForgetSCEVInLoopUnroll,
+                       BOOL)
+
+/// Cap MemorySSA clobbering-access queries in LICM.
+PIPELINE_TUNING_OPTION(unsigned, LicmMssaOptCap, SetLicmMssaOptCap, U32)
+
+/// Disable LICM scalar promotion when the number of accesses is too large.
+PIPELINE_TUNING_OPTION(unsigned, LicmMssaNoAccForPromotionCap,
+                       SetLicmMssaNoAccForPromotionCap, U32)
+
+/// Enable call-graph profiling.
+PIPELINE_TUNING_OPTION(bool, CallGraphProfile, true, BOOL)
+
+/// Enable the unified LTO pipeline.
+PIPELINE_TUNING_OPTION(bool, UnifiedLTO, false, BOOL)
+
+/// Enable function merging.
+PIPELINE_TUNING_OPTION(bool, MergeFunctions, EnableMergeFunctions, BOOL)
+
+/// Override the default inliner threshold.
+PIPELINE_TUNING_OPTION(int, InlinerThreshold, -1, I32)
+
+/// Eagerly invalidate analyses to potentially reduce peak memory usage.
+PIPELINE_TUNING_OPTION(bool, EagerlyInvalidateAnalyses,
+                       EnableEagerlyInvalidateAnalyses, BOOL)
+
+/// Enable speculative devirtualization.
+PIPELINE_TUNING_OPTION(bool, DevirtualizeSpeculatively,
+                       EnableDevirtualizeSpeculatively, BOOL)
+
+#undef PIPELINE_TUNING_OPTION
diff --git a/llvm/include/llvm/Target/TargetOptions.def b/llvm/include/llvm/Target/TargetOptions.def
new file mode 100644
index 00000000000000..9583a1a7809374
--- /dev/null
+++ b/llvm/include/llvm/Target/TargetOptions.def
@@ -0,0 +1,199 @@
+//===-- TargetOptions.def - Target option database ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the options stored in llvm::TargetOptions. Users of this
+// file must define TARGET_OPTION before including it.
+//
+// NOTE: NO INCLUDE GUARD DESIRED!
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TARGET_OPTION
+#error "Define TARGET_OPTION before including TargetOptions.def"
+#endif
+
+/// Enable the extended Altivec ABI on AIX.
+TARGET_OPTION((unsigned), EnableAIXExtendedAltivecABI, 1, false, BOOL)
+
+/// Honor a dynamically changing, sign-dependent floating-point rounding mode.
+TARGET_OPTION((unsigned), HonorSignDependentRoundingFPMathOption, 1, false,
+              BOOL)
+
+/// Do not place zero-initialized data in the BSS section.
+TARGET_OPTION((unsigned), NoZerosInBSS, 1, false, BOOL)
+
+/// Perform guaranteed tail-call optimization for eligible fastcc calls.
+TARGET_OPTION((unsigned), GuaranteedTailCallOpt, 1, false, BOOL)
+
+/// Allow CodeGen to order local stack symbols.
+TARGET_OPTION((unsigned), StackSymbolOrdering, 1, true, BOOL)
+
+/// Enable fast-path instruction selection.
+TARGET_OPTION((unsigned), EnableFastISel, 1, false, BOOL)
+
+/// Enable global instruction selection.
+TARGET_OPTION((unsigned), EnableGlobalISel, 1, false, BOOL)
+
+/// Control abort behavior when global instruction selection fails.
+TARGET_OPTION((GlobalISelAbortMode), GlobalISelAbort, 0,
+              GlobalISelAbortMode::Enable, ENUM)
+
+/// Control when and how the Swift async frame pointer bit is set.
+TARGET_OPTION((SwiftAsyncFramePointerMode), SwiftAsyncFramePointer, 0,
+              SwiftAsyncFramePointerMode::Always, ENUM)
+
+/// Use .init_array instead of .ctors for static constructors.
+TARGET_OPTION((unsigned), UseInitArray, 1, false, BOOL)
+
+/// Emit functions into separate sections.
+TARGET_OPTION((unsigned), FunctionSections, 1, false, BOOL)
+
+/// Emit data into separate sections.
+TARGET_OPTION((unsigned), DataSections, 1, false, BOOL)
+
+/// Do not emit visibility attributes for XCOFF.
+TARGET_OPTION((unsigned), IgnoreXCOFFVisibility, 1, false, BOOL)
+
+/// Emit the XCOFF traceback table.
+TARGET_OPTION((unsigned), XCOFFTracebackTable, 1, true, BOOL)
+
+TARGET_OPTION((unsigned), UniqueSectionNames, 1, true, BOOL)
+
+/// Use unique names for basic block sections.
+TARGET_OPTION((unsigned), UniqueBasicBlockSectionNames, 1, false, BOOL)
+
+/// Emit named sections with the same name into different sections.
+TARGET_OPTION((unsigned), SeparateNamedSections, 1, false, BOOL)
+
+/// Emit a target-specific trap instruction for unreachable IR instructions.
+TARGET_OPTION((unsigned), TrapUnreachable, 1, false, BOOL)
+
+/// Do not emit a trap after noreturn calls even when TrapUnreachable is true.
+TARGET_OPTION((unsigned), NoTrapAfterNoreturn, 1, false, BOOL)
+
+/// Bit size of immediate TLS offsets (0 means use the default).
+TARGET_OPTION((unsigned), TLSSize, 8, 0, U32_BITFIELD)
+
+/// Enable the emulated TLS model.
+TARGET_OPTION((unsigned), EmulatedTLS, 1, false, BOOL)
+
+/// Enable TLS descriptors.
+TARGET_OPTION((unsigned), EnableTLSDESC, 1, false, BOOL)
+
+/// Enable interprocedural register allocation.
+TARGET_OPTION((unsigned), EnableIPRA, 1, false, BOOL)
+
+/// Emit a section containing function stack-size metadata.
+TARGET_OPTION((unsigned), EmitStackSizeSection, 1, false, BOOL)
+
+/// Enable the MachineOutliner pass.
+TARGET_OPTION((unsigned), EnableMachineOutliner, 1, false, BOOL)
+
+/// Enable the MachineFunctionSplitter pass.
+TARGET_OPTION((unsigned), EnableMachineFunctionSplitter, 1, false, BOOL)
+
+/// Enable the StaticDataSplitter pass.
+TARGET_OPTION((unsigned), EnableStaticDataPartitioning, 1, false, BOOL)
+
+/// Set if the target supports default outlining behavior.
+TARGET_OPTION((unsigned), SupportsDefaultOutlining, 1, false, BOOL)
+
+/// Enable the machine verifier at the end of default NPM CodeGen pipelines.
+TARGET_OPTION((unsigned), EnableDefaultMachineVerifier, 1, true, BOOL)
+
+/// Emit an address-significance table.
+TARGET_OPTION((unsigned), EmitAddrsig, 1, false, BOOL)
+
+/// Emit the SHT_LLVM_BB_ADDR_MAP section.
+TARGET_OPTION((unsigned), BBAddrMap, 1, false, BOOL)
+
+/// Select which basic blocks are emitted into separate sections.
+TARGET_OPTION((BasicBlockSection), BBSections, 0, BasicBlockSection::None, ENUM)
+
+/// Sampled basic-block information used to select basic block sections.
+TARGET_OPTION((std::shared_ptr<MemoryBuffer>), BBSectionsFuncListBuf, 0, {},
+              BUFFER)
+
+/// Emit a section containing call-graph metadata.
+TARGET_OPTION((unsigned), EmitCallGraphSection, 1, false, BOOL)
+
+/// Enable call-site information production.
+TARGET_OPTION((unsigned), EmitCallSiteInfo, 1, false, BOOL)
+
+/// Set if the target supports debug entry values by default.
+TARGET_OPTION((unsigned), SupportsDebugEntryValues, 1, false, BOOL)
+
+/// Force production of debug entry values (for testing only).
+TARGET_OPTION((unsigned), EnableDebugEntryValues, 1, false, BOOL)
+
+/// Use experimental value-tracking variable locations.
+TARGET_OPTION((unsigned), ValueTrackingVariableLocations, 1, false, BOOL)
+
+/// Emit the DWARF debug frame section.
+TARGET_OPTION((unsigned), ForceDwarfFrameSection, 1, false, BOOL)
+
+/// Emit the XRay function index section.
+TARGET_OPTION((unsigned), XRayFunctionIndex, 1, true, BOOL)
+
+/// Do not use DWARF extensions in later DWARF versions.
+TARGET_OPTION((unsigned), DebugStrictDwarf, 1, false, BOOL)
+
+/// Emit the hotpatch flag in CodeView debug information.
+TARGET_OPTION((unsigned), Hotpatch, 1, false, BOOL)
+
+/// Enable scalar MASS conversions.
+TARGET_OPTION((unsigned), PPCGenScalarMASSEntries, 1, false, BOOL)
+
+/// Enable JustMyCode instrumentation.
+TARGET_OPTION((unsigned), JMCInstrument, 1, false, BOOL)
+
+/// Enable the CFIFixup pass.
+TARGET_OPTION((unsigned), EnableCFIFixup, 1, false, BOOL)
+
+/// Enable MisExpect diagnostics.
+TARGET_OPTION((unsigned), MisExpect, 1, false, BOOL)
+
+/// Put const objects with relocatable addresses in XCOFF read-only data.
+TARGET_OPTION((unsigned), XCOFFReadOnlyPointers, 1, false, BOOL)
+
+/// Verify narrow-integer call/return argument extensions in target backends.
+TARGET_OPTION((unsigned), VerifyArgABICompliance, 1, true, BOOL)
+
+/// Name of the stack-usage output file.
+TARGET_OPTION((std::string), StackUsageFile, 0, {}, STRING)
+
+/// Override TargetLoweringBase::PrefLoopAlignment when greater than zero.
+TARGET_OPTION((unsigned), LoopAlignment, 0, 0, U32)
+
+/// Control formation of fused floating-point operations.
+TARGET_OPTION((FPOpFusion::FPOpFusionMode), AllowFPOpFusion, 0,
+              FPOpFusion::Standard, ENUM)
+
+/// Threading model assumed for operations such as atomics.
+TARGET_OPTION((ThreadModel::Model), ThreadModel, 0, ThreadModel::POSIX, ENUM)
+
+/// EABI version.
+TARGET_OPTION((EABI), EABIVersion, 0, EABI::Default, ENUM)
+
+/// Debugger for which debug information is tuned.
+TARGET_OPTION((DebuggerKind), DebuggerTuning, 0, DebuggerKind::Default, ENUM)
+
+/// Vector math library to use.
+TARGET_OPTION((VectorLibrary), VecLib, 0, VectorLibrary::NoLibrary, ENUM)
+
+/// Exception-handling model to use.
+TARGET_OPTION((ExceptionHandling), ExceptionModel, 0, ExceptionHandling::None,
+              ENUM)
+
+/// Machine-level options.
+TARGET_OPTION((MCTargetOptions), MCOptions, 0, {}, MC)
+
+/// Final object filename/path to record in CodeView debug information.
+TARGET_OPTION((std::string), ObjectFilenameForDebug, 0, {}, STRING)
+
+#undef TARGET_OPTION
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index 71e7b17ba3bd84..5de01237dfa6ad 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -21,6 +21,7 @@
 #include "llvm/Support/Compiler.h"
 
 #include <memory>
+#include <string>
 
 namespace llvm {
 struct fltSemantics;
@@ -118,266 +119,64 @@ enum CodeObjectVersionKind {
 
 class TargetOptions {
 public:
-  TargetOptions()
-      : EnableAIXExtendedAltivecABI(false),
-        HonorSignDependentRoundingFPMathOption(false), NoZerosInBSS(false),
-        GuaranteedTailCallOpt(false), StackSymbolOrdering(true),
-        EnableFastISel(false), EnableGlobalISel(false), UseInitArray(false),
-        FunctionSections(false), DataSections(false),
-        IgnoreXCOFFVisibility(false), XCOFFTracebackTable(true),
-        UniqueSectionNames(true), UniqueBasicBlockSectionNames(false),
-        SeparateNamedSections(false), TrapUnreachable(false),
-        NoTrapAfterNoreturn(false), TLSSize(0), EmulatedTLS(false),
-        EnableTLSDESC(false), EnableIPRA(false), EmitStackSizeSection(false),
-        EnableMachineOutliner(false), EnableMachineFunctionSplitter(false),
-        EnableStaticDataPartitioning(false), SupportsDefaultOutlining(false),
-        EnableDefaultMachineVerifier(true), EmitAddrsig(false),
-        BBAddrMap(false), EmitCallGraphSection(false), EmitCallSiteInfo(false),
-        SupportsDebugEntryValues(false), EnableDebugEntryValues(false),
-        ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false),
-        XRayFunctionIndex(true), DebugStrictDwarf(false), Hotpatch(false),
-        PPCGenScalarMASSEntries(false), JMCInstrument(false),
-        EnableCFIFixup(false), MisExpect(false), XCOFFReadOnlyPointers(false),
-        VerifyArgABICompliance(true) {}
+  TargetOptions() {
+#define TARGET_OPTION_INIT_BOOL(Type, Name, Bits, Default) Name = Default;
+#define TARGET_OPTION_INIT_U32_BITFIELD(Type, Name, Bits, Default)             \
+  Name = Default;
+#define TARGET_OPTION_INIT_PAIR(Type, Name, Bits, Default)
+#define TARGET_OPTION_INIT_ENUM(Type, Name, Bits, Default)
+#define TARGET_OPTION_INIT_STRING(Type, Name, Bits, Default)
+#define TARGET_OPTION_INIT_U32(Type, Name, Bits, Default)
+#define TARGET_OPTION_INIT_BUFFER(Type, Name, Bits, Default)
+#define TARGET_OPTION_INIT_MC(Type, Name, Bits, Default)
+#define TARGET_OPTION(Type, Name, Bits, Default, Kind)                         \
+  TARGET_OPTION_INIT_##Kind(Type, Name, Bits, Default)
+#include "llvm/Target/TargetOptions.def"
+#undef TARGET_OPTION_INIT_BOOL
+#undef TARGET_OPTION_INIT_U32_BITFIELD
+#undef TARGET_OPTION_INIT_PAIR
+#undef TARGET_OPTION_INIT_ENUM
+#undef TARGET_OPTION_INIT_STRING
+#undef TARGET_OPTION_INIT_U32
+#undef TARGET_OPTION_INIT_BUFFER
+#undef TARGET_OPTION_INIT_MC
+  }
 
-  /// EnableAIXExtendedAltivecABI - This flag returns true when -vec-extabi is
-  /// specified. The code generator is then able to use both volatile and
-  /// nonvolitle vector registers. When false, the code generator only uses
-  /// volatile vector registers which is the default setting on AIX.
-  unsigned EnableAIXExtendedAltivecABI : 1;
-
-  /// HonorSignDependentRoundingFPMath - This returns true when the
-  /// -enable-sign-dependent-rounding-fp-math is specified.  If this returns
-  /// false (the default), the code generator is allowed to assume that the
-  /// rounding behavior is the default (round-to-zero for all floating point
-  /// to integer conversions, and round-to-nearest for all other arithmetic
-  /// truncations).  If this is enabled (set to true), the code generator must
-  /// assume that the rounding mode may dynamically change.
-  unsigned HonorSignDependentRoundingFPMathOption : 1;
   LLVM_ABI bool HonorSignDependentRoundingFPMath() const;
 
-  /// NoZerosInBSS - By default some codegens place zero-initialized data to
-  /// .bss section. This flag disables such behaviour (necessary, e.g. for
-  /// crt*.o compiling).
-  unsigned NoZerosInBSS : 1;
-
-  /// GuaranteedTailCallOpt - This flag is enabled when -tailcallopt is
-  /// specified on the commandline. When the flag is on, participating targets
-  /// will perform tail call optimization on all calls which use the fastcc
-  /// calling convention and which satisfy certain target-independent
-  /// criteria (being at the end of a function, having the same return type
-  /// as their parent function, etc.), using an alternate ABI if necessary.
-  unsigned GuaranteedTailCallOpt : 1;
-
-  /// StackSymbolOrdering - When true, this will allow CodeGen to order
-  /// the local stack symbols (for code size, code locality, or any other
-  /// heuristics). When false, the local symbols are left in whatever order
-  /// they were generated. Default is true.
-  unsigned StackSymbolOrdering : 1;
-
-  /// EnableFastISel - This flag enables fast-path instruction selection
-  /// which trades away generated code quality in favor of reducing
-  /// compile time.
-  unsigned EnableFastISel : 1;
-
-  /// EnableGlobalISel - This flag enables global instruction selection.
-  unsigned EnableGlobalISel : 1;
-
-  /// EnableGlobalISelAbort - Control abort behaviour when global instruction
-  /// selection fails to lower/select an instruction.
-  GlobalISelAbortMode GlobalISelAbort = GlobalISelAbortMode::Enable;
-
-  /// Control when and how the Swift async frame pointer bit should
-  /// be set.
-  SwiftAsyncFramePointerMode SwiftAsyncFramePointer =
-      SwiftAsyncFramePointerMode::Always;
-
-  /// UseInitArray - Use .init_array instead of .ctors for static
-  /// constructors.
-  unsigned UseInitArray : 1;
-
-  /// Emit functions into separate sections.
-  unsigned FunctionSections : 1;
-
-  /// Emit data into separate sections.
-  unsigned DataSections : 1;
-
-  /// Do not emit visibility attribute for xcoff.
-  unsigned IgnoreXCOFFVisibility : 1;
-
-  /// Emit XCOFF traceback table.
-  unsigned XCOFFTracebackTable : 1;
-
-  unsigned UniqueSectionNames : 1;
-
-  /// Use unique names for basic block sections.
-  unsigned UniqueBasicBlockSectionNames : 1;
-
-  /// Emit named sections with the same name into different sections.
-  unsigned SeparateNamedSections : 1;
-
-  /// Emit target-specific trap instruction for 'unreachable' IR instructions.
-  unsigned TrapUnreachable : 1;
-
-  /// Do not emit a trap instruction for 'unreachable' IR instructions behind
-  /// noreturn calls, even if TrapUnreachable is true.
-  unsigned NoTrapAfterNoreturn : 1;
-
-  /// Bit size of immediate TLS offsets (0 == use the default).
-  unsigned TLSSize : 8;
-
-  /// EmulatedTLS - This flag enables emulated TLS model, using emutls
-  /// function in the runtime library..
-  unsigned EmulatedTLS : 1;
-
-  /// EnableTLSDESC - This flag enables TLS Descriptors.
-  unsigned EnableTLSDESC : 1;
-
-  /// This flag enables InterProcedural Register Allocation (IPRA).
-  unsigned EnableIPRA : 1;
-
-  /// Emit section containing metadata on function stack sizes.
-  unsigned EmitStackSizeSection : 1;
-
-  /// Enables the MachineOutliner pass.
-  unsigned EnableMachineOutliner : 1;
-
-  /// Enables the MachineFunctionSplitter pass.
-  unsigned EnableMachineFunctionSplitter : 1;
-
-  /// Enables the StaticDataSplitter pass.
-  unsigned EnableStaticDataPartitioning : 1;
-
-  /// Set if the target supports default outlining behaviour.
-  unsigned SupportsDefaultOutlining : 1;
-
-  /// Enable Machine verifier at the end of default codegen pipelines. (Only
-  /// used with NPM)
-  unsigned EnableDefaultMachineVerifier : 1;
-
-  /// Emit address-significance table.
-  unsigned EmitAddrsig : 1;
-
-  // Emit the SHT_LLVM_BB_ADDR_MAP section containing basic block address
-  // which can be used to map virtual addresses to machine basic blocks.
-  unsigned BBAddrMap : 1;
-
-  /// Emit basic blocks into separate sections.
-  BasicBlockSection BBSections = BasicBlockSection::None;
-
-  /// Memory Buffer that contains information on sampled basic blocks and used
-  /// to selectively generate basic block sections.
-  std::shared_ptr<MemoryBuffer> BBSectionsFuncListBuf;
-
-  /// Emit section containing call graph metadata.
-  unsigned EmitCallGraphSection : 1;
-
-  /// The flag enables call site info production. It is used only for debug
-  /// info, and it is restricted only to optimized code. This can be used for
-  /// something else, so that should be controlled in the frontend.
-  unsigned EmitCallSiteInfo : 1;
-  /// Set if the target supports the debug entry values by default.
-  unsigned SupportsDebugEntryValues : 1;
-  /// When set to true, the EnableDebugEntryValues option forces production
-  /// of debug entry values even if the target does not officially support
-  /// it. Useful for testing purposes only. This flag should never be checked
-  /// directly, always use \ref ShouldEmitDebugEntryValues instead.
-  unsigned EnableDebugEntryValues : 1;
   /// NOTE: There are targets that still do not support the debug entry values
   /// production.
   LLVM_ABI bool ShouldEmitDebugEntryValues() const;
 
-  // When set to true, use experimental new debug variable location tracking,
-  // which seeks to follow the values of variables rather than their location,
-  // post isel.
-  unsigned ValueTrackingVariableLocations : 1;
-
-  /// Emit DWARF debug frame section.
-  unsigned ForceDwarfFrameSection : 1;
-
-  /// Emit XRay Function Index section
-  unsigned XRayFunctionIndex : 1;
-
-  /// When set to true, don't use DWARF extensions in later DWARF versions.
-  /// By default, it is set to false.
-  unsigned DebugStrictDwarf : 1;
-
-  /// Emit the hotpatch flag in CodeView debug.
-  unsigned Hotpatch : 1;
-
-  /// Enables scalar MASS conversions
-  unsigned PPCGenScalarMASSEntries : 1;
-
-  /// Enable JustMyCode instrumentation.
-  unsigned JMCInstrument : 1;
-
-  /// Enable the CFIFixup pass.
-  unsigned EnableCFIFixup : 1;
-
-  /// When set to true, enable MisExpect Diagnostics
-  /// By default, it is set to false
-  unsigned MisExpect : 1;
-
-  /// When set to true, const objects with relocatable address values are put
-  /// into the RO data section.
-  unsigned XCOFFReadOnlyPointers : 1;
-
-  /// When set to true, call/return argument extensions of narrow integers
-  /// are verified in the target backend if it cares about them. This is
-  /// not done with internal tools like llc that run many tests that ignore
-  /// (lack) these extensions.
-  unsigned VerifyArgABICompliance : 1;
-
-  /// Name of the stack usage file (i.e., .su file) if user passes
-  /// -fstack-usage. If empty, it can be implied that -fstack-usage is not
-  /// passed on the command line.
-  std::string StackUsageFile;
-
-  /// If greater than 0, override TargetLoweringBase::PrefLoopAlignment.
-  unsigned LoopAlignment = 0;
-
-  /// AllowFPOpFusion - This flag is set by the -fp-contract=xxx option.
-  /// This controls the creation of fused FP ops that store intermediate
-  /// results in higher precision than IEEE allows (E.g. FMAs).
-  ///
-  /// Fast mode - allows formation of fused FP ops whenever they're
-  /// profitable.
-  /// Standard mode - allow fusion only for 'blessed' FP ops. At present the
-  /// only blessed op is the fmuladd intrinsic. In the future more blessed ops
-  /// may be added.
-  /// Strict mode - allow fusion only if/when it can be proven that the excess
-  /// precision won't effect the result.
-  ///
-  /// Note: This option only controls formation of fused ops by the
-  /// optimizers.  Fused operations that are explicitly specified (e.g. FMA
-  /// via the llvm.fma.* intrinsic) will always be honored, regardless of
-  /// the value of this option.
-  FPOpFusion::FPOpFusionMode AllowFPOpFusion = FPOpFusion::Standard;
-
-  /// ThreadModel - This flag specifies the type of threading model to assume
-  /// for things like atomics
-  ThreadModel::Model ThreadModel = ThreadModel::POSIX;
-
-  /// EABIVersion - This flag specifies the EABI version
-  EABI EABIVersion = EABI::Default;
-
-  /// Which debugger to tune for.
-  DebuggerKind DebuggerTuning = DebuggerKind::Default;
-
-  /// Vector math library to use.
-  VectorLibrary VecLib = VectorLibrary::NoLibrary;
-
-public:
-  /// What exception model to use
-  ExceptionHandling ExceptionModel = ExceptionHandling::None;
-
-  /// Machine level options.
-  MCTargetOptions MCOptions;
-
-  /// Stores the filename/path of the final .o/.obj file, to be written in the
-  /// debug information. This is used for emitting the CodeView S_OBJNAME
-  /// record.
-  std::string ObjectFilenameForDebug;
+#define TARGET_OPTION_TYPE(...) __VA_ARGS__
+#define TARGET_OPTION_DECLARE_BOOL(Type, Name, Bits, Default)                  \
+  TARGET_OPTION_TYPE Type Name : Bits;
+#define TARGET_OPTION_DECLARE_U32_BITFIELD(Type, Name, Bits, Default)          \
+  TARGET_OPTION_TYPE Type Name : Bits;
+#define TARGET_OPTION_DECLARE_PAIR(Type, Name, Bits, Default)                  \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION_DECLARE_ENUM(Type, Name, Bits, Default)                  \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION_DECLARE_STRING(Type, Name, Bits, Default)                \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION_DECLARE_U32(Type, Name, Bits, Default)                   \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION_DECLARE_BUFFER(Type, Name, Bits, Default)                \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION_DECLARE_MC(Type, Name, Bits, Default)                    \
+  TARGET_OPTION_TYPE Type Name = Default;
+#define TARGET_OPTION(Type, Name, Bits, Default, Kind)                         \
+  TARGET_OPTION_DECLARE_##Kind(Type, Name, Bits, Default)
+#include "llvm/Target/TargetOptions.def"
+#undef TARGET_OPTION_TYPE
+#undef TARGET_OPTION_DECLARE_BOOL
+#undef TARGET_OPTION_DECLARE_U32_BITFIELD
+#undef TARGET_OPTION_DECLARE_PAIR
+#undef TARGET_OPTION_DECLARE_ENUM
+#undef TARGET_OPTION_DECLARE_STRING
+#undef TARGET_OPTION_DECLARE_U32
+#undef TARGET_OPTION_DECLARE_BUFFER
+#undef TARGET_OPTION_DECLARE_MC
 };
 
 } // namespace llvm
diff --git a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
index 03cb755038b562..6e4a197fa72d6f 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
@@ -1005,4 +1005,3 @@ Error BitcodeAnalyzer::parseBlock(unsigned BlockID, unsigned IndentLevel,
       return Skipped.takeError();
   }
 }
-
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 859e073b91cc50..e74ee137acda8a 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -322,6 +322,10 @@ class ModuleBitcodeWriter : public ModuleBitcodeWriterBase {
   /// Emit the current module to the bitstream.
   void write();
 
+  /// Emit the blocks required for module-level metadata into an already open
+  /// module block.
+  void writeModuleMetadataOnly();
+
 private:
   uint64_t bitcodeStartBit() { return BitcodeStartBit; }
 
@@ -477,6 +481,9 @@ class IndexBitcodeWriter : public BitcodeWriterBase {
   /// provides a map of modules to the corresponding GUIDs/summaries to write.
   const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex;
 
+  /// Optional module whose module-level metadata is emitted into the index.
+  const Module *ModuleMetadata;
+
   /// Map that holds the correspondence between the GUID used in the combined
   /// index and a value id generated by this class to use in references.
   std::map<GlobalValue::GUID, unsigned> GUIDToValueIdMap;
@@ -508,10 +515,12 @@ class IndexBitcodeWriter : public BitcodeWriterBase {
       BitstreamWriter &Stream, StringTableBuilder &StrtabBuilder,
       const ModuleSummaryIndex &Index,
       const GVSummaryPtrSet *DecSummaries = nullptr,
-      const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex = nullptr)
+      const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex = nullptr,
+      const Module *ModuleMetadata = nullptr)
       : BitcodeWriterBase(Stream, StrtabBuilder), Index(Index),
         DecSummaries(DecSummaries),
-        ModuleToSummariesForIndex(ModuleToSummariesForIndex) {
+        ModuleToSummariesForIndex(ModuleToSummariesForIndex),
+        ModuleMetadata(ModuleMetadata) {
 
     // See if the StackIdIndex was already added to the StackId map and
     // vector. If not, record it.
@@ -5566,6 +5575,14 @@ void ModuleBitcodeWriter::write() {
   Stream.ExitBlock();
 }
 
+void ModuleBitcodeWriter::writeModuleMetadataOnly() {
+  writeBlockInfo();
+  writeTypeTable();
+  writeModuleConstants();
+  writeModuleMetadataKinds();
+  writeModuleMetadata();
+}
+
 static void writeInt32ToBuffer(uint32_t Value, SmallVectorImpl<char> &Buffer,
                                uint32_t &Position) {
   support::endian::write32le(&Buffer[Position], Value);
@@ -5740,9 +5757,9 @@ void BitcodeWriter::writeModule(const Module &M,
 void BitcodeWriter::writeIndex(
     const ModuleSummaryIndex *Index,
     const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex,
-    const GVSummaryPtrSet *DecSummaries) {
+    const GVSummaryPtrSet *DecSummaries, const Module *ModuleMetadata) {
   IndexBitcodeWriter IndexWriter(*Stream, StrtabBuilder, *Index, DecSummaries,
-                                 ModuleToSummariesForIndex);
+                                 ModuleToSummariesForIndex, ModuleMetadata);
   IndexWriter.write();
 }
 
@@ -5781,6 +5798,14 @@ void IndexBitcodeWriter::write() {
 
   writeModuleVersion();
 
+  if (ModuleMetadata) {
+    ModuleBitcodeWriter MetadataWriter(*ModuleMetadata, StrtabBuilder, Stream,
+                                       /*ShouldPreserveUseListOrder=*/false,
+                                       /*Index=*/nullptr,
+                                       /*GenerateHash=*/false);
+    MetadataWriter.writeModuleMetadataOnly();
+  }
+
   // Write the module paths in the combined index.
   writeModStrings();
 
@@ -5797,12 +5822,13 @@ void IndexBitcodeWriter::write() {
 void llvm::writeIndexToFile(
     const ModuleSummaryIndex &Index, raw_ostream &Out,
     const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex,
-    const GVSummaryPtrSet *DecSummaries) {
+    const GVSummaryPtrSet *DecSummaries, const Module *ModuleMetadata) {
   SmallVector<char, 0> Buffer;
   Buffer.reserve(256 * 1024);
 
   BitcodeWriter Writer(Buffer);
-  Writer.writeIndex(&Index, ModuleToSummariesForIndex, DecSummaries);
+  Writer.writeIndex(&Index, ModuleToSummariesForIndex, DecSummaries,
+                    ModuleMetadata);
   Writer.writeStrtab();
 
   Out.write((char *)&Buffer.front(), Buffer.size());
diff --git a/llvm/lib/LTO/BitcodeMetadataUtils.h b/llvm/lib/LTO/BitcodeMetadataUtils.h
new file mode 100644
index 00000000000000..7a32e1a5f3b8bd
--- /dev/null
+++ b/llvm/lib/LTO/BitcodeMetadataUtils.h
@@ -0,0 +1,247 @@
+//===- BitcodeMetadataUtils.h - shared LTO metadata helpers -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// Internal helpers shared by LTOConfigBitcode.cpp and TargetOptionsBitcode.cpp.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_LTO_BITCODEMETADATAUTILS_H
+#define LLVM_LIB_LTO_BITCODEMETADATAUTILS_H
+
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/Support/Error.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace llvm {
+namespace lto {
+namespace bitcodemeta {
+
+inline Error metadataError(const Twine &Msg) {
+  return make_error<StringError>(Msg.str(), inconvertibleErrorCode());
+}
+
+inline Metadata *getI32Value(LLVMContext &Ctx, int32_t V) {
+  return ConstantAsMetadata::get(
+      ConstantInt::getSigned(Type::getInt32Ty(Ctx), V));
+}
+
+inline Metadata *getU32Value(LLVMContext &Ctx, uint32_t V) {
+  return ConstantAsMetadata::get(ConstantInt::get(Type::getInt32Ty(Ctx), V));
+}
+
+inline Metadata *getI64Value(LLVMContext &Ctx, uint64_t V) {
+  return ConstantAsMetadata::get(ConstantInt::get(Type::getInt64Ty(Ctx), V));
+}
+
+inline Metadata *getStringValue(LLVMContext &Ctx, StringRef S) {
+  return MDString::get(Ctx, S);
+}
+
+class MetadataWriter {
+  SmallVectorImpl<Metadata *> &Out;
+  LLVMContext &Ctx;
+
+public:
+  MetadataWriter(SmallVectorImpl<Metadata *> &Out, LLVMContext &Ctx)
+      : Out(Out), Ctx(Ctx) {}
+
+  LLVMContext &getContext() const { return Ctx; }
+
+  void putEntry(StringRef Key, Metadata *Value) {
+    Metadata *Ops[] = {getStringValue(Ctx, Key), Value};
+    Out.push_back(MDNode::get(Ctx, Ops));
+  }
+
+  void putI32(StringRef Key, int32_t V) { putEntry(Key, getI32Value(Ctx, V)); }
+
+  void putU32(StringRef Key, uint32_t V) { putEntry(Key, getU32Value(Ctx, V)); }
+
+  void putI64(StringRef Key, uint64_t V) { putEntry(Key, getI64Value(Ctx, V)); }
+
+  void putBool(StringRef Key, bool V) { putI32(Key, V ? 1 : 0); }
+
+  void putString(StringRef Key, StringRef V) {
+    if (!V.empty())
+      putEntry(Key, getStringValue(Ctx, V));
+  }
+
+  void putNode(StringRef Key, MDNode *Node) { putEntry(Key, Node); }
+
+  void putStringList(StringRef Key, ArrayRef<std::string> Values) {
+    if (Values.empty())
+      return;
+    SmallVector<Metadata *, 8> Elems;
+    for (const std::string &S : Values)
+      Elems.push_back(getStringValue(Ctx, S));
+    putEntry(Key, MDNode::get(Ctx, Elems));
+  }
+};
+
+inline Expected<int32_t> getI32Field(const MDNode &Entry,
+                                     StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *Val = mdconst::dyn_extract<ConstantInt>(Entry.getOperand(1));
+  if (!Val || !Val->getType()->isIntegerTy(32))
+    return metadataError(EntryKind + " value must be i32");
+  return static_cast<int32_t>(Val->getSExtValue());
+}
+
+inline Expected<uint32_t> getU32Field(const MDNode &Entry,
+                                      StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *Val = mdconst::dyn_extract<ConstantInt>(Entry.getOperand(1));
+  if (!Val || !Val->getType()->isIntegerTy(32))
+    return metadataError(EntryKind + " value must be i32");
+  return static_cast<uint32_t>(Val->getZExtValue());
+}
+
+inline Expected<int64_t> getI64Field(const MDNode &Entry,
+                                     StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *Val = mdconst::dyn_extract<ConstantInt>(Entry.getOperand(1));
+  if (!Val || !Val->getType()->isIntegerTy(64))
+    return metadataError(EntryKind + " value must be i64");
+  return static_cast<int64_t>(Val->getSExtValue());
+}
+
+inline Expected<StringRef>
+getStringField(const MDNode &Entry, StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *Val = dyn_cast<MDString>(Entry.getOperand(1));
+  if (!Val)
+    return metadataError(EntryKind + " value must be a string");
+  return Val->getString();
+}
+
+inline Expected<std::vector<std::string>>
+getStringListField(const MDNode &Entry,
+                   StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *List = dyn_cast<MDNode>(Entry.getOperand(1));
+  if (!List)
+    return metadataError(EntryKind + " value must be a string list");
+  std::vector<std::string> Out;
+  Out.reserve(List->getNumOperands());
+  for (Metadata *Op : List->operands()) {
+    auto *S = dyn_cast<MDString>(Op);
+    if (!S)
+      return metadataError(EntryKind + " string list element must be a string");
+    Out.push_back(S->getString().str());
+  }
+  return Out;
+}
+
+inline Expected<MDNode *> getNodeField(const MDNode &Entry,
+                                       StringRef EntryKind = "metadata entry") {
+  if (Entry.getNumOperands() != 2)
+    return metadataError(EntryKind + " must have 2 operands");
+  auto *Node = dyn_cast<MDNode>(Entry.getOperand(1));
+  if (!Node)
+    return metadataError(EntryKind + " value must be a metadata node");
+  return Node;
+}
+
+struct EntryApplier {
+  const MDNode &Entry;
+  StringRef EntryKind;
+
+  Error applyI32(function_ref<void(int32_t)> Setter) {
+    auto V = getI32Field(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    Setter(*V);
+    return Error::success();
+  }
+
+  Error applyU32(function_ref<void(uint32_t)> Setter) {
+    auto V = getU32Field(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    Setter(*V);
+    return Error::success();
+  }
+
+  Error applyI64(function_ref<void(int64_t)> Setter) {
+    auto V = getI64Field(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    Setter(*V);
+    return Error::success();
+  }
+
+  Error applyBool(function_ref<void(bool)> Setter) {
+    auto V = getI32Field(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    if (*V != 0 && *V != 1)
+      return metadataError(EntryKind + " boolean value must be 0 or 1");
+    Setter(*V != 0);
+    return Error::success();
+  }
+
+  Error applyString(function_ref<void(StringRef)> Setter) {
+    auto V = getStringField(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    Setter(*V);
+    return Error::success();
+  }
+
+  Error applyStringList(function_ref<void(std::vector<std::string>)> Setter) {
+    auto V = getStringListField(Entry, EntryKind);
+    if (!V)
+      return V.takeError();
+    Setter(std::move(*V));
+    return Error::success();
+  }
+};
+
+template <typename T, typename ApplyEntryFn>
+Expected<T>
+decodeVersionedMetadata(const MDNode *Root, unsigned ExpectedVersion,
+                        StringRef RootKind, ApplyEntryFn ApplyEntry) {
+  if (!Root || Root->getNumOperands() < 1)
+    return metadataError("malformed " + RootKind + " metadata root");
+
+  auto *VersionVal = mdconst::dyn_extract<ConstantInt>(Root->getOperand(0));
+  if (!VersionVal || !VersionVal->getType()->isIntegerTy(32))
+    return metadataError("malformed " + RootKind + " metadata version");
+  if (VersionVal->getZExtValue() != ExpectedVersion)
+    return metadataError("unsupported " + RootKind + " metadata version");
+
+  T Result{};
+  for (unsigned I = 1; I < Root->getNumOperands(); ++I) {
+    auto *Entry = dyn_cast<MDNode>(Root->getOperand(I));
+    if (!Entry || Entry->getNumOperands() != 2)
+      return metadataError("malformed " + RootKind + " metadata entry");
+    auto *KeyMD = dyn_cast<MDString>(Entry->getOperand(0));
+    if (!KeyMD)
+      return metadataError(RootKind + " key must be a string");
+    if (Error E = ApplyEntry(Result, KeyMD->getString(), *Entry))
+      return std::move(E);
+  }
+  return Result;
+}
+
+} // namespace bitcodemeta
+} // namespace lto
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/LTO/CMakeLists.txt b/llvm/lib/LTO/CMakeLists.txt
index cf455ff04c1122..8d3aab5d322699 100644
--- a/llvm/lib/LTO/CMakeLists.txt
+++ b/llvm/lib/LTO/CMakeLists.txt
@@ -3,6 +3,8 @@ add_llvm_component_library(LLVMLTO
   LTOBackend.cpp
   LTOModule.cpp
   LTOCodeGenerator.cpp
+  LTOConfigBitcode.cpp
+  TargetOptionsBitcode.cpp
   UpdateCompilerUsed.cpp
   ThinLTOCodeGenerator.cpp
 
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 4594c52fb5f6e1..0061de57d0ee18 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -35,6 +35,7 @@
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/RuntimeLibcalls.h"
 #include "llvm/LTO/LTOBackend.h"
+#include "llvm/LTO/LTOConfigBitcode.h"
 #include "llvm/Linker/IRMover.h"
 #include "llvm/MC/TargetRegistry.h"
 #include "llvm/Object/IRObjectFile.h"
@@ -1567,8 +1568,15 @@ Error ThinBackendProc::emitFiles(
     OS = std::move(FileOS);
   }
 
-  writeIndexToFile(CombinedIndex, *OS, &ModuleToSummariesForIndex,
-                   &DeclarationSummaries);
+  if (Conf.Dtlto) {
+    if (Error Err = writeIndexWithLTOConfigToFile(CombinedIndex, Conf, *OS,
+                                                  &ModuleToSummariesForIndex,
+                                                  &DeclarationSummaries))
+      return Err;
+  } else {
+    writeIndexToFile(CombinedIndex, *OS, &ModuleToSummariesForIndex,
+                     &DeclarationSummaries);
+  }
 
   // Emit imports files if requested, using callback if provided.
   if (Conf.GetImportsListOutputArray) {
diff --git a/llvm/lib/LTO/LTOConfigBitcode.cpp b/llvm/lib/LTO/LTOConfigBitcode.cpp
new file mode 100644
index 00000000000000..836c080ed5df52
--- /dev/null
+++ b/llvm/lib/LTO/LTOConfigBitcode.cpp
@@ -0,0 +1,355 @@
+//===- LTOConfigBitcode.cpp - lto::Config in bitcode ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// Encodes serializable lto::Config fields as module metadata in bitcode.
+//
+// Layout:
+//   !llvm.lto.config = !{ !0 }
+//   !0 = !{ i32 <version>, !1, !2, ... }
+//   !1 = !{ !"<key>", <value> }
+//
+// Value kinds:
+//   - i32 / i64 ConstantInt for scalars
+//   - MDString for strings
+//   - MDNode list of MDStrings for vector<string>
+//   - nested MDNode for TargetOptions (via encodeTargetOptionsAsNode)
+//
+// Omitted fields (process-local / non-data):
+//   LoadedPassPlugins, PreCodeGenPassesHook, DiagHandler, ResolutionFile,
+//   PreOptModuleHook, PostPromoteModuleHook, PostInternalizeModuleHook,
+//   PostImportModuleHook, PostOptModuleHook, PreCodeGenModuleHook,
+//   CombinedIndexHook, GetSummaryIndexOutputStream, GetImportsListOutputArray,
+//   GetCacheKeyOutputString
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/LTO/LTOConfigBitcode.h"
+
+#include "BitcodeMetadataUtils.h"
+
+#include "llvm/Bitcode/BitcodeReader.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/LTO/TargetOptionsBitcode.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace llvm::lto;
+using namespace llvm::lto::bitcodemeta;
+
+namespace {
+
+constexpr unsigned kVersion = 1;
+constexpr StringRef kEntryKind = "lto config entry";
+
+Error writeConfigBitcode(raw_ostream &Out, const Config &Config) {
+  LLVMContext Ctx;
+  Module M("llvm.lto.config", Ctx);
+  if (Error Err = encodeLTOConfigToModule(M, Config))
+    return Err;
+  WriteBitcodeToFile(M, Out);
+  return Error::success();
+}
+
+Expected<std::optional<Config>>
+readConfigBitcodeIfPresent(MemoryBufferRef Buffer) {
+  LLVMContext Ctx;
+  Expected<std::unique_ptr<Module>> M = parseBitcodeFile(Buffer, Ctx);
+  if (!M)
+    return M.takeError();
+  if (!hasEncodedLTOConfig(**M))
+    return std::nullopt;
+  return decodeLTOConfigFromModule(**M);
+}
+
+Expected<Config> readConfigBitcode(MemoryBufferRef Buffer) {
+  Expected<std::optional<Config>> Config = readConfigBitcodeIfPresent(Buffer);
+  if (!Config)
+    return Config.takeError();
+  if (!*Config)
+    return metadataError("missing lto config metadata");
+  return std::move(**Config);
+}
+
+void encodePipelineTuningOptions(MetadataWriter &Writer,
+                                 const PipelineTuningOptions &PTO) {
+#define PIPELINE_TUNING_OPTION_BOOL(Name)                                      \
+  Writer.putBool("pto." #Name, PTO.Name);
+#define PIPELINE_TUNING_OPTION_I32(Name) Writer.putI32("pto." #Name, PTO.Name);
+#define PIPELINE_TUNING_OPTION_U32(Name) Writer.putU32("pto." #Name, PTO.Name);
+#define PIPELINE_TUNING_OPTION(Type, Name, Default, Kind)                      \
+  PIPELINE_TUNING_OPTION_##Kind(Name)
+#include "llvm/Passes/PipelineTuningOptions.def"
+#undef PIPELINE_TUNING_OPTION_BOOL
+#undef PIPELINE_TUNING_OPTION_I32
+#undef PIPELINE_TUNING_OPTION_U32
+}
+
+void encodeRemarksHotnessThreshold(MetadataWriter &Writer,
+                                   const std::optional<uint64_t> &Threshold) {
+  int32_t Mode = 0;
+  uint64_t Value = 0;
+  if (!Threshold.has_value()) {
+    Mode = 2; // auto
+  } else if (*Threshold == 0) {
+    Mode = 0; // disabled
+  } else {
+    Mode = 1; // manual
+    Value = *Threshold;
+  }
+  LLVMContext &Ctx = Writer.getContext();
+  Metadata *Ops[] = {getI32Value(Ctx, Mode), getI64Value(Ctx, Value)};
+  Writer.putEntry("RemarksHotnessThreshold", MDNode::get(Ctx, Ops));
+}
+
+Error decodeRemarksHotnessThreshold(std::optional<uint64_t> &Threshold,
+                                    const MDNode &Entry) {
+  auto Node = getNodeField(Entry, kEntryKind);
+  if (!Node)
+    return Node.takeError();
+  if ((*Node)->getNumOperands() != 2)
+    return metadataError("RemarksHotnessThreshold must have mode and value");
+  auto *Mode = mdconst::dyn_extract<ConstantInt>((*Node)->getOperand(0));
+  auto *Value = mdconst::dyn_extract<ConstantInt>((*Node)->getOperand(1));
+  if (!Mode || !Mode->getType()->isIntegerTy(32) || !Value ||
+      !Value->getType()->isIntegerTy(64))
+    return metadataError("malformed RemarksHotnessThreshold metadata");
+  switch (Mode->getZExtValue()) {
+  case 0:
+    Threshold = 0;
+    break;
+  case 1:
+    Threshold = Value->getZExtValue();
+    break;
+  case 2:
+    Threshold = std::nullopt;
+    break;
+  default:
+    return metadataError("invalid RemarksHotnessThreshold mode");
+  }
+  return Error::success();
+}
+
+void encodeConfigFields(MetadataWriter &Writer, const Config &C) {
+#define LTO_CONFIG_ENCODE_STRING(Type, Name) Writer.putString(#Name, C.Name);
+#define LTO_CONFIG_ENCODE_TARGET_OPTIONS(Type, Name)                           \
+  Writer.putNode(#Name, encodeTargetOptionsAsNode(Writer.getContext(), C.Name));
+#define LTO_CONFIG_ENCODE_STRING_LIST(Type, Name)                              \
+  Writer.putStringList(#Name, C.Name);
+#define LTO_CONFIG_ENCODE_NONE(Type, Name)
+#define LTO_CONFIG_ENCODE_OPTIONAL_RELOC_MODEL(Type, Name)                     \
+  Writer.putBool(#Name ".HasValue", C.Name.has_value());                       \
+  if (C.Name)                                                                  \
+    Writer.putI32(#Name, static_cast<int32_t>(*C.Name));
+#define LTO_CONFIG_ENCODE_OPTIONAL_ENUM(Type, Name)                            \
+  if (C.Name)                                                                  \
+    Writer.putI32(#Name, static_cast<int32_t>(*C.Name));
+#define LTO_CONFIG_ENCODE_ENUM(Type, Name)                                     \
+  Writer.putI32(#Name, static_cast<int32_t>(C.Name));
+#define LTO_CONFIG_ENCODE_I32(Type, Name)                                      \
+  Writer.putI32(#Name, static_cast<int32_t>(C.Name));
+#define LTO_CONFIG_ENCODE_U32(Type, Name) Writer.putU32(#Name, C.Name);
+#define LTO_CONFIG_ENCODE_BOOL(Type, Name) Writer.putBool(#Name, C.Name);
+#define LTO_CONFIG_ENCODE_REMARKS_HOTNESS(Type, Name)                          \
+  encodeRemarksHotnessThreshold(Writer, C.Name);
+#define LTO_CONFIG_ENCODE_PIPELINE_TUNING_OPTIONS(Type, Name)                  \
+  encodePipelineTuningOptions(Writer, C.Name);
+#define LTO_CONFIG_OPTION(Type, Name, Default, Kind)                           \
+  LTO_CONFIG_ENCODE_##Kind(Type, Name)
+#define LTO_CONFIG_MUTABLE_OPTION(Type, Name, Default, Kind)                   \
+  LTO_CONFIG_ENCODE_##Kind(Type, Name)
+#include "llvm/LTO/Config.def"
+#undef LTO_CONFIG_ENCODE_STRING
+#undef LTO_CONFIG_ENCODE_TARGET_OPTIONS
+#undef LTO_CONFIG_ENCODE_STRING_LIST
+#undef LTO_CONFIG_ENCODE_NONE
+#undef LTO_CONFIG_ENCODE_OPTIONAL_RELOC_MODEL
+#undef LTO_CONFIG_ENCODE_OPTIONAL_ENUM
+#undef LTO_CONFIG_ENCODE_ENUM
+#undef LTO_CONFIG_ENCODE_I32
+#undef LTO_CONFIG_ENCODE_U32
+#undef LTO_CONFIG_ENCODE_BOOL
+#undef LTO_CONFIG_ENCODE_REMARKS_HOTNESS
+#undef LTO_CONFIG_ENCODE_PIPELINE_TUNING_OPTIONS
+}
+
+Error applyEntry(Config &C, StringRef Key, const MDNode &Entry) {
+  EntryApplier Applier{Entry, kEntryKind};
+
+#define LTO_CONFIG_DECODE_STRING(Type, Name)                                   \
+  if (Key == #Name)                                                            \
+    return Applier.applyString([&](StringRef V) { C.Name = V.str(); });
+#define LTO_CONFIG_DECODE_TARGET_OPTIONS(Type, Name)                           \
+  if (Key == #Name) {                                                          \
+    auto Node = getNodeField(Entry, kEntryKind);                               \
+    if (!Node)                                                                 \
+      return Node.takeError();                                                 \
+    auto Opt = decodeTargetOptionsFromNode(*Node);                             \
+    if (!Opt)                                                                  \
+      return Opt.takeError();                                                  \
+    C.Name = std::move(*Opt);                                                  \
+    return Error::success();                                                   \
+  }
+#define LTO_CONFIG_DECODE_STRING_LIST(Type, Name)                              \
+  if (Key == #Name)                                                            \
+    return Applier.applyStringList(                                            \
+        [&](std::vector<std::string> V) { C.Name = std::move(V); });
+#define LTO_CONFIG_DECODE_NONE(Type, Name)
+#define LTO_CONFIG_DECODE_OPTIONAL_RELOC_MODEL(Type, Name)                     \
+  if (Key == #Name)                                                            \
+    return Applier.applyI32(                                                   \
+        [&](int32_t V) { C.Name = static_cast<Reloc::Model>(V); });            \
+  if (Key == #Name ".HasValue")                                                \
+    return Applier.applyBool([&](bool V) {                                     \
+      if (!V)                                                                  \
+        C.Name = std::nullopt;                                                 \
+    });
+#define LTO_CONFIG_DECODE_OPTIONAL_ENUM(Type, Name)                            \
+  if (Key == #Name)                                                            \
+    return Applier.applyI32([&](int32_t V) {                                   \
+      C.Name = static_cast<typename Type::value_type>(V);                      \
+    });
+#define LTO_CONFIG_DECODE_ENUM(Type, Name)                                     \
+  if (Key == #Name)                                                            \
+    return Applier.applyI32([&](int32_t V) { C.Name = static_cast<Type>(V); });
+#define LTO_CONFIG_DECODE_I32(Type, Name)                                      \
+  if (Key == #Name)                                                            \
+    return Applier.applyI32([&](int32_t V) { C.Name = static_cast<Type>(V); });
+#define LTO_CONFIG_DECODE_U32(Type, Name)                                      \
+  if (Key == #Name)                                                            \
+    return Applier.applyU32([&](uint32_t V) { C.Name = V; });
+#define LTO_CONFIG_DECODE_BOOL(Type, Name)                                     \
+  if (Key == #Name)                                                            \
+    return Applier.applyBool([&](bool V) { C.Name = V; });
+#define LTO_CONFIG_DECODE_REMARKS_HOTNESS(Type, Name)                          \
+  if (Key == #Name)                                                            \
+    return decodeRemarksHotnessThreshold(C.Name, Entry);
+#define LTO_CONFIG_DECODE_PIPELINE_TUNING_OPTIONS(Type, Name)                  \
+  PipelineTuningOptions &PTO = C.Name;
+#define LTO_CONFIG_OPTION(Type, Name, Default, Kind)                           \
+  LTO_CONFIG_DECODE_##Kind(Type, Name)
+#define LTO_CONFIG_MUTABLE_OPTION(Type, Name, Default, Kind)                   \
+  LTO_CONFIG_DECODE_##Kind(Type, Name)
+#include "llvm/LTO/Config.def"
+#undef LTO_CONFIG_DECODE_STRING
+#undef LTO_CONFIG_DECODE_TARGET_OPTIONS
+#undef LTO_CONFIG_DECODE_STRING_LIST
+#undef LTO_CONFIG_DECODE_NONE
+#undef LTO_CONFIG_DECODE_OPTIONAL_RELOC_MODEL
+#undef LTO_CONFIG_DECODE_OPTIONAL_ENUM
+#undef LTO_CONFIG_DECODE_ENUM
+#undef LTO_CONFIG_DECODE_I32
+#undef LTO_CONFIG_DECODE_U32
+#undef LTO_CONFIG_DECODE_BOOL
+#undef LTO_CONFIG_DECODE_REMARKS_HOTNESS
+#undef LTO_CONFIG_DECODE_PIPELINE_TUNING_OPTIONS
+
+#define PIPELINE_TUNING_DECODE_BOOL(Type, Name)                                \
+  if (Key == "pto." #Name)                                                     \
+    return Applier.applyBool([&](bool V) { PTO.Name = V; });
+#define PIPELINE_TUNING_DECODE_I32(Type, Name)                                 \
+  if (Key == "pto." #Name)                                                     \
+    return Applier.applyI32(                                                   \
+        [&](int32_t V) { PTO.Name = static_cast<Type>(V); });
+#define PIPELINE_TUNING_DECODE_U32(Type, Name)                                 \
+  if (Key == "pto." #Name)                                                     \
+    return Applier.applyU32([&](uint32_t V) { PTO.Name = V; });
+#define PIPELINE_TUNING_OPTION(Type, Name, Default, Kind)                      \
+  PIPELINE_TUNING_DECODE_##Kind(Type, Name)
+#include "llvm/Passes/PipelineTuningOptions.def"
+#undef PIPELINE_TUNING_DECODE_BOOL
+#undef PIPELINE_TUNING_DECODE_I32
+#undef PIPELINE_TUNING_DECODE_U32
+
+  return metadataError("unknown lto config key: " + Key);
+}
+
+Expected<Config> decodeConfigFromRoot(const MDNode *Root) {
+  return decodeVersionedMetadata<Config>(
+      Root, kVersion, "lto config",
+      [](Config &C, StringRef Key, const MDNode &Entry) {
+        return applyEntry(C, Key, Entry);
+      });
+}
+
+} // namespace
+
+bool lto::hasEncodedLTOConfig(const Module &M) {
+  return M.getNamedMetadata(LTOConfigMetadataName) != nullptr;
+}
+
+Error lto::encodeLTOConfigToModule(Module &M, const Config &Config) {
+  LLVMContext &Ctx = M.getContext();
+  SmallVector<Metadata *, 64> Entries;
+  Entries.push_back(getI32Value(Ctx, kVersion));
+  MetadataWriter Writer(Entries, Ctx);
+  encodeConfigFields(Writer, Config);
+
+  MDNode *Root = MDNode::get(Ctx, Entries);
+  NamedMDNode *NMD = M.getOrInsertNamedMetadata(LTOConfigMetadataName);
+  NMD->clearOperands();
+  NMD->addOperand(Root);
+  return Error::success();
+}
+
+Expected<Config> lto::decodeLTOConfigFromModule(const Module &M) {
+  NamedMDNode *NMD = M.getNamedMetadata(LTOConfigMetadataName);
+  if (!NMD || NMD->getNumOperands() == 0)
+    return metadataError("missing lto config metadata");
+  return decodeConfigFromRoot(dyn_cast<MDNode>(NMD->getOperand(0)));
+}
+
+Error lto::writeLTOConfigToFile(StringRef Path, const Config &Config) {
+  std::error_code EC;
+  raw_fd_ostream OS(Path, EC, sys::fs::OF_None);
+  if (EC)
+    return createStringError(EC, "cannot open LTO config file '%s'",
+                             Path.str().c_str());
+  if (Error Err = writeConfigBitcode(OS, Config))
+    return Err;
+  OS.close();
+  if (OS.has_error())
+    return createStringError(OS.error(), "cannot write LTO config file '%s'",
+                             Path.str().c_str());
+  return Error::success();
+}
+
+Expected<Config> lto::readLTOConfigFromFile(StringRef Path) {
+  ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = MemoryBuffer::getFile(Path);
+  if (!Buffer)
+    return createStringError(Buffer.getError(),
+                             "cannot read LTO config file '%s'",
+                             Path.str().c_str());
+
+  return readConfigBitcode((*Buffer)->getMemBufferRef());
+}
+
+Error lto::writeIndexWithLTOConfigToFile(
+    const ModuleSummaryIndex &Index, const Config &Config, raw_ostream &Out,
+    const ModuleToSummariesForIndexTy *ModuleToSummariesForIndex,
+    const GVSummaryPtrSet *DecSummaries) {
+  LLVMContext Ctx;
+  Module MetadataModule("llvm.lto.config", Ctx);
+  if (Error Err = encodeLTOConfigToModule(MetadataModule, Config))
+    return Err;
+  writeIndexToFile(Index, Out, ModuleToSummariesForIndex, DecSummaries,
+                   &MetadataModule);
+  return Error::success();
+}
+
+Expected<Config> lto::readLTOConfigFromSummaryIndex(MemoryBufferRef Buffer) {
+  return readConfigBitcode(Buffer);
+}
+
+Expected<std::optional<Config>>
+lto::readLTOConfigFromSummaryIndexIfPresent(MemoryBufferRef Buffer) {
+  return readConfigBitcodeIfPresent(Buffer);
+}
diff --git a/llvm/lib/LTO/TargetOptionsBitcode.cpp b/llvm/lib/LTO/TargetOptionsBitcode.cpp
new file mode 100644
index 00000000000000..d3eede5bd4065d
--- /dev/null
+++ b/llvm/lib/LTO/TargetOptionsBitcode.cpp
@@ -0,0 +1,265 @@
+//===- TargetOptionsBitcode.cpp - TargetOptions in bitcode ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// Encodes llvm::TargetOptions as module metadata that is stored in bitcode.
+//
+// Layout:
+//   !llvm.lto.target_options = !{ !0 }
+//   !0 = !{ i32 <version>, !1, !2, ... }
+//   !1 = !{ !"<key>", <value> }
+//
+// Value kinds:
+//   - i32 ConstantInt for bools, enums, and small integers
+//   - MDString for std::string fields
+//   - nested MDNode for structured fields such as MemoryBuffer
+//
+// Fields that cannot be represented in IR (such as callbacks) are
+// intentionally omitted.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/LTO/TargetOptionsBitcode.h"
+
+#include "BitcodeMetadataUtils.h"
+
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+using namespace llvm;
+using namespace llvm::lto;
+using namespace llvm::lto::bitcodemeta;
+
+namespace {
+
+constexpr unsigned kVersion = 1;
+constexpr StringRef kEntryKind = "target options entry";
+
+void encodeMemoryBuffer(MetadataWriter &Writer, StringRef Key,
+                        const std::shared_ptr<MemoryBuffer> &Buffer) {
+  if (!Buffer)
+    return;
+
+  Metadata *Fields[] = {
+      getStringValue(Writer.getContext(), Buffer->getBufferIdentifier()),
+      getStringValue(Writer.getContext(), Buffer->getBuffer())};
+  Writer.putNode(Key, MDNode::get(Writer.getContext(), Fields));
+}
+
+Error decodeMemoryBuffer(std::shared_ptr<MemoryBuffer> &Buffer,
+                         const MDNode &Entry) {
+  Expected<MDNode *> Fields = getNodeField(Entry, kEntryKind);
+  if (!Fields)
+    return Fields.takeError();
+  if ((*Fields)->getNumOperands() != 2)
+    return metadataError(kEntryKind +
+                         " memory buffer must contain an identifier and data");
+
+  auto *Identifier = dyn_cast<MDString>((*Fields)->getOperand(0));
+  auto *Data = dyn_cast<MDString>((*Fields)->getOperand(1));
+  if (!Identifier || !Data)
+    return metadataError(kEntryKind + " memory buffer fields must be strings");
+
+  Buffer = MemoryBuffer::getMemBufferCopy(Data->getString(),
+                                          Identifier->getString());
+  return Error::success();
+}
+
+void encodeMCTargetOptions(MetadataWriter &Writer, const MCTargetOptions &MC) {
+#define MC_TARGET_OPTION_ENCODE_BITFIELD(Type, Name, Bits, Default)            \
+  Writer.putBool("mc." #Name, MC.Name);
+#define MC_TARGET_OPTION_ENCODE_BOOL(Type, Name, Bits, Default)                \
+  Writer.putBool("mc." #Name, MC.Name);
+#define MC_TARGET_OPTION_ENCODE_ENUM(Type, Name, Bits, Default)                \
+  Writer.putI32("mc." #Name, static_cast<int32_t>(MC.Name));
+#define MC_TARGET_OPTION_ENCODE_OPTIONAL_UINT(Type, Name, Bits, Default)       \
+  if (MC.Name)                                                                 \
+    Writer.putU32("mc." #Name, *MC.Name);
+#define MC_TARGET_OPTION_ENCODE_INT(Type, Name, Bits, Default)                 \
+  Writer.putI32("mc." #Name, MC.Name);
+#define MC_TARGET_OPTION_ENCODE_PAIR(Type, Name, Bits, Default)                \
+  Writer.putI32("mc." #Name "Major", MC.Name.first);                           \
+  Writer.putI32("mc." #Name "Minor", MC.Name.second);
+#define MC_TARGET_OPTION_ENCODE_STRING(Type, Name, Bits, Default)              \
+  Writer.putString("mc." #Name, MC.Name);
+#define MC_TARGET_OPTION_ENCODE_STRING_LIST(Type, Name, Bits, Default)         \
+  Writer.putStringList("mc." #Name, MC.Name);
+#define MC_TARGET_OPTION(Type, Name, Bits, Default, Kind)                      \
+  MC_TARGET_OPTION_ENCODE_##Kind(Type, Name, Bits, Default)
+#include "llvm/MC/MCTargetOptions.def"
+#undef MC_TARGET_OPTION_ENCODE_BITFIELD
+#undef MC_TARGET_OPTION_ENCODE_BOOL
+#undef MC_TARGET_OPTION_ENCODE_ENUM
+#undef MC_TARGET_OPTION_ENCODE_OPTIONAL_UINT
+#undef MC_TARGET_OPTION_ENCODE_INT
+#undef MC_TARGET_OPTION_ENCODE_PAIR
+#undef MC_TARGET_OPTION_ENCODE_STRING
+#undef MC_TARGET_OPTION_ENCODE_STRING_LIST
+}
+
+void encodeTargetOptionsFields(MetadataWriter &Writer,
+                               const TargetOptions &Opt) {
+#define TARGET_OPTION_ENCODE_BOOL(Type, Name, Bits, Default)                   \
+  Writer.putBool(#Name, Opt.Name);
+#define TARGET_OPTION_ENCODE_U32_BITFIELD(Type, Name, Bits, Default)           \
+  Writer.putU32(#Name, Opt.Name);
+#define TARGET_OPTION_ENCODE_U32(Type, Name, Bits, Default)                    \
+  Writer.putU32(#Name, Opt.Name);
+#define TARGET_OPTION_ENCODE_ENUM(Type, Name, Bits, Default)                   \
+  Writer.putI32(#Name, static_cast<int32_t>(Opt.Name));
+#define TARGET_OPTION_ENCODE_STRING(Type, Name, Bits, Default)                 \
+  Writer.putString(#Name, Opt.Name);
+#define TARGET_OPTION_ENCODE_PAIR(Type, Name, Bits, Default)                   \
+  Writer.putI32(#Name "Major", Opt.Name.first);                                \
+  Writer.putI32(#Name "Minor", Opt.Name.second);
+#define TARGET_OPTION_ENCODE_BUFFER(Type, Name, Bits, Default)                 \
+  encodeMemoryBuffer(Writer, #Name, Opt.Name);
+#define TARGET_OPTION_ENCODE_MC(Type, Name, Bits, Default)                     \
+  encodeMCTargetOptions(Writer, Opt.Name);
+#define TARGET_OPTION(Type, Name, Bits, Default, Kind)                         \
+  TARGET_OPTION_ENCODE_##Kind(Type, Name, Bits, Default)
+#include "llvm/Target/TargetOptions.def"
+#undef TARGET_OPTION_ENCODE_BOOL
+#undef TARGET_OPTION_ENCODE_U32_BITFIELD
+#undef TARGET_OPTION_ENCODE_U32
+#undef TARGET_OPTION_ENCODE_ENUM
+#undef TARGET_OPTION_ENCODE_STRING
+#undef TARGET_OPTION_ENCODE_PAIR
+#undef TARGET_OPTION_ENCODE_BUFFER
+#undef TARGET_OPTION_ENCODE_MC
+}
+
+Error applyEntry(TargetOptions &Opt, StringRef Key, const MDNode &Entry) {
+  EntryApplier Applier{Entry, kEntryKind};
+
+#define TARGET_OPTION_TYPE(...) __VA_ARGS__
+#define TARGET_OPTION_DECODE_BOOL(Type, Name, Bits, Default)                   \
+  if (Key == #Name)                                                            \
+    return Applier.applyBool([&](bool V) { Opt.Name = V; });
+#define TARGET_OPTION_DECODE_U32_BITFIELD(Type, Name, Bits, Default)           \
+  if (Key == #Name)                                                            \
+    return Applier.applyU32([&](uint32_t V) { Opt.Name = V; });
+#define TARGET_OPTION_DECODE_U32(Type, Name, Bits, Default)                    \
+  if (Key == #Name)                                                            \
+    return Applier.applyU32([&](uint32_t V) { Opt.Name = V; });
+#define TARGET_OPTION_DECODE_ENUM(Type, Name, Bits, Default)                   \
+  if (Key == #Name)                                                            \
+    return Applier.applyI32([&](int32_t V) {                                   \
+      Opt.Name = static_cast<TARGET_OPTION_TYPE Type>(V);                      \
+    });
+#define TARGET_OPTION_DECODE_STRING(Type, Name, Bits, Default)                 \
+  if (Key == #Name)                                                            \
+    return Applier.applyString([&](StringRef V) { Opt.Name = V.str(); });
+#define TARGET_OPTION_DECODE_PAIR(Type, Name, Bits, Default)                   \
+  if (Key == #Name "Major")                                                    \
+    return Applier.applyI32([&](int32_t V) { Opt.Name.first = V; });           \
+  if (Key == #Name "Minor")                                                    \
+    return Applier.applyI32([&](int32_t V) { Opt.Name.second = V; });
+#define TARGET_OPTION_DECODE_BUFFER(Type, Name, Bits, Default)                 \
+  if (Key == #Name)                                                            \
+    return decodeMemoryBuffer(Opt.Name, Entry);
+#define TARGET_OPTION_DECODE_MC(Type, Name, Bits, Default)                     \
+  MCTargetOptions &MC = Opt.Name;
+#define TARGET_OPTION(Type, Name, Bits, Default, Kind)                         \
+  TARGET_OPTION_DECODE_##Kind(Type, Name, Bits, Default)
+#include "llvm/Target/TargetOptions.def"
+#undef TARGET_OPTION_TYPE
+#undef TARGET_OPTION_DECODE_BOOL
+#undef TARGET_OPTION_DECODE_U32_BITFIELD
+#undef TARGET_OPTION_DECODE_U32
+#undef TARGET_OPTION_DECODE_ENUM
+#undef TARGET_OPTION_DECODE_STRING
+#undef TARGET_OPTION_DECODE_PAIR
+#undef TARGET_OPTION_DECODE_BUFFER
+#undef TARGET_OPTION_DECODE_MC
+
+#define MC_TARGET_OPTION_TYPE(...) __VA_ARGS__
+#define MC_TARGET_OPTION_DECODE_BITFIELD(Type, Name, Bits, Default)            \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyBool([&](bool V) { MC.Name = V; });
+#define MC_TARGET_OPTION_DECODE_BOOL(Type, Name, Bits, Default)                \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyBool([&](bool V) { MC.Name = V; });
+#define MC_TARGET_OPTION_DECODE_ENUM(Type, Name, Bits, Default)                \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyI32([&](int32_t V) {                                   \
+      MC.Name = static_cast<MC_TARGET_OPTION_TYPE Type>(V);                    \
+    });
+#define MC_TARGET_OPTION_DECODE_OPTIONAL_UINT(Type, Name, Bits, Default)       \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyU32([&](uint32_t V) { MC.Name = V; });
+#define MC_TARGET_OPTION_DECODE_INT(Type, Name, Bits, Default)                 \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyI32([&](int32_t V) { MC.Name = V; });
+#define MC_TARGET_OPTION_DECODE_PAIR(Type, Name, Bits, Default)                \
+  if (Key == "mc." #Name "Major")                                              \
+    return Applier.applyI32([&](int32_t V) { MC.Name.first = V; });            \
+  if (Key == "mc." #Name "Minor")                                              \
+    return Applier.applyI32([&](int32_t V) { MC.Name.second = V; });
+#define MC_TARGET_OPTION_DECODE_STRING(Type, Name, Bits, Default)              \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyString([&](StringRef V) { MC.Name = V.str(); });
+#define MC_TARGET_OPTION_DECODE_STRING_LIST(Type, Name, Bits, Default)         \
+  if (Key == "mc." #Name)                                                      \
+    return Applier.applyStringList(                                            \
+        [&](std::vector<std::string> V) { MC.Name = std::move(V); });
+#define MC_TARGET_OPTION(Type, Name, Bits, Default, Kind)                      \
+  MC_TARGET_OPTION_DECODE_##Kind(Type, Name, Bits, Default)
+#include "llvm/MC/MCTargetOptions.def"
+#undef MC_TARGET_OPTION_TYPE
+#undef MC_TARGET_OPTION_DECODE_BITFIELD
+#undef MC_TARGET_OPTION_DECODE_BOOL
+#undef MC_TARGET_OPTION_DECODE_ENUM
+#undef MC_TARGET_OPTION_DECODE_OPTIONAL_UINT
+#undef MC_TARGET_OPTION_DECODE_INT
+#undef MC_TARGET_OPTION_DECODE_PAIR
+#undef MC_TARGET_OPTION_DECODE_STRING
+#undef MC_TARGET_OPTION_DECODE_STRING_LIST
+
+  return metadataError("unknown target options key: " + Key);
+}
+
+} // namespace
+
+bool lto::hasEncodedTargetOptions(const Module &M) {
+  return M.getNamedMetadata(TargetOptionsMetadataName) != nullptr;
+}
+
+Error lto::encodeTargetOptionsToModule(Module &M,
+                                       const TargetOptions &Options) {
+  MDNode *Root = encodeTargetOptionsAsNode(M.getContext(), Options);
+  NamedMDNode *NMD = M.getOrInsertNamedMetadata(TargetOptionsMetadataName);
+  NMD->clearOperands();
+  NMD->addOperand(Root);
+  return Error::success();
+}
+
+MDNode *lto::encodeTargetOptionsAsNode(LLVMContext &Ctx,
+                                       const TargetOptions &Options) {
+  SmallVector<Metadata *, 32> Entries;
+  Entries.push_back(getI32Value(Ctx, kVersion));
+  MetadataWriter Writer(Entries, Ctx);
+  encodeTargetOptionsFields(Writer, Options);
+  return MDNode::get(Ctx, Entries);
+}
+
+Expected<TargetOptions> lto::decodeTargetOptionsFromNode(const MDNode *Root) {
+  return decodeVersionedMetadata<TargetOptions>(
+      Root, kVersion, "target options",
+      [](TargetOptions &Opt, StringRef Key, const MDNode &Entry) {
+        return applyEntry(Opt, Key, Entry);
+      });
+}
+
+Expected<TargetOptions> lto::decodeTargetOptionsFromModule(const Module &M) {
+  NamedMDNode *NMD = M.getNamedMetadata(TargetOptionsMetadataName);
+  if (!NMD || NMD->getNumOperands() == 0)
+    return metadataError("missing target options metadata");
+
+  return decodeTargetOptionsFromNode(dyn_cast<MDNode>(NMD->getOperand(0)));
+}
diff --git a/llvm/lib/MC/MCTargetOptions.cpp b/llvm/lib/MC/MCTargetOptions.cpp
index dc20019ccdf6d7..c3a0115f1a2eea 100644
--- a/llvm/lib/MC/MCTargetOptions.cpp
+++ b/llvm/lib/MC/MCTargetOptions.cpp
@@ -12,16 +12,28 @@
 
 using namespace llvm;
 
-MCTargetOptions::MCTargetOptions()
-    : MCRelaxAll(false), MCNoExecStack(false), MCFatalWarnings(false),
-      MCNoWarn(false), MCNoDeprecatedWarn(false), MCNoTypeCheck(false),
-      MCSaveTempLabels(false), MCIncrementalLinkerCompatible(false),
-      FDPIC(false), ShowMCEncoding(false), ShowMCInst(false), AsmVerbose(false),
-      PreserveAsmComments(true), Dwarf64(false),
-      EmitDwarfUnwind(EmitDwarfUnwindType::Default),
-      MCUseDwarfDirectory(DefaultDwarfDirectory),
-      EmitCompactUnwindNonCanonical(false), EmitSFrameUnwind(false),
-      PPCUseFullRegisterNames(false), LargeEHEncoding(false) {}
+MCTargetOptions::MCTargetOptions() {
+#define MC_TARGET_OPTION_INIT_BITFIELD(Type, Name, Bits, Default)              \
+  Name = Default;
+#define MC_TARGET_OPTION_INIT_BOOL(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_ENUM(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_OPTIONAL_UINT(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_INT(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_PAIR(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_STRING(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION_INIT_STRING_LIST(Type, Name, Bits, Default)
+#define MC_TARGET_OPTION(Type, Name, Bits, Default, Kind)                      \
+  MC_TARGET_OPTION_INIT_##Kind(Type, Name, Bits, Default)
+#include "llvm/MC/MCTargetOptions.def"
+#undef MC_TARGET_OPTION_INIT_BITFIELD
+#undef MC_TARGET_OPTION_INIT_BOOL
+#undef MC_TARGET_OPTION_INIT_ENUM
+#undef MC_TARGET_OPTION_INIT_OPTIONAL_UINT
+#undef MC_TARGET_OPTION_INIT_INT
+#undef MC_TARGET_OPTION_INIT_PAIR
+#undef MC_TARGET_OPTION_INIT_STRING
+#undef MC_TARGET_OPTION_INIT_STRING_LIST
+}
 
 std::pair<int, int> MCTargetOptions::parseBinutilsVersion(StringRef Version) {
   if (Version == "none")
@@ -32,9 +44,7 @@ std::pair<int, int> MCTargetOptions::parseBinutilsVersion(StringRef Version) {
   return Ret;
 }
 
-StringRef MCTargetOptions::getABIName() const {
-  return ABIName;
-}
+StringRef MCTargetOptions::getABIName() const { return ABIName; }
 
 StringRef MCTargetOptions::getAssemblyLanguage() const {
   return AssemblyLanguage;
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 901250fdd787e1..cceb0c9e0ff8f9 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -200,9 +200,9 @@ static cl::opt<bool>
     TriggerCrash("opt-pipeline-trigger-crash", cl::init(false), cl::Hidden,
                  cl::desc("Trigger crash in optimization pipeline"));
 
-static cl::opt<bool> EnableGlobalAnalyses(
-    "enable-global-analyses", cl::init(true), cl::Hidden,
-    cl::desc("Enable inter-procedural analyses"));
+static cl::opt<bool>
+    EnableGlobalAnalyses("enable-global-analyses", cl::init(true), cl::Hidden,
+                         cl::desc("Enable inter-procedural analyses"));
 
 static cl::opt<bool> RunPartialInlining("enable-partial-inlining",
                                         cl::init(false), cl::Hidden,
@@ -330,21 +330,8 @@ extern cl::opt<bool> EnableMemProfContextDisambiguation;
 } // namespace llvm
 
 PipelineTuningOptions::PipelineTuningOptions() {
-  LoopInterleaving = true;
-  LoopVectorization = true;
-  SLPVectorization = false;
-  LoopUnrolling = true;
-  LoopInterchange = EnableLoopInterchange;
-  LoopFusion = false;
-  ForgetAllSCEVInLoopUnroll = ForgetSCEVInLoopUnroll;
-  LicmMssaOptCap = SetLicmMssaOptCap;
-  LicmMssaNoAccForPromotionCap = SetLicmMssaNoAccForPromotionCap;
-  CallGraphProfile = true;
-  UnifiedLTO = false;
-  MergeFunctions = EnableMergeFunctions;
-  InlinerThreshold = -1;
-  EagerlyInvalidateAnalyses = EnableEagerlyInvalidateAnalyses;
-  DevirtualizeSpeculatively = EnableDevirtualizeSpeculatively;
+#define PIPELINE_TUNING_OPTION(Type, Name, Default, Kind) Name = Default;
+#include "llvm/Passes/PipelineTuningOptions.def"
 }
 
 namespace llvm {
diff --git a/llvm/test/ThinLTO/X86/dtlto/summary.ll b/llvm/test/ThinLTO/X86/dtlto/summary.ll
index 2365fa4f4ea42e..7c1738fbf2731d 100644
--- a/llvm/test/ThinLTO/X86/dtlto/summary.ll
+++ b/llvm/test/ThinLTO/X86/dtlto/summary.ll
@@ -1,5 +1,5 @@
-; Check that DTLTO creates identical summary index shard files as are created
-; for an equivalent ThinLTO link.
+; Check that DTLTO creates equivalent summary index shards to an ordinary
+; ThinLTO link and embeds the serialized LTO configuration as metadata.
 
 RUN: rm -rf %t && split-file %s %t && cd %t
 
@@ -25,9 +25,22 @@ RUN:     -dtlto-distributor-arg=%llvm_src_root/utils/dtlto/mock.py,t1.o,t2.o
 ; Perform ThinLTO.
 RUN: %{command}
 
-; Check for equivalence. We use a wildcard to account for the PID.
-RUN: cmp t1.1.*.native.o.thinlto.bc t1.bc.thinlto.bc
-RUN: cmp t2.2.*.native.o.thinlto.bc t2.bc.thinlto.bc
+; Check the underlying indexes for equivalence. We use a wildcard to account
+; for the PID in the DTLTO filenames.
+RUN: llvm-dis t1.1.*.native.o.thinlto.bc -o - | grep '^\^' > t1.dtlto.ll
+RUN: llvm-dis t1.bc.thinlto.bc -o - | grep '^\^' > t1.thinlto.ll
+RUN: cmp t1.dtlto.ll t1.thinlto.ll
+RUN: llvm-dis t2.2.*.native.o.thinlto.bc -o - | grep '^\^' > t2.dtlto.ll
+RUN: llvm-dis t2.bc.thinlto.bc -o - | grep '^\^' > t2.thinlto.ll
+RUN: cmp t2.dtlto.ll t2.thinlto.ll
+
+; Check that each DTLTO index contains the configuration metadata.
+RUN: llvm-bcanalyzer -dump t1.1.*.native.o.thinlto.bc | FileCheck %s --check-prefix=CONFIG
+RUN: llvm-bcanalyzer -dump t2.2.*.native.o.thinlto.bc | FileCheck %s --check-prefix=CONFIG
+
+; CONFIG: <METADATA_BLOCK
+; CONFIG: record string = 'llvm.lto.config'
+; CONFIG: </METADATA_BLOCK>
 
 ;--- t1.ll
 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt
index c6ce09b3d67f20..c18243e224d88b 100644
--- a/llvm/unittests/CMakeLists.txt
+++ b/llvm/unittests/CMakeLists.txt
@@ -53,6 +53,7 @@ add_subdirectory(InterfaceStub)
 add_subdirectory(IR)
 add_subdirectory(LineEditor)
 add_subdirectory(Linker)
+add_subdirectory(LTO)
 add_subdirectory(MC)
 add_subdirectory(MI)
 add_subdirectory(MIR)
diff --git a/llvm/unittests/LTO/CMakeLists.txt b/llvm/unittests/LTO/CMakeLists.txt
new file mode 100644
index 00000000000000..0253d8d9c0ba41
--- /dev/null
+++ b/llvm/unittests/LTO/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(LLVM_LINK_COMPONENTS
+  BitReader
+  BitWriter
+  Core
+  LTO
+  Support
+  )
+
+add_llvm_unittest(LTOTests
+  LTOConfigBitcodeTest.cpp
+  )
+
+target_link_libraries(LTOTests PRIVATE LLVMTestingSupport)
diff --git a/llvm/unittests/LTO/LTOConfigBitcodeTest.cpp b/llvm/unittests/LTO/LTOConfigBitcodeTest.cpp
new file mode 100644
index 00000000000000..6b6cf4dab36f07
--- /dev/null
+++ b/llvm/unittests/LTO/LTOConfigBitcodeTest.cpp
@@ -0,0 +1,181 @@
+//===- LTOConfigBitcodeTest.cpp - LTO config bitcode tests --------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/LTO/LTOConfigBitcode.h"
+#include "llvm/Bitcode/BitcodeReader.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/LTO/TargetOptionsBitcode.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FileUtilities.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/MemoryBufferRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+#include <limits>
+
+using namespace llvm;
+using namespace llvm::lto;
+
+TEST(TargetOptionsBitcodeTest, RoundTripThroughBitcode) {
+  TargetOptions Input;
+  Input.MCOptions.BinutilsVersion = {2, 41};
+  Input.FunctionSections = true;
+  Input.DataSections = true;
+  Input.GlobalISelAbort = GlobalISelAbortMode::DisableWithDiag;
+  Input.TLSSize = 64;
+  Input.BBSections = BasicBlockSection::List;
+  Input.BBSectionsFuncListBuf = MemoryBuffer::getMemBufferCopy(
+      "v1\nf foo\nc 0 1\n", "basic-block-sections.profile");
+  Input.EnableDefaultMachineVerifier = false;
+  Input.StackUsageFile = "output.su";
+  Input.LoopAlignment = std::numeric_limits<unsigned>::max();
+  Input.ExceptionModel = ExceptionHandling::Wasm;
+  Input.MCOptions.MCRelaxAll = true;
+  Input.MCOptions.RelocSectionSym = RelocSectionSymType::Internal;
+  Input.MCOptions.DwarfVersion = 5;
+  Input.MCOptions.ABIName = "test-abi";
+  Input.MCOptions.OutputAsmVariant = std::numeric_limits<unsigned>::max();
+  Input.MCOptions.IASSearchPaths = {"include/one", "include/two"};
+  Input.MCOptions.InstPrinterOptions = {"no-aliases"};
+  Input.MCOptions.LargeEHEncoding = true;
+
+  LLVMContext WriteCtx;
+  Module M("target-options", WriteCtx);
+  ASSERT_THAT_ERROR(encodeTargetOptionsToModule(M, Input), Succeeded());
+  EXPECT_TRUE(hasEncodedTargetOptions(M));
+
+  SmallString<0> Storage;
+  raw_svector_ostream OS(Storage);
+  WriteBitcodeToFile(M, OS);
+
+  LLVMContext ReadCtx;
+  Expected<std::unique_ptr<Module>> Parsed = parseBitcodeFile(
+      MemoryBufferRef(StringRef(Storage.data(), Storage.size()), "options.bc"),
+      ReadCtx);
+  ASSERT_THAT_EXPECTED(Parsed, Succeeded());
+  Expected<TargetOptions> Output = decodeTargetOptionsFromModule(**Parsed);
+  ASSERT_THAT_EXPECTED(Output, Succeeded());
+
+  EXPECT_EQ(Output->MCOptions.BinutilsVersion, Input.MCOptions.BinutilsVersion);
+  EXPECT_TRUE(Output->FunctionSections);
+  EXPECT_TRUE(Output->DataSections);
+  EXPECT_EQ(Output->GlobalISelAbort, GlobalISelAbortMode::DisableWithDiag);
+  EXPECT_EQ(Output->TLSSize, 64u);
+  EXPECT_EQ(Output->BBSections, BasicBlockSection::List);
+  ASSERT_TRUE(Output->BBSectionsFuncListBuf);
+  EXPECT_EQ(Output->BBSectionsFuncListBuf->getBufferIdentifier(),
+            "basic-block-sections.profile");
+  EXPECT_EQ(Output->BBSectionsFuncListBuf->getBuffer(), "v1\nf foo\nc 0 1\n");
+  EXPECT_FALSE(Output->EnableDefaultMachineVerifier);
+  EXPECT_EQ(Output->StackUsageFile, "output.su");
+  EXPECT_EQ(Output->LoopAlignment, std::numeric_limits<unsigned>::max());
+  EXPECT_EQ(Output->ExceptionModel, ExceptionHandling::Wasm);
+  EXPECT_TRUE(Output->MCOptions.MCRelaxAll);
+  EXPECT_EQ(Output->MCOptions.RelocSectionSym, RelocSectionSymType::Internal);
+  EXPECT_EQ(Output->MCOptions.DwarfVersion, 5);
+  EXPECT_EQ(Output->MCOptions.ABIName, "test-abi");
+  EXPECT_EQ(Output->MCOptions.OutputAsmVariant,
+            std::numeric_limits<unsigned>::max());
+  EXPECT_EQ(Output->MCOptions.IASSearchPaths, Input.MCOptions.IASSearchPaths);
+  EXPECT_EQ(Output->MCOptions.InstPrinterOptions,
+            Input.MCOptions.InstPrinterOptions);
+  EXPECT_TRUE(Output->MCOptions.LargeEHEncoding);
+}
+
+TEST(LTOConfigBitcodeTest, RoundTripThroughFile) {
+  Config Input;
+  Input.CPU = "generic";
+  Input.MAttrs = {"+crc", "+simd"};
+  Input.MllvmArgs = {"-inline-threshold=42"};
+  Input.PassPluginFilenames = {"plugin.so"};
+  Input.RelocModel = std::nullopt;
+  Input.CodeModel = CodeModel::Large;
+  Input.CGOptLevel = CodeGenOptLevel::Aggressive;
+  Input.OptLevel = 3;
+  Input.Dtlto = true;
+  Input.RemarksHotnessThreshold = std::numeric_limits<uint64_t>::max();
+  Input.TimeTraceGranularity = std::numeric_limits<unsigned>::max();
+  Input.ThinLTOModulesToCompile = {"one.bc", "two.bc"};
+  Input.PTO.LoopInterchange = true;
+  Input.PTO.LicmMssaOptCap = std::numeric_limits<unsigned>::max();
+  Input.PTO.InlinerThreshold = 42;
+  Input.Options.FunctionSections = true;
+  Input.Options.MCOptions.IASSearchPaths = {"sdk/include"};
+
+  SmallString<128> Path;
+  ASSERT_FALSE(sys::fs::createTemporaryFile("lto-config", "bc", Path));
+  FileRemover Cleanup(Path);
+
+  ASSERT_THAT_ERROR(writeLTOConfigToFile(Path, Input), Succeeded());
+  Expected<Config> Output = readLTOConfigFromFile(Path);
+  ASSERT_THAT_EXPECTED(Output, Succeeded());
+
+  EXPECT_EQ(Output->CPU, "generic");
+  EXPECT_EQ(Output->MAttrs, Input.MAttrs);
+  EXPECT_EQ(Output->MllvmArgs, Input.MllvmArgs);
+  EXPECT_EQ(Output->PassPluginFilenames, Input.PassPluginFilenames);
+  EXPECT_EQ(Output->RelocModel, std::nullopt);
+  EXPECT_EQ(Output->CodeModel, CodeModel::Large);
+  EXPECT_EQ(Output->CGOptLevel, CodeGenOptLevel::Aggressive);
+  EXPECT_EQ(Output->OptLevel, 3u);
+  EXPECT_TRUE(Output->Dtlto);
+  EXPECT_EQ(Output->RemarksHotnessThreshold,
+            std::numeric_limits<uint64_t>::max());
+  EXPECT_EQ(Output->TimeTraceGranularity, std::numeric_limits<unsigned>::max());
+  EXPECT_EQ(Output->ThinLTOModulesToCompile, Input.ThinLTOModulesToCompile);
+  EXPECT_TRUE(Output->PTO.LoopInterchange);
+  EXPECT_EQ(Output->PTO.LicmMssaOptCap, std::numeric_limits<unsigned>::max());
+  EXPECT_EQ(Output->PTO.InlinerThreshold, 42);
+  EXPECT_TRUE(Output->Options.FunctionSections);
+  EXPECT_EQ(Output->Options.MCOptions.IASSearchPaths,
+            Input.Options.MCOptions.IASSearchPaths);
+}
+
+TEST(LTOConfigBitcodeTest, RoundTripThroughThinLTOSummaryIndex) {
+  ModuleSummaryIndex InputIndex(/*HaveGVs=*/false);
+  Config InputConfig;
+  InputConfig.CPU = "summary-cpu";
+  InputConfig.OptLevel = 3;
+  InputConfig.Options.DataSections = true;
+
+  SmallString<0> Storage;
+  raw_svector_ostream OS(Storage);
+  ASSERT_THAT_ERROR(writeIndexWithLTOConfigToFile(InputIndex, InputConfig, OS),
+                    Succeeded());
+
+  MemoryBufferRef Buffer(StringRef(Storage.data(), Storage.size()),
+                         "summary.thinlto.bc");
+
+  // Existing summary-index readers must accept the embedded metadata.
+  ModuleSummaryIndex OutputIndex(/*HaveGVs=*/false);
+  ASSERT_THAT_ERROR(readModuleSummaryIndex(Buffer, OutputIndex), Succeeded());
+
+  Expected<Config> OutputConfig = readLTOConfigFromSummaryIndex(Buffer);
+  ASSERT_THAT_EXPECTED(OutputConfig, Succeeded());
+  EXPECT_EQ(OutputConfig->CPU, "summary-cpu");
+  EXPECT_EQ(OutputConfig->OptLevel, 3u);
+  EXPECT_TRUE(OutputConfig->Options.DataSections);
+}
+
+TEST(LTOConfigBitcodeTest, ThinLTOSummaryIndexWithoutConfig) {
+  ModuleSummaryIndex Index(/*HaveGVs=*/false);
+  SmallString<0> Storage;
+  raw_svector_ostream OS(Storage);
+  writeIndexToFile(Index, OS);
+
+  MemoryBufferRef Buffer(StringRef(Storage.data(), Storage.size()),
+                         "summary.thinlto.bc");
+  Expected<std::optional<Config>> OutputConfig =
+      readLTOConfigFromSummaryIndexIfPresent(Buffer);
+  ASSERT_THAT_EXPECTED(OutputConfig, Succeeded());
+  EXPECT_FALSE(OutputConfig->has_value());
+}



More information about the cfe-commits mailing list