Deduce captures(none) for a return place and parameters
Extend attribute deduction to determine whether parameters using indirect pass mode might have their address captured. Similarly to the deduction of `readonly` attribute this information facilitates memcpy optimizations.
This commit is contained in:
@@ -39,13 +39,11 @@ trait ArgAttributesExt {
|
|||||||
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
|
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
|
||||||
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
|
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
|
||||||
|
|
||||||
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
|
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 4] = [
|
||||||
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
|
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
|
||||||
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
|
|
||||||
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
|
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
|
||||||
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
|
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
|
||||||
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
|
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
|
||||||
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
|
fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
|
||||||
@@ -81,15 +79,23 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
|
|||||||
}
|
}
|
||||||
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
|
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
|
||||||
if regular.contains(attr) {
|
if regular.contains(attr) {
|
||||||
// captures(...) is only available since LLVM 21.
|
|
||||||
if (attr == ArgAttribute::CapturesReadOnly || attr == ArgAttribute::CapturesAddress)
|
|
||||||
&& llvm_util::get_version() < (21, 0, 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attrs.push(llattr.create_attr(cx.llcx));
|
attrs.push(llattr.create_attr(cx.llcx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// captures(...) is only available since LLVM 21.
|
||||||
|
if (21, 0, 0) <= llvm_util::get_version() {
|
||||||
|
const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [
|
||||||
|
(ArgAttribute::CapturesNone, llvm::AttributeKind::CapturesNone),
|
||||||
|
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
|
||||||
|
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
|
||||||
|
];
|
||||||
|
for (attr, llattr) in CAPTURES_ATTRIBUTES {
|
||||||
|
if regular.contains(attr) {
|
||||||
|
attrs.push(llattr.create_attr(cx.llcx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) {
|
} else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) {
|
||||||
// If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects
|
// If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects
|
||||||
// memory sanitizer's behavior.
|
// memory sanitizer's behavior.
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ pub(crate) enum AttributeKind {
|
|||||||
DeadOnUnwind = 43,
|
DeadOnUnwind = 43,
|
||||||
DeadOnReturn = 44,
|
DeadOnReturn = 44,
|
||||||
CapturesReadOnly = 45,
|
CapturesReadOnly = 45,
|
||||||
|
CapturesNone = 46,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// LLVMIntPredicate
|
/// LLVMIntPredicate
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ enum class LLVMRustAttributeKind {
|
|||||||
DeadOnUnwind = 43,
|
DeadOnUnwind = 43,
|
||||||
DeadOnReturn = 44,
|
DeadOnReturn = 44,
|
||||||
CapturesReadOnly = 45,
|
CapturesReadOnly = 45,
|
||||||
|
CapturesNone = 46,
|
||||||
};
|
};
|
||||||
|
|
||||||
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
|
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
|
||||||
@@ -339,6 +340,7 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
|
|||||||
#endif
|
#endif
|
||||||
case LLVMRustAttributeKind::CapturesAddress:
|
case LLVMRustAttributeKind::CapturesAddress:
|
||||||
case LLVMRustAttributeKind::CapturesReadOnly:
|
case LLVMRustAttributeKind::CapturesReadOnly:
|
||||||
|
case LLVMRustAttributeKind::CapturesNone:
|
||||||
report_fatal_error("Should be handled separately");
|
report_fatal_error("Should be handled separately");
|
||||||
}
|
}
|
||||||
report_fatal_error("bad LLVMRustAttributeKind");
|
report_fatal_error("bad LLVMRustAttributeKind");
|
||||||
@@ -390,6 +392,9 @@ extern "C" void LLVMRustEraseInstFromParent(LLVMValueRef Instr) {
|
|||||||
extern "C" LLVMAttributeRef
|
extern "C" LLVMAttributeRef
|
||||||
LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
|
LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
|
||||||
#if LLVM_VERSION_GE(21, 0)
|
#if LLVM_VERSION_GE(21, 0)
|
||||||
|
if (RustAttr == LLVMRustAttributeKind::CapturesNone) {
|
||||||
|
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
|
||||||
|
}
|
||||||
if (RustAttr == LLVMRustAttributeKind::CapturesAddress) {
|
if (RustAttr == LLVMRustAttributeKind::CapturesAddress) {
|
||||||
return wrap(Attribute::getWithCaptureInfo(
|
return wrap(Attribute::getWithCaptureInfo(
|
||||||
*unwrap(C), CaptureInfo(CaptureComponents::Address)));
|
*unwrap(C), CaptureInfo(CaptureComponents::Address)));
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ use rustc_macros::{Decodable, Encodable, HashStable};
|
|||||||
|
|
||||||
use crate::ty::{Ty, TyCtxt, TypingEnv};
|
use crate::ty::{Ty, TyCtxt, TypingEnv};
|
||||||
|
|
||||||
/// Flags that dictate how a parameter is mutated. If the flags are empty, the param is
|
/// Summarizes how a parameter (a return place or an argument) is used inside a MIR body.
|
||||||
/// read-only. If non-empty, it is read-only if *all* flags' conditions are met.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
|
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
|
||||||
pub struct DeducedReadOnlyParam(u8);
|
pub struct UsageSummary(u8);
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
impl DeducedReadOnlyParam: u8 {
|
impl UsageSummary: u8 {
|
||||||
/// This parameter is dropped. It is read-only if `!needs_drop`.
|
/// This parameter is dropped when it `needs_drop`.
|
||||||
const IF_NO_DROP = 1 << 0;
|
const DROP = 1 << 0;
|
||||||
/// This parameter is borrowed. It is read-only if `Freeze`.
|
/// There is a shared borrow to this parameter.
|
||||||
const IF_FREEZE = 1 << 1;
|
/// It allows for mutation unless parameter is `Freeze`.
|
||||||
/// This parameter is mutated. It is never read-only.
|
const SHARED_BORROW = 1 << 1;
|
||||||
const MUTATED = 1 << 2;
|
/// This parameter is mutated (excluding through a drop or a shared borrow).
|
||||||
|
const MUTATE = 1 << 2;
|
||||||
|
/// This parameter is captured (excluding through a drop).
|
||||||
|
const CAPTURE = 1 << 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,43 +26,53 @@ bitflags::bitflags! {
|
|||||||
/// These can be useful for optimization purposes when a function is directly called. We compute
|
/// These can be useful for optimization purposes when a function is directly called. We compute
|
||||||
/// them and store them into the crate metadata so that downstream crates can make use of them.
|
/// them and store them into the crate metadata so that downstream crates can make use of them.
|
||||||
///
|
///
|
||||||
/// Right now, we only have `read_only`, but `no_capture` and `no_alias` might be useful in the
|
/// Right now, we have `readonly` and `captures(none)`, but `no_alias` might be useful in the
|
||||||
/// future.
|
/// future.
|
||||||
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
|
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
|
||||||
pub struct DeducedParamAttrs {
|
pub struct DeducedParamAttrs {
|
||||||
/// The parameter is marked immutable in the function.
|
pub usage: UsageSummary,
|
||||||
pub read_only: DeducedReadOnlyParam,
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, consider the parameters to be mutated.
|
|
||||||
impl Default for DeducedParamAttrs {
|
|
||||||
#[inline]
|
|
||||||
fn default() -> DeducedParamAttrs {
|
|
||||||
DeducedParamAttrs { read_only: DeducedReadOnlyParam::MUTATED }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeducedParamAttrs {
|
impl DeducedParamAttrs {
|
||||||
|
/// Returns true if no attributes have been deduced.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_default(self) -> bool {
|
pub fn is_default(self) -> bool {
|
||||||
self.read_only.contains(DeducedReadOnlyParam::MUTATED)
|
self.usage.contains(UsageSummary::MUTATE | UsageSummary::CAPTURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For parameters passed indirectly, returns true if pointer is never written through.
|
||||||
pub fn read_only<'tcx>(
|
pub fn read_only<'tcx>(
|
||||||
&self,
|
&self,
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
typing_env: TypingEnv<'tcx>,
|
typing_env: TypingEnv<'tcx>,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let read_only = self.read_only;
|
// Only if all checks pass is this truly read-only.
|
||||||
// We have to check *all* set bits; only if all checks pass is this truly read-only.
|
if self.usage.contains(UsageSummary::MUTATE) {
|
||||||
if read_only.contains(DeducedReadOnlyParam::MUTATED) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if read_only.contains(DeducedReadOnlyParam::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
|
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if read_only.contains(DeducedReadOnlyParam::IF_FREEZE) && !ty.is_freeze(tcx, typing_env) {
|
if self.usage.contains(UsageSummary::SHARED_BORROW) && !ty.is_freeze(tcx, typing_env) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For parameters passed indirectly, returns true if pointer is not captured, i.e., its
|
||||||
|
/// address is not captured, and pointer is used neither for reads nor writes after function
|
||||||
|
/// returns.
|
||||||
|
pub fn captures_none<'tcx>(
|
||||||
|
&self,
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
typing_env: TypingEnv<'tcx>,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
) -> bool {
|
||||||
|
if self.usage.contains(UsageSummary::CAPTURE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -11,65 +11,91 @@
|
|||||||
|
|
||||||
use rustc_hir::def_id::LocalDefId;
|
use rustc_hir::def_id::LocalDefId;
|
||||||
use rustc_index::IndexVec;
|
use rustc_index::IndexVec;
|
||||||
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, DeducedReadOnlyParam};
|
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, UsageSummary};
|
||||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||||
use rustc_session::config::OptLevel;
|
use rustc_session::config::OptLevel;
|
||||||
|
|
||||||
/// A visitor that determines which arguments have been mutated. We can't use the mutability field
|
/// A visitor that determines how a return place and arguments are used inside MIR body.
|
||||||
/// on LocalDecl for this because it has no meaning post-optimization.
|
/// To determine whether a local is mutated we can't use the mutability field on LocalDecl
|
||||||
struct DeduceReadOnly {
|
/// because it has no meaning post-optimization.
|
||||||
/// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
|
struct DeduceParamAttrs {
|
||||||
/// 1). The bit is false if the argument may have been mutated or true if we know it hasn't
|
/// Summarizes how a return place and arguments are used inside MIR body.
|
||||||
/// been up to the point we're at.
|
usage: IndexVec<Local, UsageSummary>,
|
||||||
read_only: IndexVec<usize, DeducedReadOnlyParam>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeduceReadOnly {
|
impl DeduceParamAttrs {
|
||||||
/// Returns a new DeduceReadOnly instance.
|
/// Returns a new DeduceParamAttrs instance.
|
||||||
fn new(arg_count: usize) -> Self {
|
fn new(body: &Body<'_>) -> Self {
|
||||||
Self { read_only: IndexVec::from_elem_n(DeducedReadOnlyParam::empty(), arg_count) }
|
let mut this =
|
||||||
|
Self { usage: IndexVec::from_elem_n(UsageSummary::empty(), body.arg_count + 1) };
|
||||||
|
// Code generation indicates that a return place is writable. To avoid setting both
|
||||||
|
// `readonly` and `writable` attributes, when return place is never written to, mark it as
|
||||||
|
// mutated.
|
||||||
|
this.usage[RETURN_PLACE] |= UsageSummary::MUTATE;
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the given local is a parameter and its index.
|
/// Returns whether a local is the return place or an argument and returns its index.
|
||||||
fn as_param(&self, local: Local) -> Option<usize> {
|
fn as_param(&self, local: Local) -> Option<Local> {
|
||||||
// Locals and parameters are shifted by `RETURN_PLACE`.
|
if local.index() < self.usage.len() { Some(local) } else { None }
|
||||||
let param_index = local.as_usize().checked_sub(1)?;
|
|
||||||
if param_index < self.read_only.len() { Some(param_index) } else { None }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
|
impl<'tcx> Visitor<'tcx> for DeduceParamAttrs {
|
||||||
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
|
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
|
||||||
// We're only interested in arguments.
|
// We're only interested in the return place or an argument.
|
||||||
let Some(param_index) = self.as_param(place.local) else { return };
|
let Some(i) = self.as_param(place.local) else { return };
|
||||||
|
|
||||||
match context {
|
match context {
|
||||||
// Not mutating, so it's fine.
|
// Not actually using the local.
|
||||||
PlaceContext::NonUse(..) => {}
|
PlaceContext::NonUse(..) => {}
|
||||||
// Dereference is not a mutation.
|
// Neither mutated nor captured.
|
||||||
_ if place.is_indirect_first_projection() => {}
|
_ if place.is_indirect_first_projection() => {}
|
||||||
// This is a `Drop`. It could disappear at monomorphization, so mark it specially.
|
// This is a `Drop`. It could disappear at monomorphization, so mark it specially.
|
||||||
PlaceContext::MutatingUse(MutatingUseContext::Drop)
|
PlaceContext::MutatingUse(MutatingUseContext::Drop)
|
||||||
// Projection changes the place's type, so `needs_drop(local.ty)` is not
|
// Projection changes the place's type, so `needs_drop(local.ty)` is not
|
||||||
// `needs_drop(place.ty)`.
|
// `needs_drop(place.ty)`.
|
||||||
if place.projection.is_empty() => {
|
if place.projection.is_empty() => {
|
||||||
self.read_only[param_index] |= DeducedReadOnlyParam::IF_NO_DROP;
|
self.usage[i] |= UsageSummary::DROP;
|
||||||
|
}
|
||||||
|
PlaceContext::MutatingUse(
|
||||||
|
MutatingUseContext::Call
|
||||||
|
| MutatingUseContext::Yield
|
||||||
|
| MutatingUseContext::Drop
|
||||||
|
| MutatingUseContext::Borrow
|
||||||
|
| MutatingUseContext::RawBorrow) => {
|
||||||
|
self.usage[i] |= UsageSummary::MUTATE;
|
||||||
|
self.usage[i] |= UsageSummary::CAPTURE;
|
||||||
|
}
|
||||||
|
PlaceContext::MutatingUse(
|
||||||
|
MutatingUseContext::Store
|
||||||
|
| MutatingUseContext::SetDiscriminant
|
||||||
|
| MutatingUseContext::AsmOutput
|
||||||
|
| MutatingUseContext::Projection
|
||||||
|
| MutatingUseContext::Retag) => {
|
||||||
|
self.usage[i] |= UsageSummary::MUTATE;
|
||||||
}
|
}
|
||||||
// This is a mutation, so mark it as such.
|
|
||||||
PlaceContext::MutatingUse(..)
|
|
||||||
// Whether mutating though a `&raw const` is allowed is still undecided, so we
|
|
||||||
// disable any sketchy `readonly` optimizations for now.
|
|
||||||
| PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
|
| PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
|
||||||
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
|
// Whether mutating though a `&raw const` is allowed is still undecided, so we
|
||||||
|
// disable any sketchy `readonly` optimizations for now.
|
||||||
|
self.usage[i] |= UsageSummary::MUTATE;
|
||||||
|
self.usage[i] |= UsageSummary::CAPTURE;
|
||||||
}
|
}
|
||||||
// Not mutating if the parameter is `Freeze`.
|
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
|
||||||
self.read_only[param_index] |= DeducedReadOnlyParam::IF_FREEZE;
|
// Not mutating if the parameter is `Freeze`.
|
||||||
|
self.usage[i] |= UsageSummary::SHARED_BORROW;
|
||||||
|
self.usage[i] |= UsageSummary::CAPTURE;
|
||||||
}
|
}
|
||||||
// Not mutating, so it's fine.
|
// Not mutating, so it's fine.
|
||||||
PlaceContext::NonMutatingUse(..) => {}
|
PlaceContext::NonMutatingUse(
|
||||||
|
NonMutatingUseContext::Inspect
|
||||||
|
| NonMutatingUseContext::Copy
|
||||||
|
| NonMutatingUseContext::Move
|
||||||
|
| NonMutatingUseContext::FakeBorrow
|
||||||
|
| NonMutatingUseContext::PlaceMention
|
||||||
|
| NonMutatingUseContext::Projection) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,11 +124,11 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
|
|||||||
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
|
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
if let Operand::Move(place) = arg.node
|
if let Operand::Move(place) = arg.node
|
||||||
// We're only interested in arguments.
|
|
||||||
&& let Some(param_index) = self.as_param(place.local)
|
|
||||||
&& !place.is_indirect_first_projection()
|
&& !place.is_indirect_first_projection()
|
||||||
|
&& let Some(i) = self.as_param(place.local)
|
||||||
{
|
{
|
||||||
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
|
self.usage[i] |= UsageSummary::MUTATE;
|
||||||
|
self.usage[i] |= UsageSummary::CAPTURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -154,10 +180,9 @@ pub(super) fn deduced_param_attrs<'tcx>(
|
|||||||
if matches!(fn_ty.kind(), ty::FnDef(..))
|
if matches!(fn_ty.kind(), ty::FnDef(..))
|
||||||
&& fn_ty
|
&& fn_ty
|
||||||
.fn_sig(tcx)
|
.fn_sig(tcx)
|
||||||
.inputs()
|
.inputs_and_output()
|
||||||
.skip_binder()
|
.skip_binder()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
|
||||||
.all(type_will_always_be_passed_directly)
|
.all(type_will_always_be_passed_directly)
|
||||||
{
|
{
|
||||||
return &[];
|
return &[];
|
||||||
@@ -170,13 +195,13 @@ pub(super) fn deduced_param_attrs<'tcx>(
|
|||||||
|
|
||||||
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
|
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
|
||||||
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
|
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
|
||||||
let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
|
let mut deduce = DeduceParamAttrs::new(body);
|
||||||
deduce_read_only.visit_body(body);
|
deduce.visit_body(body);
|
||||||
tracing::trace!(?deduce_read_only.read_only);
|
tracing::trace!(?deduce.usage);
|
||||||
|
|
||||||
let mut deduced_param_attrs: &[_] = tcx.arena.alloc_from_iter(
|
let mut deduced_param_attrs: &[_] = tcx
|
||||||
deduce_read_only.read_only.into_iter().map(|read_only| DeducedParamAttrs { read_only }),
|
.arena
|
||||||
);
|
.alloc_from_iter(deduce.usage.into_iter().map(|usage| DeducedParamAttrs { usage }));
|
||||||
|
|
||||||
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
|
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
|
||||||
// default set of attributes, so we don't have to store them explicitly. Pop them off to save a
|
// default set of attributes, so we don't have to store them explicitly. Pop them off to save a
|
||||||
|
|||||||
@@ -113,13 +113,14 @@ mod attr_impl {
|
|||||||
pub struct ArgAttribute(u8);
|
pub struct ArgAttribute(u8);
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
impl ArgAttribute: u8 {
|
impl ArgAttribute: u8 {
|
||||||
const NoAlias = 1 << 1;
|
const CapturesNone = 0b111;
|
||||||
const CapturesAddress = 1 << 2;
|
const CapturesAddress = 0b110;
|
||||||
const NonNull = 1 << 3;
|
const CapturesReadOnly = 0b100;
|
||||||
const ReadOnly = 1 << 4;
|
const NoAlias = 1 << 3;
|
||||||
const InReg = 1 << 5;
|
const NonNull = 1 << 4;
|
||||||
const NoUndef = 1 << 6;
|
const ReadOnly = 1 << 5;
|
||||||
const CapturesReadOnly = 1 << 7;
|
const InReg = 1 << 6;
|
||||||
|
const NoUndef = 1 << 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rustc_data_structures::external_bitflags_debug! { ArgAttribute }
|
rustc_data_structures::external_bitflags_debug! { ArgAttribute }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use rustc_abi::{BackendRepr, ExternAbi, PointerKind, Scalar, Size};
|
|||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::lang_items::LangItem;
|
use rustc_hir::lang_items::LangItem;
|
||||||
use rustc_middle::bug;
|
use rustc_middle::bug;
|
||||||
|
use rustc_middle::middle::deduced_param_attrs::DeducedParamAttrs;
|
||||||
use rustc_middle::query::Providers;
|
use rustc_middle::query::Providers;
|
||||||
use rustc_middle::ty::layout::{
|
use rustc_middle::ty::layout::{
|
||||||
FnAbiError, HasTyCtxt, HasTypingEnv, LayoutCx, LayoutOf, TyAndLayout, fn_can_unwind,
|
FnAbiError, HasTyCtxt, HasTypingEnv, LayoutCx, LayoutOf, TyAndLayout, fn_can_unwind,
|
||||||
@@ -614,37 +615,19 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||||||
|
|
||||||
if abi.is_rustic_abi() {
|
if abi.is_rustic_abi() {
|
||||||
fn_abi.adjust_for_rust_abi(cx);
|
fn_abi.adjust_for_rust_abi(cx);
|
||||||
|
|
||||||
// Look up the deduced parameter attributes for this function, if we have its def ID and
|
// Look up the deduced parameter attributes for this function, if we have its def ID and
|
||||||
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
|
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
|
||||||
// as appropriate.
|
// as appropriate.
|
||||||
let deduced_param_attrs =
|
let deduced =
|
||||||
if tcx.sess.opts.optimize != OptLevel::No && tcx.sess.opts.incremental.is_none() {
|
if tcx.sess.opts.optimize != OptLevel::No && tcx.sess.opts.incremental.is_none() {
|
||||||
fn_def_id.map(|fn_def_id| tcx.deduced_param_attrs(fn_def_id)).unwrap_or_default()
|
fn_def_id.map(|fn_def_id| tcx.deduced_param_attrs(fn_def_id)).unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
&[]
|
&[]
|
||||||
};
|
};
|
||||||
|
if !deduced.is_empty() {
|
||||||
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
|
apply_deduced_attributes(cx, deduced, 0, &mut fn_abi.ret);
|
||||||
if arg.is_ignore() {
|
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
|
||||||
continue;
|
apply_deduced_attributes(cx, deduced, arg_idx + 1, arg);
|
||||||
}
|
|
||||||
|
|
||||||
// If we deduced that this parameter was read-only, add that to the attribute list now.
|
|
||||||
//
|
|
||||||
// The `readonly` parameter only applies to pointers, so we can only do this if the
|
|
||||||
// argument was passed indirectly. (If the argument is passed directly, it's an SSA
|
|
||||||
// value, so it's implicitly immutable.)
|
|
||||||
if let &mut PassMode::Indirect { ref mut attrs, .. } = &mut arg.mode {
|
|
||||||
// The `deduced_param_attrs` list could be empty if this is a type of function
|
|
||||||
// we can't deduce any parameters for, so make sure the argument index is in
|
|
||||||
// bounds.
|
|
||||||
if let Some(deduced_param_attrs) = deduced_param_attrs.get(arg_idx)
|
|
||||||
&& deduced_param_attrs.read_only(tcx, cx.typing_env, arg.layout.ty)
|
|
||||||
{
|
|
||||||
debug!("added deduced read-only attribute");
|
|
||||||
attrs.regular.insert(ArgAttribute::ReadOnly);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -652,6 +635,34 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply deduced optimization attributes to a parameter using an indirect pass mode.
|
||||||
|
///
|
||||||
|
/// `deduced` is a possibly truncated list of deduced attributes for a return place and arguments.
|
||||||
|
/// `idx` the index of the parameter on the list (0 for a return place, and 1.. for arguments).
|
||||||
|
fn apply_deduced_attributes<'tcx>(
|
||||||
|
cx: &LayoutCx<'tcx>,
|
||||||
|
deduced: &[DeducedParamAttrs],
|
||||||
|
idx: usize,
|
||||||
|
arg: &mut ArgAbi<'tcx, Ty<'tcx>>,
|
||||||
|
) {
|
||||||
|
// Deduction is performed under the assumption of the indirection pass mode.
|
||||||
|
let PassMode::Indirect { ref mut attrs, .. } = arg.mode else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// The default values at the tail of the list are not encoded.
|
||||||
|
let Some(deduced) = deduced.get(idx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if deduced.read_only(cx.tcx(), cx.typing_env, arg.layout.ty) {
|
||||||
|
debug!("added deduced ReadOnly attribute");
|
||||||
|
attrs.regular.insert(ArgAttribute::ReadOnly);
|
||||||
|
}
|
||||||
|
if deduced.captures_none(cx.tcx(), cx.typing_env, arg.layout.ty) {
|
||||||
|
debug!("added deduced CapturesNone attribute");
|
||||||
|
attrs.regular.insert(ArgAttribute::CapturesNone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(cx))]
|
#[tracing::instrument(level = "debug", skip(cx))]
|
||||||
fn make_thin_self_ptr<'tcx>(
|
fn make_thin_self_ptr<'tcx>(
|
||||||
cx: &(impl HasTyCtxt<'tcx> + HasTypingEnv<'tcx>),
|
cx: &(impl HasTyCtxt<'tcx> + HasTypingEnv<'tcx>),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
|
// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
|
||||||
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias noundef readonly align {{[0-9]+}}{{( captures\(address\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
|
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
|
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
|
||||||
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();
|
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();
|
||||||
|
|||||||
@@ -1,81 +1,172 @@
|
|||||||
//@ compile-flags: -Copt-level=3 -Cno-prepopulate-passes
|
//@ compile-flags: -Copt-level=3 -Cno-prepopulate-passes
|
||||||
|
//@ compile-flags: -Cpanic=abort -Csymbol-mangling-version=v0
|
||||||
|
//@ revisions: LLVM21 LLVM20
|
||||||
|
//@ [LLVM21] min-llvm-version: 21
|
||||||
|
//@ [LLVM20] max-llvm-major-version: 20
|
||||||
|
#![feature(custom_mir, core_intrinsics)]
|
||||||
#![crate_type = "lib"]
|
#![crate_type = "lib"]
|
||||||
#![allow(internal_features)]
|
extern crate core;
|
||||||
#![feature(unsized_fn_params)]
|
use core::intrinsics::mir::*;
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::hint;
|
use std::hint::black_box;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
// Check to make sure that we can deduce the `readonly` attribute from function bodies for
|
pub struct Big {
|
||||||
// parameters passed indirectly.
|
pub blah: [i32; 1024],
|
||||||
|
|
||||||
pub struct BigStruct {
|
|
||||||
blah: [i32; 1024],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BigCellContainer {
|
pub struct BigCell {
|
||||||
blah: [Cell<i32>; 1024],
|
pub blah: [Cell<i32>; 1024],
|
||||||
}
|
}
|
||||||
|
|
||||||
// The by-value parameter for this big struct can be marked readonly.
|
pub struct BigDrop {
|
||||||
//
|
pub blah: [u8; 1024],
|
||||||
// CHECK: @use_big_struct_immutably({{.*}} readonly {{.*}} %big_struct)
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn use_big_struct_immutably(big_struct: BigStruct) {
|
|
||||||
hint::black_box(&big_struct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The by-value parameter for this big struct can't be marked readonly, because we mutate it.
|
impl Drop for BigDrop {
|
||||||
//
|
#[inline(never)]
|
||||||
// CHECK: @use_big_struct_mutably(
|
fn drop(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @mutate(
|
||||||
// CHECK-NOT: readonly
|
// CHECK-NOT: readonly
|
||||||
// CHECK-SAME: %big_struct)
|
// CHECK-SAME: %b)
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
pub fn use_big_struct_mutably(mut big_struct: BigStruct) {
|
pub fn mutate(mut b: Big) {
|
||||||
big_struct.blah[987] = 654;
|
b.blah[987] = 654;
|
||||||
hint::black_box(&big_struct);
|
black_box(&b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The by-value parameter for this big struct can't be marked readonly, because it contains
|
// LLVM21-LABEL: @deref_mut({{.*}}readonly {{.*}}captures(none) {{.*}}%c)
|
||||||
// UnsafeCell.
|
// LLVM20-LABEL: @deref_mut({{.*}}readonly {{.*}}%c)
|
||||||
//
|
#[unsafe(no_mangle)]
|
||||||
// CHECK: @use_big_cell_container(
|
pub fn deref_mut(c: (BigCell, &mut usize)) {
|
||||||
// CHECK-NOT: readonly
|
*c.1 = 42;
|
||||||
// CHECK-SAME: %big_cell_container)
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn use_big_cell_container(big_cell_container: BigCellContainer) {
|
|
||||||
hint::black_box(&big_cell_container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that we don't mistakenly mark a big struct as `readonly` when passed through a generic
|
// LLVM21-LABEL: @call_copy_arg(ptr {{.*}}readonly {{.*}}captures(none){{.*}})
|
||||||
// type parameter if it contains UnsafeCell.
|
// LLVM20-LABEL: @call_copy_arg(ptr {{.*}}readonly {{.*}})
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
#[custom_mir(dialect = "runtime", phase = "optimized")]
|
||||||
|
pub fn call_copy_arg(a: Big) {
|
||||||
|
mir! {
|
||||||
|
{
|
||||||
|
Call(RET = call_copy_arg(a), ReturnTo(bb1), UnwindUnreachable())
|
||||||
|
}
|
||||||
|
bb1 = {
|
||||||
|
Return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @call_move_arg(
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// LLVM21-SAME: captures(address)
|
||||||
|
// CHECK-SAME: )
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
#[custom_mir(dialect = "runtime", phase = "optimized")]
|
||||||
|
pub fn call_move_arg(a: Big) {
|
||||||
|
mir! {
|
||||||
|
{
|
||||||
|
Call(RET = call_move_arg(Move(a)), ReturnTo(bb1), UnwindUnreachable())
|
||||||
|
}
|
||||||
|
bb1 = {
|
||||||
|
Return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shared_borrow<T>(a: T) {
|
||||||
|
black_box(&a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Freeze parameter cannot be mutated through a shared borrow.
|
||||||
//
|
//
|
||||||
// CHECK: @use_something(
|
// CHECK-LABEL: ; deduced_param_attrs::shared_borrow::<deduced_param_attrs::Big>
|
||||||
// CHECK-NOT: readonly
|
// CHECK-NEXT: ;
|
||||||
// CHECK-SAME: %something)
|
// LLVM21-NEXT: (ptr {{.*}}readonly {{.*}}captures(address) {{.*}}%a)
|
||||||
#[no_mangle]
|
// LLVM20-NEXT: (ptr {{.*}}readonly {{.*}}%a)
|
||||||
|
pub static A0: fn(Big) = shared_borrow;
|
||||||
|
|
||||||
|
// !Freeze parameter can be mutated through a shared borrow.
|
||||||
|
//
|
||||||
|
// CHECK-LABEL: ; deduced_param_attrs::shared_borrow::<deduced_param_attrs::BigCell>
|
||||||
|
// CHECK-NEXT: ;
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// CHECK-NEXT: %a)
|
||||||
|
pub static A1: fn(BigCell) = shared_borrow;
|
||||||
|
|
||||||
|
// The parameter can be mutated through a raw const borrow.
|
||||||
|
//
|
||||||
|
// CHECK-LABEL: ; deduced_param_attrs::raw_const_borrow
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// CHECK-NEXT : %a)
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn use_something<T>(something: T) {
|
pub fn raw_const_borrow(a: Big) {
|
||||||
hint::black_box(&something);
|
black_box(&raw const a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that we still mark a big `Freeze` struct as `readonly` when passed through a generic
|
fn consume<T>(_: T) {}
|
||||||
// type parameter.
|
|
||||||
|
// The parameter doesn't need to be dropped.
|
||||||
//
|
//
|
||||||
// CHECK: @use_something_freeze(
|
// CHECK-LABEL: ; deduced_param_attrs::consume::<deduced_param_attrs::BigCell>
|
||||||
// CHECK-SAME: readonly
|
// CHECK-NEXT: ;
|
||||||
// CHECK-SAME: %x)
|
// LLVM21-NEXT: (ptr {{.*}}readonly {{.*}}captures(none) {{.*}})
|
||||||
#[no_mangle]
|
// LLVM20-NEXT: (ptr {{.*}}readonly {{.*}})
|
||||||
#[inline(never)]
|
pub static B0: fn(BigCell) = consume;
|
||||||
pub fn use_something_freeze<T>(x: T) {}
|
|
||||||
|
|
||||||
#[no_mangle]
|
// The parameter needs to be dropped.
|
||||||
pub fn forward_big_cell_container(big_cell_container: BigCellContainer) {
|
//
|
||||||
use_something(big_cell_container)
|
// CHECK-LABEL: ; deduced_param_attrs::consume::<deduced_param_attrs::BigDrop>
|
||||||
|
// CHECK-NEXT: ;
|
||||||
|
// LLVM21-NEXT: (ptr {{.*}}captures(address) {{.*}})
|
||||||
|
// LLVM20-NEXT: (ptr {{.*}})
|
||||||
|
pub static B1: fn(BigDrop) = consume;
|
||||||
|
|
||||||
|
fn consume_parts<T>(t: (T, T)) {
|
||||||
|
let (_t0, ..) = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
// In principle it would be possible to deduce readonly here.
|
||||||
pub fn forward_big_container(big_struct: BigStruct) {
|
//
|
||||||
use_something_freeze(big_struct)
|
// CHECK-LABEL: ; deduced_param_attrs::consume_parts::<[u8; 40]>
|
||||||
|
// CHECK-NEXT: ;
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// CHECK-NEXT: %t)
|
||||||
|
pub static C1: fn(([u8; 40], [u8; 40])) = consume_parts;
|
||||||
|
|
||||||
|
// The inner field of ManuallyDrop<BigDrop> needs to be dropped.
|
||||||
|
//
|
||||||
|
// CHECK-LABEL: @manually_drop_field(
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// CHECK-SAME: %b)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub fn manually_drop_field(a: fn() -> BigDrop, mut b: ManuallyDrop<BigDrop>) {
|
||||||
|
// FIXME(tmiasko) replace with custom MIR, instead of expecting MIR optimizations to turn this
|
||||||
|
// into: drop((_2.0: BigDrop))
|
||||||
|
*b = a();
|
||||||
|
unsafe { core::intrinsics::unreachable() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// `readonly` is omitted from the return place, even when applicable.
|
||||||
|
//
|
||||||
|
// CHECK-LABEL: @never_returns(
|
||||||
|
// CHECK-NOT: readonly
|
||||||
|
// CHECK-SAME: %_0)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub fn never_returns() -> [u8; 80] {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLVM21-LABEL: @not_captured_return_place(ptr{{.*}} captures(none) {{.*}}%_0)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub fn not_captured_return_place() -> [u8; 80] {
|
||||||
|
[0u8; 80]
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLVM21-LABEL: @captured_return_place(ptr{{.*}} captures(address) {{.*}}%_0)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub fn captured_return_place() -> [u8; 80] {
|
||||||
|
black_box([0u8; 80])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {}
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn notunpin_borrow(_: &NotUnpin) {}
|
pub fn notunpin_borrow(_: &NotUnpin) {}
|
||||||
|
|
||||||
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias noundef readonly align 4{{( captures\(address\))?}} dereferenceable(32) %_1)
|
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn indirect_struct(_: S) {}
|
pub fn indirect_struct(_: S) {}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ pub fn notunpin_box(x: Box<NotUnpin>) -> Box<NotUnpin> {
|
|||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(address\))?}} dereferenceable(32){{( %_0)?}})
|
// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}})
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn struct_return() -> S {
|
pub fn struct_return() -> S {
|
||||||
S { _field: [0, 0, 0, 0, 0, 0, 0, 0] }
|
S { _field: [0, 0, 0, 0, 0, 0, 0, 0] }
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(0 bytes),
|
pointee_size: Size(0 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(0 bytes),
|
pointee_size: Size(0 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(0 bytes),
|
pointee_size: Size(0 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(0 bytes),
|
pointee_size: Size(0 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(32 bytes),
|
pointee_size: Size(32 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -527,7 +527,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(128 bytes),
|
pointee_size: Size(128 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Direct(
|
mode: Direct(
|
||||||
ArgAttributes {
|
ArgAttributes {
|
||||||
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
|
regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(2 bytes),
|
pointee_size: Size(2 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(32 bytes),
|
pointee_size: Size(32 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -527,7 +527,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(128 bytes),
|
pointee_size: Size(128 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Direct(
|
mode: Direct(
|
||||||
ArgAttributes {
|
ArgAttributes {
|
||||||
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
|
regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(2 bytes),
|
pointee_size: Size(2 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(32 bytes),
|
pointee_size: Size(32 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -527,7 +527,7 @@ error: ABIs are not compatible
|
|||||||
},
|
},
|
||||||
mode: Indirect {
|
mode: Indirect {
|
||||||
attrs: ArgAttributes {
|
attrs: ArgAttributes {
|
||||||
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
|
regular: CapturesAddress | NoAlias | NonNull | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(128 bytes),
|
pointee_size: Size(128 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
@@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
|
|||||||
},
|
},
|
||||||
mode: Direct(
|
mode: Direct(
|
||||||
ArgAttributes {
|
ArgAttributes {
|
||||||
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
|
regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef,
|
||||||
arg_ext: None,
|
arg_ext: None,
|
||||||
pointee_size: Size(2 bytes),
|
pointee_size: Size(2 bytes),
|
||||||
pointee_align: Some(
|
pointee_align: Some(
|
||||||
|
|||||||
Reference in New Issue
Block a user