rustc_trans: compute better align/dereferenceable attributes from pointees.

This commit is contained in:
Eduard-Mihai Burtescu 2017-10-03 10:45:07 +03:00
parent ced5e04e8b
commit f8d5d0c30c
5 changed files with 242 additions and 128 deletions

View File

@ -730,7 +730,9 @@ extern "C" {
FunctionTy: TypeRef)
-> ValueRef;
pub fn LLVMSetFunctionCallConv(Fn: ValueRef, CC: c_uint);
pub fn LLVMRustAddAlignmentAttr(Fn: ValueRef, index: c_uint, bytes: u32);
pub fn LLVMRustAddDereferenceableAttr(Fn: ValueRef, index: c_uint, bytes: u64);
pub fn LLVMRustAddDereferenceableOrNullAttr(Fn: ValueRef, index: c_uint, bytes: u64);
pub fn LLVMRustAddFunctionAttribute(Fn: ValueRef, index: c_uint, attr: Attribute);
pub fn LLVMRustAddFunctionAttrStringValue(Fn: ValueRef,
index: c_uint,
@ -760,7 +762,11 @@ extern "C" {
// Operations on call sites
pub fn LLVMSetInstructionCallConv(Instr: ValueRef, CC: c_uint);
pub fn LLVMRustAddCallSiteAttribute(Instr: ValueRef, index: c_uint, attr: Attribute);
pub fn LLVMRustAddAlignmentCallSiteAttr(Instr: ValueRef, index: c_uint, bytes: u32);
pub fn LLVMRustAddDereferenceableCallSiteAttr(Instr: ValueRef, index: c_uint, bytes: u64);
pub fn LLVMRustAddDereferenceableOrNullCallSiteAttr(Instr: ValueRef,
index: c_uint,
bytes: u64);
// Operations on load/store instructions (only)
pub fn LLVMSetVolatile(MemoryAccessInst: ValueRef, volatile: Bool);

View File

@ -96,20 +96,24 @@ impl ArgAttribute {
/// A compact representation of LLVM attributes (at least those relevant for this module)
/// that can be manipulated without interacting with LLVM's Attribute machinery.
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
pub struct ArgAttributes {
regular: ArgAttribute,
dereferenceable_bytes: u64,
pointee_size: Size,
pointee_align: Option<Align>
}
impl ArgAttributes {
pub fn set(&mut self, attr: ArgAttribute) -> &mut Self {
self.regular = self.regular | attr;
self
fn new() -> Self {
ArgAttributes {
regular: ArgAttribute::default(),
pointee_size: Size::from_bytes(0),
pointee_align: None,
}
}
pub fn set_dereferenceable(&mut self, size: Size) -> &mut Self {
self.dereferenceable_bytes = size.bytes();
pub fn set(&mut self, attr: ArgAttribute) -> &mut Self {
self.regular = self.regular | attr;
self
}
@ -118,24 +122,52 @@ impl ArgAttributes {
}
pub fn apply_llfn(&self, idx: AttributePlace, llfn: ValueRef) {
let mut regular = self.regular;
unsafe {
self.regular.for_each_kind(|attr| attr.apply_llfn(idx, llfn));
if self.dereferenceable_bytes != 0 {
llvm::LLVMRustAddDereferenceableAttr(llfn,
idx.as_uint(),
self.dereferenceable_bytes);
let deref = self.pointee_size.bytes();
if deref != 0 {
if regular.contains(ArgAttribute::NonNull) {
llvm::LLVMRustAddDereferenceableAttr(llfn,
idx.as_uint(),
deref);
} else {
llvm::LLVMRustAddDereferenceableOrNullAttr(llfn,
idx.as_uint(),
deref);
}
regular -= ArgAttribute::NonNull;
}
if let Some(align) = self.pointee_align {
llvm::LLVMRustAddAlignmentAttr(llfn,
idx.as_uint(),
align.abi() as u32);
}
regular.for_each_kind(|attr| attr.apply_llfn(idx, llfn));
}
}
pub fn apply_callsite(&self, idx: AttributePlace, callsite: ValueRef) {
let mut regular = self.regular;
unsafe {
self.regular.for_each_kind(|attr| attr.apply_callsite(idx, callsite));
if self.dereferenceable_bytes != 0 {
llvm::LLVMRustAddDereferenceableCallSiteAttr(callsite,
idx.as_uint(),
self.dereferenceable_bytes);
let deref = self.pointee_size.bytes();
if deref != 0 {
if regular.contains(ArgAttribute::NonNull) {
llvm::LLVMRustAddDereferenceableCallSiteAttr(callsite,
idx.as_uint(),
deref);
} else {
llvm::LLVMRustAddDereferenceableOrNullCallSiteAttr(callsite,
idx.as_uint(),
deref);
}
regular -= ArgAttribute::NonNull;
}
if let Some(align) = self.pointee_align {
llvm::LLVMRustAddAlignmentCallSiteAttr(callsite,
idx.as_uint(),
align.abi() as u32);
}
regular.for_each_kind(|attr| attr.apply_callsite(idx, callsite));
}
}
}
@ -439,12 +471,20 @@ pub struct ArgType<'tcx> {
impl<'a, 'tcx> ArgType<'tcx> {
fn new(layout: TyLayout<'tcx>) -> ArgType<'tcx> {
let mut attrs = ArgAttributes::new();
if let layout::Abi::Scalar(ref scalar) = layout.abi {
if scalar.is_bool() {
attrs.set(ArgAttribute::ZExt);
}
}
ArgType {
kind: ArgKind::Direct,
layout,
cast: None,
pad: None,
attrs: ArgAttributes::default(),
attrs,
nested: vec![]
}
}
@ -454,14 +494,16 @@ impl<'a, 'tcx> ArgType<'tcx> {
assert_eq!(self.kind, ArgKind::Direct);
// Wipe old attributes, likely not valid through indirection.
self.attrs = ArgAttributes::default();
self.attrs = ArgAttributes::new();
// For non-immediate arguments the callee gets its own copy of
// the value on the stack, so there are no aliases. It's also
// program-invisible so can't possibly capture
self.attrs.set(ArgAttribute::NoAlias)
.set(ArgAttribute::NoCapture)
.set_dereferenceable(self.layout.size);
.set(ArgAttribute::NonNull);
self.attrs.pointee_size = self.layout.size;
self.attrs.pointee_align = Some(self.layout.align);
self.kind = ArgKind::Indirect;
}
@ -472,6 +514,22 @@ impl<'a, 'tcx> ArgType<'tcx> {
self.kind = ArgKind::Ignore;
}
fn safe_pointee(&mut self, layout: TyLayout) {
match self.layout.abi {
layout::Abi::Scalar(layout::Scalar {
value: layout::Pointer,
ref valid_range
}) => {
if valid_range.start > 0 {
self.attrs.set(ArgAttribute::NonNull);
}
self.attrs.pointee_size = layout.size;
self.attrs.pointee_align = Some(layout.align);
}
_ => bug!("ArgType::safe_pointee({:#?}): not a pointer", self.layout)
}
}
pub fn extend_integer_width_to(&mut self, bits: u64) {
// Only integers have signedness
if let layout::Abi::Scalar(ref scalar) = self.layout.abi {
@ -694,13 +752,85 @@ impl<'a, 'tcx> FnType<'tcx> {
_ => false
};
// Handle safe Rust thin and fat pointers.
let adjust_for_rust_type = |arg: &mut ArgType<'tcx>, is_return: bool| {
// We only handle thin pointers here.
match arg.layout.abi {
layout::Abi::Scalar(layout::Scalar { value: layout::Pointer, .. }) => {}
_ => return
}
let mut ty = arg.layout.ty;
// FIXME(eddyb) detect more nested cases than `Option<&T>` here.
match arg.layout.variants {
layout::Variants::NicheFilling { dataful_variant, .. } => {
let variant = arg.layout.for_variant(ccx, dataful_variant);
for i in 0..variant.fields.count() {
let field = variant.field(ccx, i);
match field.abi {
layout::Abi::Scalar(layout::Scalar { value: layout::Pointer, .. }) => {
// We found the pointer field, use its type.
ty = field.ty;
break;
}
_ => {}
}
}
}
_ => {}
}
match ty.sty {
// `Box` pointer parameters never alias because ownership is transferred
ty::TyAdt(def, _) if def.is_box() => {
arg.attrs.set(ArgAttribute::NoAlias);
arg.safe_pointee(ccx.layout_of(ty.boxed_ty()));
}
ty::TyRef(_, mt) => {
// `&mut` pointer parameters never alias other parameters,
// or mutable global data
//
// `&T` where `T` contains no `UnsafeCell<U>` is immutable,
// and can be marked as both `readonly` and `noalias`, as
// LLVM's definition of `noalias` is based solely on memory
// dependencies rather than pointer equality
let is_freeze = ccx.shared().type_is_freeze(mt.ty);
let no_alias_is_safe =
if ccx.shared().tcx().sess.opts.debugging_opts.mutable_noalias ||
ccx.shared().tcx().sess.panic_strategy() == PanicStrategy::Abort {
// Mutable refrences or immutable shared references
mt.mutbl == hir::MutMutable || is_freeze
} else {
// Only immutable shared references
mt.mutbl != hir::MutMutable && is_freeze
};
if no_alias_is_safe {
arg.attrs.set(ArgAttribute::NoAlias);
}
if mt.mutbl == hir::MutImmutable && is_freeze && !is_return {
arg.attrs.set(ArgAttribute::ReadOnly);
}
arg.safe_pointee(ccx.layout_of(mt.ty));
}
_ => {}
}
// HACK(eddyb) LLVM inserts `llvm.assume` calls when inlining functions
// with align attributes, and those calls later block optimizations.
if !is_return {
arg.attrs.pointee_align = None;
}
};
let arg_of = |ty: Ty<'tcx>, is_return: bool| {
let mut arg = ArgType::new(ccx.layout_of(ty));
if let layout::Abi::Scalar(ref scalar) = arg.layout.abi {
if scalar.is_bool() {
arg.attrs.set(ArgAttribute::ZExt);
}
}
if arg.layout.is_zst() {
// For some forsaken reason, x86_64-pc-windows-gnu
// doesn't ignore zero-sized struct arguments.
@ -710,107 +840,27 @@ impl<'a, 'tcx> FnType<'tcx> {
arg.ignore();
}
}
// FIXME(eddyb) other ABIs don't have logic for nested.
if !is_return && type_is_fat_ptr(ccx, arg.layout.ty) && rust_abi {
arg.nested = vec![
ArgType::new(arg.layout.field(ccx, 0)),
ArgType::new(arg.layout.field(ccx, 1))
];
adjust_for_rust_type(&mut arg.nested[0], false);
adjust_for_rust_type(&mut arg.nested[1], false);
} else {
adjust_for_rust_type(&mut arg, is_return);
}
arg
};
let ret_ty = sig.output();
let mut ret = arg_of(ret_ty, true);
if !type_is_fat_ptr(ccx, ret_ty) {
// The `noalias` attribute on the return value is useful to a
// function ptr caller.
if ret_ty.is_box() {
// `Box` pointer return values never alias because ownership
// is transferred
ret.attrs.set(ArgAttribute::NoAlias);
}
// We can also mark the return value as `dereferenceable` in certain cases
match ret_ty.sty {
// These are not really pointers but pairs, (pointer, len)
ty::TyRef(_, ty::TypeAndMut { ty, .. }) => {
ret.attrs.set_dereferenceable(ccx.size_of(ty));
}
ty::TyAdt(def, _) if def.is_box() => {
ret.attrs.set_dereferenceable(ccx.size_of(ret_ty.boxed_ty()));
}
_ => {}
}
}
let mut args = Vec::with_capacity(inputs.len() + extra_args.len());
// Handle safe Rust thin and fat pointers.
let rust_ptr_attrs = |ty: Ty<'tcx>, arg: &mut ArgType| match ty.sty {
// `Box` pointer parameters never alias because ownership is transferred
ty::TyAdt(def, _) if def.is_box() => {
arg.attrs.set(ArgAttribute::NoAlias);
Some(ty.boxed_ty())
}
ty::TyRef(_, mt) => {
// `&mut` pointer parameters never alias other parameters, or mutable global data
//
// `&T` where `T` contains no `UnsafeCell<U>` is immutable, and can be marked as
// both `readonly` and `noalias`, as LLVM's definition of `noalias` is based solely
// on memory dependencies rather than pointer equality
let is_freeze = ccx.shared().type_is_freeze(mt.ty);
let no_alias_is_safe =
if ccx.shared().tcx().sess.opts.debugging_opts.mutable_noalias ||
ccx.shared().tcx().sess.panic_strategy() == PanicStrategy::Abort {
// Mutable refrences or immutable shared references
mt.mutbl == hir::MutMutable || is_freeze
} else {
// Only immutable shared references
mt.mutbl != hir::MutMutable && is_freeze
};
if no_alias_is_safe {
arg.attrs.set(ArgAttribute::NoAlias);
}
if mt.mutbl == hir::MutImmutable && is_freeze {
arg.attrs.set(ArgAttribute::ReadOnly);
}
Some(mt.ty)
}
_ => None
};
for ty in inputs.iter().chain(extra_args.iter()) {
let mut arg = arg_of(ty, false);
if type_is_fat_ptr(ccx, ty) {
let mut data = ArgType::new(arg.layout.field(ccx, 0));
let mut info = ArgType::new(arg.layout.field(ccx, 1));
if let Some(inner) = rust_ptr_attrs(ty, &mut data) {
data.attrs.set(ArgAttribute::NonNull);
if ccx.tcx().struct_tail(inner).is_trait() {
// vtables can be safely marked non-null, readonly
// and noalias.
info.attrs.set(ArgAttribute::NonNull);
info.attrs.set(ArgAttribute::ReadOnly);
info.attrs.set(ArgAttribute::NoAlias);
}
}
// FIXME(eddyb) other ABIs don't have logic for nested.
if rust_abi {
arg.nested = vec![data, info];
}
} else {
if let Some(inner) = rust_ptr_attrs(ty, &mut arg) {
arg.attrs.set_dereferenceable(ccx.size_of(inner));
}
}
args.push(arg);
}
FnType {
args,
ret,
ret: arg_of(sig.output(), true),
args: inputs.iter().chain(extra_args.iter()).map(|ty| {
arg_of(ty, false)
}).collect(),
variadic: sig.variadic,
cconv,
}

View File

@ -178,6 +178,22 @@ extern "C" void LLVMRustAddCallSiteAttribute(LLVMValueRef Instr, unsigned Index,
#endif
}
extern "C" void LLVMRustAddAlignmentCallSiteAttr(LLVMValueRef Instr,
unsigned Index,
uint32_t Bytes) {
CallSite Call = CallSite(unwrap<Instruction>(Instr));
AttrBuilder B;
B.addAlignmentAttr(Bytes);
#if LLVM_VERSION_GE(5, 0)
Call.setAttributes(Call.getAttributes().addAttributes(
Call->getContext(), Index, B));
#else
Call.setAttributes(Call.getAttributes().addAttributes(
Call->getContext(), Index,
AttributeSet::get(Call->getContext(), Index, B)));
#endif
}
extern "C" void LLVMRustAddDereferenceableCallSiteAttr(LLVMValueRef Instr,
unsigned Index,
uint64_t Bytes) {
@ -194,6 +210,22 @@ extern "C" void LLVMRustAddDereferenceableCallSiteAttr(LLVMValueRef Instr,
#endif
}
extern "C" void LLVMRustAddDereferenceableOrNullCallSiteAttr(LLVMValueRef Instr,
unsigned Index,
uint64_t Bytes) {
CallSite Call = CallSite(unwrap<Instruction>(Instr));
AttrBuilder B;
B.addDereferenceableOrNullAttr(Bytes);
#if LLVM_VERSION_GE(5, 0)
Call.setAttributes(Call.getAttributes().addAttributes(
Call->getContext(), Index, B));
#else
Call.setAttributes(Call.getAttributes().addAttributes(
Call->getContext(), Index,
AttributeSet::get(Call->getContext(), Index, B)));
#endif
}
extern "C" void LLVMRustAddFunctionAttribute(LLVMValueRef Fn, unsigned Index,
LLVMRustAttribute RustAttr) {
Function *A = unwrap<Function>(Fn);
@ -206,6 +238,19 @@ extern "C" void LLVMRustAddFunctionAttribute(LLVMValueRef Fn, unsigned Index,
#endif
}
extern "C" void LLVMRustAddAlignmentAttr(LLVMValueRef Fn,
unsigned Index,
uint32_t Bytes) {
Function *A = unwrap<Function>(Fn);
AttrBuilder B;
B.addAlignmentAttr(Bytes);
#if LLVM_VERSION_GE(5, 0)
A->addAttributes(Index, B);
#else
A->addAttributes(Index, AttributeSet::get(A->getContext(), Index, B));
#endif
}
extern "C" void LLVMRustAddDereferenceableAttr(LLVMValueRef Fn, unsigned Index,
uint64_t Bytes) {
Function *A = unwrap<Function>(Fn);
@ -218,6 +263,19 @@ extern "C" void LLVMRustAddDereferenceableAttr(LLVMValueRef Fn, unsigned Index,
#endif
}
extern "C" void LLVMRustAddDereferenceableOrNullAttr(LLVMValueRef Fn,
unsigned Index,
uint64_t Bytes) {
Function *A = unwrap<Function>(Fn);
AttrBuilder B;
B.addDereferenceableOrNullAttr(Bytes);
#if LLVM_VERSION_GE(5, 0)
A->addAttributes(Index, B);
#else
A->addAttributes(Index, AttributeSet::get(A->getContext(), Index, B));
#endif
}
extern "C" void LLVMRustAddFunctionAttrStringValue(LLVMValueRef Fn,
unsigned Index,
const char *Name,

View File

@ -15,7 +15,7 @@
#![feature(custom_attribute)]
pub struct S {
_field: [i64; 4],
_field: [i32; 8],
}
pub struct UnsafeInner {
@ -66,7 +66,7 @@ pub fn mutable_unsafe_borrow(_: &mut UnsafeInner) {
pub fn mutable_borrow(_: &mut i32) {
}
// CHECK: @indirect_struct(%S* noalias nocapture dereferenceable(32) %arg0)
// CHECK: @indirect_struct(%S* noalias nocapture align 4 dereferenceable(32) %arg0)
#[no_mangle]
pub fn indirect_struct(_: S) {
}
@ -77,17 +77,17 @@ pub fn indirect_struct(_: S) {
pub fn borrowed_struct(_: &S) {
}
// CHECK: noalias dereferenceable(4) i32* @_box(i32* noalias dereferenceable(4) %x)
// CHECK: noalias align 4 dereferenceable(4) i32* @_box(i32* noalias dereferenceable(4) %x)
#[no_mangle]
pub fn _box(x: Box<i32>) -> Box<i32> {
x
}
// CHECK: @struct_return(%S* noalias nocapture sret dereferenceable(32))
// CHECK: @struct_return(%S* noalias nocapture sret align 4 dereferenceable(32))
#[no_mangle]
pub fn struct_return() -> S {
S {
_field: [0, 0, 0, 0]
_field: [0, 0, 0, 0, 0, 0, 0, 0]
}
}

View File

@ -39,7 +39,7 @@ pub struct BigPacked {
#[no_mangle]
pub fn call_pkd(f: fn() -> Array) -> BigPacked {
// CHECK: [[ALLOCA:%[_a-z0-9]+]] = alloca %Array
// CHECK: call void %{{.*}}(%Array* noalias nocapture sret dereferenceable(32) [[ALLOCA]])
// CHECK: call void %{{.*}}(%Array* noalias nocapture sret align 4 dereferenceable(32) [[ALLOCA]])
// CHECK: call void @llvm.memcpy.{{.*}}(i8* %{{.*}}, i8* %{{.*}}, i{{[0-9]+}} 32, i32 1, i1 false)
// check that calls whose destination is a field of a packed struct
// go through an alloca rather than calling the function with an