Make #[target_feature] safe always on WASM

Even when the feature isn't enabled, as it's not UB to invoke an undefined feature in WASM (just a trap).
This commit is contained in:
Chayim Refael Friedman 2025-09-09 13:49:11 +03:00
parent 74a8f7451e
commit d6638e9bd2
5 changed files with 69 additions and 11 deletions

View file

@ -15,8 +15,9 @@ use hir_def::{
use span::Edition;
use crate::{
InferenceResult, Interner, TargetFeatures, TyExt, TyKind, db::HirDatabase,
utils::is_fn_unsafe_to_call,
InferenceResult, Interner, TargetFeatures, TyExt, TyKind,
db::HirDatabase,
utils::{is_fn_unsafe_to_call, target_feature_is_safe_in_target},
};
#[derive(Debug, Default)]
@ -144,6 +145,9 @@ struct UnsafeVisitor<'db> {
def_target_features: TargetFeatures,
// FIXME: This needs to be the edition of the span of each call.
edition: Edition,
/// On some targets (WASM), calling safe functions with `#[target_feature]` is always safe, even when
/// the target feature is not enabled. This flag encodes that.
target_feature_is_safe: bool,
}
impl<'db> UnsafeVisitor<'db> {
@ -159,7 +163,12 @@ impl<'db> UnsafeVisitor<'db> {
DefWithBodyId::FunctionId(func) => TargetFeatures::from_attrs(&db.attrs(func.into())),
_ => TargetFeatures::default(),
};
let edition = resolver.module().krate().data(db).edition;
let krate = resolver.module().krate();
let edition = krate.data(db).edition;
let target_feature_is_safe = match &krate.workspace_data(db).target {
Ok(target) => target_feature_is_safe_in_target(target),
Err(_) => false,
};
Self {
db,
infer,
@ -172,6 +181,7 @@ impl<'db> UnsafeVisitor<'db> {
callback: unsafe_expr_cb,
def_target_features,
edition,
target_feature_is_safe,
}
}
@ -184,7 +194,13 @@ impl<'db> UnsafeVisitor<'db> {
}
fn check_call(&mut self, node: ExprId, func: FunctionId) {
let unsafety = is_fn_unsafe_to_call(self.db, func, &self.def_target_features, self.edition);
let unsafety = is_fn_unsafe_to_call(
self.db,
func,
&self.def_target_features,
self.edition,
self.target_feature_is_safe,
);
match unsafety {
crate::utils::Unsafety::Safe => {}
crate::utils::Unsafety::Unsafe => {

View file

@ -129,7 +129,10 @@ pub use mapping::{
pub use method_resolution::check_orphan_rules;
pub use target_feature::TargetFeatures;
pub use traits::TraitEnvironment;
pub use utils::{Unsafety, all_super_traits, direct_super_traits, is_fn_unsafe_to_call};
pub use utils::{
Unsafety, all_super_traits, direct_super_traits, is_fn_unsafe_to_call,
target_feature_is_safe_in_target,
};
pub use variance::Variance;
pub use chalk_ir::{

View file

@ -3,7 +3,10 @@
use std::{cell::LazyCell, iter};
use base_db::Crate;
use base_db::{
Crate,
target::{self, TargetData},
};
use chalk_ir::{DebruijnIndex, fold::FallibleTypeFolder};
use hir_def::{
EnumId, EnumVariantId, FunctionId, Lookup, TraitId, TypeAliasId, TypeOrConstParamId,
@ -275,18 +278,23 @@ pub enum Unsafety {
DeprecatedSafe2024,
}
pub fn target_feature_is_safe_in_target(target: &TargetData) -> bool {
matches!(target.arch, target::Arch::Wasm32 | target::Arch::Wasm64)
}
pub fn is_fn_unsafe_to_call(
db: &dyn HirDatabase,
func: FunctionId,
caller_target_features: &TargetFeatures,
call_edition: Edition,
target_feature_is_safe: bool,
) -> Unsafety {
let data = db.function_signature(func);
if data.is_unsafe() {
return Unsafety::Unsafe;
}
if data.has_target_feature() {
if data.has_target_feature() && !target_feature_is_safe {
// RFC 2396 <https://rust-lang.github.io/rfcs/2396-target-feature-1.1.html>.
let callee_target_features =
TargetFeatures::from_attrs_no_implications(&db.attrs(func.into()));

View file

@ -2542,11 +2542,26 @@ impl Function {
caller: Option<Function>,
call_edition: Edition,
) -> bool {
let target_features = caller
.map(|caller| hir_ty::TargetFeatures::from_attrs(&db.attrs(caller.id.into())))
.unwrap_or_default();
let (target_features, target_feature_is_safe_in_target) = caller
.map(|caller| {
let target_features =
hir_ty::TargetFeatures::from_attrs(&db.attrs(caller.id.into()));
let target_feature_is_safe_in_target =
match &caller.krate(db).id.workspace_data(db).target {
Ok(target) => hir_ty::target_feature_is_safe_in_target(target),
Err(_) => false,
};
(target_features, target_feature_is_safe_in_target)
})
.unwrap_or_else(|| (hir_ty::TargetFeatures::default(), false));
matches!(
hir_ty::is_fn_unsafe_to_call(db, self.id, &target_features, call_edition),
hir_ty::is_fn_unsafe_to_call(
db,
self.id,
&target_features,
call_edition,
target_feature_is_safe_in_target
),
hir_ty::Unsafety::Unsafe
)
}

View file

@ -998,4 +998,20 @@ extern "C" fn naked() {
"#,
);
}
#[test]
fn target_feature_safe_on_wasm() {
check_diagnostics(
r#"
//- target_arch: wasm32
#[target_feature(enable = "simd128")]
fn requires_target_feature() {}
fn main() {
requires_target_feature();
}
"#,
);
}
}