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

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.

>From d32004c9420606d24ffadf4a987d09a1d8e00c81 Mon Sep 17 00:00:00 2001
From: Konstantin Belochapka <[email protected]>
Date: Mon, 17 Aug 2026 03:10:47 -0700
Subject: [PATCH] [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.h                |   4 +-
 llvm/include/llvm/LTO/LTOConfigBitcode.h      |  63 +++
 llvm/include/llvm/LTO/TargetOptionsBitcode.h  |  49 ++
 llvm/include/llvm/MC/MCTargetOptions.h        |   2 +
 llvm/include/llvm/Target/TargetOptions.h      |   2 +
 llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp   |   1 -
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     |  38 +-
 llvm/lib/LTO/BitcodeMetadataUtils.h           | 222 ++++++++
 llvm/lib/LTO/CMakeLists.txt                   |   2 +
 llvm/lib/LTO/LTO.cpp                          |  12 +-
 llvm/lib/LTO/LTOConfigBitcode.cpp             | 486 ++++++++++++++++
 llvm/lib/LTO/TargetOptionsBitcode.cpp         | 522 ++++++++++++++++++
 llvm/test/ThinLTO/X86/dtlto/summary.ll        |  23 +-
 llvm/unittests/CMakeLists.txt                 |   1 +
 llvm/unittests/LTO/CMakeLists.txt             |  13 +
 llvm/unittests/LTO/LTOConfigBitcodeTest.cpp   | 165 ++++++
 22 files changed, 1705 insertions(+), 58 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/LTOConfigBitcode.h
 create mode 100644 llvm/include/llvm/LTO/TargetOptionsBitcode.h
 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 6aa6bc1bd41e8..3ef228f932c9f 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/TargetRegistry.h"
 #include "llvm/Object/OffloadBinary.h"
 #include "llvm/Passes/PassBuilder.h"
@@ -1395,7 +1396,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)) {
@@ -1405,47 +1427,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 0000000000000..5e7323e195e64
--- /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 0000000000000..6e2a798662415
--- /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 ae4647d33672e..94019275f5809 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 b8992b6dca45e..0f57041e6d752 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 d88e261f8c684..9ce577f4ca9ca 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.h b/llvm/include/llvm/LTO/Config.h
index f322f753813ff..68c3bd8753660 100644
--- a/llvm/include/llvm/LTO/Config.h
+++ b/llvm/include/llvm/LTO/Config.h
@@ -46,7 +46,9 @@ struct Config {
     ELF,
   };
   // Note: when adding fields here, consider whether they need to be added to
-  // computeLTOCacheKey in LTO.cpp.
+  // computeLTOCacheKey in LTO.cpp. The structured binding in
+  // LTOConfigBitcode.cpp will also require the field to be explicitly handled
+  // or documented as non-serializable.
   std::string CPU;
   TargetOptions Options;
   std::vector<std::string> MAttrs;
diff --git a/llvm/include/llvm/LTO/LTOConfigBitcode.h 
b/llvm/include/llvm/LTO/LTOConfigBitcode.h
new file mode 100644
index 0000000000000..6cbd68be1a94a
--- /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 0000000000000..4f15c908ca477
--- /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.h 
b/llvm/include/llvm/MC/MCTargetOptions.h
index 1ef26da9afdbc..c7914e55545ed 100644
--- a/llvm/include/llvm/MC/MCTargetOptions.h
+++ b/llvm/include/llvm/MC/MCTargetOptions.h
@@ -37,6 +37,8 @@ class StringRef;
 
 class MCTargetOptions {
 public:
+  // When adding fields, update the structured binding and serialization in
+  // llvm/lib/LTO/TargetOptionsBitcode.cpp.
   enum AsmInstrumentation {
     AsmInstrumentationNone,
     AsmInstrumentationAddress
diff --git a/llvm/include/llvm/Target/TargetOptions.h 
b/llvm/include/llvm/Target/TargetOptions.h
index f6c862e99b98f..df5da377a99a0 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -118,6 +118,8 @@ enum CodeObjectVersionKind {
 
 class TargetOptions {
 public:
+  // When adding fields, update the structured binding and serialization in
+  // llvm/lib/LTO/TargetOptionsBitcode.cpp.
   TargetOptions()
       : NoTrappingFPMath(true), EnableAIXExtendedAltivecABI(false),
         HonorSignDependentRoundingFPMathOption(false), NoZerosInBSS(false),
diff --git a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp 
b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
index 6574ab7a93c58..3e99d015434b8 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
@@ -1004,4 +1004,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 0b9b1bccb1fb8..d267115ebbedd 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.
@@ -5563,6 +5572,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);
@@ -5737,9 +5754,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();
 }
 
@@ -5778,6 +5795,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();
 
@@ -5794,12 +5819,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 0000000000000..5a6ed0327d9a5
--- /dev/null
+++ b/llvm/lib/LTO/BitcodeMetadataUtils.h
@@ -0,0 +1,222 @@
+//===- 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 <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 *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 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<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 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 cf455ff04c112..8d3aab5d32269 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 a8705e3d925a5..43aee33b9ec30 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"
@@ -1568,8 +1569,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 0000000000000..8848f0c1fe97e
--- /dev/null
+++ b/llvm/lib/LTO/LTOConfigBitcode.cpp
@@ -0,0 +1,486 @@
+//===- 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) {
+  Writer.putBool("pto.LoopInterleaving", PTO.LoopInterleaving);
+  Writer.putBool("pto.LoopVectorization", PTO.LoopVectorization);
+  Writer.putBool("pto.SLPVectorization", PTO.SLPVectorization);
+  Writer.putBool("pto.LoopUnrolling", PTO.LoopUnrolling);
+  Writer.putBool("pto.LoopInterchange", PTO.LoopInterchange);
+  Writer.putBool("pto.LoopFusion", PTO.LoopFusion);
+  Writer.putBool("pto.ForgetAllSCEVInLoopUnroll",
+                 PTO.ForgetAllSCEVInLoopUnroll);
+  Writer.putI32("pto.LicmMssaOptCap", PTO.LicmMssaOptCap);
+  Writer.putI32("pto.LicmMssaNoAccForPromotionCap",
+                PTO.LicmMssaNoAccForPromotionCap);
+  Writer.putBool("pto.CallGraphProfile", PTO.CallGraphProfile);
+  Writer.putBool("pto.UnifiedLTO", PTO.UnifiedLTO);
+  Writer.putBool("pto.MergeFunctions", PTO.MergeFunctions);
+  Writer.putI32("pto.InlinerThreshold", PTO.InlinerThreshold);
+  Writer.putBool("pto.EagerlyInvalidateAnalyses",
+                 PTO.EagerlyInvalidateAnalyses);
+  Writer.putBool("pto.DevirtualizeSpeculatively",
+                 PTO.DevirtualizeSpeculatively);
+}
+
+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));
+}
+
+void encodeOptionalCodeModel(MetadataWriter &Writer,
+                             const std::optional<CodeModel::Model> &CodeModel) 
{
+  if (!CodeModel)
+    return;
+  Writer.putI32("CodeModel", static_cast<int32_t>(*CodeModel));
+}
+
+void encodeConfigFields(MetadataWriter &Writer, const Config &C) {
+  // Keep this decomposition in sync with Config. It intentionally includes
+  // non-serializable fields so that adding or removing any field produces a
+  // compile error here. New fields must either be added to encodeConfigFields
+  // and applyEntry or explicitly documented as non-serializable below.
+  [[maybe_unused]] const auto
+      &[CPU, Options, MAttrs, MllvmArgs,
+        LoadedPassPlugins, // Non-serializable process-local pointers.
+        PassPluginFilenames,
+        PreCodeGenPassesHook, // Non-serializable callback.
+        RelocModel, CodeModel, CGOptLevel, CGFileType, OptLevel, VerifyEach,
+        DisableVerify, Freestanding, CodeGenOnly, RunCSIRInstr, 
PGOWarnMismatch,
+        HasWholeProgramVisibility, ValidateAllVtablesHaveTypeInfos,
+        AllVtablesHaveTypeInfos, AlwaysEmitRegularLTOObj, KeepSymbolNameCopies,
+        Dtlto, VisibilityScheme, OptPipeline, AAPipeline, OverrideTriple,
+        DefaultTriple, CSIRProfile, SampleProfile, ProfileRemapping, DwoDir,
+        SplitDwarfFile, SplitDwarfOutput, RemarksFilename, RemarksPasses,
+        RemarksWithHotness, RemarksHotnessThreshold, RemarksFormat,
+        DebugPassManager, StatsFile, ThinLTOModulesToCompile, TimeTraceEnabled,
+        TimeTraceGranularity, ShouldDiscardValueNames,
+        DiagHandler, // Non-serializable callback.
+        AddFSDiscriminator,
+        ResolutionFile, // Non-serializable stream.
+        PTO,
+        PreOptModuleHook, // Non-serializable callbacks.
+        PostPromoteModuleHook, PostInternalizeModuleHook, PostImportModuleHook,
+        PostOptModuleHook, PreCodeGenModuleHook, CombinedIndexHook,
+        GetSummaryIndexOutputStream, GetImportsListOutputArray,
+        GetCacheKeyOutputString] = C;
+
+  Writer.putString("CPU", C.CPU);
+  Writer.putNode("Options",
+                 encodeTargetOptionsAsNode(Writer.getContext(), C.Options));
+  Writer.putStringList("MAttrs", C.MAttrs);
+  Writer.putStringList("MllvmArgs", C.MllvmArgs);
+  Writer.putStringList("PassPluginFilenames", C.PassPluginFilenames);
+
+  Writer.putBool("RelocModel.HasValue", C.RelocModel.has_value());
+  if (C.RelocModel)
+    Writer.putI32("RelocModel", static_cast<int32_t>(*C.RelocModel));
+  encodeOptionalCodeModel(Writer, C.CodeModel);
+
+  Writer.putI32("CGOptLevel", static_cast<int32_t>(C.CGOptLevel));
+  Writer.putI32("CGFileType", static_cast<int32_t>(C.CGFileType));
+  Writer.putI32("OptLevel", C.OptLevel);
+
+  Writer.putBool("VerifyEach", C.VerifyEach);
+  Writer.putBool("DisableVerify", C.DisableVerify);
+  Writer.putBool("Freestanding", C.Freestanding);
+  Writer.putBool("CodeGenOnly", C.CodeGenOnly);
+  Writer.putBool("RunCSIRInstr", C.RunCSIRInstr);
+  Writer.putBool("PGOWarnMismatch", C.PGOWarnMismatch);
+  Writer.putBool("HasWholeProgramVisibility", C.HasWholeProgramVisibility);
+  Writer.putBool("ValidateAllVtablesHaveTypeInfos",
+                 C.ValidateAllVtablesHaveTypeInfos);
+  Writer.putBool("AllVtablesHaveTypeInfos", C.AllVtablesHaveTypeInfos);
+  Writer.putBool("AlwaysEmitRegularLTOObj", C.AlwaysEmitRegularLTOObj);
+  Writer.putBool("KeepSymbolNameCopies", C.KeepSymbolNameCopies);
+  Writer.putBool("Dtlto", C.Dtlto);
+  Writer.putI32("VisibilityScheme", static_cast<int32_t>(C.VisibilityScheme));
+
+  Writer.putString("OptPipeline", C.OptPipeline);
+  Writer.putString("AAPipeline", C.AAPipeline);
+  Writer.putString("OverrideTriple", C.OverrideTriple);
+  Writer.putString("DefaultTriple", C.DefaultTriple);
+  Writer.putString("CSIRProfile", C.CSIRProfile);
+  Writer.putString("SampleProfile", C.SampleProfile);
+  Writer.putString("ProfileRemapping", C.ProfileRemapping);
+  Writer.putString("DwoDir", C.DwoDir);
+  Writer.putString("SplitDwarfFile", C.SplitDwarfFile);
+  Writer.putString("SplitDwarfOutput", C.SplitDwarfOutput);
+  Writer.putString("RemarksFilename", C.RemarksFilename);
+  Writer.putString("RemarksPasses", C.RemarksPasses);
+  Writer.putBool("RemarksWithHotness", C.RemarksWithHotness);
+  encodeRemarksHotnessThreshold(Writer, C.RemarksHotnessThreshold);
+  Writer.putString("RemarksFormat", C.RemarksFormat);
+  Writer.putBool("DebugPassManager", C.DebugPassManager);
+  Writer.putString("StatsFile", C.StatsFile);
+  Writer.putStringList("ThinLTOModulesToCompile", C.ThinLTOModulesToCompile);
+  Writer.putBool("TimeTraceEnabled", C.TimeTraceEnabled);
+  Writer.putI32("TimeTraceGranularity", C.TimeTraceGranularity);
+  Writer.putBool("ShouldDiscardValueNames", C.ShouldDiscardValueNames);
+  Writer.putBool("AddFSDiscriminator", C.AddFSDiscriminator);
+
+  encodePipelineTuningOptions(Writer, C.PTO);
+}
+
+Error applyEntry(Config &C, StringRef Key, const MDNode &Entry) {
+  EntryApplier Applier{Entry, kEntryKind};
+
+  if (Key == "CPU")
+    return Applier.applyString([&](StringRef V) { C.CPU = V.str(); });
+  if (Key == "Options") {
+    auto Node = getNodeField(Entry, kEntryKind);
+    if (!Node)
+      return Node.takeError();
+    auto Opt = decodeTargetOptionsFromNode(*Node);
+    if (!Opt)
+      return Opt.takeError();
+    C.Options = std::move(*Opt);
+    return Error::success();
+  }
+  if (Key == "MAttrs")
+    return Applier.applyStringList(
+        [&](std::vector<std::string> V) { C.MAttrs = std::move(V); });
+  if (Key == "MllvmArgs")
+    return Applier.applyStringList(
+        [&](std::vector<std::string> V) { C.MllvmArgs = std::move(V); });
+  if (Key == "PassPluginFilenames")
+    return Applier.applyStringList([&](std::vector<std::string> V) {
+      C.PassPluginFilenames = std::move(V);
+    });
+  if (Key == "RelocModel")
+    return Applier.applyI32(
+        [&](int32_t V) { C.RelocModel = static_cast<Reloc::Model>(V); });
+  if (Key == "RelocModel.HasValue")
+    return Applier.applyBool([&](bool V) {
+      if (!V)
+        C.RelocModel = std::nullopt;
+    });
+  if (Key == "CodeModel")
+    return Applier.applyI32(
+        [&](int32_t V) { C.CodeModel = static_cast<CodeModel::Model>(V); });
+  if (Key == "CGOptLevel")
+    return Applier.applyI32(
+        [&](int32_t V) { C.CGOptLevel = static_cast<CodeGenOptLevel>(V); });
+  if (Key == "CGFileType")
+    return Applier.applyI32(
+        [&](int32_t V) { C.CGFileType = static_cast<CodeGenFileType>(V); });
+  if (Key == "OptLevel")
+    return Applier.applyI32([&](int32_t V) { C.OptLevel = V; });
+
+  if (Key == "VerifyEach")
+    return Applier.applyBool([&](bool V) { C.VerifyEach = V; });
+  if (Key == "DisableVerify")
+    return Applier.applyBool([&](bool V) { C.DisableVerify = V; });
+  if (Key == "Freestanding")
+    return Applier.applyBool([&](bool V) { C.Freestanding = V; });
+  if (Key == "CodeGenOnly")
+    return Applier.applyBool([&](bool V) { C.CodeGenOnly = V; });
+  if (Key == "RunCSIRInstr")
+    return Applier.applyBool([&](bool V) { C.RunCSIRInstr = V; });
+  if (Key == "PGOWarnMismatch")
+    return Applier.applyBool([&](bool V) { C.PGOWarnMismatch = V; });
+  if (Key == "HasWholeProgramVisibility")
+    return Applier.applyBool([&](bool V) { C.HasWholeProgramVisibility = V; });
+  if (Key == "ValidateAllVtablesHaveTypeInfos")
+    return Applier.applyBool(
+        [&](bool V) { C.ValidateAllVtablesHaveTypeInfos = V; });
+  if (Key == "AllVtablesHaveTypeInfos")
+    return Applier.applyBool([&](bool V) { C.AllVtablesHaveTypeInfos = V; });
+  if (Key == "AlwaysEmitRegularLTOObj")
+    return Applier.applyBool([&](bool V) { C.AlwaysEmitRegularLTOObj = V; });
+  if (Key == "KeepSymbolNameCopies")
+    return Applier.applyBool([&](bool V) { C.KeepSymbolNameCopies = V; });
+  if (Key == "Dtlto")
+    return Applier.applyBool([&](bool V) { C.Dtlto = V; });
+  if (Key == "VisibilityScheme")
+    return Applier.applyI32([&](int32_t V) {
+      C.VisibilityScheme = static_cast<Config::VisScheme>(V);
+    });
+
+  if (Key == "OptPipeline")
+    return Applier.applyString([&](StringRef V) { C.OptPipeline = V.str(); });
+  if (Key == "AAPipeline")
+    return Applier.applyString([&](StringRef V) { C.AAPipeline = V.str(); });
+  if (Key == "OverrideTriple")
+    return Applier.applyString(
+        [&](StringRef V) { C.OverrideTriple = V.str(); });
+  if (Key == "DefaultTriple")
+    return Applier.applyString([&](StringRef V) { C.DefaultTriple = V.str(); 
});
+  if (Key == "CSIRProfile")
+    return Applier.applyString([&](StringRef V) { C.CSIRProfile = V.str(); });
+  if (Key == "SampleProfile")
+    return Applier.applyString([&](StringRef V) { C.SampleProfile = V.str(); 
});
+  if (Key == "ProfileRemapping")
+    return Applier.applyString(
+        [&](StringRef V) { C.ProfileRemapping = V.str(); });
+  if (Key == "DwoDir")
+    return Applier.applyString([&](StringRef V) { C.DwoDir = V.str(); });
+  if (Key == "SplitDwarfFile")
+    return Applier.applyString(
+        [&](StringRef V) { C.SplitDwarfFile = V.str(); });
+  if (Key == "SplitDwarfOutput")
+    return Applier.applyString(
+        [&](StringRef V) { C.SplitDwarfOutput = V.str(); });
+  if (Key == "RemarksFilename")
+    return Applier.applyString(
+        [&](StringRef V) { C.RemarksFilename = V.str(); });
+  if (Key == "RemarksPasses")
+    return Applier.applyString([&](StringRef V) { C.RemarksPasses = V.str(); 
});
+  if (Key == "RemarksWithHotness")
+    return Applier.applyBool([&](bool V) { C.RemarksWithHotness = V; });
+  if (Key == "RemarksHotnessThreshold") {
+    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:
+      C.RemarksHotnessThreshold = 0;
+      break;
+    case 1:
+      C.RemarksHotnessThreshold = Value->getZExtValue();
+      break;
+    case 2:
+      C.RemarksHotnessThreshold = std::nullopt;
+      break;
+    default:
+      return metadataError("invalid RemarksHotnessThreshold mode");
+    }
+    return Error::success();
+  }
+  if (Key == "RemarksFormat")
+    return Applier.applyString([&](StringRef V) { C.RemarksFormat = V.str(); 
});
+  if (Key == "DebugPassManager")
+    return Applier.applyBool([&](bool V) { C.DebugPassManager = V; });
+  if (Key == "StatsFile")
+    return Applier.applyString([&](StringRef V) { C.StatsFile = V.str(); });
+  if (Key == "ThinLTOModulesToCompile")
+    return Applier.applyStringList([&](std::vector<std::string> V) {
+      C.ThinLTOModulesToCompile = std::move(V);
+    });
+  if (Key == "TimeTraceEnabled")
+    return Applier.applyBool([&](bool V) { C.TimeTraceEnabled = V; });
+  if (Key == "TimeTraceGranularity")
+    return Applier.applyI32([&](int32_t V) { C.TimeTraceGranularity = V; });
+  if (Key == "ShouldDiscardValueNames")
+    return Applier.applyBool([&](bool V) { C.ShouldDiscardValueNames = V; });
+  if (Key == "AddFSDiscriminator")
+    return Applier.applyBool([&](bool V) { C.AddFSDiscriminator = V; });
+
+  PipelineTuningOptions &PTO = C.PTO;
+  if (Key == "pto.LoopInterleaving")
+    return Applier.applyBool([&](bool V) { PTO.LoopInterleaving = V; });
+  if (Key == "pto.LoopVectorization")
+    return Applier.applyBool([&](bool V) { PTO.LoopVectorization = V; });
+  if (Key == "pto.SLPVectorization")
+    return Applier.applyBool([&](bool V) { PTO.SLPVectorization = V; });
+  if (Key == "pto.LoopUnrolling")
+    return Applier.applyBool([&](bool V) { PTO.LoopUnrolling = V; });
+  if (Key == "pto.LoopInterchange")
+    return Applier.applyBool([&](bool V) { PTO.LoopInterchange = V; });
+  if (Key == "pto.LoopFusion")
+    return Applier.applyBool([&](bool V) { PTO.LoopFusion = V; });
+  if (Key == "pto.ForgetAllSCEVInLoopUnroll")
+    return Applier.applyBool(
+        [&](bool V) { PTO.ForgetAllSCEVInLoopUnroll = V; });
+  if (Key == "pto.LicmMssaOptCap")
+    return Applier.applyI32([&](int32_t V) { PTO.LicmMssaOptCap = V; });
+  if (Key == "pto.LicmMssaNoAccForPromotionCap")
+    return Applier.applyI32(
+        [&](int32_t V) { PTO.LicmMssaNoAccForPromotionCap = V; });
+  if (Key == "pto.CallGraphProfile")
+    return Applier.applyBool([&](bool V) { PTO.CallGraphProfile = V; });
+  if (Key == "pto.UnifiedLTO")
+    return Applier.applyBool([&](bool V) { PTO.UnifiedLTO = V; });
+  if (Key == "pto.MergeFunctions")
+    return Applier.applyBool([&](bool V) { PTO.MergeFunctions = V; });
+  if (Key == "pto.InlinerThreshold")
+    return Applier.applyI32([&](int32_t V) { PTO.InlinerThreshold = V; });
+  if (Key == "pto.EagerlyInvalidateAnalyses")
+    return Applier.applyBool(
+        [&](bool V) { PTO.EagerlyInvalidateAnalyses = V; });
+  if (Key == "pto.DevirtualizeSpeculatively")
+    return Applier.applyBool(
+        [&](bool V) { PTO.DevirtualizeSpeculatively = V; });
+
+  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 0000000000000..359ed125ba62c
--- /dev/null
+++ b/llvm/lib/LTO/TargetOptionsBitcode.cpp
@@ -0,0 +1,522 @@
+//===- 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) {
+  // Keep this decomposition in sync with MCTargetOptions so that adding or
+  // removing a field produces a compile error here.
+  [[maybe_unused]] const auto &[MCRelaxAll, MCNoExecStack, MCFatalWarnings,
+                                MCNoWarn, MCNoDeprecatedWarn, MCNoTypeCheck,
+                                MCSaveTempLabels, 
MCIncrementalLinkerCompatible,
+                                FDPIC, ShowMCEncoding, ShowMCInst, AsmVerbose,
+                                PreserveAsmComments, Dwarf64, Crel,
+                                ImplicitMapSyms, X86RelaxRelocations,
+                                X86Sse2Avx, RelocSectionSym, OutputAsmVariant,
+                                EmitDwarfUnwind, DwarfVersion,
+                                MCUseDwarfDirectory, CompressDebugSections,
+                                ABIName, AssemblyLanguage, SplitDwarfFile,
+                                AsSecureLogFile, Argv0, CommandlineArgs,
+                                IASSearchPaths, InstPrinterOptions,
+                                EmitCompactUnwindNonCanonical, 
EmitSFrameUnwind,
+                                PPCUseFullRegisterNames, LargeEHEncoding] = MC;
+
+  Writer.putBool("mc.MCRelaxAll", MC.MCRelaxAll);
+  Writer.putBool("mc.MCNoExecStack", MC.MCNoExecStack);
+  Writer.putBool("mc.MCFatalWarnings", MC.MCFatalWarnings);
+  Writer.putBool("mc.MCNoWarn", MC.MCNoWarn);
+  Writer.putBool("mc.MCNoDeprecatedWarn", MC.MCNoDeprecatedWarn);
+  Writer.putBool("mc.MCNoTypeCheck", MC.MCNoTypeCheck);
+  Writer.putBool("mc.MCSaveTempLabels", MC.MCSaveTempLabels);
+  Writer.putBool("mc.MCIncrementalLinkerCompatible",
+                 MC.MCIncrementalLinkerCompatible);
+  Writer.putBool("mc.FDPIC", MC.FDPIC);
+  Writer.putBool("mc.ShowMCEncoding", MC.ShowMCEncoding);
+  Writer.putBool("mc.ShowMCInst", MC.ShowMCInst);
+  Writer.putBool("mc.AsmVerbose", MC.AsmVerbose);
+  Writer.putBool("mc.PreserveAsmComments", MC.PreserveAsmComments);
+  Writer.putBool("mc.Dwarf64", MC.Dwarf64);
+  Writer.putBool("mc.Crel", MC.Crel);
+  Writer.putBool("mc.ImplicitMapSyms", MC.ImplicitMapSyms);
+  Writer.putBool("mc.X86RelaxRelocations", MC.X86RelaxRelocations);
+  Writer.putBool("mc.X86Sse2Avx", MC.X86Sse2Avx);
+  Writer.putI32("mc.RelocSectionSym", 
static_cast<int32_t>(MC.RelocSectionSym));
+  if (MC.OutputAsmVariant)
+    Writer.putI32("mc.OutputAsmVariant",
+                  static_cast<int32_t>(*MC.OutputAsmVariant));
+  Writer.putI32("mc.EmitDwarfUnwind", 
static_cast<int32_t>(MC.EmitDwarfUnwind));
+  Writer.putI32("mc.DwarfVersion", MC.DwarfVersion);
+  Writer.putI32("mc.MCUseDwarfDirectory",
+                static_cast<int32_t>(MC.MCUseDwarfDirectory));
+  Writer.putI32("mc.CompressDebugSections",
+                static_cast<int32_t>(MC.CompressDebugSections));
+  Writer.putString("mc.ABIName", MC.ABIName);
+  Writer.putString("mc.AssemblyLanguage", MC.AssemblyLanguage);
+  Writer.putString("mc.SplitDwarfFile", MC.SplitDwarfFile);
+  Writer.putString("mc.AsSecureLogFile", MC.AsSecureLogFile);
+  Writer.putString("mc.Argv0", MC.Argv0);
+  Writer.putString("mc.CommandlineArgs", MC.CommandlineArgs);
+  Writer.putStringList("mc.IASSearchPaths", MC.IASSearchPaths);
+  Writer.putStringList("mc.InstPrinterOptions", MC.InstPrinterOptions);
+  Writer.putBool("mc.EmitCompactUnwindNonCanonical",
+                 MC.EmitCompactUnwindNonCanonical);
+  Writer.putBool("mc.EmitSFrameUnwind", MC.EmitSFrameUnwind);
+  Writer.putBool("mc.PPCUseFullRegisterNames", MC.PPCUseFullRegisterNames);
+  Writer.putBool("mc.LargeEHEncoding", MC.LargeEHEncoding);
+}
+
+void encodeTargetOptionsFields(MetadataWriter &Writer,
+                               const TargetOptions &Opt) {
+  // Keep this decomposition in sync with TargetOptions. It intentionally
+  // includes non-serializable fields so that adding or removing any field
+  // produces a compile error here. New fields must either be serialized or
+  // explicitly documented as non-serializable below.
+  [[maybe_unused]] const auto
+      &[BinutilsVersion, NoTrappingFPMath, EnableAIXExtendedAltivecABI,
+        HonorSignDependentRoundingFPMathOption, NoZerosInBSS,
+        GuaranteedTailCallOpt, StackSymbolOrdering, EnableFastISel,
+        EnableGlobalISel, GlobalISelAbort, SwiftAsyncFramePointer, 
UseInitArray,
+        DisableIntegratedAS, FunctionSections, DataSections,
+        IgnoreXCOFFVisibility, XCOFFTracebackTable, UniqueSectionNames,
+        UniqueBasicBlockSectionNames, SeparateNamedSections, TrapUnreachable,
+        NoTrapAfterNoreturn, TLSSize, EmulatedTLS, EnableTLSDESC, EnableIPRA,
+        EmitStackSizeSection, EnableMachineOutliner,
+        EnableMachineFunctionSplitter, EnableStaticDataPartitioning,
+        SupportsDefaultOutlining, EnableDefaultMachineVerifier, EmitAddrsig,
+        BBAddrMap, BBSections, BBSectionsFuncListBuf,
+        EmitCallGraphSection, EmitCallSiteInfo, SupportsDebugEntryValues,
+        EnableDebugEntryValues, ValueTrackingVariableLocations,
+        ForceDwarfFrameSection, XRayFunctionIndex, DebugStrictDwarf, Hotpatch,
+        PPCGenScalarMASSEntries, JMCInstrument, EnableCFIFixup, MisExpect,
+        XCOFFReadOnlyPointers, VerifyArgABICompliance, StackUsageFile,
+        LoopAlignment, AllowFPOpFusion, ThreadModel, EABIVersion,
+        DebuggerTuning, VecLib, ExceptionModel, MCOptions,
+        ObjectFilenameForDebug] = Opt;
+
+  Writer.putI32("BinutilsVersionMajor", Opt.BinutilsVersion.first);
+  Writer.putI32("BinutilsVersionMinor", Opt.BinutilsVersion.second);
+
+  Writer.putBool("NoTrappingFPMath", Opt.NoTrappingFPMath);
+  Writer.putBool("EnableAIXExtendedAltivecABI",
+                 Opt.EnableAIXExtendedAltivecABI);
+  Writer.putBool("HonorSignDependentRoundingFPMathOption",
+                 Opt.HonorSignDependentRoundingFPMathOption);
+  Writer.putBool("NoZerosInBSS", Opt.NoZerosInBSS);
+  Writer.putBool("GuaranteedTailCallOpt", Opt.GuaranteedTailCallOpt);
+  Writer.putBool("StackSymbolOrdering", Opt.StackSymbolOrdering);
+  Writer.putBool("EnableFastISel", Opt.EnableFastISel);
+  Writer.putBool("EnableGlobalISel", Opt.EnableGlobalISel);
+  Writer.putI32("GlobalISelAbort", static_cast<int32_t>(Opt.GlobalISelAbort));
+  Writer.putI32("SwiftAsyncFramePointer",
+                static_cast<int32_t>(Opt.SwiftAsyncFramePointer));
+  Writer.putBool("UseInitArray", Opt.UseInitArray);
+  Writer.putBool("DisableIntegratedAS", Opt.DisableIntegratedAS);
+  Writer.putBool("FunctionSections", Opt.FunctionSections);
+  Writer.putBool("DataSections", Opt.DataSections);
+  Writer.putBool("IgnoreXCOFFVisibility", Opt.IgnoreXCOFFVisibility);
+  Writer.putBool("XCOFFTracebackTable", Opt.XCOFFTracebackTable);
+  Writer.putBool("UniqueSectionNames", Opt.UniqueSectionNames);
+  Writer.putBool("UniqueBasicBlockSectionNames",
+                 Opt.UniqueBasicBlockSectionNames);
+  Writer.putBool("SeparateNamedSections", Opt.SeparateNamedSections);
+  Writer.putBool("TrapUnreachable", Opt.TrapUnreachable);
+  Writer.putBool("NoTrapAfterNoreturn", Opt.NoTrapAfterNoreturn);
+  Writer.putI32("TLSSize", Opt.TLSSize);
+  Writer.putBool("EmulatedTLS", Opt.EmulatedTLS);
+  Writer.putBool("EnableTLSDESC", Opt.EnableTLSDESC);
+  Writer.putBool("EnableIPRA", Opt.EnableIPRA);
+  Writer.putBool("EmitStackSizeSection", Opt.EmitStackSizeSection);
+  Writer.putBool("EnableMachineOutliner", Opt.EnableMachineOutliner);
+  Writer.putBool("EnableMachineFunctionSplitter",
+                 Opt.EnableMachineFunctionSplitter);
+  Writer.putBool("EnableStaticDataPartitioning",
+                 Opt.EnableStaticDataPartitioning);
+  Writer.putBool("SupportsDefaultOutlining", Opt.SupportsDefaultOutlining);
+  Writer.putBool("EnableDefaultMachineVerifier",
+                 Opt.EnableDefaultMachineVerifier);
+  Writer.putBool("EmitAddrsig", Opt.EmitAddrsig);
+  Writer.putBool("BBAddrMap", Opt.BBAddrMap);
+  Writer.putI32("BBSections", static_cast<int32_t>(Opt.BBSections));
+  encodeMemoryBuffer(Writer, "BBSectionsFuncListBuf",
+                     Opt.BBSectionsFuncListBuf);
+  Writer.putBool("EmitCallGraphSection", Opt.EmitCallGraphSection);
+  Writer.putBool("EmitCallSiteInfo", Opt.EmitCallSiteInfo);
+  Writer.putBool("SupportsDebugEntryValues", Opt.SupportsDebugEntryValues);
+  Writer.putBool("EnableDebugEntryValues", Opt.EnableDebugEntryValues);
+  Writer.putBool("ValueTrackingVariableLocations",
+                 Opt.ValueTrackingVariableLocations);
+  Writer.putBool("ForceDwarfFrameSection", Opt.ForceDwarfFrameSection);
+  Writer.putBool("XRayFunctionIndex", Opt.XRayFunctionIndex);
+  Writer.putBool("DebugStrictDwarf", Opt.DebugStrictDwarf);
+  Writer.putBool("Hotpatch", Opt.Hotpatch);
+  Writer.putBool("PPCGenScalarMASSEntries", Opt.PPCGenScalarMASSEntries);
+  Writer.putBool("JMCInstrument", Opt.JMCInstrument);
+  Writer.putBool("EnableCFIFixup", Opt.EnableCFIFixup);
+  Writer.putBool("MisExpect", Opt.MisExpect);
+  Writer.putBool("XCOFFReadOnlyPointers", Opt.XCOFFReadOnlyPointers);
+  Writer.putBool("VerifyArgABICompliance", Opt.VerifyArgABICompliance);
+
+  Writer.putString("StackUsageFile", Opt.StackUsageFile);
+  Writer.putI32("LoopAlignment", Opt.LoopAlignment);
+  Writer.putI32("AllowFPOpFusion", static_cast<int32_t>(Opt.AllowFPOpFusion));
+  Writer.putI32("ThreadModel", static_cast<int32_t>(Opt.ThreadModel));
+  Writer.putI32("EABIVersion", static_cast<int32_t>(Opt.EABIVersion));
+  Writer.putI32("DebuggerTuning", static_cast<int32_t>(Opt.DebuggerTuning));
+  Writer.putI32("VecLib", static_cast<int32_t>(Opt.VecLib));
+  Writer.putI32("ExceptionModel", static_cast<int32_t>(Opt.ExceptionModel));
+  Writer.putString("ObjectFilenameForDebug", Opt.ObjectFilenameForDebug);
+
+  encodeMCTargetOptions(Writer, Opt.MCOptions);
+}
+
+Error applyEntry(TargetOptions &Opt, StringRef Key, const MDNode &Entry) {
+  EntryApplier Applier{Entry, kEntryKind};
+
+  if (Key == "BinutilsVersionMajor")
+    return Applier.applyI32([&](int32_t V) { Opt.BinutilsVersion.first = V; });
+  if (Key == "BinutilsVersionMinor")
+    return Applier.applyI32([&](int32_t V) { Opt.BinutilsVersion.second = V; 
});
+
+  if (Key == "NoTrappingFPMath")
+    return Applier.applyBool([&](bool V) { Opt.NoTrappingFPMath = V; });
+  if (Key == "EnableAIXExtendedAltivecABI")
+    return Applier.applyBool(
+        [&](bool V) { Opt.EnableAIXExtendedAltivecABI = V; });
+  if (Key == "HonorSignDependentRoundingFPMathOption")
+    return Applier.applyBool(
+        [&](bool V) { Opt.HonorSignDependentRoundingFPMathOption = V; });
+  if (Key == "NoZerosInBSS")
+    return Applier.applyBool([&](bool V) { Opt.NoZerosInBSS = V; });
+  if (Key == "GuaranteedTailCallOpt")
+    return Applier.applyBool([&](bool V) { Opt.GuaranteedTailCallOpt = V; });
+  if (Key == "StackSymbolOrdering")
+    return Applier.applyBool([&](bool V) { Opt.StackSymbolOrdering = V; });
+  if (Key == "EnableFastISel")
+    return Applier.applyBool([&](bool V) { Opt.EnableFastISel = V; });
+  if (Key == "EnableGlobalISel")
+    return Applier.applyBool([&](bool V) { Opt.EnableGlobalISel = V; });
+  if (Key == "GlobalISelAbort")
+    return Applier.applyI32([&](int32_t V) {
+      Opt.GlobalISelAbort = static_cast<GlobalISelAbortMode>(V);
+    });
+  if (Key == "SwiftAsyncFramePointer")
+    return Applier.applyI32([&](int32_t V) {
+      Opt.SwiftAsyncFramePointer = static_cast<SwiftAsyncFramePointerMode>(V);
+    });
+  if (Key == "UseInitArray")
+    return Applier.applyBool([&](bool V) { Opt.UseInitArray = V; });
+  if (Key == "DisableIntegratedAS")
+    return Applier.applyBool([&](bool V) { Opt.DisableIntegratedAS = V; });
+  if (Key == "FunctionSections")
+    return Applier.applyBool([&](bool V) { Opt.FunctionSections = V; });
+  if (Key == "DataSections")
+    return Applier.applyBool([&](bool V) { Opt.DataSections = V; });
+  if (Key == "IgnoreXCOFFVisibility")
+    return Applier.applyBool([&](bool V) { Opt.IgnoreXCOFFVisibility = V; });
+  if (Key == "XCOFFTracebackTable")
+    return Applier.applyBool([&](bool V) { Opt.XCOFFTracebackTable = V; });
+  if (Key == "UniqueSectionNames")
+    return Applier.applyBool([&](bool V) { Opt.UniqueSectionNames = V; });
+  if (Key == "UniqueBasicBlockSectionNames")
+    return Applier.applyBool(
+        [&](bool V) { Opt.UniqueBasicBlockSectionNames = V; });
+  if (Key == "SeparateNamedSections")
+    return Applier.applyBool([&](bool V) { Opt.SeparateNamedSections = V; });
+  if (Key == "TrapUnreachable")
+    return Applier.applyBool([&](bool V) { Opt.TrapUnreachable = V; });
+  if (Key == "NoTrapAfterNoreturn")
+    return Applier.applyBool([&](bool V) { Opt.NoTrapAfterNoreturn = V; });
+  if (Key == "TLSSize")
+    return Applier.applyI32([&](int32_t V) { Opt.TLSSize = V; });
+  if (Key == "EmulatedTLS")
+    return Applier.applyBool([&](bool V) { Opt.EmulatedTLS = V; });
+  if (Key == "EnableTLSDESC")
+    return Applier.applyBool([&](bool V) { Opt.EnableTLSDESC = V; });
+  if (Key == "EnableIPRA")
+    return Applier.applyBool([&](bool V) { Opt.EnableIPRA = V; });
+  if (Key == "EmitStackSizeSection")
+    return Applier.applyBool([&](bool V) { Opt.EmitStackSizeSection = V; });
+  if (Key == "EnableMachineOutliner")
+    return Applier.applyBool([&](bool V) { Opt.EnableMachineOutliner = V; });
+  if (Key == "EnableMachineFunctionSplitter")
+    return Applier.applyBool(
+        [&](bool V) { Opt.EnableMachineFunctionSplitter = V; });
+  if (Key == "EnableStaticDataPartitioning")
+    return Applier.applyBool(
+        [&](bool V) { Opt.EnableStaticDataPartitioning = V; });
+  if (Key == "SupportsDefaultOutlining")
+    return Applier.applyBool([&](bool V) { Opt.SupportsDefaultOutlining = V; 
});
+  if (Key == "EnableDefaultMachineVerifier")
+    return Applier.applyBool(
+        [&](bool V) { Opt.EnableDefaultMachineVerifier = V; });
+  if (Key == "EmitAddrsig")
+    return Applier.applyBool([&](bool V) { Opt.EmitAddrsig = V; });
+  if (Key == "BBAddrMap")
+    return Applier.applyBool([&](bool V) { Opt.BBAddrMap = V; });
+  if (Key == "BBSections")
+    return Applier.applyI32(
+        [&](int32_t V) { Opt.BBSections = static_cast<BasicBlockSection>(V); 
});
+  if (Key == "BBSectionsFuncListBuf")
+    return decodeMemoryBuffer(Opt.BBSectionsFuncListBuf, Entry);
+  if (Key == "EmitCallGraphSection")
+    return Applier.applyBool([&](bool V) { Opt.EmitCallGraphSection = V; });
+  if (Key == "EmitCallSiteInfo")
+    return Applier.applyBool([&](bool V) { Opt.EmitCallSiteInfo = V; });
+  if (Key == "SupportsDebugEntryValues")
+    return Applier.applyBool([&](bool V) { Opt.SupportsDebugEntryValues = V; 
});
+  if (Key == "EnableDebugEntryValues")
+    return Applier.applyBool([&](bool V) { Opt.EnableDebugEntryValues = V; });
+  if (Key == "ValueTrackingVariableLocations")
+    return Applier.applyBool(
+        [&](bool V) { Opt.ValueTrackingVariableLocations = V; });
+  if (Key == "ForceDwarfFrameSection")
+    return Applier.applyBool([&](bool V) { Opt.ForceDwarfFrameSection = V; });
+  if (Key == "XRayFunctionIndex")
+    return Applier.applyBool([&](bool V) { Opt.XRayFunctionIndex = V; });
+  if (Key == "DebugStrictDwarf")
+    return Applier.applyBool([&](bool V) { Opt.DebugStrictDwarf = V; });
+  if (Key == "Hotpatch")
+    return Applier.applyBool([&](bool V) { Opt.Hotpatch = V; });
+  if (Key == "PPCGenScalarMASSEntries")
+    return Applier.applyBool([&](bool V) { Opt.PPCGenScalarMASSEntries = V; });
+  if (Key == "JMCInstrument")
+    return Applier.applyBool([&](bool V) { Opt.JMCInstrument = V; });
+  if (Key == "EnableCFIFixup")
+    return Applier.applyBool([&](bool V) { Opt.EnableCFIFixup = V; });
+  if (Key == "MisExpect")
+    return Applier.applyBool([&](bool V) { Opt.MisExpect = V; });
+  if (Key == "XCOFFReadOnlyPointers")
+    return Applier.applyBool([&](bool V) { Opt.XCOFFReadOnlyPointers = V; });
+  if (Key == "VerifyArgABICompliance")
+    return Applier.applyBool([&](bool V) { Opt.VerifyArgABICompliance = V; });
+  if (Key == "StackUsageFile")
+    return Applier.applyString(
+        [&](StringRef V) { Opt.StackUsageFile = V.str(); });
+  if (Key == "LoopAlignment")
+    return Applier.applyI32([&](int32_t V) { Opt.LoopAlignment = V; });
+  if (Key == "AllowFPOpFusion")
+    return Applier.applyI32([&](int32_t V) {
+      Opt.AllowFPOpFusion = static_cast<FPOpFusion::FPOpFusionMode>(V);
+    });
+  if (Key == "ThreadModel")
+    return Applier.applyI32([&](int32_t V) {
+      Opt.ThreadModel = static_cast<ThreadModel::Model>(V);
+    });
+  if (Key == "EABIVersion")
+    return Applier.applyI32(
+        [&](int32_t V) { Opt.EABIVersion = static_cast<EABI>(V); });
+  if (Key == "DebuggerTuning")
+    return Applier.applyI32(
+        [&](int32_t V) { Opt.DebuggerTuning = static_cast<DebuggerKind>(V); });
+  if (Key == "VecLib")
+    return Applier.applyI32(
+        [&](int32_t V) { Opt.VecLib = static_cast<VectorLibrary>(V); });
+  if (Key == "ExceptionModel")
+    return Applier.applyI32([&](int32_t V) {
+      Opt.ExceptionModel = static_cast<ExceptionHandling>(V);
+    });
+  if (Key == "ObjectFilenameForDebug")
+    return Applier.applyString(
+        [&](StringRef V) { Opt.ObjectFilenameForDebug = V.str(); });
+
+  MCTargetOptions &MC = Opt.MCOptions;
+  if (Key == "mc.MCRelaxAll")
+    return Applier.applyBool([&](bool V) { MC.MCRelaxAll = V; });
+  if (Key == "mc.MCNoExecStack")
+    return Applier.applyBool([&](bool V) { MC.MCNoExecStack = V; });
+  if (Key == "mc.MCFatalWarnings")
+    return Applier.applyBool([&](bool V) { MC.MCFatalWarnings = V; });
+  if (Key == "mc.MCNoWarn")
+    return Applier.applyBool([&](bool V) { MC.MCNoWarn = V; });
+  if (Key == "mc.MCNoDeprecatedWarn")
+    return Applier.applyBool([&](bool V) { MC.MCNoDeprecatedWarn = V; });
+  if (Key == "mc.MCNoTypeCheck")
+    return Applier.applyBool([&](bool V) { MC.MCNoTypeCheck = V; });
+  if (Key == "mc.MCSaveTempLabels")
+    return Applier.applyBool([&](bool V) { MC.MCSaveTempLabels = V; });
+  if (Key == "mc.MCIncrementalLinkerCompatible")
+    return Applier.applyBool(
+        [&](bool V) { MC.MCIncrementalLinkerCompatible = V; });
+  if (Key == "mc.FDPIC")
+    return Applier.applyBool([&](bool V) { MC.FDPIC = V; });
+  if (Key == "mc.ShowMCEncoding")
+    return Applier.applyBool([&](bool V) { MC.ShowMCEncoding = V; });
+  if (Key == "mc.ShowMCInst")
+    return Applier.applyBool([&](bool V) { MC.ShowMCInst = V; });
+  if (Key == "mc.AsmVerbose")
+    return Applier.applyBool([&](bool V) { MC.AsmVerbose = V; });
+  if (Key == "mc.PreserveAsmComments")
+    return Applier.applyBool([&](bool V) { MC.PreserveAsmComments = V; });
+  if (Key == "mc.Dwarf64")
+    return Applier.applyBool([&](bool V) { MC.Dwarf64 = V; });
+  if (Key == "mc.Crel")
+    return Applier.applyBool([&](bool V) { MC.Crel = V; });
+  if (Key == "mc.ImplicitMapSyms")
+    return Applier.applyBool([&](bool V) { MC.ImplicitMapSyms = V; });
+  if (Key == "mc.X86RelaxRelocations")
+    return Applier.applyBool([&](bool V) { MC.X86RelaxRelocations = V; });
+  if (Key == "mc.X86Sse2Avx")
+    return Applier.applyBool([&](bool V) { MC.X86Sse2Avx = V; });
+  if (Key == "mc.RelocSectionSym")
+    return Applier.applyI32([&](int32_t V) {
+      MC.RelocSectionSym = static_cast<RelocSectionSymType>(V);
+    });
+  if (Key == "mc.OutputAsmVariant")
+    return Applier.applyI32(
+        [&](int32_t V) { MC.OutputAsmVariant = static_cast<unsigned>(V); });
+  if (Key == "mc.EmitDwarfUnwind")
+    return Applier.applyI32([&](int32_t V) {
+      MC.EmitDwarfUnwind = static_cast<EmitDwarfUnwindType>(V);
+    });
+  if (Key == "mc.DwarfVersion")
+    return Applier.applyI32([&](int32_t V) { MC.DwarfVersion = V; });
+  if (Key == "mc.MCUseDwarfDirectory")
+    return Applier.applyI32([&](int32_t V) {
+      MC.MCUseDwarfDirectory = static_cast<MCTargetOptions::DwarfDirectory>(V);
+    });
+  if (Key == "mc.CompressDebugSections")
+    return Applier.applyI32([&](int32_t V) {
+      MC.CompressDebugSections = static_cast<DebugCompressionType>(V);
+    });
+  if (Key == "mc.ABIName")
+    return Applier.applyString([&](StringRef V) { MC.ABIName = V.str(); });
+  if (Key == "mc.AssemblyLanguage")
+    return Applier.applyString(
+        [&](StringRef V) { MC.AssemblyLanguage = V.str(); });
+  if (Key == "mc.SplitDwarfFile")
+    return Applier.applyString(
+        [&](StringRef V) { MC.SplitDwarfFile = V.str(); });
+  if (Key == "mc.AsSecureLogFile")
+    return Applier.applyString(
+        [&](StringRef V) { MC.AsSecureLogFile = V.str(); });
+  if (Key == "mc.Argv0")
+    return Applier.applyString([&](StringRef V) { MC.Argv0 = V.str(); });
+  if (Key == "mc.CommandlineArgs")
+    return Applier.applyString(
+        [&](StringRef V) { MC.CommandlineArgs = V.str(); });
+  if (Key == "mc.IASSearchPaths")
+    return Applier.applyStringList(
+        [&](std::vector<std::string> V) { MC.IASSearchPaths = std::move(V); });
+  if (Key == "mc.InstPrinterOptions")
+    return Applier.applyStringList([&](std::vector<std::string> V) {
+      MC.InstPrinterOptions = std::move(V);
+    });
+  if (Key == "mc.EmitCompactUnwindNonCanonical")
+    return Applier.applyBool(
+        [&](bool V) { MC.EmitCompactUnwindNonCanonical = V; });
+  if (Key == "mc.EmitSFrameUnwind")
+    return Applier.applyBool([&](bool V) { MC.EmitSFrameUnwind = V; });
+  if (Key == "mc.PPCUseFullRegisterNames")
+    return Applier.applyBool([&](bool V) { MC.PPCUseFullRegisterNames = V; });
+  if (Key == "mc.LargeEHEncoding")
+    return Applier.applyBool([&](bool V) { MC.LargeEHEncoding = V; });
+
+  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/test/ThinLTO/X86/dtlto/summary.ll 
b/llvm/test/ThinLTO/X86/dtlto/summary.ll
index 2365fa4f4ea42..7c1738fbf2731 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 c6ce09b3d67f2..c18243e224d88 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 0000000000000..0253d8d9c0ba4
--- /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 0000000000000..f188f12443be3
--- /dev/null
+++ b/llvm/unittests/LTO/LTOConfigBitcodeTest.cpp
@@ -0,0 +1,165 @@
+//===- 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.BinutilsVersion = {2, 41};
+  Input.FunctionSections = true;
+  Input.DataSections = true;
+  Input.GlobalISelAbort = GlobalISelAbortMode::DisableWithDiag;
+  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.ExceptionModel = ExceptionHandling::Wasm;
+  Input.MCOptions.ABIName = "test-abi";
+  Input.MCOptions.OutputAsmVariant = 1;
+  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->BinutilsVersion, Input.BinutilsVersion);
+  EXPECT_TRUE(Output->FunctionSections);
+  EXPECT_TRUE(Output->DataSections);
+  EXPECT_EQ(Output->GlobalISelAbort, GlobalISelAbortMode::DisableWithDiag);
+  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->ExceptionModel, ExceptionHandling::Wasm);
+  EXPECT_EQ(Output->MCOptions.ABIName, "test-abi");
+  EXPECT_EQ(Output->MCOptions.OutputAsmVariant, 1u);
+  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.ThinLTOModulesToCompile = {"one.bc", "two.bc"};
+  Input.PTO.LoopInterchange = true;
+  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->ThinLTOModulesToCompile, Input.ThinLTOModulesToCompile);
+  EXPECT_TRUE(Output->PTO.LoopInterchange);
+  EXPECT_EQ(Output->PTO.InlinerThreshold, Input.PTO.InlinerThreshold);
+  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());
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to