diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 241bc35857a4f..f9cb5f217a59b 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -269,6 +269,7 @@ fn configure_and_expand(
 
     resolver.resolve_crate(&krate);
 
+    CStore::from_tcx(tcx).report_incompatible_target_modifiers(tcx, &krate);
     krate
 }
 
diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl
index d2b5ae5318592..6fc84b06647f5 100644
--- a/compiler/rustc_metadata/messages.ftl
+++ b/compiler/rustc_metadata/messages.ftl
@@ -113,6 +113,14 @@ metadata_incompatible_rustc =
     found crate `{$crate_name}` compiled by an incompatible version of rustc{$add_info}
     .help = please recompile that crate using this compiler ({$rustc_version}) (consider running `cargo clean` first)
 
+metadata_incompatible_target_modifiers =
+    mixing `{$flag_name_prefixed}` will cause an ABI mismatch in crate `{$local_crate}`
+    .note = `{$flag_name_prefixed}={$flag_local_value}` in this crate is incompatible with `{$flag_name_prefixed}={$flag_extern_value}` in dependency `{$extern_crate}`
+    .help = the `{$flag_name_prefixed}` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely
+
+metadata_incompatible_target_modifiers_help_allow = if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch={$flag_name}` to silence this error
+metadata_incompatible_target_modifiers_help_fix = set `{$flag_name_prefixed}={$flag_extern_value}` in this crate or `{$flag_name_prefixed}={$flag_local_value}` in `{$extern_crate}`
+
 metadata_incompatible_wasm_link =
     `wasm_import_module` is incompatible with other arguments in `#[link]` attributes
 
@@ -284,6 +292,8 @@ metadata_unknown_link_kind =
 metadata_unknown_link_modifier =
     unknown linking modifier `{$modifier}`, expected one of: bundle, verbatim, whole-archive, as-needed
 
+metadata_unknown_target_modifier_unsafe_allowed = unknown target modifier `{$flag_name}`, requested by `-Cunsafe-allow-abi-mismatch={$flag_name}`
+
 metadata_unsupported_abi =
     ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture
 
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index a6cd0ecafd0de..6bad831279005 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -23,7 +23,10 @@ use rustc_hir::definitions::Definitions;
 use rustc_index::IndexVec;
 use rustc_middle::bug;
 use rustc_middle::ty::{TyCtxt, TyCtxtFeed};
-use rustc_session::config::{self, CrateType, ExternLocation};
+use rustc_session::config::{
+    self, CrateType, ExtendedTargetModifierInfo, ExternLocation, OptionsTargetModifiers,
+    TargetModifier,
+};
 use rustc_session::cstore::{CrateDepKind, CrateSource, ExternCrate, ExternCrateSource};
 use rustc_session::lint::{self, BuiltinLintDiag};
 use rustc_session::output::validate_crate_name;
@@ -35,7 +38,9 @@ use tracing::{debug, info, trace};
 
 use crate::errors;
 use crate::locator::{CrateError, CrateLocator, CratePaths};
-use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob};
+use crate::rmeta::{
+    CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob, TargetModifiers,
+};
 
 /// The backend's way to give the crate store access to the metadata in a library.
 /// Note that it returns the raw metadata bytes stored in the library file, whether
@@ -296,6 +301,94 @@ impl CStore {
         }
     }
 
+    fn report_target_modifiers_extended(
+        tcx: TyCtxt<'_>,
+        krate: &Crate,
+        mods: &TargetModifiers,
+        dep_mods: &TargetModifiers,
+        data: &CrateMetadata,
+    ) {
+        let span = krate.spans.inner_span.shrink_to_lo();
+        let allowed_flag_mismatches = &tcx.sess.opts.cg.unsafe_allow_abi_mismatch;
+        let name = tcx.crate_name(LOCAL_CRATE);
+        let tmod_extender = |tmod: &TargetModifier| (tmod.extend(), tmod.clone());
+        let report_diff = |prefix: &String,
+                           opt_name: &String,
+                           flag_local_value: &String,
+                           flag_extern_value: &String| {
+            if allowed_flag_mismatches.contains(&opt_name) {
+                return;
+            }
+            tcx.dcx().emit_err(errors::IncompatibleTargetModifiers {
+                span,
+                extern_crate: data.name(),
+                local_crate: name,
+                flag_name: opt_name.clone(),
+                flag_name_prefixed: format!("-{}{}", prefix, opt_name),
+                flag_local_value: flag_local_value.to_string(),
+                flag_extern_value: flag_extern_value.to_string(),
+            });
+        };
+        let mut it1 = mods.iter().map(tmod_extender);
+        let mut it2 = dep_mods.iter().map(tmod_extender);
+        let mut left_name_val: Option<(ExtendedTargetModifierInfo, TargetModifier)> = None;
+        let mut right_name_val: Option<(ExtendedTargetModifierInfo, TargetModifier)> = None;
+        let no_val = "*".to_string();
+        loop {
+            left_name_val = left_name_val.or_else(|| it1.next());
+            right_name_val = right_name_val.or_else(|| it2.next());
+            match (&left_name_val, &right_name_val) {
+                (Some(l), Some(r)) => match l.1.opt.cmp(&r.1.opt) {
+                    cmp::Ordering::Equal => {
+                        if l.0.tech_value != r.0.tech_value {
+                            report_diff(&l.0.prefix, &l.0.name, &l.1.value_name, &r.1.value_name);
+                        }
+                        left_name_val = None;
+                        right_name_val = None;
+                    }
+                    cmp::Ordering::Greater => {
+                        report_diff(&r.0.prefix, &r.0.name, &no_val, &r.1.value_name);
+                        right_name_val = None;
+                    }
+                    cmp::Ordering::Less => {
+                        report_diff(&l.0.prefix, &l.0.name, &l.1.value_name, &no_val);
+                        left_name_val = None;
+                    }
+                },
+                (Some(l), None) => {
+                    report_diff(&l.0.prefix, &l.0.name, &l.1.value_name, &no_val);
+                    left_name_val = None;
+                }
+                (None, Some(r)) => {
+                    report_diff(&r.0.prefix, &r.0.name, &no_val, &r.1.value_name);
+                    right_name_val = None;
+                }
+                (None, None) => break,
+            }
+        }
+    }
+
+    pub fn report_incompatible_target_modifiers(&self, tcx: TyCtxt<'_>, krate: &Crate) {
+        for flag_name in &tcx.sess.opts.cg.unsafe_allow_abi_mismatch {
+            if !OptionsTargetModifiers::is_target_modifier(flag_name) {
+                tcx.dcx().emit_err(errors::UnknownTargetModifierUnsafeAllowed {
+                    span: krate.spans.inner_span.shrink_to_lo(),
+                    flag_name: flag_name.clone(),
+                });
+            }
+        }
+        let mods = tcx.sess.opts.gather_target_modifiers();
+        for (_cnum, data) in self.iter_crate_data() {
+            if data.is_proc_macro_crate() {
+                continue;
+            }
+            let dep_mods = data.target_modifiers();
+            if mods != dep_mods {
+                Self::report_target_modifiers_extended(tcx, krate, &mods, &dep_mods, data);
+            }
+        }
+    }
+
     pub fn new(metadata_loader: Box<MetadataLoaderDyn>) -> CStore {
         CStore {
             metadata_loader,
diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs
index cefc6498f68fe..a77f9bc623b5b 100644
--- a/compiler/rustc_metadata/src/errors.rs
+++ b/compiler/rustc_metadata/src/errors.rs
@@ -739,3 +739,28 @@ pub(crate) struct WasmCAbi {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(metadata_incompatible_target_modifiers)]
+#[help]
+#[note]
+#[help(metadata_incompatible_target_modifiers_help_fix)]
+#[help(metadata_incompatible_target_modifiers_help_allow)]
+pub struct IncompatibleTargetModifiers {
+    #[primary_span]
+    pub span: Span,
+    pub extern_crate: Symbol,
+    pub local_crate: Symbol,
+    pub flag_name: String,
+    pub flag_name_prefixed: String,
+    pub flag_local_value: String,
+    pub flag_extern_value: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(metadata_unknown_target_modifier_unsafe_allowed)]
+pub struct UnknownTargetModifierUnsafeAllowed {
+    #[primary_span]
+    pub span: Span,
+    pub flag_name: String,
+}
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index e02c4871f35bd..2ae4a6a6066d0 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -29,6 +29,7 @@ use rustc_middle::{bug, implement_ty_decoder};
 use rustc_serialize::opaque::MemDecoder;
 use rustc_serialize::{Decodable, Decoder};
 use rustc_session::Session;
+use rustc_session::config::TargetModifier;
 use rustc_session::cstore::{CrateSource, ExternCrate};
 use rustc_span::hygiene::HygieneDecodeContext;
 use rustc_span::{BytePos, DUMMY_SP, Pos, SpanData, SpanDecoder, SyntaxContext, kw};
@@ -73,6 +74,9 @@ impl MetadataBlob {
 /// own crate numbers.
 pub(crate) type CrateNumMap = IndexVec<CrateNum, CrateNum>;
 
+/// Target modifiers - abi or exploit mitigations flags
+pub(crate) type TargetModifiers = Vec<TargetModifier>;
+
 pub(crate) struct CrateMetadata {
     /// The primary crate data - binary metadata blob.
     blob: MetadataBlob,
@@ -961,6 +965,13 @@ impl CrateRoot {
     ) -> impl ExactSizeIterator<Item = CrateDep> + Captures<'a> {
         self.crate_deps.decode(metadata)
     }
+
+    pub(crate) fn decode_target_modifiers<'a>(
+        &self,
+        metadata: &'a MetadataBlob,
+    ) -> impl ExactSizeIterator<Item = TargetModifier> + Captures<'a> {
+        self.target_modifiers.decode(metadata)
+    }
 }
 
 impl<'a> CrateMetadataRef<'a> {
@@ -1883,6 +1894,10 @@ impl CrateMetadata {
         self.dependencies.push(cnum);
     }
 
+    pub(crate) fn target_modifiers(&self) -> TargetModifiers {
+        self.root.decode_target_modifiers(&self.blob).collect()
+    }
+
     pub(crate) fn update_extern_crate(&mut self, new_extern_crate: ExternCrate) -> bool {
         let update =
             Some(new_extern_crate.rank()) > self.extern_crate.as_ref().map(ExternCrate::rank);
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index c538ab99fb54b..161388a290b0f 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -25,7 +25,7 @@ use rustc_middle::ty::{AssocItemContainer, SymbolName};
 use rustc_middle::util::common::to_readable_str;
 use rustc_middle::{bug, span_bug};
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, opaque};
-use rustc_session::config::{CrateType, OptLevel};
+use rustc_session::config::{CrateType, OptLevel, TargetModifier};
 use rustc_span::hygiene::HygieneEncodeContext;
 use rustc_span::{
     ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, StableSourceFileId, SyntaxContext,
@@ -692,6 +692,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         // Encode source_map. This needs to be done last, because encoding `Span`s tells us which
         // `SourceFiles` we actually need to encode.
         let source_map = stat!("source-map", || self.encode_source_map());
+        let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers());
 
         let root = stat!("final", || {
             let attrs = tcx.hir().krate_attrs();
@@ -735,6 +736,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
                 native_libraries,
                 foreign_modules,
                 source_map,
+                target_modifiers,
                 traits,
                 impls,
                 incoherent_impls,
@@ -2009,6 +2011,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         self.lazy_array(deps.iter().map(|(_, dep)| dep))
     }
 
+    fn encode_target_modifiers(&mut self) -> LazyArray<TargetModifier> {
+        empty_proc_macro!(self);
+        let tcx = self.tcx;
+        self.lazy_array(tcx.sess.opts.gather_target_modifiers())
+    }
+
     fn encode_lib_features(&mut self) -> LazyArray<(Symbol, FeatureStability)> {
         empty_proc_macro!(self);
         let tcx = self.tcx;
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 4f9cdc9a474bd..fd0f9bd05b31c 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -1,7 +1,7 @@
 use std::marker::PhantomData;
 use std::num::NonZero;
 
-pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob};
+pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob, TargetModifiers};
 use decoder::{DecodeContext, Metadata};
 use def_path_hash_map::DefPathHashMapRef;
 use encoder::EncodeContext;
@@ -32,7 +32,7 @@ use rustc_middle::ty::{
 use rustc_middle::util::Providers;
 use rustc_middle::{mir, trivially_parameterized_over_tcx};
 use rustc_serialize::opaque::FileEncoder;
-use rustc_session::config::SymbolManglingVersion;
+use rustc_session::config::{SymbolManglingVersion, TargetModifier};
 use rustc_session::cstore::{CrateDepKind, ForeignModule, LinkagePreference, NativeLib};
 use rustc_span::edition::Edition;
 use rustc_span::hygiene::{ExpnIndex, MacroKind, SyntaxContextData};
@@ -282,6 +282,7 @@ pub(crate) struct CrateRoot {
     def_path_hash_map: LazyValue<DefPathHashMapRef<'static>>,
 
     source_map: LazyTable<u32, Option<LazyValue<rustc_span::SourceFile>>>,
+    target_modifiers: LazyArray<TargetModifier>,
 
     compiler_builtins: bool,
     needs_allocator: bool,
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index 6b6c6f3c72feb..8eaf0a58f7086 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -101,6 +101,7 @@ trivially_parameterized_over_tcx! {
     rustc_session::cstore::ForeignModule,
     rustc_session::cstore::LinkagePreference,
     rustc_session::cstore::NativeLib,
+    rustc_session::config::TargetModifier,
     rustc_span::ExpnData,
     rustc_span::ExpnHash,
     rustc_span::ExpnId,
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 97bd2670aa640..a301a04580181 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1191,6 +1191,7 @@ impl Default for Options {
             color: ColorConfig::Auto,
             logical_env: FxIndexMap::default(),
             verbose: false,
+            target_modifiers: BTreeMap::default(),
         }
     }
 }
@@ -2337,14 +2338,16 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
     let crate_types = parse_crate_types_from_list(unparsed_crate_types)
         .unwrap_or_else(|e| early_dcx.early_fatal(e));
 
-    let mut unstable_opts = UnstableOptions::build(early_dcx, matches);
+    let mut target_modifiers = BTreeMap::<OptionsTargetModifiers, String>::new();
+
+    let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers);
     let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
 
     check_error_format_stability(early_dcx, &unstable_opts, error_format);
 
     let output_types = parse_output_types(early_dcx, &unstable_opts, matches);
 
-    let mut cg = CodegenOptions::build(early_dcx, matches);
+    let mut cg = CodegenOptions::build(early_dcx, matches, &mut target_modifiers);
     let (disable_local_thinlto, codegen_units) = should_override_cgus_and_disable_thinlto(
         early_dcx,
         &output_types,
@@ -2615,6 +2618,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
         color,
         logical_env,
         verbose,
+        target_modifiers,
     }
 }
 
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
index dcf86d1a4089f..924350b8cbcdf 100644
--- a/compiler/rustc_session/src/lib.rs
+++ b/compiler/rustc_session/src/lib.rs
@@ -4,6 +4,9 @@
 #![feature(let_chains)]
 #![feature(map_many_mut)]
 #![feature(rustc_attrs)]
+// To generate CodegenOptionsTargetModifiers and UnstableOptionsTargetModifiers enums
+// with macro_rules, it is necessary to use recursive mechanic ("Incremental TT Munchers").
+#![recursion_limit = "256"]
 #![warn(unreachable_pub)]
 // tidy-alphabetical-end
 
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 63aaa3abc8e56..b804a5881ccd2 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -10,6 +10,7 @@ use rustc_data_structures::profiling::TimePassesFormat;
 use rustc_data_structures::stable_hasher::Hash64;
 use rustc_errors::{ColorConfig, LanguageIdentifier, TerminalUrl};
 use rustc_feature::UnstableFeatures;
+use rustc_macros::{Decodable, Encodable};
 use rustc_span::edition::Edition;
 use rustc_span::{RealFileName, SourceFileHashAlgorithm};
 use rustc_target::spec::{
@@ -59,18 +60,194 @@ macro_rules! hash_substruct {
     };
 }
 
+/// Extended target modifier info.
+/// For example, when external target modifier is '-Zregparm=2':
+/// Target modifier enum value + user value ('2') from external crate
+/// is converted into description: prefix ('Z'), name ('regparm'), tech value ('Some(2)').
+pub struct ExtendedTargetModifierInfo {
+    /// Flag prefix (usually, 'C' for codegen flags or 'Z' for unstable flags)
+    pub prefix: String,
+    /// Flag name
+    pub name: String,
+    /// Flag parsed technical value
+    pub tech_value: String,
+}
+
+/// A recorded -Zopt_name=opt_value (or -Copt_name=opt_value)
+/// which alter the ABI or effectiveness of exploit mitigations.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable)]
+pub struct TargetModifier {
+    /// Option enum value
+    pub opt: OptionsTargetModifiers,
+    /// User-provided option value (before parsing)
+    pub value_name: String,
+}
+
+impl TargetModifier {
+    pub fn extend(&self) -> ExtendedTargetModifierInfo {
+        self.opt.reparse(&self.value_name)
+    }
+}
+
+fn tmod_push_impl(
+    opt: OptionsTargetModifiers,
+    tmod_vals: &BTreeMap<OptionsTargetModifiers, String>,
+    tmods: &mut Vec<TargetModifier>,
+) {
+    tmods.push(TargetModifier { opt, value_name: tmod_vals.get(&opt).cloned().unwrap_or_default() })
+}
+
+macro_rules! tmod_push {
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $mods:expr, $tmod_vals:expr) => {
+        tmod_push_impl(
+            OptionsTargetModifiers::$struct_name($tmod_enum_name::$opt_name),
+            $tmod_vals,
+            $mods,
+        );
+    };
+}
+
+macro_rules! gather_tmods {
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [SUBSTRUCT], [TARGET_MODIFIER]) => {
+        compile_error!("SUBSTRUCT can't be target modifier");
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [UNTRACKED], [TARGET_MODIFIER]) => {
+        tmod_push!($struct_name, $tmod_enum_name, $opt_name, $mods, $tmod_vals)
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [TRACKED], [TARGET_MODIFIER]) => {
+        tmod_push!($struct_name, $tmod_enum_name, $opt_name, $mods, $tmod_vals)
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [TRACKED_NO_CRATE_HASH], [TARGET_MODIFIER]) => {
+        tmod_push!($struct_name, $tmod_enum_name, $opt_name, $mods, $tmod_vals)
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [SUBSTRUCT], []) => {
+        $opt_expr.gather_target_modifiers($mods, $tmod_vals);
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [UNTRACKED], []) => {{}};
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [TRACKED], []) => {{}};
+    ($struct_name:ident, $tmod_enum_name:ident, $opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr,
+        [TRACKED_NO_CRATE_HASH], []) => {{}};
+}
+
+macro_rules! gather_tmods_top_level {
+    ($_opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [SUBSTRUCT $substruct_enum:ident]) => {
+        $opt_expr.gather_target_modifiers($mods, $tmod_vals);
+    };
+    ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [$non_substruct:ident TARGET_MODIFIER]) => {
+        compile_error!("Top level option can't be target modifier");
+    };
+    ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [$non_substruct:ident]) => {};
+}
+
+/// Macro for generating OptionsTargetsModifiers top-level enum with impl.
+/// Will generate something like:
+/// ```rust,ignore (illustrative)
+/// pub enum OptionsTargetModifiers {
+///     CodegenOptions(CodegenOptionsTargetModifiers),
+///     UnstableOptions(UnstableOptionsTargetModifiers),
+/// }
+/// impl OptionsTargetModifiers {
+///     pub fn reparse(&self, user_value: &str) -> ExtendedTargetModifierInfo {
+///         match self {
+///             Self::CodegenOptions(v) => v.reparse(user_value),
+///             Self::UnstableOptions(v) => v.reparse(user_value),
+///         }
+///     }
+///     pub fn is_target_modifier(flag_name: &str) -> bool {
+///         CodegenOptionsTargetModifiers::is_target_modifier(flag_name) ||
+///         UnstableOptionsTargetModifiers::is_target_modifier(flag_name)
+///     }
+/// }
+/// ```
+macro_rules! top_level_tmod_enum {
+    ($( {$($optinfo:tt)*} ),* $(,)*) => {
+        top_level_tmod_enum! { @parse {}, (user_value){}; $($($optinfo)*|)* }
+    };
+    // Termination
+    (
+        @parse
+        {$($variant:tt($substruct_enum:tt))*},
+        ($user_value:ident){$($pout:tt)*};
+    ) => {
+        #[allow(non_camel_case_types)]
+        #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Encodable, Decodable)]
+        pub enum OptionsTargetModifiers {
+            $($variant($substruct_enum)),*
+        }
+        impl OptionsTargetModifiers {
+            #[allow(unused_variables)]
+            pub fn reparse(&self, $user_value: &str) -> ExtendedTargetModifierInfo {
+                #[allow(unreachable_patterns)]
+                match self {
+                    $($pout)*
+                    _ => panic!("unknown target modifier option: {:?}", *self)
+                }
+            }
+            pub fn is_target_modifier(flag_name: &str) -> bool {
+                $($substruct_enum::is_target_modifier(flag_name))||*
+            }
+        }
+    };
+    // Adding SUBSTRUCT option group into $eout
+    (
+        @parse {$($eout:tt)*}, ($puser_value:ident){$($pout:tt)*};
+            [SUBSTRUCT $substruct_enum:ident $variant:ident] |
+        $($tail:tt)*
+    ) => {
+        top_level_tmod_enum! {
+            @parse
+            {
+                $($eout)*
+                $variant($substruct_enum)
+            },
+            ($puser_value){
+                $($pout)*
+                Self::$variant(v) => v.reparse($puser_value),
+            };
+            $($tail)*
+        }
+    };
+    // Skipping non-target-modifier and non-substruct
+    (
+        @parse {$($eout:tt)*}, ($puser_value:ident){$($pout:tt)*};
+            [$non_substruct:ident] |
+        $($tail:tt)*
+    ) => {
+        top_level_tmod_enum! {
+            @parse
+            {
+                $($eout)*
+            },
+            ($puser_value){
+                $($pout)*
+            };
+            $($tail)*
+        }
+    };
+}
+
 macro_rules! top_level_options {
     ( $( #[$top_level_attr:meta] )* pub struct Options { $(
         $( #[$attr:meta] )*
-        $opt:ident : $t:ty [$dep_tracking_marker:ident],
+        $opt:ident : $t:ty [$dep_tracking_marker:ident $( $tmod:ident $variant:ident )?],
     )* } ) => (
+        top_level_tmod_enum!( {$([$dep_tracking_marker $($tmod $variant),*])|*} );
+
         #[derive(Clone)]
         $( #[$top_level_attr] )*
         pub struct Options {
             $(
                 $( #[$attr] )*
                 pub $opt: $t
-            ),*
+            ),*,
+            pub target_modifiers: BTreeMap<OptionsTargetModifiers, String>,
         }
 
         impl Options {
@@ -98,6 +275,17 @@ macro_rules! top_level_options {
                 })*
                 hasher.finish()
             }
+
+            pub fn gather_target_modifiers(&self) -> Vec<TargetModifier> {
+                let mut mods = Vec::<TargetModifier>::new();
+                $({
+                    gather_tmods_top_level!($opt,
+                        &self.$opt, &mut mods, &self.target_modifiers,
+                        [$dep_tracking_marker $($tmod),*]);
+                })*
+                mods.sort_by(|a, b| a.opt.cmp(&b.opt));
+                mods
+            }
         }
     );
 }
@@ -165,9 +353,9 @@ top_level_options!(
         #[rustc_lint_opt_deny_field_access("should only be used via `Config::hash_untracked_state`")]
         untracked_state_hash: Hash64 [TRACKED_NO_CRATE_HASH],
 
-        unstable_opts: UnstableOptions [SUBSTRUCT],
+        unstable_opts: UnstableOptions [SUBSTRUCT UnstableOptionsTargetModifiers UnstableOptions],
         prints: Vec<PrintRequest> [UNTRACKED],
-        cg: CodegenOptions [SUBSTRUCT],
+        cg: CodegenOptions [SUBSTRUCT CodegenOptionsTargetModifiers CodegenOptions],
         externs: Externs [UNTRACKED],
         crate_name: Option<String> [TRACKED],
         /// Indicates how the compiler should treat unstable features.
@@ -226,6 +414,98 @@ top_level_options!(
     }
 );
 
+macro_rules! tmod_enum_opt {
+    ($struct_name:ident, $tmod_enum_name:ident, $opt:ident, $v:ident) => {
+        Some(OptionsTargetModifiers::$struct_name($tmod_enum_name::$opt))
+    };
+    ($struct_name:ident, $tmod_enum_name:ident, $opt:ident, ) => {
+        None
+    };
+}
+
+macro_rules! tmod_enum {
+    ($tmod_enum_name:ident, $prefix:expr, $( {$($optinfo:tt)*} ),* $(,)*) => {
+        tmod_enum! { $tmod_enum_name, $prefix, @parse {}, (user_value){}; $($($optinfo)*|)* }
+    };
+    // Termination
+    (
+        $tmod_enum_name:ident, $prefix:expr,
+        @parse
+        {$($eout:tt)*},
+        ($user_value:ident){$($pout:tt)*};
+    ) => {
+        #[allow(non_camel_case_types)]
+        #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Encodable, Decodable)]
+        pub enum $tmod_enum_name {
+            $($eout),*
+        }
+        impl $tmod_enum_name {
+            #[allow(unused_variables)]
+            pub fn reparse(&self, $user_value: &str) -> ExtendedTargetModifierInfo {
+                #[allow(unreachable_patterns)]
+                match self {
+                    $($pout)*
+                    _ => panic!("unknown target modifier option: {:?}", *self)
+                }
+            }
+            pub fn is_target_modifier(flag_name: &str) -> bool {
+                match flag_name.replace('-', "_").as_str() {
+                    $(stringify!($eout) => true,)*
+                    _ => false,
+                }
+            }
+        }
+    };
+    // Adding target-modifier option into $eout
+    (
+        $tmod_enum_name:ident, $prefix:expr,
+        @parse {$($eout:tt)*}, ($puser_value:ident){$($pout:tt)*};
+            $opt:ident, $parse:ident, $t:ty, [TARGET_MODIFIER] |
+        $($tail:tt)*
+    ) => {
+        tmod_enum! {
+            $tmod_enum_name, $prefix,
+            @parse
+            {
+                $($eout)*
+                $opt
+            },
+            ($puser_value){
+                $($pout)*
+                Self::$opt => {
+                    let mut parsed : $t = Default::default();
+                    parse::$parse(&mut parsed, Some($puser_value));
+                    ExtendedTargetModifierInfo {
+                        prefix: $prefix.to_string(),
+                        name: stringify!($opt).to_string().replace('_', "-"),
+                        tech_value: format!("{:?}", parsed),
+                    }
+                },
+            };
+            $($tail)*
+        }
+    };
+    // Skipping non-target-modifier
+    (
+        $tmod_enum_name:ident, $prefix:expr,
+        @parse {$($eout:tt)*}, ($puser_value:ident){$($pout:tt)*};
+            $opt:ident, $parse:ident, $t:ty, [] |
+        $($tail:tt)*
+    ) => {
+        tmod_enum! {
+            $tmod_enum_name, $prefix,
+            @parse
+            {
+                $($eout)*
+            },
+            ($puser_value){
+                $($pout)*
+            };
+            $($tail)*
+        }
+    };
+}
+
 /// Defines all `CodegenOptions`/`DebuggingOptions` fields and parsers all at once. The goal of this
 /// macro is to define an interface that can be programmatically used by the option parser
 /// to initialize the struct without hardcoding field names all over the place.
@@ -235,11 +515,11 @@ top_level_options!(
 /// generated code to parse an option into its respective field in the struct. There are a few
 /// hand-written parsers for parsing specific types of values in this module.
 macro_rules! options {
-    ($struct_name:ident, $stat:ident, $optmod:ident, $prefix:expr, $outputname:expr,
+    ($struct_name:ident, $tmod_enum_name:ident, $stat:ident, $optmod:ident, $prefix:expr, $outputname:expr,
      $($( #[$attr:meta] )* $opt:ident : $t:ty = (
         $init:expr,
         $parse:ident,
-        [$dep_tracking_marker:ident],
+        [$dep_tracking_marker:ident $( $tmod:ident )?],
         $desc:expr
         $(, deprecated_do_nothing: $dnn:literal )?)
      ),* ,) =>
@@ -248,6 +528,8 @@ macro_rules! options {
     #[rustc_lint_opt_ty]
     pub struct $struct_name { $( $( #[$attr] )* pub $opt: $t),* }
 
+    tmod_enum!( $tmod_enum_name, $prefix, {$($opt, $parse, $t, [$($tmod),*])|*} );
+
     impl Default for $struct_name {
         fn default() -> $struct_name {
             $struct_name { $($opt: $init),* }
@@ -258,8 +540,9 @@ macro_rules! options {
         pub fn build(
             early_dcx: &EarlyDiagCtxt,
             matches: &getopts::Matches,
+            target_modifiers: &mut BTreeMap<OptionsTargetModifiers, String>,
         ) -> $struct_name {
-            build_options(early_dcx, matches, $stat, $prefix, $outputname)
+            build_options(early_dcx, matches, target_modifiers, $stat, $prefix, $outputname)
         }
 
         fn dep_tracking_hash(&self, for_crate_hash: bool, error_format: ErrorOutputType) -> u64 {
@@ -279,11 +562,23 @@ macro_rules! options {
                                         );
             hasher.finish()
         }
+
+        pub fn gather_target_modifiers(
+            &self,
+            _mods: &mut Vec<TargetModifier>,
+            _tmod_vals: &BTreeMap<OptionsTargetModifiers, String>,
+        ) {
+            $({
+                gather_tmods!($struct_name, $tmod_enum_name, $opt, &self.$opt, _mods, _tmod_vals,
+                    [$dep_tracking_marker], [$($tmod),*]);
+            })*
+        }
     }
 
     pub const $stat: OptionDescrs<$struct_name> =
         &[ $( OptionDesc{ name: stringify!($opt), setter: $optmod::$opt,
-            type_desc: desc::$parse, desc: $desc, is_deprecated_and_do_nothing: false $( || $dnn )? } ),* ];
+            type_desc: desc::$parse, desc: $desc, is_deprecated_and_do_nothing: false $( || $dnn )?,
+            tmod: tmod_enum_opt!($struct_name, $tmod_enum_name, $opt, $($tmod),*) } ),* ];
 
     mod $optmod {
     $(
@@ -328,6 +623,7 @@ pub struct OptionDesc<O> {
     // description for option from options table
     desc: &'static str,
     is_deprecated_and_do_nothing: bool,
+    tmod: Option<OptionsTargetModifiers>,
 }
 
 impl<O> OptionDesc<O> {
@@ -344,6 +640,7 @@ impl<O> OptionDesc<O> {
 fn build_options<O: Default>(
     early_dcx: &EarlyDiagCtxt,
     matches: &getopts::Matches,
+    target_modifiers: &mut BTreeMap<OptionsTargetModifiers, String>,
     descrs: OptionDescrs<O>,
     prefix: &str,
     outputname: &str,
@@ -357,7 +654,14 @@ fn build_options<O: Default>(
 
         let option_to_lookup = key.replace('-', "_");
         match descrs.iter().find(|opt_desc| opt_desc.name == option_to_lookup) {
-            Some(OptionDesc { name: _, setter, type_desc, desc, is_deprecated_and_do_nothing }) => {
+            Some(OptionDesc {
+                name: _,
+                setter,
+                type_desc,
+                desc,
+                is_deprecated_and_do_nothing,
+                tmod,
+            }) => {
                 if *is_deprecated_and_do_nothing {
                     // deprecation works for prefixed options only
                     assert!(!prefix.is_empty());
@@ -377,6 +681,11 @@ fn build_options<O: Default>(
                         ),
                     }
                 }
+                if let Some(tmod) = *tmod
+                    && let Some(value) = value
+                {
+                    target_modifiers.insert(tmod, value.to_string());
+                }
             }
             None => early_dcx.early_fatal(format!("unknown {outputname} option: `{key}`")),
         }
@@ -1581,7 +1890,7 @@ pub mod parse {
 }
 
 options! {
-    CodegenOptions, CG_OPTIONS, cgopts, "C", "codegen",
+    CodegenOptions, CodegenOptionsTargetModifiers, CG_OPTIONS, cgopts, "C", "codegen",
 
     // If you add a new option, please update:
     // - compiler/rustc_interface/src/tests.rs
@@ -1712,6 +2021,8 @@ options! {
     target_feature: String = (String::new(), parse_target_feature, [TRACKED],
         "target specific attributes. (`rustc --print target-features` for details). \
         This feature is unsafe."),
+    unsafe_allow_abi_mismatch: Vec<String> = (Vec::new(), parse_comma_list, [UNTRACKED],
+        "Allow incompatible target modifiers in dependency crates (comma separated list)"),
     // tidy-alphabetical-end
 
     // If you add a new option, please update:
@@ -1720,7 +2031,7 @@ options! {
 }
 
 options! {
-    UnstableOptions, Z_OPTIONS, dbopts, "Z", "unstable",
+    UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable",
 
     // If you add a new option, please update:
     // - compiler/rustc_interface/src/tests.rs
@@ -2052,10 +2363,10 @@ options! {
         "enable queries of the dependency graph for regression testing (default: no)"),
     randomize_layout: bool = (false, parse_bool, [TRACKED],
         "randomize the layout of types (default: no)"),
-    reg_struct_return: bool = (false, parse_bool, [TRACKED],
+    reg_struct_return: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER],
         "On x86-32 targets, it overrides the default ABI to return small structs in registers.
         It is UNSOUND to link together crates that use different values for this flag!"),
-    regparm: Option<u32> = (None, parse_opt_number, [TRACKED],
+    regparm: Option<u32> = (None, parse_opt_number, [TRACKED TARGET_MODIFIER],
         "On x86-32 targets, setting this to N causes the compiler to pass N arguments \
         in registers EAX, EDX, and ECX instead of on the stack for\
         \"C\", \"cdecl\", and \"stdcall\" fn.\
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 80bc6cebd2aa9..7437f2045c59c 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -9,8 +9,8 @@ use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::DiagCtxtHandle;
 use rustc_session::config::{
     self, CodegenOptions, CrateType, ErrorOutputType, Externs, Input, JsonUnusedExterns,
-    UnstableOptions, get_cmd_lint_options, nightly_options, parse_crate_types_from_list,
-    parse_externs, parse_target_triple,
+    OptionsTargetModifiers, UnstableOptions, get_cmd_lint_options, nightly_options,
+    parse_crate_types_from_list, parse_externs, parse_target_triple,
 };
 use rustc_session::lint::Level;
 use rustc_session::search_paths::SearchPath;
@@ -387,8 +387,9 @@ impl Options {
             config::parse_error_format(early_dcx, matches, color, json_color, json_rendered);
         let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default();
 
-        let codegen_options = CodegenOptions::build(early_dcx, matches);
-        let unstable_opts = UnstableOptions::build(early_dcx, matches);
+        let mut target_modifiers = BTreeMap::<OptionsTargetModifiers, String>::new();
+        let codegen_options = CodegenOptions::build(early_dcx, matches, &mut target_modifiers);
+        let unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers);
 
         let remap_path_prefix = match parse_remap_path_prefix(matches) {
             Ok(prefix_mappings) => prefix_mappings,
diff --git a/tests/ui/target_modifiers/auxiliary/default_reg_struct_return.rs b/tests/ui/target_modifiers/auxiliary/default_reg_struct_return.rs
new file mode 100644
index 0000000000000..355e7c56e9462
--- /dev/null
+++ b/tests/ui/target_modifiers/auxiliary/default_reg_struct_return.rs
@@ -0,0 +1,20 @@
+//@ compile-flags: --target i686-unknown-linux-gnu -Cpanic=abort
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+#![crate_type = "rlib"]
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+#[lang = "sized"]
+trait Sized {}
+#[lang = "copy"]
+trait Copy {}
+
+pub fn somefun() {}
+
+pub struct S;
diff --git a/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs b/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs
new file mode 100644
index 0000000000000..2e16f1ee747c8
--- /dev/null
+++ b/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs
@@ -0,0 +1,20 @@
+//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=2 -Cpanic=abort
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+#![crate_type = "rlib"]
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+#[lang = "sized"]
+trait Sized {}
+#[lang = "copy"]
+trait Copy {}
+
+pub fn somefun() {}
+
+pub struct S;
diff --git a/tests/ui/target_modifiers/auxiliary/wrong_regparm_and_ret.rs b/tests/ui/target_modifiers/auxiliary/wrong_regparm_and_ret.rs
new file mode 100644
index 0000000000000..39c6be9d5892e
--- /dev/null
+++ b/tests/ui/target_modifiers/auxiliary/wrong_regparm_and_ret.rs
@@ -0,0 +1,20 @@
+//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=2 -Zreg-struct-return=true -Cpanic=abort
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+#![crate_type = "rlib"]
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+#[lang = "sized"]
+trait Sized {}
+#[lang = "copy"]
+trait Copy {}
+
+pub fn somefun() {}
+
+pub struct S;
diff --git a/tests/ui/target_modifiers/defaults_check.error.stderr b/tests/ui/target_modifiers/defaults_check.error.stderr
new file mode 100644
index 0000000000000..c545dd710690a
--- /dev/null
+++ b/tests/ui/target_modifiers/defaults_check.error.stderr
@@ -0,0 +1,13 @@
+error: mixing `-Zreg-struct-return` will cause an ABI mismatch in crate `defaults_check`
+  --> $DIR/defaults_check.rs:20:1
+   |
+LL | #![crate_type = "rlib"]
+   | ^
+   |
+   = help: the `-Zreg-struct-return` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely
+   = note: `-Zreg-struct-return=true` in this crate is incompatible with `-Zreg-struct-return=` in dependency `default_reg_struct_return`
+   = help: set `-Zreg-struct-return=` in this crate or `-Zreg-struct-return=true` in `default_reg_struct_return`
+   = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=reg-struct-return` to silence this error
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/target_modifiers/defaults_check.rs b/tests/ui/target_modifiers/defaults_check.rs
new file mode 100644
index 0000000000000..b8f4848d3a422
--- /dev/null
+++ b/tests/ui/target_modifiers/defaults_check.rs
@@ -0,0 +1,27 @@
+// Tests that default unspecified target modifier value in dependency crate is ok linked
+// with the same value, explicitly specified
+//@ aux-crate:default_reg_struct_return=default_reg_struct_return.rs
+//@ compile-flags: --target i686-unknown-linux-gnu -Cpanic=abort
+//@ revisions:error ok ok_explicit
+//@[ok] compile-flags:
+//@[ok_explicit] compile-flags: -Zreg-struct-return=false
+//@[error] compile-flags: -Zreg-struct-return=true
+
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+//@[ok] build-pass
+//@[ok_explicit] build-pass
+
+#![crate_type = "rlib"]
+//[error]~^ ERROR mixing `-Zreg-struct-return` will cause an ABI mismatch in crate `defaults_check`
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+fn foo() {
+    default_reg_struct_return::somefun();
+}
diff --git a/tests/ui/target_modifiers/incompatible_regparm.allow_no_value.stderr b/tests/ui/target_modifiers/incompatible_regparm.allow_no_value.stderr
new file mode 100644
index 0000000000000..8597332825b56
--- /dev/null
+++ b/tests/ui/target_modifiers/incompatible_regparm.allow_no_value.stderr
@@ -0,0 +1,2 @@
+error: codegen option `unsafe-allow-abi-mismatch` requires a comma-separated list of strings (C unsafe-allow-abi-mismatch=<value>)
+
diff --git a/tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr b/tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr
new file mode 100644
index 0000000000000..692fc7a4e3fa7
--- /dev/null
+++ b/tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr
@@ -0,0 +1,13 @@
+error: mixing `-Zregparm` will cause an ABI mismatch in crate `incompatible_regparm`
+  --> $DIR/incompatible_regparm.rs:16:1
+   |
+LL | #![crate_type = "rlib"]
+   | ^
+   |
+   = help: the `-Zregparm` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely
+   = note: `-Zregparm=1` in this crate is incompatible with `-Zregparm=2` in dependency `wrong_regparm`
+   = help: set `-Zregparm=2` in this crate or `-Zregparm=1` in `wrong_regparm`
+   = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=regparm` to silence this error
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/target_modifiers/incompatible_regparm.rs b/tests/ui/target_modifiers/incompatible_regparm.rs
new file mode 100644
index 0000000000000..e866c5aa891b0
--- /dev/null
+++ b/tests/ui/target_modifiers/incompatible_regparm.rs
@@ -0,0 +1,23 @@
+//@ aux-crate:wrong_regparm=wrong_regparm.rs
+//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=1 -Cpanic=abort
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+//@ revisions:error_generated allow_regparm_mismatch allow_no_value
+
+//@[allow_regparm_mismatch] compile-flags: -Cunsafe-allow-abi-mismatch=regparm
+//@[allow_regparm_mismatch] build-pass
+//@[allow_no_value] compile-flags: -Cunsafe-allow-abi-mismatch
+
+#![crate_type = "rlib"]
+//[error_generated]~^ ERROR mixing `-Zregparm` will cause an ABI mismatch in crate `incompatible_regparm`
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+fn foo() {
+    wrong_regparm::somefun();
+}
diff --git a/tests/ui/target_modifiers/two_flags.rs b/tests/ui/target_modifiers/two_flags.rs
new file mode 100644
index 0000000000000..ca17117a267b1
--- /dev/null
+++ b/tests/ui/target_modifiers/two_flags.rs
@@ -0,0 +1,23 @@
+//@ aux-crate:wrong_regparm_and_ret=wrong_regparm_and_ret.rs
+//@ compile-flags: --target i686-unknown-linux-gnu -Cpanic=abort
+// Auxiliary build problems with aarch64-apple:
+// Shared library linking cc seems to convert "-m32" flag into -arch armv4t
+// Auxiliary build problems with i686-mingw: linker `cc` not found
+//@ only-x86
+//@ ignore-windows
+//@ ignore-apple
+//@ needs-llvm-components: x86
+//@ revisions:two_allowed unknown_allowed
+
+//@[two_allowed] compile-flags: -Cunsafe-allow-abi-mismatch=regparm,reg-struct-return
+//@[two_allowed] build-pass
+//@[unknown_allowed] compile-flags: -Cunsafe-allow-abi-mismatch=unknown_flag -Zregparm=2 -Zreg-struct-return=true
+
+#![crate_type = "rlib"]
+//[unknown_allowed]~^ ERROR unknown target modifier `unknown_flag`, requested by `-Cunsafe-allow-abi-mismatch=unknown_flag`
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+
+fn foo() {
+    wrong_regparm_and_ret::somefun();
+}
diff --git a/tests/ui/target_modifiers/two_flags.unknown_allowed.stderr b/tests/ui/target_modifiers/two_flags.unknown_allowed.stderr
new file mode 100644
index 0000000000000..c8040c6e389b5
--- /dev/null
+++ b/tests/ui/target_modifiers/two_flags.unknown_allowed.stderr
@@ -0,0 +1,8 @@
+error: unknown target modifier `unknown_flag`, requested by `-Cunsafe-allow-abi-mismatch=unknown_flag`
+  --> $DIR/two_flags.rs:16:1
+   |
+LL | #![crate_type = "rlib"]
+   | ^
+
+error: aborting due to 1 previous error
+