Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow reifying intrinsics to fn pointers. (rebase of #86699) #126595

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::rc::Rc;
use std::{fmt, iter, mem};

use rustc_abi::FieldIdx;
use rustc_abi::{ExternAbi, FieldIdx};
use rustc_data_structures::frozen::Frozen;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::ErrorGuaranteed;
Expand Down Expand Up @@ -39,7 +39,8 @@ use rustc_mir_dataflow::move_paths::MoveData;
use rustc_mir_dataflow::points::DenseLocationMap;
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span, sym};
use rustc_span::symbol::sym;
use rustc_span::{DUMMY_SP, Span};
use rustc_trait_selection::traits::query::type_op::custom::scrape_region_constraints;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
use tracing::{debug, instrument, trace};
Expand Down Expand Up @@ -1743,6 +1744,16 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
};
}

// NOTE(eddyb) see comment on `prepare_fn_sig_for_reify`
// in `rustc_typeck::check::coercion`.
let src_sig = src_sig.map_bound(|mut sig| -> _ {
if matches!(sig.abi, ExternAbi::RustIntrinsic) {
sig.abi = ExternAbi::Rust;
}

sig
});

let src_ty = Ty::new_fn_ptr(tcx, src_sig);
// HACK: We want to assert that the signature of the source fn is
// well-formed, because we don't enforce that via the WF of FnDef
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_hir_typeck/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ use rustc_macros::{TypeFoldable, TypeVisitable};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::cast::{CastKind, CastTy};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, VariantDef};
use rustc_middle::{bug, span_bug};
use rustc_session::lint;
Expand Down Expand Up @@ -748,9 +747,6 @@ impl<'a, 'tcx> CastCheck<'tcx> {
AllowTwoPhase::No,
None,
);
if let Err(TypeError::IntrinsicCast) = res {
return Err(CastError::IllegalCast);
}
if res.is_err() {
return Err(CastError::NonScalar);
}
Expand Down
29 changes: 20 additions & 9 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> {

type CoerceResult<'tcx> = InferResult<'tcx, (Vec<Adjustment<'tcx>>, Ty<'tcx>)>;

/// Make any adjustments necessary for a function signature to be compatible
/// with reification to a `fn` pointer. In particular, intrinsics are imported
/// using pseudo-ABIs (`extern "rust-intrinsic" {...}`) currently, but that's
/// an implementation detail and any `fn` pointers that may be taken to them
/// should be indistinguishable from those to regular Rust functions, in order
/// to allow e.g. libcore public APIs to be replaced with intrinsics, without
/// breaking code that was, explicitly or implicitly, creating `fn` pointers.
// FIXME(eddyb) intrinsics shouldn't use pseudo-ABIs, but rather the Rust ABI
// and some other way to indicate that they are intrinsics (e.g. new attributes).
fn prepare_fn_sig_for_reify<'tcx>(mut sig: ty::FnSig<'tcx>) -> ty::FnSig<'tcx> {
if matches!(sig.abi, ExternAbi::RustIntrinsic) {
sig.abi = ExternAbi::Rust;
}

sig
}

/// Coercing a mutable reference to an immutable works, while
/// coercing `&T` to `&mut T` should be forbidden.
fn coerce_mutbls<'tcx>(
Expand Down Expand Up @@ -911,12 +928,10 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
match b.kind() {
ty::FnPtr(_, b_hdr) => {
let mut a_sig = a.fn_sig(self.tcx);
if let ty::FnDef(def_id, _) = *a.kind() {
// Intrinsics are not coercible to function pointers
if self.tcx.intrinsic(def_id).is_some() {
return Err(TypeError::IntrinsicCast);
}
// NOTE(eddyb) see comment on `prepare_fn_sig_for_reify`.
a_sig = a_sig.map_bound(prepare_fn_sig_for_reify);

if let ty::FnDef(def_id, _) = *a.kind() {
let fn_attrs = self.tcx.codegen_fn_attrs(def_id);
if matches!(fn_attrs.inline, InlineAttr::Force { .. }) {
return Err(TypeError::ForceInlineCast);
Expand Down Expand Up @@ -1266,10 +1281,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
};
if let (Some(a_sig), Some(b_sig)) = (a_sig, b_sig) {
// Intrinsics are not coercible to function pointers.
if a_sig.abi() == ExternAbi::RustIntrinsic || b_sig.abi() == ExternAbi::RustIntrinsic {
return Err(TypeError::IntrinsicCast);
}
// The signature must match.
let (a_sig, b_sig) = self.normalize(new.span, (a_sig, b_sig));
let sig = self
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_middle/src/ty/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ impl<'tcx> TypeError<'tcx> {
TypeError::ForceInlineCast => {
"cannot coerce functions which must be inlined to function pointers".into()
}
TypeError::IntrinsicCast => "cannot coerce intrinsics to function pointers".into(),
TypeError::TargetFeatureCast(_) => {
"cannot coerce functions with `#[target_feature]` to safe function pointers".into()
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/ty/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,10 @@ impl<'tcx> Instance<'tcx> {
// unresolved instance.
resolved = Instance { def: InstanceKind::ReifyShim(def_id, reason), args }
}
InstanceKind::Intrinsic(def_id) => {
debug!(" => fn pointer created for intrinsic call");
resolved.def = InstanceKind::ReifyShim(def_id, reason);
}
_ => {}
}

Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_mir_transform/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use tracing::{debug, instrument};

use crate::{
abort_unwinding_calls, add_call_guards, add_moves_for_packed_drops, deref_separator, inline,
instsimplify, mentioned_items, pass_manager as pm, remove_noop_landing_pads, simplify,
instsimplify, lower_intrinsics, mentioned_items, pass_manager as pm, remove_noop_landing_pads,
simplify,
};

mod async_destructor_ctor;
Expand Down Expand Up @@ -154,6 +155,7 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceKind<'tcx>) -> Body<
&add_moves_for_packed_drops::AddMovesForPackedDrops,
&deref_separator::Derefer,
&remove_noop_landing_pads::RemoveNoopLandingPads,
&lower_intrinsics::LowerIntrinsics,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this added?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I know why. I wonder if it's better to place this in build_call_shim, or even make our own function like build_reified_intrinsic.

&simplify::SimplifyCfg::MakeShim,
&instsimplify::InstSimplify::BeforeInline,
// Perform inlining of `#[rustc_force_inline]`-annotated callees.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2321,7 +2321,7 @@ impl<'tcx> ObligationCause<'tcx> {
{
FailureCode::Error0644
}
TypeError::IntrinsicCast | TypeError::ForceInlineCast => FailureCode::Error0308,
TypeError::ForceInlineCast => FailureCode::Error0308,
_ => FailureCode::Error0308,
},
}
Expand Down Expand Up @@ -2387,9 +2387,6 @@ impl<'tcx> ObligationCause<'tcx> {
TypeError::ForceInlineCast => {
ObligationCauseFailureCode::CantCoerceForceInline { span, subdiags }
}
TypeError::IntrinsicCast => {
ObligationCauseFailureCode::CantCoerceIntrinsic { span, subdiags }
}
_ => ObligationCauseFailureCode::Generic { span, subdiags },
},
}
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_type_ir/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ pub enum TypeError<I: Interner> {
ExistentialMismatch(ExpectedFound<I::BoundExistentialPredicates>),
ConstMismatch(ExpectedFound<I::Const>),

IntrinsicCast,
/// `#[rustc_force_inline]` functions must be inlined and must not be codegened independently,
/// so casting to a function pointer must be prohibited.
ForceInlineCast,
Expand Down Expand Up @@ -86,8 +85,7 @@ impl<I: Interner> TypeError<I> {
| ProjectionMismatched(_)
| ExistentialMismatch(_)
| ConstMismatch(_)
| ForceInlineCast
| IntrinsicCast => true,
| ForceInlineCast => true,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- // MIR for `discriminant_value` before LowerIntrinsics
+ // MIR for `discriminant_value` after LowerIntrinsics

fn discriminant_value(_1: &T) -> <T as DiscriminantKind>::Discriminant {
let mut _0: <T as std::marker::DiscriminantKind>::Discriminant; // return place in scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL

bb0: {
- _0 = discriminant_value::<T>(move _1) -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // mir::Constant
- // + span: $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // + literal: Const { ty: for<'r> extern "rust-intrinsic" fn(&'r T) -> <T as std::marker::DiscriminantKind>::Discriminant {std::intrinsics::discriminant_value::<T>}, val: Value(Scalar(<ZST>)) }
+ _0 = discriminant((*_1)); // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+ goto -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb1: {
return; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb2 (cleanup): {
resume; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- // MIR for `std::intrinsics::forget` before LowerIntrinsics
+ // MIR for `std::intrinsics::forget` after LowerIntrinsics

fn std::intrinsics::forget(_1: T) -> () {
let mut _0: (); // return place in scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL

bb0: {
- _0 = std::intrinsics::forget::<T>(move _1) -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // mir::Constant
- // + span: $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // + literal: Const { ty: extern "rust-intrinsic" fn(T) {std::intrinsics::forget::<T>}, val: Value(Scalar(<ZST>)) }
+ _0 = const (); // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+ goto -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb1: {
return; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb2 (cleanup): {
resume; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- // MIR for `std::intrinsics::size_of` before LowerIntrinsics
+ // MIR for `std::intrinsics::size_of` after LowerIntrinsics

fn std::intrinsics::size_of() -> usize {
let mut _0: usize; // return place in scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL

bb0: {
- _0 = std::intrinsics::size_of::<T>() -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // mir::Constant
- // + span: $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // + literal: Const { ty: extern "rust-intrinsic" fn() -> usize {std::intrinsics::size_of::<T>}, val: Value(Scalar(<ZST>)) }
+ _0 = SizeOf(T); // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+ goto -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb1: {
return; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb2 (cleanup): {
resume; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- // MIR for `std::intrinsics::unreachable` before LowerIntrinsics
+ // MIR for `std::intrinsics::unreachable` after LowerIntrinsics

fn std::intrinsics::unreachable() -> ! {
let mut _0: !; // return place in scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL

bb0: {
- _0 = std::intrinsics::unreachable() -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // mir::Constant
- // + span: $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn() -> ! {std::intrinsics::unreachable}, val: Value(Scalar(<ZST>)) }
+ unreachable; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb1: {
return; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb2 (cleanup): {
resume; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- // MIR for `wrapping_add` before LowerIntrinsics
+ // MIR for `wrapping_add` after LowerIntrinsics

fn wrapping_add(_1: T, _2: T) -> T {
let mut _0: T; // return place in scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL

bb0: {
- _0 = wrapping_add::<T>(move _1, move _2) -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // mir::Constant
- // + span: $SRC_DIR/core/src/intrinsics.rs:LL:COL
- // + literal: Const { ty: extern "rust-intrinsic" fn(T, T) -> T {std::intrinsics::wrapping_add::<T>}, val: Value(Scalar(<ZST>)) }
+ _0 = Add(move _1, move _2); // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+ goto -> bb1; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb1: {
return; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}

bb2 (cleanup): {
resume; // scope 0 at $SRC_DIR/core/src/intrinsics.rs:LL:COL
}
}
12 changes: 12 additions & 0 deletions tests/mir-opt/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,15 @@ pub fn get_metadata(a: *const i32, b: *const [u8], c: *const dyn std::fmt::Debug
let _usize = ptr_metadata(b);
let _vtable = ptr_metadata(c);
}

// Check that the MIR shims used for reifying intrinsics to `fn` pointers,
// also go through the lowering pass.
pub fn reify_intrinsics() -> impl Copy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe

// EMIT_MIR lower_intrinsics.reify_intrinsics.LowerIntrinsics.mir

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I think this should be a .diff to preserve the original intentions. Plus I was not able to make it generate the same code as in #86699.

(
core::intrinsics::wrapping_add::<u32> as unsafe fn(_, _) -> _,
core::intrinsics::size_of::<u8> as unsafe fn() -> _,
core::intrinsics::unreachable as unsafe fn() -> !,
core::intrinsics::forget::<E> as unsafe fn(_),
core::intrinsics::discriminant_value::<E> as unsafe fn(_) -> _,
)
}
46 changes: 35 additions & 11 deletions tests/ui/intrinsics/reify-intrinsic.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
//@ check-fail
//@ run-pass

#![feature(core_intrinsics, intrinsics)]

fn a() {
let _: unsafe fn(isize) -> usize = std::mem::transmute;
//~^ ERROR cannot coerce
// NOTE(eddyb) `#[inline(never)]` and returning `fn` pointers from functions is
// done to force codegen (of the reification-to-`fn`-ptr shims around intrinsics).

#[inline(never)]
fn a() -> unsafe fn(isize) -> usize {
let f: unsafe fn(isize) -> usize = std::mem::transmute;
f
}

fn b() {
let _ = std::mem::transmute as unsafe fn(isize) -> usize;
//~^ ERROR casting
#[inline(never)]
fn b() -> unsafe fn(isize) -> usize {
let f = std::mem::transmute as unsafe fn(isize) -> usize;
f
}

fn c() {
let _: [unsafe fn(f32) -> f32; 2] = [
std::intrinsics::floorf32, //~ ERROR cannot coerce
#[inline(never)]
fn c() -> [unsafe fn(f32) -> f32; 2] {
let fs = [
std::intrinsics::floorf32,
std::intrinsics::log2f32,
];
fs
}

fn call_by_ptr() {
let ptr: fn(u8, u8) -> u8 = std::intrinsics::wrapping_add::<u8>;
let res = ptr(u8::MAX, 1);
assert_eq!(res, 0);
}

fn main() {}
fn main() {
unsafe {
assert_eq!(a()(-1), !0);
assert_eq!(b()(-1), !0);

let [floorf32_ptr, log2f32_ptr] = c();
assert_eq!(floorf32_ptr(1.5), 1.0);
assert_eq!(log2f32_ptr(2.0), 1.0);
}

call_by_ptr();
}
Loading
Loading