diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index c62968e535420..8b6bf886b0d0b 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -242,6 +242,9 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs {
                     // Note that this is also allowed if `actually_rustdoc` so
                     // if a target is documenting some wasm-specific code then
                     // it's not spuriously denied.
+                    //
+                    // This exception needs to be kept in sync with allowing
+                    // `#[target_feature]` on `main` and `start`.
                 } else if !tcx.features().target_feature_11 {
                     let mut err = feature_err(
                         &tcx.sess.parse_sess,
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index dbe15ed5e9b99..0105cbf36dee2 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -128,9 +128,14 @@ hir_analysis_where_clause_on_main = `main` function is not allowed to have a `wh
 hir_analysis_track_caller_on_main = `main` function is not allowed to be `#[track_caller]`
     .suggestion = remove this annotation
 
+hir_analysis_target_feature_on_main = `main` function is not allowed to have `#[target_feature]`
+
 hir_analysis_start_not_track_caller = `start` is not allowed to be `#[track_caller]`
     .label = `start` is not allowed to be `#[track_caller]`
 
+hir_analysis_start_not_target_feature = `start` is not allowed to have `#[target_feature]`
+    .label = `start` is not allowed to have `#[target_feature]`
+
 hir_analysis_start_not_async = `start` is not allowed to be `async`
     .label = `start` is not allowed to be `async`
 
diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs
index 8a697b4c51488..f57197edeb74d 100644
--- a/compiler/rustc_hir_analysis/src/errors.rs
+++ b/compiler/rustc_hir_analysis/src/errors.rs
@@ -327,6 +327,14 @@ pub(crate) struct TrackCallerOnMain {
     pub annotated: Span,
 }
 
+#[derive(Diagnostic)]
+#[diag(hir_analysis_target_feature_on_main)]
+pub(crate) struct TargetFeatureOnMain {
+    #[primary_span]
+    #[label(hir_analysis_target_feature_on_main)]
+    pub main: Span,
+}
+
 #[derive(Diagnostic)]
 #[diag(hir_analysis_start_not_track_caller)]
 pub(crate) struct StartTrackCaller {
@@ -336,6 +344,15 @@ pub(crate) struct StartTrackCaller {
     pub start: Span,
 }
 
+#[derive(Diagnostic)]
+#[diag(hir_analysis_start_not_target_feature)]
+pub(crate) struct StartTargetFeature {
+    #[primary_span]
+    pub span: Span,
+    #[label]
+    pub start: Span,
+}
+
 #[derive(Diagnostic)]
 #[diag(hir_analysis_start_not_async, code = "E0752")]
 pub(crate) struct StartAsync {
diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs
index 6a4f538287897..62abcbbdc9f6e 100644
--- a/compiler/rustc_hir_analysis/src/lib.rs
+++ b/compiler/rustc_hir_analysis/src/lib.rs
@@ -283,6 +283,15 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
         error = true;
     }
 
+    if !tcx.codegen_fn_attrs(main_def_id).target_features.is_empty()
+        // Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988
+        && !tcx.sess.target.is_like_wasm
+        && !tcx.sess.opts.actually_rustdoc
+    {
+        tcx.sess.emit_err(errors::TargetFeatureOnMain { main: main_span });
+        error = true;
+    }
+
     if error {
         return;
     }
@@ -373,6 +382,18 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
                             });
                             error = true;
                         }
+                        if attr.has_name(sym::target_feature)
+                            // Calling functions with `#[target_feature]` is
+                            // not unsafe on WASM, see #84988
+                            && !tcx.sess.target.is_like_wasm
+                            && !tcx.sess.opts.actually_rustdoc
+                        {
+                            tcx.sess.emit_err(errors::StartTargetFeature {
+                                span: attr.span,
+                                start: start_span,
+                            });
+                            error = true;
+                        }
                     }
 
                     if error {
diff --git a/tests/ui/asm/x86_64/issue-89875.rs b/tests/ui/asm/x86_64/issue-89875.rs
index 669fd7e7e46e5..39c6456402223 100644
--- a/tests/ui/asm/x86_64/issue-89875.rs
+++ b/tests/ui/asm/x86_64/issue-89875.rs
@@ -7,7 +7,7 @@
 use std::arch::asm;
 
 #[target_feature(enable = "avx")]
-fn main() {
+fn foo() {
     unsafe {
         asm!(
             "/* {} */",
@@ -15,3 +15,5 @@ fn main() {
         );
     }
 }
+
+fn main() {}
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.rs b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.rs
new file mode 100644
index 0000000000000..0d59e50264e1b
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.rs
@@ -0,0 +1,7 @@
+// only-x86_64
+
+#![feature(target_feature_11)]
+
+#[target_feature(enable = "avx2")]
+fn main() {}
+//~^ ERROR `main` function is not allowed to have `#[target_feature]`
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.stderr
new file mode 100644
index 0000000000000..cfafbd52286a7
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-main.stderr
@@ -0,0 +1,8 @@
+error: `main` function is not allowed to have `#[target_feature]`
+  --> $DIR/issue-108645-target-feature-on-main.rs:6:1
+   |
+LL | fn main() {}
+   | ^^^^^^^^^ `main` function is not allowed to have `#[target_feature]`
+
+error: aborting due to previous error
+
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.rs b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.rs
new file mode 100644
index 0000000000000..50e8ce2fdd5ed
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.rs
@@ -0,0 +1,9 @@
+// only-x86_64
+
+#![feature(start)]
+#![feature(target_feature_11)]
+
+#[start]
+#[target_feature(enable = "avx2")]
+//~^ ERROR `start` is not allowed to have `#[target_feature]`
+fn start(_argc: isize, _argv: *const *const u8) -> isize { 0 }
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.stderr
new file mode 100644
index 0000000000000..07687f3c7f4a2
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/issue-108645-target-feature-on-start.stderr
@@ -0,0 +1,11 @@
+error: `start` is not allowed to have `#[target_feature]`
+  --> $DIR/issue-108645-target-feature-on-start.rs:7:1
+   |
+LL | #[target_feature(enable = "avx2")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
+LL | fn start(_argc: isize, _argv: *const *const u8) -> isize { 0 }
+   | -------------------------------------------------------- `start` is not allowed to have `#[target_feature]`
+
+error: aborting due to previous error
+