about summary refs log tree commit diff
path: root/compiler/rustc_codegen_ssa/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-08-28 22:54:55 +0000
committerbors <bors@rust-lang.org>2024-08-28 22:54:55 +0000
commitacb4e8b6251f1d8da36f08e7a70fa23fc581839e (patch)
treed34182dd7a2cb603a9aafa52839d64101c2dcc3f /compiler/rustc_codegen_ssa/src
parent100fde5246bf56f22fb5cc85374dd841296fce0e (diff)
parent7eb4cfeaced28d49952e4ef54f8fe02258125854 (diff)
downloadrust-acb4e8b6251f1d8da36f08e7a70fa23fc581839e.tar.gz
rust-acb4e8b6251f1d8da36f08e7a70fa23fc581839e.zip
Auto merge of #127537 - veluca93:struct_tf, r=BoxyUwU
Implement a first version of RFC 3525: struct target features

This PR is an attempt at implementing https://github.com/rust-lang/rfcs/pull/3525, behind a feature gate `struct_target_features`.

There's obviously a few tasks that ought to be done before this is merged; in no particular order:
- add proper error messages
- add tests
- create a tracking issue for the RFC
- properly serialize/deserialize the new target_features field in `rmeta` (assuming I even understood that correctly :-))

That said, as I am definitely not a `rustc` expert, I'd like to get some early feedback on the overall approach before fixing those things (and perhaps some pointers for `rmeta`...), hence this early PR :-)

Here's an example piece of code that I have been using for testing - with the new code, the calls to intrinsics get correctly inlined:
```rust
#![feature(struct_target_features)]

use std::arch::x86_64::*;

/*
// fails to compile
#[target_feature(enable = "avx")]
struct Invalid(u32);
*/

#[target_feature(enable = "avx")]
struct Avx {}

#[target_feature(enable = "sse")]
struct Sse();

/*
// fails to compile
extern "C" fn bad_fun(_: Avx) {}
*/

/*
// fails to compile
#[inline(always)]
fn inline_fun(_: Avx) {}
*/

trait Simd {
    fn do_something(&self);
}

impl Simd for Avx {
    fn do_something(&self) {
        unsafe {
            println!("{:?}", _mm256_setzero_ps());
        }
    }
}

impl Simd for Sse {
    fn do_something(&self) {
        unsafe {
            println!("{:?}", _mm_setzero_ps());
        }
    }
}

struct WithAvx {
    #[allow(dead_code)]
    avx: Avx,
}

impl Simd for WithAvx {
    fn do_something(&self) {
        unsafe {
            println!("{:?}", _mm256_setzero_ps());
        }
    }
}

#[inline(never)]
fn dosomething<S: Simd>(simd: &S) {
    simd.do_something();
}

fn main() {
    /*
    // fails to compile
    Avx {};
    */

    if is_x86_feature_detected!("avx") {
        let avx = unsafe { Avx {} };
        dosomething(&avx);
        dosomething(&WithAvx { avx });
    }
    if is_x86_feature_detected!("sse") {
        dosomething(&unsafe { Sse {} })
    }
}
```

Tracking:

- https://github.com/rust-lang/rust/issues/129107
Diffstat (limited to 'compiler/rustc_codegen_ssa/src')
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs125
1 files changed, 115 insertions, 10 deletions
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 4ab20c154cc..209750d6ba6 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -1,5 +1,6 @@
 use rustc_ast::{ast, attr, MetaItemKind, NestedMetaItem};
 use rustc_attr::{list_contains_name, InlineAttr, InstructionSetAttr, OptimizeAttr};
+use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::codes::*;
 use rustc_errors::{struct_span_code_err, DiagMessage, SubdiagMessage};
 use rustc_hir as hir;
@@ -8,7 +9,7 @@ use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
 use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
 use rustc_hir::{lang_items, LangItem};
 use rustc_middle::middle::codegen_fn_attrs::{
-    CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry,
+    CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry, TargetFeature,
 };
 use rustc_middle::mir::mono::Linkage;
 use rustc_middle::query::Providers;
@@ -17,6 +18,7 @@ use rustc_session::lint;
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::Ident;
 use rustc_span::{sym, Span};
+use rustc_target::abi::VariantIdx;
 use rustc_target::spec::{abi, SanitizerSet};
 
 use crate::errors;
@@ -78,6 +80,13 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
     let mut link_ordinal_span = None;
     let mut no_sanitize_span = None;
 
+    let fn_sig_outer = || {
+        use DefKind::*;
+
+        let def_kind = tcx.def_kind(did);
+        if let Fn | AssocFn | Variant | Ctor(..) = def_kind { Some(tcx.fn_sig(did)) } else { None }
+    };
+
     for attr in attrs.iter() {
         // In some cases, attribute are only valid on functions, but it's the `check_attr`
         // pass that check that they aren't used anywhere else, rather this module.
@@ -85,16 +94,12 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         // functions (such as calling `fn_sig`, which ICEs if given a non-function). We also
         // report a delayed bug, just in case `check_attr` isn't doing its job.
         let fn_sig = || {
-            use DefKind::*;
-
-            let def_kind = tcx.def_kind(did);
-            if let Fn | AssocFn | Variant | Ctor(..) = def_kind {
-                Some(tcx.fn_sig(did))
-            } else {
+            let sig = fn_sig_outer();
+            if sig.is_none() {
                 tcx.dcx()
                     .span_delayed_bug(attr.span, "this attribute can only be applied to functions");
-                None
             }
+            sig
         };
 
         let Some(Ident { name, .. }) = attr.ident() else {
@@ -613,7 +618,93 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         }
     }
 
-    // If a function uses #[target_feature] it can't be inlined into general
+    if let Some(sig) = fn_sig_outer() {
+        // Collect target features from types reachable from arguments.
+        // We define a type as "reachable" if:
+        //  - it is a function argument
+        //  - it is a field of a reachable struct
+        //  - there is a reachable reference to it
+        // FIXME(struct_target_features): we may want to cache the result of this computation.
+        let mut visited_types = FxHashSet::default();
+        let mut reachable_types: Vec<_> = sig.skip_binder().inputs().skip_binder().to_owned();
+        let mut additional_tf = vec![];
+
+        while let Some(ty) = reachable_types.pop() {
+            if visited_types.contains(&ty) {
+                continue;
+            }
+            visited_types.insert(ty);
+            match ty.kind() {
+                ty::Alias(..) => {
+                    if let Ok(t) =
+                        tcx.try_normalize_erasing_regions(tcx.param_env(did.to_def_id()), ty)
+                    {
+                        reachable_types.push(t)
+                    }
+                }
+
+                ty::Ref(_, inner, _) => reachable_types.push(*inner),
+                ty::Tuple(tys) => reachable_types.extend(tys.iter()),
+                ty::Adt(adt_def, args) => {
+                    additional_tf.extend_from_slice(tcx.struct_target_features(adt_def.did()));
+                    // This only recurses into structs as i.e. an Option<TargetFeature> is an ADT
+                    // that doesn't actually always contain a TargetFeature.
+                    if adt_def.is_struct() {
+                        reachable_types.extend(
+                            adt_def
+                                .variant(VariantIdx::from_usize(0))
+                                .fields
+                                .iter()
+                                .map(|field| field.ty(tcx, args)),
+                        );
+                    }
+                }
+                ty::Bool
+                | ty::Char
+                | ty::Int(..)
+                | ty::Uint(..)
+                | ty::Float(..)
+                | ty::Foreign(..)
+                | ty::Str
+                | ty::Array(..)
+                | ty::Pat(..)
+                | ty::Slice(..)
+                | ty::RawPtr(..)
+                | ty::FnDef(..)
+                | ty::FnPtr(..)
+                | ty::Dynamic(..)
+                | ty::Closure(..)
+                | ty::CoroutineClosure(..)
+                | ty::Coroutine(..)
+                | ty::CoroutineWitness(..)
+                | ty::Never
+                | ty::Param(..)
+                | ty::Bound(..)
+                | ty::Placeholder(..)
+                | ty::Infer(..)
+                | ty::Error(..) => (),
+            }
+        }
+
+        // FIXME(struct_target_features): is this really necessary?
+        if !additional_tf.is_empty() && sig.skip_binder().abi() != abi::Abi::Rust {
+            tcx.dcx().span_err(
+                tcx.hir().span(tcx.local_def_id_to_hir_id(did)),
+                "cannot use a struct with target features in a function with non-Rust ABI",
+            );
+        }
+        if !additional_tf.is_empty() && codegen_fn_attrs.inline == InlineAttr::Always {
+            tcx.dcx().span_err(
+                tcx.hir().span(tcx.local_def_id_to_hir_id(did)),
+                "cannot use a struct with target features in a #[inline(always)] function",
+            );
+        }
+        codegen_fn_attrs
+            .target_features
+            .extend(additional_tf.iter().map(|tf| TargetFeature { implied: true, ..*tf }));
+    }
+
+    // If a function uses non-default target_features it can't be inlined into general
     // purpose functions as they wouldn't have the right target features
     // enabled. For that reason we also forbid #[inline(always)] as it can't be
     // respected.
@@ -758,6 +849,20 @@ fn check_link_name_xor_ordinal(
     }
 }
 
+fn struct_target_features(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &[TargetFeature] {
+    let mut features = vec![];
+    let supported_features = tcx.supported_target_features(LOCAL_CRATE);
+    for attr in tcx.get_attrs(def_id, sym::target_feature) {
+        from_target_feature(tcx, attr, supported_features, &mut features);
+    }
+    tcx.arena.alloc_slice(&features)
+}
+
 pub fn provide(providers: &mut Providers) {
-    *providers = Providers { codegen_fn_attrs, should_inherit_track_caller, ..*providers };
+    *providers = Providers {
+        codegen_fn_attrs,
+        should_inherit_track_caller,
+        struct_target_features,
+        ..*providers
+    };
 }