about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEduard-Mihai Burtescu <edy.burt@gmail.com>2017-10-10 20:54:50 +0300
committerEduard-Mihai Burtescu <edy.burt@gmail.com>2017-11-19 02:43:55 +0200
commit18ecc564f2cee4da3ef9397ba58e19d3fd9be3de (patch)
treedd9970b4cfa69a7c7d52bf428280d0c196eb4f98
parent7a36141465d1f97936cfceca87ed428dbfafdd3f (diff)
downloadrust-18ecc564f2cee4da3ef9397ba58e19d3fd9be3de.tar.gz
rust-18ecc564f2cee4da3ef9397ba58e19d3fd9be3de.zip
rustc_trans: support scalar pairs directly in the Rust ABI.
-rw-r--r--src/librustc_llvm/lib.rs9
-rw-r--r--src/librustc_trans/abi.rs453
-rw-r--r--src/librustc_trans/attributes.rs2
-rw-r--r--src/librustc_trans/cabi_asmjs.rs5
-rw-r--r--src/librustc_trans/cabi_x86.rs17
-rw-r--r--src/librustc_trans/cabi_x86_64.rs6
-rw-r--r--src/librustc_trans/intrinsic.rs6
-rw-r--r--src/librustc_trans/mir/block.rs134
-rw-r--r--src/librustc_trans/mir/mod.rs98
9 files changed, 367 insertions, 363 deletions
diff --git a/src/librustc_llvm/lib.rs b/src/librustc_llvm/lib.rs
index 5ccce8de706..592bd620564 100644
--- a/src/librustc_llvm/lib.rs
+++ b/src/librustc_llvm/lib.rs
@@ -74,22 +74,19 @@ pub fn AddFunctionAttrStringValue(llfn: ValueRef,
     }
 }
 
-#[repr(C)]
 #[derive(Copy, Clone)]
 pub enum AttributePlace {
+    ReturnValue,
     Argument(u32),
     Function,
 }
 
 impl AttributePlace {
-    pub fn ReturnValue() -> Self {
-        AttributePlace::Argument(0)
-    }
-
     pub fn as_uint(self) -> c_uint {
         match self {
+            AttributePlace::ReturnValue => 0,
+            AttributePlace::Argument(i) => 1 + i,
             AttributePlace::Function => !0,
-            AttributePlace::Argument(i) => i,
         }
     }
 }
diff --git a/src/librustc_trans/abi.rs b/src/librustc_trans/abi.rs
index d69103bbb52..7ef89597b11 100644
--- a/src/librustc_trans/abi.rs
+++ b/src/librustc_trans/abi.rs
@@ -11,7 +11,7 @@
 use llvm::{self, ValueRef, AttributePlace};
 use base;
 use builder::Builder;
-use common::{instance_ty, ty_fn_sig, type_is_fat_ptr, C_usize};
+use common::{instance_ty, ty_fn_sig, C_usize};
 use context::CrateContext;
 use cabi_x86;
 use cabi_x86_64;
@@ -30,7 +30,8 @@ use cabi_sparc64;
 use cabi_nvptx;
 use cabi_nvptx64;
 use cabi_hexagon;
-use mir::lvalue::LvalueRef;
+use mir::lvalue::{Alignment, LvalueRef};
+use mir::operand::OperandValue;
 use type_::Type;
 use type_of::{LayoutLlvmExt, PointerKind};
 
@@ -44,15 +45,19 @@ use std::{cmp, iter};
 pub use syntax::abi::Abi;
 pub use rustc::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
 
-#[derive(Clone, Copy, PartialEq, Debug)]
-enum ArgKind {
-    /// Pass the argument directly using the normal converted
-    /// LLVM type or by coercing to another specified type
-    Direct,
-    /// Pass the argument indirectly via a hidden pointer
-    Indirect,
-    /// Ignore the argument (useful for empty struct)
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum PassMode {
+    /// Ignore the argument (useful for empty struct).
     Ignore,
+    /// Pass the argument directly.
+    Direct(ArgAttributes),
+    /// Pass a pair's elements directly in two arguments.
+    Pair(ArgAttributes, ArgAttributes),
+    /// Pass the argument after casting it, to either
+    /// a single uniform or a pair of registers.
+    Cast(CastTarget),
+    /// Pass the argument indirectly via a hidden pointer.
+    Indirect(ArgAttributes),
 }
 
 // Hack to disable non_upper_case_globals only for the bitflags! and not for the rest
@@ -94,7 +99,7 @@ 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)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct ArgAttributes {
     regular: ArgAttribute,
     pointee_size: Size,
@@ -248,7 +253,7 @@ impl Reg {
 
 /// An argument passed entirely registers with the
 /// same kind (e.g. HFA / HVA on PPC64 and AArch64).
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub struct Uniform {
     pub unit: Reg,
 
@@ -399,7 +404,7 @@ impl<'tcx> LayoutExt<'tcx> for TyLayout<'tcx> {
     }
 }
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub enum CastTarget {
     Uniform(Uniform),
     Pair(Reg, Reg)
@@ -452,66 +457,53 @@ impl CastTarget {
     }
 }
 
-/// Information about how a specific C type
-/// should be passed to or returned from a function
-///
-/// This is borrowed from clang's ABIInfo.h
+/// Information about how to pass an argument to,
+/// or return a value from, a function, under some ABI.
 #[derive(Debug)]
 pub struct ArgType<'tcx> {
-    kind: ArgKind,
     pub layout: TyLayout<'tcx>,
-    /// Cast target, either a single uniform or a pair of registers.
-    pub cast: Option<CastTarget>,
+
     /// Dummy argument, which is emitted before the real argument.
     pub pad: Option<Reg>,
-    /// Attributes of argument.
-    pub attrs: ArgAttributes,
-    pub nested: Vec<ArgType<'tcx>>
+
+    pub mode: PassMode,
 }
 
 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,
-            nested: vec![]
+            mode: PassMode::Direct(ArgAttributes::new()),
         }
     }
 
     pub fn make_indirect(&mut self) {
-        assert!(self.nested.is_empty());
-        assert_eq!(self.kind, ArgKind::Direct);
+        assert_eq!(self.mode, PassMode::Direct(ArgAttributes::new()));
 
-        // Wipe old attributes, likely not valid through indirection.
-        self.attrs = ArgAttributes::new();
+        // Start with fresh attributes for the pointer.
+        let mut 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(ArgAttribute::NonNull);
-        self.attrs.pointee_size = self.layout.size;
-        self.attrs.pointee_align = Some(self.layout.align);
+        attrs.set(ArgAttribute::NoAlias)
+             .set(ArgAttribute::NoCapture)
+             .set(ArgAttribute::NonNull);
+        attrs.pointee_size = self.layout.size;
+        attrs.pointee_align = Some(self.layout.align);
 
-        self.kind = ArgKind::Indirect;
+        self.mode = PassMode::Indirect(attrs);
     }
 
-    pub fn ignore(&mut self) {
-        assert!(self.nested.is_empty());
-        assert_eq!(self.kind, ArgKind::Direct);
-        self.kind = ArgKind::Ignore;
+    pub fn make_indirect_byval(&mut self) {
+        self.make_indirect();
+        match self.mode {
+            PassMode::Indirect(ref mut attrs) => {
+                attrs.set(ArgAttribute::ByVal);
+            }
+            _ => bug!()
+        }
     }
 
     pub fn extend_integer_width_to(&mut self, bits: u64) {
@@ -519,32 +511,36 @@ impl<'a, 'tcx> ArgType<'tcx> {
         if let layout::Abi::Scalar(ref scalar) = self.layout.abi {
             if let layout::Int(i, signed) = scalar.value {
                 if i.size().bits() < bits {
-                    self.attrs.set(if signed {
-                        ArgAttribute::SExt
-                    } else {
-                        ArgAttribute::ZExt
-                    });
+                    if let PassMode::Direct(ref mut attrs) = self.mode {
+                        attrs.set(if signed {
+                            ArgAttribute::SExt
+                        } else {
+                            ArgAttribute::ZExt
+                        });
+                    }
                 }
             }
         }
     }
 
     pub fn cast_to<T: Into<CastTarget>>(&mut self, target: T) {
-        assert!(self.nested.is_empty());
-        self.cast = Some(target.into());
+        assert_eq!(self.mode, PassMode::Direct(ArgAttributes::new()));
+        self.mode = PassMode::Cast(target.into());
     }
 
     pub fn pad_with(&mut self, reg: Reg) {
-        assert!(self.nested.is_empty());
         self.pad = Some(reg);
     }
 
     pub fn is_indirect(&self) -> bool {
-        self.kind == ArgKind::Indirect
+        match self.mode {
+            PassMode::Indirect(_) => true,
+            _ => false
+        }
     }
 
     pub fn is_ignore(&self) -> bool {
-        self.kind == ArgKind::Ignore
+        self.mode == PassMode::Ignore
     }
 
     /// Get the LLVM type for an lvalue of the original Rust type of
@@ -557,20 +553,19 @@ impl<'a, 'tcx> ArgType<'tcx> {
     /// lvalue for the original Rust type of this argument/return.
     /// Can be used for both storing formal arguments into Rust variables
     /// or results of call/invoke instructions into their destinations.
-    pub fn store(&self, bcx: &Builder<'a, 'tcx>, mut val: ValueRef, dst: LvalueRef<'tcx>) {
+    pub fn store(&self, bcx: &Builder<'a, 'tcx>, val: ValueRef, dst: LvalueRef<'tcx>) {
         if self.is_ignore() {
             return;
         }
         let ccx = bcx.ccx;
         if self.is_indirect() {
-            let llsz = C_usize(ccx, self.layout.size.bytes());
-            base::call_memcpy(bcx, dst.llval, val, llsz, self.layout.align);
-        } else if let Some(ty) = self.cast {
+            OperandValue::Ref(val, Alignment::AbiAligned).store(bcx, dst)
+        } else if let PassMode::Cast(cast) = self.mode {
             // FIXME(eddyb): Figure out when the simpler Store is safe, clang
             // uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
             let can_store_through_cast_ptr = false;
             if can_store_through_cast_ptr {
-                let cast_dst = bcx.pointercast(dst.llval, ty.llvm_type(ccx).ptr_to());
+                let cast_dst = bcx.pointercast(dst.llval, cast.llvm_type(ccx).ptr_to());
                 bcx.store(val, cast_dst, Some(self.layout.align));
             } else {
                 // The actual return type is a struct, but the ABI
@@ -588,8 +583,8 @@ impl<'a, 'tcx> ArgType<'tcx> {
                 //   bitcasting to the struct type yields invalid cast errors.
 
                 // We instead thus allocate some scratch space...
-                let llscratch = bcx.alloca(ty.llvm_type(ccx), "abi_cast", None);
-                let scratch_size = ty.size(ccx);
+                let llscratch = bcx.alloca(cast.llvm_type(ccx), "abi_cast", None);
+                let scratch_size = cast.size(ccx);
                 bcx.lifetime_start(llscratch, scratch_size);
 
                 // ...where we first store the value...
@@ -600,32 +595,33 @@ impl<'a, 'tcx> ArgType<'tcx> {
                                   bcx.pointercast(dst.llval, Type::i8p(ccx)),
                                   bcx.pointercast(llscratch, Type::i8p(ccx)),
                                   C_usize(ccx, self.layout.size.bytes()),
-                                  self.layout.align.min(ty.align(ccx)));
+                                  self.layout.align.min(cast.align(ccx)));
 
                 bcx.lifetime_end(llscratch, scratch_size);
             }
         } else {
-            val = base::from_immediate(bcx, val);
-            bcx.store(val, dst.llval, None);
+            OperandValue::Immediate(val).store(bcx, dst);
         }
     }
 
     pub fn store_fn_arg(&self, bcx: &Builder<'a, 'tcx>, idx: &mut usize, dst: LvalueRef<'tcx>) {
-        if !self.nested.is_empty() {
-            for (i, arg) in self.nested.iter().enumerate() {
-                arg.store_fn_arg(bcx, idx, dst.project_field(bcx, i));
-            }
-            return;
-        }
         if self.pad.is_some() {
             *idx += 1;
         }
-        if self.is_ignore() {
-            return;
+        let mut next = || {
+            let val = llvm::get_param(bcx.llfn(), *idx as c_uint);
+            *idx += 1;
+            val
+        };
+        match self.mode {
+            PassMode::Ignore => {},
+            PassMode::Pair(..) => {
+                OperandValue::Pair(next(), next()).store(bcx, dst);
+            }
+            PassMode::Direct(_) | PassMode::Indirect(_) | PassMode::Cast(_) => {
+                self.store(bcx, next(), dst);
+            }
         }
-        let val = llvm::get_param(bcx.llfn(), *idx as c_uint);
-        *idx += 1;
-        self.store(bcx, val, dst);
     }
 }
 
@@ -660,7 +656,7 @@ impl<'a, 'tcx> FnType<'tcx> {
                sig: ty::FnSig<'tcx>,
                extra_args: &[Ty<'tcx>]) -> FnType<'tcx> {
         let mut fn_ty = FnType::unadjusted(ccx, sig, extra_args);
-        fn_ty.adjust_for_abi(ccx, sig);
+        fn_ty.adjust_for_abi(ccx, sig.abi);
         fn_ty
     }
 
@@ -669,9 +665,23 @@ impl<'a, 'tcx> FnType<'tcx> {
                       extra_args: &[Ty<'tcx>]) -> FnType<'tcx> {
         let mut fn_ty = FnType::unadjusted(ccx, sig, extra_args);
         // Don't pass the vtable, it's not an argument of the virtual fn.
-        assert_eq!(fn_ty.args[0].nested.len(), 2);
-        fn_ty.args[0].nested[1].ignore();
-        fn_ty.adjust_for_abi(ccx, sig);
+        {
+            let self_arg = &mut fn_ty.args[0];
+            match self_arg.mode {
+                PassMode::Pair(data_ptr, _) => {
+                    self_arg.mode = PassMode::Direct(data_ptr);
+                }
+                _ => bug!("FnType::new_vtable: non-pair self {:?}", self_arg)
+            }
+
+            let pointee = self_arg.layout.ty.builtin_deref(true, ty::NoPreference)
+                .unwrap_or_else(|| {
+                    bug!("FnType::new_vtable: non-pointer self {:?}", self_arg)
+                }).ty;
+            let fat_ptr_ty = ccx.tcx().mk_mut_ptr(pointee);
+            self_arg.layout = ccx.layout_of(fat_ptr_ty).field(ccx, 0);
+        }
+        fn_ty.adjust_for_abi(ccx, sig.abi);
         fn_ty
     }
 
@@ -737,31 +747,37 @@ impl<'a, 'tcx> FnType<'tcx> {
         };
 
         // Handle safe Rust thin and fat pointers.
-        let adjust_for_rust_type = |arg: &mut ArgType<'tcx>, is_return: bool| {
-            match arg.layout.abi {
-                layout::Abi::Scalar(layout::Scalar {
-                    value: layout::Pointer,
-                    ref valid_range
-                }) => {
-                    if valid_range.start > 0 && valid_range.start < valid_range.end {
-                        arg.attrs.set(ArgAttribute::NonNull);
-                    }
-                }
-                _ => {
-                    // Nothing to do for non-pointer types.
-                    return;
+        let adjust_for_rust_scalar = |attrs: &mut ArgAttributes,
+                                      scalar: &layout::Scalar,
+                                      layout: TyLayout<'tcx>,
+                                      offset: Size,
+                                      is_return: bool| {
+            // Booleans are always an i1 that needs to be zero-extended.
+            if scalar.is_bool() {
+                attrs.set(ArgAttribute::ZExt);
+                return;
+            }
+
+            // Only pointer types handled below.
+            if scalar.value != layout::Pointer {
+                return;
+            }
+
+            if scalar.valid_range.start < scalar.valid_range.end {
+                if scalar.valid_range.start > 0 {
+                    attrs.set(ArgAttribute::NonNull);
                 }
             }
 
-            if let Some(pointee) = arg.layout.pointee_info_at(ccx, Size::from_bytes(0)) {
+            if let Some(pointee) = layout.pointee_info_at(ccx, offset) {
                 if let Some(kind) = pointee.safe {
-                    arg.attrs.pointee_size = pointee.size;
-                    arg.attrs.pointee_align = Some(pointee.align);
+                    attrs.pointee_size = pointee.size;
+                    attrs.pointee_align = Some(pointee.align);
 
                     // 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;
+                        attrs.pointee_align = None;
                     }
 
                     // `Box` pointer parameters never alias because ownership is transferred
@@ -778,11 +794,11 @@ impl<'a, 'tcx> FnType<'tcx> {
                         PointerKind::UniqueBorrowed => !is_return
                     };
                     if no_alias {
-                        arg.attrs.set(ArgAttribute::NoAlias);
+                        attrs.set(ArgAttribute::NoAlias);
                     }
 
                     if kind == PointerKind::Frozen && !is_return {
-                        arg.attrs.set(ArgAttribute::ReadOnly);
+                        attrs.set(ArgAttribute::ReadOnly);
                     }
                 }
             }
@@ -794,22 +810,39 @@ impl<'a, 'tcx> FnType<'tcx> {
                 // For some forsaken reason, x86_64-pc-windows-gnu
                 // doesn't ignore zero-sized struct arguments.
                 // The same is true for s390x-unknown-linux-gnu.
-                if is_return || rust_abi ||
-                    (!win_x64_gnu && !linux_s390x) {
-                    arg.ignore();
+                if is_return || rust_abi || (!win_x64_gnu && !linux_s390x) {
+                    arg.mode = PassMode::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);
+            // FIXME(eddyb) other ABIs don't have logic for scalar pairs.
+            if !is_return && rust_abi {
+                if let layout::Abi::ScalarPair(ref a, ref b) = arg.layout.abi {
+                    let mut a_attrs = ArgAttributes::new();
+                    let mut b_attrs = ArgAttributes::new();
+                    adjust_for_rust_scalar(&mut a_attrs,
+                                           a,
+                                           arg.layout,
+                                           Size::from_bytes(0),
+                                           false);
+                    adjust_for_rust_scalar(&mut b_attrs,
+                                           b,
+                                           arg.layout,
+                                           a.value.size(ccx).abi_align(b.value.align(ccx)),
+                                           false);
+                    arg.mode = PassMode::Pair(a_attrs, b_attrs);
+                    return arg;
+                }
+            }
+
+            if let layout::Abi::Scalar(ref scalar) = arg.layout.abi {
+                if let PassMode::Direct(ref mut attrs) = arg.mode {
+                    adjust_for_rust_scalar(attrs,
+                                           scalar,
+                                           arg.layout,
+                                           Size::from_bytes(0),
+                                           is_return);
+                }
             }
 
             arg
@@ -827,40 +860,20 @@ impl<'a, 'tcx> FnType<'tcx> {
 
     fn adjust_for_abi(&mut self,
                       ccx: &CrateContext<'a, 'tcx>,
-                      sig: ty::FnSig<'tcx>) {
-        let abi = sig.abi;
+                      abi: Abi) {
         if abi == Abi::Unadjusted { return }
 
         if abi == Abi::Rust || abi == Abi::RustCall ||
            abi == Abi::RustIntrinsic || abi == Abi::PlatformIntrinsic {
             let fixup = |arg: &mut ArgType<'tcx>| {
+                if arg.is_ignore() { return; }
+
                 match arg.layout.abi {
                     layout::Abi::Aggregate { .. } => {}
                     _ => return
                 }
 
                 let size = arg.layout.size;
-
-                if let Some(unit) = arg.layout.homogeneous_aggregate(ccx) {
-                    // Replace newtypes with their inner-most type.
-                    if unit.size == size {
-                        // Needs a cast as we've unpacked a newtype.
-                        arg.cast_to(unit);
-                        return;
-                    }
-
-                    // Pairs of floats.
-                    if unit.kind == RegKind::Float {
-                        if unit.size.checked_mul(2, ccx) == Some(size) {
-                            // FIXME(eddyb) This should be using Uniform instead of a pair,
-                            // but the resulting [2 x float/double] breaks emscripten.
-                            // See https://github.com/kripken/emscripten-fastcomp/issues/178.
-                            arg.cast_to(CastTarget::Pair(unit, unit));
-                            return;
-                        }
-                    }
-                }
-
                 if size > layout::Pointer.size(ccx) {
                     arg.make_indirect();
                 } else {
@@ -873,25 +886,12 @@ impl<'a, 'tcx> FnType<'tcx> {
                     });
                 }
             };
-            // Fat pointers are returned by-value.
-            if !self.ret.is_ignore() {
-                if !type_is_fat_ptr(ccx, sig.output()) {
-                    fixup(&mut self.ret);
-                }
-            }
+            fixup(&mut self.ret);
             for arg in &mut self.args {
-                if arg.is_ignore() { continue; }
-                if !arg.nested.is_empty() {
-                    for arg in &mut arg.nested {
-                        assert!(arg.nested.is_empty());
-                        fixup(arg);
-                    }
-                    continue;
-                }
                 fixup(arg);
             }
-            if self.ret.is_indirect() {
-                self.ret.attrs.set(ArgAttribute::StructRet);
+            if let PassMode::Indirect(ref mut attrs) = self.ret.mode {
+                attrs.set(ArgAttribute::StructRet);
             }
             return;
         }
@@ -930,55 +930,44 @@ impl<'a, 'tcx> FnType<'tcx> {
             a => ccx.sess().fatal(&format!("unrecognized arch \"{}\" in target specification", a))
         }
 
-        if self.ret.is_indirect() {
-            self.ret.attrs.set(ArgAttribute::StructRet);
+        if let PassMode::Indirect(ref mut attrs) = self.ret.mode {
+            attrs.set(ArgAttribute::StructRet);
         }
     }
 
     pub fn llvm_type(&self, ccx: &CrateContext<'a, 'tcx>) -> Type {
         let mut llargument_tys = Vec::new();
 
-        let llreturn_ty = if self.ret.is_ignore() {
-            Type::void(ccx)
-        } else if self.ret.is_indirect() {
-            llargument_tys.push(self.ret.memory_ty(ccx).ptr_to());
-            Type::void(ccx)
-        } else if let Some(cast) = self.ret.cast {
-            cast.llvm_type(ccx)
-        } else {
-            self.ret.layout.immediate_llvm_type(ccx)
+        let llreturn_ty = match self.ret.mode {
+            PassMode::Ignore => Type::void(ccx),
+            PassMode::Direct(_) | PassMode::Pair(..) => {
+                self.ret.layout.immediate_llvm_type(ccx)
+            }
+            PassMode::Cast(cast) => cast.llvm_type(ccx),
+            PassMode::Indirect(_) => {
+                llargument_tys.push(self.ret.memory_ty(ccx).ptr_to());
+                Type::void(ccx)
+            }
         };
 
-        {
-            let mut push = |arg: &ArgType<'tcx>| {
-                if arg.is_ignore() {
-                    return;
-                }
-                // add padding
-                if let Some(ty) = arg.pad {
-                    llargument_tys.push(ty.llvm_type(ccx));
-                }
-
-                let llarg_ty = if arg.is_indirect() {
-                    arg.memory_ty(ccx).ptr_to()
-                } else if let Some(cast) = arg.cast {
-                    cast.llvm_type(ccx)
-                } else {
-                    arg.layout.immediate_llvm_type(ccx)
-                };
+        for arg in &self.args {
+            // add padding
+            if let Some(ty) = arg.pad {
+                llargument_tys.push(ty.llvm_type(ccx));
+            }
 
-                llargument_tys.push(llarg_ty);
-            };
-            for arg in &self.args {
-                if !arg.nested.is_empty() {
-                    for arg in &arg.nested {
-                        assert!(arg.nested.is_empty());
-                        push(arg);
-                    }
+            let llarg_ty = match arg.mode {
+                PassMode::Ignore => continue,
+                PassMode::Direct(_) => arg.layout.immediate_llvm_type(ccx),
+                PassMode::Pair(..) => {
+                    llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(ccx, 0));
+                    llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(ccx, 1));
                     continue;
                 }
-                push(arg);
-            }
+                PassMode::Cast(cast) => cast.llvm_type(ccx),
+                PassMode::Indirect(_) => arg.memory_ty(ccx).ptr_to(),
+            };
+            llargument_tys.push(llarg_ty);
         }
 
         if self.variadic {
@@ -989,52 +978,62 @@ impl<'a, 'tcx> FnType<'tcx> {
     }
 
     pub fn apply_attrs_llfn(&self, llfn: ValueRef) {
-        let mut i = if self.ret.is_indirect() { 1 } else { 0 };
-        if !self.ret.is_ignore() {
-            self.ret.attrs.apply_llfn(llvm::AttributePlace::Argument(i), llfn);
-        }
-        i += 1;
-        let mut apply = |arg: &ArgType| {
-            if !arg.is_ignore() {
-                if arg.pad.is_some() { i += 1; }
-                arg.attrs.apply_llfn(llvm::AttributePlace::Argument(i), llfn);
-                i += 1;
-            }
+        let mut i = 0;
+        let mut apply = |attrs: &ArgAttributes| {
+            attrs.apply_llfn(llvm::AttributePlace::Argument(i), llfn);
+            i += 1;
         };
+        match self.ret.mode {
+            PassMode::Direct(ref attrs) => {
+                attrs.apply_llfn(llvm::AttributePlace::ReturnValue, llfn);
+            }
+            PassMode::Indirect(ref attrs) => apply(attrs),
+            _ => {}
+        }
         for arg in &self.args {
-            if !arg.nested.is_empty() {
-                for arg in &arg.nested {
-                    assert!(arg.nested.is_empty());
-                    apply(arg);
+            if arg.pad.is_some() {
+                apply(&ArgAttributes::new());
+            }
+            match arg.mode {
+                PassMode::Ignore => {}
+                PassMode::Direct(ref attrs) |
+                PassMode::Indirect(ref attrs) => apply(attrs),
+                PassMode::Pair(ref a, ref b) => {
+                    apply(a);
+                    apply(b);
                 }
-                continue;
+                PassMode::Cast(_) => apply(&ArgAttributes::new()),
             }
-            apply(arg);
         }
     }
 
     pub fn apply_attrs_callsite(&self, callsite: ValueRef) {
-        let mut i = if self.ret.is_indirect() { 1 } else { 0 };
-        if !self.ret.is_ignore() {
-            self.ret.attrs.apply_callsite(llvm::AttributePlace::Argument(i), callsite);
-        }
-        i += 1;
-        let mut apply = |arg: &ArgType| {
-            if !arg.is_ignore() {
-                if arg.pad.is_some() { i += 1; }
-                arg.attrs.apply_callsite(llvm::AttributePlace::Argument(i), callsite);
-                i += 1;
-            }
+        let mut i = 0;
+        let mut apply = |attrs: &ArgAttributes| {
+            attrs.apply_callsite(llvm::AttributePlace::Argument(i), callsite);
+            i += 1;
         };
+        match self.ret.mode {
+            PassMode::Direct(ref attrs) => {
+                attrs.apply_callsite(llvm::AttributePlace::ReturnValue, callsite);
+            }
+            PassMode::Indirect(ref attrs) => apply(attrs),
+            _ => {}
+        }
         for arg in &self.args {
-            if !arg.nested.is_empty() {
-                for arg in &arg.nested {
-                    assert!(arg.nested.is_empty());
-                    apply(arg);
+            if arg.pad.is_some() {
+                apply(&ArgAttributes::new());
+            }
+            match arg.mode {
+                PassMode::Ignore => {}
+                PassMode::Direct(ref attrs) |
+                PassMode::Indirect(ref attrs) => apply(attrs),
+                PassMode::Pair(ref a, ref b) => {
+                    apply(a);
+                    apply(b);
                 }
-                continue;
+                PassMode::Cast(_) => apply(&ArgAttributes::new()),
             }
-            apply(arg);
         }
 
         if self.cconv != llvm::CCallConv {
diff --git a/src/librustc_trans/attributes.rs b/src/librustc_trans/attributes.rs
index b6ca1460a7d..745aa0da829 100644
--- a/src/librustc_trans/attributes.rs
+++ b/src/librustc_trans/attributes.rs
@@ -116,7 +116,7 @@ pub fn from_fn_attrs(ccx: &CrateContext, attrs: &[ast::Attribute], llfn: ValueRe
             naked(llfn, true);
         } else if attr.check_name("allocator") {
             Attribute::NoAlias.apply_llfn(
-                llvm::AttributePlace::ReturnValue(), llfn);
+                llvm::AttributePlace::ReturnValue, llfn);
         } else if attr.check_name("unwind") {
             unwind(llfn, true);
         } else if attr.check_name("rustc_allocator_nounwind") {
diff --git a/src/librustc_trans/cabi_asmjs.rs b/src/librustc_trans/cabi_asmjs.rs
index da13b75c414..1664251cf89 100644
--- a/src/librustc_trans/cabi_asmjs.rs
+++ b/src/librustc_trans/cabi_asmjs.rs
@@ -8,7 +8,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use abi::{FnType, ArgType, ArgAttribute, LayoutExt, Uniform};
+use abi::{FnType, ArgType, LayoutExt, Uniform};
 use context::CrateContext;
 
 // Data layout: e-p:32:32-i64:64-v128:32:128-n32-S128
@@ -35,8 +35,7 @@ fn classify_ret_ty<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, ret: &mut ArgType<'tc
 
 fn classify_arg_ty(arg: &mut ArgType) {
     if arg.layout.is_aggregate() {
-        arg.make_indirect();
-        arg.attrs.set(ArgAttribute::ByVal);
+        arg.make_indirect_byval();
     }
 }
 
diff --git a/src/librustc_trans/cabi_x86.rs b/src/librustc_trans/cabi_x86.rs
index dc9f681af52..6fd0140c399 100644
--- a/src/librustc_trans/cabi_x86.rs
+++ b/src/librustc_trans/cabi_x86.rs
@@ -8,7 +8,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use abi::{ArgAttribute, FnType, LayoutExt, Reg, RegKind};
+use abi::{ArgAttribute, FnType, LayoutExt, PassMode, Reg, RegKind};
 use common::CrateContext;
 
 use rustc::ty::layout::{self, TyLayout};
@@ -82,8 +82,7 @@ pub fn compute_abi_info<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
     for arg in &mut fty.args {
         if arg.is_ignore() { continue; }
         if arg.layout.is_aggregate() {
-            arg.make_indirect();
-            arg.attrs.set(ArgAttribute::ByVal);
+            arg.make_indirect_byval();
         } else {
             arg.extend_integer_width_to(32);
         }
@@ -102,7 +101,15 @@ pub fn compute_abi_info<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
         let mut free_regs = 2;
 
         for arg in &mut fty.args {
-            if arg.is_ignore() || arg.is_indirect() { continue; }
+            let attrs = match arg.mode {
+                PassMode::Ignore |
+                PassMode::Indirect(_) => continue,
+                PassMode::Direct(ref mut attrs) => attrs,
+                PassMode::Pair(..) |
+                PassMode::Cast(_) => {
+                    bug!("x86 shouldn't be passing arguments by {:?}", arg.mode)
+                }
+            };
 
             // At this point we know this must be a primitive of sorts.
             let unit = arg.layout.homogeneous_aggregate(ccx).unwrap();
@@ -124,7 +131,7 @@ pub fn compute_abi_info<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
             free_regs -= size_in_regs;
 
             if arg.layout.size.bits() <= 32 && unit.kind == RegKind::Integer {
-                arg.attrs.set(ArgAttribute::InReg);
+                attrs.set(ArgAttribute::InReg);
             }
 
             if free_regs == 0 {
diff --git a/src/librustc_trans/cabi_x86_64.rs b/src/librustc_trans/cabi_x86_64.rs
index eeb69276500..81eb362ca46 100644
--- a/src/librustc_trans/cabi_x86_64.rs
+++ b/src/librustc_trans/cabi_x86_64.rs
@@ -11,7 +11,7 @@
 // The classification code for the x86_64 ABI is taken from the clay language
 // https://github.com/jckarter/clay/blob/master/compiler/src/externals.cpp
 
-use abi::{ArgType, ArgAttribute, CastTarget, FnType, LayoutExt, Reg, RegKind};
+use abi::{ArgType, CastTarget, FnType, LayoutExt, Reg, RegKind};
 use context::CrateContext;
 
 use rustc::ty::layout::{self, TyLayout, Size};
@@ -214,11 +214,11 @@ pub fn compute_abi_info<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, fty: &mut FnType
         };
 
         if in_mem {
-            arg.make_indirect();
             if is_arg {
-                arg.attrs.set(ArgAttribute::ByVal);
+                arg.make_indirect_byval();
             } else {
                 // `sret` parameter thus one less integer register available
+                arg.make_indirect();
                 int_regs -= 1;
             }
         } else {
diff --git a/src/librustc_trans/intrinsic.rs b/src/librustc_trans/intrinsic.rs
index 7d08090cd7e..5abc096407d 100644
--- a/src/librustc_trans/intrinsic.rs
+++ b/src/librustc_trans/intrinsic.rs
@@ -13,7 +13,7 @@
 use intrinsics::{self, Intrinsic};
 use llvm;
 use llvm::{ValueRef};
-use abi::{Abi, FnType};
+use abi::{Abi, FnType, PassMode};
 use mir::lvalue::{LvalueRef, Alignment};
 use mir::operand::{OperandRef, OperandValue};
 use base::*;
@@ -237,7 +237,7 @@ pub fn trans_intrinsic_call<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
         "volatile_load" => {
             let tp_ty = substs.type_at(0);
             let mut ptr = args[0].immediate();
-            if let Some(ty) = fn_ty.ret.cast {
+            if let PassMode::Cast(ty) = fn_ty.ret.mode {
                 ptr = bcx.pointercast(ptr, ty.llvm_type(ccx).ptr_to());
             }
             let load = bcx.volatile_load(ptr);
@@ -671,7 +671,7 @@ pub fn trans_intrinsic_call<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
     };
 
     if !fn_ty.ret.is_ignore() {
-        if let Some(ty) = fn_ty.ret.cast {
+        if let PassMode::Cast(ty) = fn_ty.ret.mode {
             let ptr = bcx.pointercast(llresult, ty.llvm_type(ccx).ptr_to());
             bcx.store(llval, ptr, Some(ccx.align_of(ret_ty)));
         } else {
diff --git a/src/librustc_trans/mir/block.rs b/src/librustc_trans/mir/block.rs
index 67e0f35b46e..f43eba36a82 100644
--- a/src/librustc_trans/mir/block.rs
+++ b/src/librustc_trans/mir/block.rs
@@ -15,7 +15,7 @@ use rustc::ty::{self, TypeFoldable};
 use rustc::ty::layout::{self, LayoutOf};
 use rustc::traits;
 use rustc::mir;
-use abi::{Abi, FnType, ArgType};
+use abi::{Abi, FnType, ArgType, PassMode};
 use base;
 use callee;
 use builder::Builder;
@@ -207,44 +207,47 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
             }
 
             mir::TerminatorKind::Return => {
-                if self.fn_ty.ret.is_ignore() || self.fn_ty.ret.is_indirect() {
-                    bcx.ret_void();
-                    return;
-                }
+                let llval = match self.fn_ty.ret.mode {
+                    PassMode::Ignore | PassMode::Indirect(_) => {
+                        bcx.ret_void();
+                        return;
+                    }
 
-                let llval = if let Some(cast_ty) = self.fn_ty.ret.cast {
-                    let op = match self.locals[mir::RETURN_POINTER] {
-                        LocalRef::Operand(Some(op)) => op,
-                        LocalRef::Operand(None) => bug!("use of return before def"),
-                        LocalRef::Lvalue(tr_lvalue) => {
-                            OperandRef {
-                                val: Ref(tr_lvalue.llval, tr_lvalue.alignment),
-                                layout: tr_lvalue.layout
-                            }
-                        }
-                    };
-                    let llslot = match op.val {
-                        Immediate(_) | Pair(..) => {
-                            let scratch = LvalueRef::alloca(&bcx, self.fn_ty.ret.layout, "ret");
-                            op.val.store(&bcx, scratch);
-                            scratch.llval
-                        }
-                        Ref(llval, align) => {
-                            assert_eq!(align, Alignment::AbiAligned,
-                                       "return pointer is unaligned!");
-                            llval
+                    PassMode::Direct(_) | PassMode::Pair(..) => {
+                        let op = self.trans_consume(&bcx, &mir::Lvalue::Local(mir::RETURN_POINTER));
+                        if let Ref(llval, align) = op.val {
+                            bcx.load(llval, align.non_abi())
+                        } else {
+                            op.immediate_or_packed_pair(&bcx)
                         }
-                    };
-                    let load = bcx.load(
-                        bcx.pointercast(llslot, cast_ty.llvm_type(bcx.ccx).ptr_to()),
-                        Some(self.fn_ty.ret.layout.align));
-                    load
-                } else {
-                    let op = self.trans_consume(&bcx, &mir::Lvalue::Local(mir::RETURN_POINTER));
-                    if let Ref(llval, align) = op.val {
-                        bcx.load(llval, align.non_abi())
-                    } else {
-                        op.immediate_or_packed_pair(&bcx)
+                    }
+
+                    PassMode::Cast(cast_ty) => {
+                        let op = match self.locals[mir::RETURN_POINTER] {
+                            LocalRef::Operand(Some(op)) => op,
+                            LocalRef::Operand(None) => bug!("use of return before def"),
+                            LocalRef::Lvalue(tr_lvalue) => {
+                                OperandRef {
+                                    val: Ref(tr_lvalue.llval, tr_lvalue.alignment),
+                                    layout: tr_lvalue.layout
+                                }
+                            }
+                        };
+                        let llslot = match op.val {
+                            Immediate(_) | Pair(..) => {
+                                let scratch = LvalueRef::alloca(&bcx, self.fn_ty.ret.layout, "ret");
+                                op.val.store(&bcx, scratch);
+                                scratch.llval
+                            }
+                            Ref(llval, align) => {
+                                assert_eq!(align, Alignment::AbiAligned,
+                                           "return pointer is unaligned!");
+                                llval
+                            }
+                        };
+                        bcx.load(
+                            bcx.pointercast(llslot, cast_ty.llvm_type(bcx.ccx).ptr_to()),
+                            Some(self.fn_ty.ret.layout.align))
                     }
                 };
                 bcx.ret(llval);
@@ -559,12 +562,12 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
 
                 for (i, arg) in first_args.iter().enumerate() {
                     let mut op = self.trans_operand(&bcx, arg);
-                    if i == 0 {
-                        if let Pair(_, meta) = op.val {
-                            if let Some(ty::InstanceDef::Virtual(_, idx)) = def {
-                                llfn = Some(meth::VirtualIndex::from_index(idx)
-                                    .get_fn(&bcx, meta, &fn_ty));
-                            }
+                    if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
+                        if let Pair(data_ptr, meta) = op.val {
+                            llfn = Some(meth::VirtualIndex::from_index(idx)
+                                .get_fn(&bcx, meta, &fn_ty));
+                            llargs.push(data_ptr);
+                            continue;
                         }
                     }
 
@@ -604,21 +607,6 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
                       op: OperandRef<'tcx>,
                       llargs: &mut Vec<ValueRef>,
                       arg: &ArgType<'tcx>) {
-        if let Pair(a, b) = op.val {
-            // Treat the values in a fat pointer separately.
-            if !arg.nested.is_empty() {
-                assert_eq!(arg.nested.len(), 2);
-                let imm_op = |x| OperandRef {
-                    val: Immediate(x),
-                    // We won't be checking the type again.
-                    layout: bcx.ccx.layout_of(bcx.tcx().types.never)
-                };
-                self.trans_argument(bcx, imm_op(a), llargs, &arg.nested[0]);
-                self.trans_argument(bcx, imm_op(b), llargs, &arg.nested[1]);
-                return;
-            }
-        }
-
         // Fill padding with undef value, where applicable.
         if let Some(ty) = arg.pad {
             llargs.push(C_undef(ty.llvm_type(bcx.ccx)));
@@ -628,15 +616,29 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
             return;
         }
 
+        if let PassMode::Pair(..) = arg.mode {
+            match op.val {
+                Pair(a, b) => {
+                    llargs.push(a);
+                    llargs.push(b);
+                    return;
+                }
+                _ => bug!("trans_argument: {:?} invalid for pair arugment", op)
+            }
+        }
+
         // Force by-ref if we have to load through a cast pointer.
         let (mut llval, align, by_ref) = match op.val {
             Immediate(_) | Pair(..) => {
-                if arg.is_indirect() || arg.cast.is_some() {
-                    let scratch = LvalueRef::alloca(bcx, arg.layout, "arg");
-                    op.val.store(bcx, scratch);
-                    (scratch.llval, Alignment::AbiAligned, true)
-                } else {
-                    (op.immediate_or_packed_pair(bcx), Alignment::AbiAligned, false)
+                match arg.mode {
+                    PassMode::Indirect(_) | PassMode::Cast(_) => {
+                        let scratch = LvalueRef::alloca(bcx, arg.layout, "arg");
+                        op.val.store(bcx, scratch);
+                        (scratch.llval, Alignment::AbiAligned, true)
+                    }
+                    _ => {
+                        (op.immediate_or_packed_pair(bcx), Alignment::AbiAligned, false)
+                    }
                 }
             }
             Ref(llval, align @ Alignment::Packed(_)) if arg.is_indirect() => {
@@ -653,7 +655,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
 
         if by_ref && !arg.is_indirect() {
             // Have to load the argument, maybe while casting it.
-            if let Some(ty) = arg.cast {
+            if let PassMode::Cast(ty) = arg.mode {
                 llval = bcx.load(bcx.pointercast(llval, ty.llvm_type(bcx.ccx).ptr_to()),
                                  (align | Alignment::Packed(arg.layout.align))
                                     .non_abi());
@@ -890,7 +892,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
             }
             DirectOperand(index) => {
                 // If there is a cast, we have to store and reload.
-                let op = if ret_ty.cast.is_some() {
+                let op = if let PassMode::Cast(_) = ret_ty.mode {
                     let tmp = LvalueRef::alloca(bcx, ret_ty.layout, "tmp_ret");
                     tmp.storage_live(bcx);
                     ret_ty.store(bcx, llval, tmp);
diff --git a/src/librustc_trans/mir/mod.rs b/src/librustc_trans/mir/mod.rs
index 6f9d32b1a37..7f3a430c418 100644
--- a/src/librustc_trans/mir/mod.rs
+++ b/src/librustc_trans/mir/mod.rs
@@ -22,7 +22,7 @@ use builder::Builder;
 use common::{CrateContext, Funclet};
 use debuginfo::{self, declare_local, VariableAccess, VariableKind, FunctionDebugContext};
 use monomorphize::Instance;
-use abi::{ArgAttribute, FnType};
+use abi::{ArgAttribute, FnType, PassMode};
 
 use syntax_pos::{DUMMY_SP, NO_EXPANSION, BytePos, Span};
 use syntax::symbol::keywords;
@@ -429,55 +429,52 @@ fn arg_local_refs<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
 
         let arg = &mircx.fn_ty.args[idx];
         idx += 1;
-        let lvalue = if arg.is_indirect() {
-            // Don't copy an indirect argument to an alloca, the caller
-            // already put it in a temporary alloca and gave it up
-            // FIXME: lifetimes
-            if arg.pad.is_some() {
-                llarg_idx += 1;
-            }
-            let llarg = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
-            bcx.set_value_name(llarg, &name);
-            llarg_idx += 1;
-            LvalueRef::new_sized(llarg, arg.layout, Alignment::AbiAligned)
-        } else if !lvalue_locals.contains(local.index()) &&
-                  !arg.nested.is_empty() {
-            assert_eq!(arg.nested.len(), 2);
-            let (a, b) = (&arg.nested[0], &arg.nested[1]);
-            assert!(!a.is_ignore() && a.cast.is_none() && a.pad.is_none());
-            assert!(!b.is_ignore() && b.cast.is_none() && b.pad.is_none());
-
-            let a = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
-            bcx.set_value_name(a, &(name.clone() + ".0"));
-            llarg_idx += 1;
-
-            let b = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
-            bcx.set_value_name(b, &(name + ".1"));
+        if arg.pad.is_some() {
             llarg_idx += 1;
+        }
 
-            return LocalRef::Operand(Some(OperandRef {
-                val: OperandValue::Pair(a, b),
-                layout: arg.layout
-            }));
-        } else if !lvalue_locals.contains(local.index()) &&
-                  !arg.is_indirect() && arg.cast.is_none() &&
-                  arg_scope.is_none() {
-            if arg.is_ignore() {
-                return LocalRef::new_operand(bcx.ccx, arg.layout);
-            }
-
+        if arg_scope.is_none() && !lvalue_locals.contains(local.index()) {
             // We don't have to cast or keep the argument in the alloca.
             // FIXME(eddyb): We should figure out how to use llvm.dbg.value instead
             // of putting everything in allocas just so we can use llvm.dbg.declare.
-            if arg.pad.is_some() {
-                llarg_idx += 1;
+            let local = |op| LocalRef::Operand(Some(op));
+            match arg.mode {
+                PassMode::Ignore => {
+                    return local(OperandRef::new_zst(bcx.ccx, arg.layout));
+                }
+                PassMode::Direct(_) => {
+                    let llarg = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
+                    bcx.set_value_name(llarg, &name);
+                    llarg_idx += 1;
+                    return local(
+                        OperandRef::from_immediate_or_packed_pair(bcx, llarg, arg.layout));
+                }
+                PassMode::Pair(..) => {
+                    let a = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
+                    bcx.set_value_name(a, &(name.clone() + ".0"));
+                    llarg_idx += 1;
+
+                    let b = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
+                    bcx.set_value_name(b, &(name + ".1"));
+                    llarg_idx += 1;
+
+                    return local(OperandRef {
+                        val: OperandValue::Pair(a, b),
+                        layout: arg.layout
+                    });
+                }
+                _ => {}
             }
+        }
+
+        let lvalue = if arg.is_indirect() {
+            // Don't copy an indirect argument to an alloca, the caller
+            // already put it in a temporary alloca and gave it up.
+            // FIXME: lifetimes
             let llarg = llvm::get_param(bcx.llfn(), llarg_idx as c_uint);
             bcx.set_value_name(llarg, &name);
             llarg_idx += 1;
-            return LocalRef::Operand(Some(
-                OperandRef::from_immediate_or_packed_pair(bcx, llarg, arg.layout)
-            ));
+            LvalueRef::new_sized(llarg, arg.layout, Alignment::AbiAligned)
         } else {
             let tmp = LvalueRef::alloca(bcx, arg.layout, &name);
             arg.store_fn_arg(bcx, &mut llarg_idx, tmp);
@@ -489,16 +486,19 @@ fn arg_local_refs<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
                 // The Rust ABI passes indirect variables using a pointer and a manual copy, so we
                 // need to insert a deref here, but the C ABI uses a pointer and a copy using the
                 // byval attribute, for which LLVM does the deref itself, so we must not add it.
-                let variable_access = if arg.is_indirect() &&
-                    !arg.attrs.contains(ArgAttribute::ByVal) {
-                    VariableAccess::IndirectVariable {
-                        alloca: lvalue.llval,
-                        address_operations: &deref_op,
-                    }
-                } else {
-                    VariableAccess::DirectVariable { alloca: lvalue.llval }
+                let mut variable_access = VariableAccess::DirectVariable {
+                    alloca: lvalue.llval
                 };
 
+                if let PassMode::Indirect(ref attrs) = arg.mode {
+                    if !attrs.contains(ArgAttribute::ByVal) {
+                        variable_access = VariableAccess::IndirectVariable {
+                            alloca: lvalue.llval,
+                            address_operations: &deref_op,
+                        };
+                    }
+                }
+
                 declare_local(
                     bcx,
                     &mircx.debug_context,