about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDan Robertson <dan@dlrobertson.com>2018-11-30 15:53:44 +0000
committerDan Robertson <dan@dlrobertson.com>2019-02-27 10:21:35 -0500
commit58147d486bc26eb59d18d8e0d83aa6fe0b467fb9 (patch)
tree3e25be958b33c7e5727b059511157ac9f1a22722
parentcd56472cc47981e62c684ceada7922ac3731b785 (diff)
downloadrust-58147d486bc26eb59d18d8e0d83aa6fe0b467fb9.tar.gz
rust-58147d486bc26eb59d18d8e0d83aa6fe0b467fb9.zip
Support defining C compatible variadic functions
Add support for defining C compatible variadic functions in unsafe rust
with extern "C".
-rw-r--r--src/librustc/hir/intravisit.rs3
-rw-r--r--src/librustc/hir/lowering.rs6
-rw-r--r--src/librustc/hir/mod.rs3
-rw-r--r--src/librustc/hir/print.rs3
-rw-r--r--src/librustc/ich/impls_hir.rs3
-rw-r--r--src/librustc/middle/resolve_lifetime.rs31
-rw-r--r--src/librustc/ty/sty.rs6
-rw-r--r--src/librustc_codegen_llvm/abi.rs45
-rw-r--r--src/librustc_codegen_llvm/intrinsic.rs51
-rw-r--r--src/librustc_codegen_llvm/mono_item.rs8
-rw-r--r--src/librustc_codegen_llvm/va_arg.rs4
-rw-r--r--src/librustc_codegen_ssa/mir/block.rs50
-rw-r--r--src/librustc_codegen_ssa/mir/mod.rs64
-rw-r--r--src/librustc_codegen_ssa/traits/intrinsic.rs6
-rw-r--r--src/librustc_lint/types.rs9
-rw-r--r--src/librustc_mir/borrow_check/nll/type_check/mod.rs11
-rw-r--r--src/librustc_save_analysis/sig.rs1
-rw-r--r--src/librustc_target/abi/call/mod.rs17
-rw-r--r--src/librustc_target/abi/call/x86.rs2
-rw-r--r--src/librustc_typeck/astconv.rs9
-rw-r--r--src/librustc_typeck/check/callee.rs14
-rw-r--r--src/librustc_typeck/check/intrinsic.rs6
-rw-r--r--src/librustdoc/clean/mod.rs8
-rw-r--r--src/librustdoc/html/format.rs22
-rw-r--r--src/libsyntax/ast.rs2
-rw-r--r--src/libsyntax/mut_visit.rs3
-rw-r--r--src/libsyntax/parse/parser.rs106
-rw-r--r--src/libsyntax/print/pprust.rs3
-rw-r--r--src/libsyntax/visit.rs2
-rw-r--r--src/test/codegen/c-variadic.rs69
-rw-r--r--src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs27
-rw-r--r--src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c10
-rw-r--r--src/test/rustdoc/variadic.rs2
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-1.rs (renamed from src/test/ui/variadic/variadic-ffi.rs)0
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-1.stderr (renamed from src/test/ui/variadic/variadic-ffi.stderr)2
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-2.rs (renamed from src/test/ui/variadic/variadic-ffi-2.rs)0
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-2.stderr (renamed from src/test/ui/variadic/variadic-ffi-2.stderr)0
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-3.rs (renamed from src/test/ui/variadic/variadic-ffi-3.rs)4
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-3.stderr (renamed from src/test/ui/variadic/variadic-ffi-3.stderr)18
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-4.rs29
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-4.stderr198
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-5.rs31
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-5.stderr73
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-6.rs12
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-6.stderr11
-rw-r--r--src/test/ui/error-codes/E0617.rs4
-rw-r--r--src/test/ui/error-codes/E0617.stderr8
-rw-r--r--src/test/ui/parser/recover-enum2.stderr4
48 files changed, 848 insertions, 152 deletions
diff --git a/src/librustc/hir/intravisit.rs b/src/librustc/hir/intravisit.rs
index 0bc40da7e4f..8e4b9a5e8e6 100644
--- a/src/librustc/hir/intravisit.rs
+++ b/src/librustc/hir/intravisit.rs
@@ -617,6 +617,9 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty) {
         TyKind::Typeof(ref expression) => {
             visitor.visit_anon_const(expression)
         }
+        TyKind::CVarArgs(ref lt) => {
+            visitor.visit_lifetime(lt)
+        }
         TyKind::Infer | TyKind::Err => {}
     }
 }
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 0211dd72875..0e2b34d4fac 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -1345,6 +1345,12 @@ impl<'a> LoweringContext<'a> {
                 }
             }
             TyKind::Mac(_) => panic!("TyMac should have been expanded by now."),
+            TyKind::CVarArgs => {
+                // Create the implicit lifetime of the "spoofed" `VaList`.
+                let span = self.sess.source_map().next_point(t.span.shrink_to_lo());
+                let lt = self.new_implicit_lifetime(span);
+                hir::TyKind::CVarArgs(lt)
+            },
         };
 
         let LoweredNodeId { node_id: _, hir_id } = self.lower_node_id(t.id);
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index 38e6e61592b..205109d18fe 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -1829,6 +1829,9 @@ pub enum TyKind {
     Infer,
     /// Placeholder for a type that has failed to be defined.
     Err,
+    /// Placeholder for C-variadic arguments. We "spoof" the `VaList` created
+    /// from the variadic arguments. This type is only valid up to typeck.
+    CVarArgs(Lifetime),
 }
 
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
diff --git a/src/librustc/hir/print.rs b/src/librustc/hir/print.rs
index 17d37488413..8c252b0d027 100644
--- a/src/librustc/hir/print.rs
+++ b/src/librustc/hir/print.rs
@@ -434,6 +434,9 @@ impl<'a> State<'a> {
                 self.s.word("/*ERROR*/")?;
                 self.pclose()?;
             }
+            hir::TyKind::CVarArgs(_) => {
+                self.s.word("...")?;
+            }
         }
         self.end()
     }
diff --git a/src/librustc/ich/impls_hir.rs b/src/librustc/ich/impls_hir.rs
index d1161dda1e2..b7ec5889d6a 100644
--- a/src/librustc/ich/impls_hir.rs
+++ b/src/librustc/ich/impls_hir.rs
@@ -361,7 +361,8 @@ impl_stable_hash_for!(enum hir::TyKind {
     TraitObject(trait_refs, lifetime),
     Typeof(body_id),
     Err,
-    Infer
+    Infer,
+    CVarArgs(lt),
 });
 
 impl_stable_hash_for!(struct hir::FnDecl {
diff --git a/src/librustc/middle/resolve_lifetime.rs b/src/librustc/middle/resolve_lifetime.rs
index 31e9eb9b746..832391d4416 100644
--- a/src/librustc/middle/resolve_lifetime.rs
+++ b/src/librustc/middle/resolve_lifetime.rs
@@ -764,6 +764,13 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     });
                 }
             }
+            hir::TyKind::CVarArgs(ref lt) => {
+                // Resolve the generated lifetime for the C-variadic arguments.
+                // The lifetime is generated in AST -> HIR lowering.
+                if lt.name.is_elided() {
+                    self.resolve_elided_lifetimes(vec![lt])
+                }
+            }
             _ => intravisit::walk_ty(self, ty),
         }
     }
@@ -2225,18 +2232,22 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
                 if let hir::TyKind::BareFn(_) = ty.node {
                     self.outer_index.shift_in(1);
                 }
-                if let hir::TyKind::TraitObject(ref bounds, ref lifetime) = ty.node {
-                    for bound in bounds {
-                        self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None);
-                    }
+                match ty.node {
+                    hir::TyKind::TraitObject(ref bounds, ref lifetime) => {
+                        for bound in bounds {
+                            self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None);
+                        }
 
-                    // Stay on the safe side and don't include the object
-                    // lifetime default (which may not end up being used).
-                    if !lifetime.is_elided() {
-                        self.visit_lifetime(lifetime);
+                        // Stay on the safe side and don't include the object
+                        // lifetime default (which may not end up being used).
+                        if !lifetime.is_elided() {
+                            self.visit_lifetime(lifetime);
+                        }
+                    }
+                    hir::TyKind::CVarArgs(_) => {}
+                    _ => {
+                        intravisit::walk_ty(self, ty);
                     }
-                } else {
-                    intravisit::walk_ty(self, ty);
                 }
                 if let hir::TyKind::BareFn(_) = ty.node {
                     self.outer_index.shift_out(1);
diff --git a/src/librustc/ty/sty.rs b/src/librustc/ty/sty.rs
index df8b14b1f10..7ade035ce89 100644
--- a/src/librustc/ty/sty.rs
+++ b/src/librustc/ty/sty.rs
@@ -977,9 +977,9 @@ impl<'tcx> PolyGenSig<'tcx> {
 /// Signature of a function type, which I have arbitrarily
 /// decided to use to refer to the input/output types.
 ///
-/// - `inputs` is the list of arguments and their modes.
-/// - `output` is the return type.
-/// - `variadic` indicates whether this is a variadic function. (only true for foreign fns)
+/// - `inputs`: is the list of arguments and their modes.
+/// - `output`: is the return type.
+/// - `variadic`: indicates whether this is a C-variadic function.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
 pub struct FnSig<'tcx> {
     pub inputs_and_output: &'tcx List<Ty<'tcx>>,
diff --git a/src/librustc_codegen_llvm/abi.rs b/src/librustc_codegen_llvm/abi.rs
index 992149f7a47..aea62360651 100644
--- a/src/librustc_codegen_llvm/abi.rs
+++ b/src/librustc_codegen_llvm/abi.rs
@@ -258,7 +258,7 @@ impl ArgTypeExt<'ll, 'tcx> for ArgType<'tcx, Ty<'tcx>> {
             val
         };
         match self.mode {
-            PassMode::Ignore => {},
+            PassMode::Ignore(_) => {}
             PassMode::Pair(..) => {
                 OperandValue::Pair(next(), next()).store(bx, dst);
             }
@@ -507,6 +507,14 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
             }
         };
 
+        // Store the index of the last argument. This is useful for working with
+        // C-compatible variadic arguments.
+        let last_arg_idx = if sig.inputs().is_empty() {
+            None
+        } else {
+            Some(sig.inputs().len() - 1)
+        };
+
         let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| {
             let is_return = arg_idx.is_none();
             let mut arg = mk_arg_type(ty, arg_idx);
@@ -516,7 +524,30 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
                 // The same is true for s390x-unknown-linux-gnu
                 // and sparc64-unknown-linux-gnu.
                 if is_return || rust_abi || (!win_x64_gnu && !linux_s390x && !linux_sparc64) {
-                    arg.mode = PassMode::Ignore;
+                    arg.mode = PassMode::Ignore(IgnoreMode::Zst);
+                }
+            }
+
+            // If this is a C-variadic function, this is not the return value,
+            // and there is one or more fixed arguments; ensure that the `VaList`
+            // is ignored as an argument.
+            if sig.variadic {
+                match (last_arg_idx, arg_idx) {
+                    (Some(last_idx), Some(cur_idx)) if last_idx == cur_idx => {
+                        let va_list_did = match cx.tcx.lang_items().va_list() {
+                            Some(did) => did,
+                            None => bug!("`va_list` lang item required for C-variadic functions"),
+                        };
+                        match ty.sty {
+                            ty::Adt(def, _) if def.did == va_list_did => {
+                                // This is the "spoofed" `VaList`. Set the arguments mode
+                                // so that it will be ignored.
+                                arg.mode = PassMode::Ignore(IgnoreMode::CVarArgs);
+                            },
+                            _ => (),
+                        }
+                    }
+                    _ => {}
                 }
             }
 
@@ -646,7 +677,9 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
         );
 
         let llreturn_ty = match self.ret.mode {
-            PassMode::Ignore => cx.type_void(),
+            PassMode::Ignore(IgnoreMode::Zst) => cx.type_void(),
+            PassMode::Ignore(IgnoreMode::CVarArgs) =>
+                bug!("`va_list` should never be a return type"),
             PassMode::Direct(_) | PassMode::Pair(..) => {
                 self.ret.layout.immediate_llvm_type(cx)
             }
@@ -664,7 +697,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
             }
 
             let llarg_ty = match arg.mode {
-                PassMode::Ignore => continue,
+                PassMode::Ignore(_) => continue,
                 PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
                 PassMode::Pair(..) => {
                     llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
@@ -733,7 +766,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
                 apply(&ArgAttributes::new());
             }
             match arg.mode {
-                PassMode::Ignore => {}
+                PassMode::Ignore(_) => {}
                 PassMode::Direct(ref attrs) |
                 PassMode::Indirect(ref attrs, None) => apply(attrs),
                 PassMode::Indirect(ref attrs, Some(ref extra_attrs)) => {
@@ -780,7 +813,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
                 apply(&ArgAttributes::new());
             }
             match arg.mode {
-                PassMode::Ignore => {}
+                PassMode::Ignore(_) => {}
                 PassMode::Direct(ref attrs) |
                 PassMode::Indirect(ref attrs, None) => apply(attrs),
                 PassMode::Indirect(ref attrs, Some(ref extra_attrs)) => {
diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs
index d1cbe1d4dd6..3268af396a2 100644
--- a/src/librustc_codegen_llvm/intrinsic.rs
+++ b/src/librustc_codegen_llvm/intrinsic.rs
@@ -136,22 +136,18 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
                 let tp_ty = substs.type_at(0);
                 self.const_usize(self.size_of(tp_ty).bytes())
             }
-            func @ "va_start" | func @ "va_end" => {
-                let va_list = match (tcx.lang_items().va_list(), &result.layout.ty.sty) {
-                    (Some(did), ty::Adt(def, _)) if def.did == did => args[0].immediate(),
-                    (Some(_), _) => self.load(args[0].immediate(),
-                                              tcx.data_layout.pointer_align.abi),
-                    (None, _) => bug!("va_list language item must be defined")
-                };
-                let intrinsic = self.cx().get_intrinsic(&format!("llvm.{}", func));
-                self.call(intrinsic, &[va_list], None)
+            "va_start" => {
+                self.va_start(args[0].immediate())
+            }
+            "va_end" => {
+                self.va_end(args[0].immediate())
             }
             "va_copy" => {
                 let va_list = match (tcx.lang_items().va_list(), &result.layout.ty.sty) {
                     (Some(did), ty::Adt(def, _)) if def.did == did => args[0].immediate(),
                     (Some(_), _)  => self.load(args[0].immediate(),
                                                tcx.data_layout.pointer_align.abi),
-                    (None, _) => bug!("va_list language item must be defined")
+                    (None, _) => bug!("`va_list` language item must be defined")
                 };
                 let intrinsic = self.cx().get_intrinsic(&("llvm.va_copy"));
                 self.call(intrinsic, &[llresult, va_list], None);
@@ -722,6 +718,41 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
         let expect = self.get_intrinsic(&"llvm.expect.i1");
         self.call(expect, &[cond, self.const_bool(expected)], None)
     }
+
+    fn va_start(&mut self, list: &'ll Value) -> &'ll Value {
+        let target = &self.cx.tcx.sess.target.target;
+        let arch = &target.arch;
+        // A pointer to the architecture specific structure is passed to this
+        // function. For pointer variants (i686, RISC-V, Windows, etc), we
+        // should do do nothing, as the address to the pointer is needed. For
+        // architectures with a architecture specific structure (`Aarch64`,
+        // `X86_64`, etc), this function should load the structure from the
+        // address provided.
+        let va_list = match &**arch {
+            _ if target.options.is_like_windows => list,
+            "aarch64" if target.target_os == "ios" => list,
+            "aarch64" | "x86_64" | "powerpc" =>
+                self.load(list, self.tcx().data_layout.pointer_align.abi),
+            _ => list,
+        };
+        let intrinsic = self.cx().get_intrinsic("llvm.va_start");
+        self.call(intrinsic, &[va_list], None)
+    }
+
+    fn va_end(&mut self, list: &'ll Value) -> &'ll Value {
+        let target = &self.cx.tcx.sess.target.target;
+        let arch = &target.arch;
+        // See the comment in `va_start` for the purpose of the following.
+        let va_list = match &**arch {
+            _ if target.options.is_like_windows => list,
+            "aarch64" if target.target_os == "ios" => list,
+            "aarch64" | "x86_64" | "powerpc" =>
+                self.load(list, self.tcx().data_layout.pointer_align.abi),
+            _ => list,
+        };
+        let intrinsic = self.cx().get_intrinsic("llvm.va_end");
+        self.call(intrinsic, &[va_list], None)
+    }
 }
 
 fn copy_intrinsic(
diff --git a/src/librustc_codegen_llvm/mono_item.rs b/src/librustc_codegen_llvm/mono_item.rs
index 4fe6a1f4f4b..7f0cdb9f580 100644
--- a/src/librustc_codegen_llvm/mono_item.rs
+++ b/src/librustc_codegen_llvm/mono_item.rs
@@ -36,10 +36,10 @@ impl PreDefineMethods<'tcx> for CodegenCx<'ll, 'tcx> {
     }
 
     fn predefine_fn(&self,
-                              instance: Instance<'tcx>,
-                              linkage: Linkage,
-                              visibility: Visibility,
-                              symbol_name: &str) {
+                    instance: Instance<'tcx>,
+                    linkage: Linkage,
+                    visibility: Visibility,
+                    symbol_name: &str) {
         assert!(!instance.substs.needs_infer() &&
                 !instance.substs.has_param_types());
 
diff --git a/src/librustc_codegen_llvm/va_arg.rs b/src/librustc_codegen_llvm/va_arg.rs
index 8719390b51a..7aceaea4510 100644
--- a/src/librustc_codegen_llvm/va_arg.rs
+++ b/src/librustc_codegen_llvm/va_arg.rs
@@ -109,12 +109,12 @@ pub(super) fn emit_va_arg(
                             Align::from_bytes(4).unwrap(), true)
         }
         // Windows Aarch64
-        ("aarch4", true) => {
+        ("aarch64", true) => {
             emit_ptr_va_arg(bx, addr, target_ty, false,
                             Align::from_bytes(8).unwrap(), false)
         }
         // iOS Aarch64
-        ("aarch4", _) if target.target_os == "ios" => {
+        ("aarch64", _) if target.target_os == "ios" => {
             emit_ptr_va_arg(bx, addr, target_ty, false,
                             Align::from_bytes(8).unwrap(), true)
         }
diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs
index caca1789fc9..f40aa0cb6d1 100644
--- a/src/librustc_codegen_ssa/mir/block.rs
+++ b/src/librustc_codegen_ssa/mir/block.rs
@@ -3,7 +3,7 @@ use rustc::ty::{self, Ty, TypeFoldable};
 use rustc::ty::layout::{self, LayoutOf, HasTyCtxt};
 use rustc::mir;
 use rustc::mir::interpret::EvalErrorKind;
-use rustc_target::abi::call::{ArgType, FnType, PassMode};
+use rustc_target::abi::call::{ArgType, FnType, PassMode, IgnoreMode};
 use rustc_target::spec::abi::Abi;
 use rustc_mir::monomorphize;
 use crate::base;
@@ -18,7 +18,7 @@ use syntax_pos::Pos;
 
 use super::{FunctionCx, LocalRef};
 use super::place::PlaceRef;
-use super::operand::OperandRef;
+use super::operand::{OperandValue, OperandRef};
 use super::operand::OperandValue::{Pair, Ref, Immediate};
 
 impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
@@ -232,12 +232,21 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             }
 
             mir::TerminatorKind::Return => {
+                if self.fn_ty.variadic {
+                    if let Some(va_list) = self.va_list_ref {
+                        bx.va_end(va_list.llval);
+                    }
+                }
                 let llval = match self.fn_ty.ret.mode {
-                    PassMode::Ignore | PassMode::Indirect(..) => {
+                    PassMode::Ignore(IgnoreMode::Zst) | PassMode::Indirect(..) => {
                         bx.ret_void();
                         return;
                     }
 
+                    PassMode::Ignore(IgnoreMode::CVarArgs) => {
+                        bug!("C variadic arguments should never be the return type");
+                    }
+
                     PassMode::Direct(_) | PassMode::Pair(..) => {
                         let op =
                             self.codegen_consume(&mut bx, &mir::Place::Local(mir::RETURN_PLACE));
@@ -481,7 +490,10 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     return;
                 }
 
-                let extra_args = &args[sig.inputs().len()..];
+                // The "spoofed" `VaList` added to a C-variadic functions signature
+                // should not be included in the `extra_args` calculation.
+                let extra_args_start_idx = sig.inputs().len() - if sig.variadic { 1 } else { 0 };
+                let extra_args = &args[extra_args_start_idx..];
                 let extra_args = extra_args.iter().map(|op_arg| {
                     let op_ty = op_arg.ty(self.mir, bx.tcx());
                     self.monomorphize(&op_ty)
@@ -658,7 +670,37 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     (&args[..], None)
                 };
 
+                // Useful determining if the current argument is the "spoofed" `VaList`
+                let last_arg_idx = if sig.inputs().is_empty() {
+                    None
+                } else {
+                    Some(sig.inputs().len() - 1)
+                };
                 'make_args: for (i, arg) in first_args.iter().enumerate() {
+                    // If this is a C-variadic function the function signature contains
+                    // an "spoofed" `VaList`. This argument is ignored, but we need to
+                    // populate it with a dummy operand so that the users real arguments
+                    // are not overwritten.
+                    let i = if sig.variadic && last_arg_idx.map(|x| x == i).unwrap_or(false) {
+                        let layout = match tcx.lang_items().va_list() {
+                            Some(did) => bx.cx().layout_of(bx.tcx().type_of(did)),
+                            None => bug!("va_list language item required for C variadics"),
+                        };
+                        let op = OperandRef {
+                            val: OperandValue::Immediate(
+                                bx.cx().const_undef(bx.cx().immediate_backend_type(layout))
+                            ),
+                            layout: layout,
+                        };
+                        self.codegen_argument(&mut bx, op, &mut llargs, &fn_ty.args[i]);
+                        if i + 1 < fn_ty.args.len() {
+                            i + 1
+                        } else {
+                            break 'make_args
+                        }
+                    } else {
+                        i
+                    };
                     let mut op = self.codegen_operand(&mut bx, arg);
 
                     if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
diff --git a/src/librustc_codegen_ssa/mir/mod.rs b/src/librustc_codegen_ssa/mir/mod.rs
index e1528921a59..95cf8cfe2d0 100644
--- a/src/librustc_codegen_ssa/mir/mod.rs
+++ b/src/librustc_codegen_ssa/mir/mod.rs
@@ -5,7 +5,7 @@ use rustc::mir::{self, Mir};
 use rustc::ty::subst::SubstsRef;
 use rustc::session::config::DebugInfo;
 use rustc_mir::monomorphize::Instance;
-use rustc_target::abi::call::{FnType, PassMode};
+use rustc_target::abi::call::{FnType, PassMode, IgnoreMode};
 use crate::base;
 use crate::debuginfo::{self, VariableAccess, VariableKind, FunctionDebugContext};
 use crate::traits::*;
@@ -86,6 +86,10 @@ pub struct FunctionCx<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> {
 
     /// If this function is being monomorphized, this contains the type substitutions used.
     param_substs: SubstsRef<'tcx>,
+
+    /// If this function is a C-variadic function, this contains the `PlaceRef` of the
+    /// "spoofed" `VaList`.
+    va_list_ref: Option<PlaceRef<'tcx, Bx::Value>>,
 }
 
 impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
@@ -246,13 +250,18 @@ pub fn codegen_mir<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
             assert!(!instance.substs.needs_infer());
             instance.substs
         },
+        va_list_ref: None,
     };
 
     let memory_locals = analyze::non_ssa_locals(&fx);
 
     // Allocate variable and temp allocas
     fx.locals = {
-        let args = arg_local_refs(&mut bx, &fx, &fx.scopes, &memory_locals);
+        // FIXME(dlrobertson): This is ugly. Find a better way of getting the `PlaceRef` or
+        // `LocalRef` from `arg_local_refs`
+        let mut va_list_ref = None;
+        let args = arg_local_refs(&mut bx, &fx, &fx.scopes, &memory_locals, &mut va_list_ref);
+        fx.va_list_ref = va_list_ref;
 
         let mut allocate_local = |local| {
             let decl = &mir.local_decls[local];
@@ -433,6 +442,7 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
         debuginfo::MirDebugScope<Bx::DIScope>
     >,
     memory_locals: &BitSet<mir::Local>,
+    va_list_ref: &mut Option<PlaceRef<'tcx, Bx::Value>>,
 ) -> Vec<LocalRef<'tcx, Bx::Value>> {
     let mir = fx.mir;
     let tcx = fx.cx.tcx();
@@ -447,6 +457,15 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
         None
     };
 
+    // Store the index of the last argument. This is used to
+    // call va_start on the va_list instead of attempting
+    // to store_fn_arg.
+    let last_arg_idx = if fx.fn_ty.args.is_empty() {
+        None
+    } else {
+        Some(fx.fn_ty.args.len() - 1)
+    };
+
     mir.args_iter().enumerate().map(|(arg_index, local)| {
         let arg_decl = &mir.local_decls[local];
 
@@ -510,9 +529,16 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
             // of putting everything in allocas just so we can use llvm.dbg.declare.
             let local = |op| LocalRef::Operand(Some(op));
             match arg.mode {
-                PassMode::Ignore => {
+                PassMode::Ignore(IgnoreMode::Zst) => {
                     return local(OperandRef::new_zst(bx.cx(), arg.layout));
                 }
+                PassMode::Ignore(IgnoreMode::CVarArgs) => {
+                    let backend_type = bx.cx().immediate_backend_type(arg.layout);
+                    return local(OperandRef {
+                        val: OperandValue::Immediate(bx.cx().const_undef(backend_type)),
+                        layout: arg.layout,
+                    });
+                }
                 PassMode::Direct(_) => {
                     let llarg = bx.get_param(bx.llfn(), llarg_idx as c_uint);
                     bx.set_value_name(llarg, &name);
@@ -559,9 +585,35 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
             indirect_operand.store(bx, tmp);
             tmp
         } else {
-            let tmp = PlaceRef::alloca(bx, arg.layout, &name);
-            bx.store_fn_arg(arg, &mut llarg_idx, tmp);
-            tmp
+            if fx.fn_ty.variadic && last_arg_idx.map(|idx| arg_index == idx).unwrap_or(false) {
+                let va_list_impl = match arg_decl.ty.ty_adt_def() {
+                    Some(adt) => adt.non_enum_variant(),
+                    None => bug!("`va_list` language item improperly constructed")
+                };
+                match tcx.type_of(va_list_impl.fields[0].did).sty {
+                    ty::Ref(_, ty, _) => {
+                        // If the underlying structure the `VaList` contains is a structure,
+                        // we need to allocate it (e.g., X86_64 on Linux).
+                        let tmp = PlaceRef::alloca(bx, arg.layout, &name);
+                        if let ty::Adt(..) = ty.sty {
+                            let layout = bx.layout_of(ty);
+                            // Create an unnamed allocation for the backing structure
+                            // and store it in the the spoofed `VaList`.
+                            let backing = PlaceRef::alloca(bx, layout, "");
+                            bx.store(backing.llval, tmp.llval, layout.align.abi);
+                        }
+                        // Call `va_start` on the spoofed `VaList`.
+                        bx.va_start(tmp.llval);
+                        *va_list_ref = Some(tmp);
+                        tmp
+                    }
+                    _ => bug!("improperly constructed `va_list` lang item"),
+                }
+            } else {
+                let tmp = PlaceRef::alloca(bx, arg.layout, &name);
+                bx.store_fn_arg(arg, &mut llarg_idx, tmp);
+                tmp
+            }
         };
         arg_scope.map(|scope| {
             // Is this a regular argument?
diff --git a/src/librustc_codegen_ssa/traits/intrinsic.rs b/src/librustc_codegen_ssa/traits/intrinsic.rs
index 3cd0c39d413..cd527898977 100644
--- a/src/librustc_codegen_ssa/traits/intrinsic.rs
+++ b/src/librustc_codegen_ssa/traits/intrinsic.rs
@@ -20,4 +20,10 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
     fn abort(&mut self);
     fn assume(&mut self, val: Self::Value);
     fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value;
+    /// Trait method used to inject `va_start` on the "spoofed" `VaList` in
+    /// Rust defined C-variadic functions.
+    fn va_start(&mut self, val: Self::Value) -> Self::Value;
+    /// Trait method used to inject `va_end` on the "spoofed" `VaList` before
+    /// Rust defined C-variadic functions return.
+    fn va_end(&mut self, val: Self::Value) -> Self::Value;
 }
diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs
index a56c3215f9d..fb279a5d9b8 100644
--- a/src/librustc_lint/types.rs
+++ b/src/librustc_lint/types.rs
@@ -766,8 +766,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         let def_id = self.cx.tcx.hir().local_def_id(id);
         let sig = self.cx.tcx.fn_sig(def_id);
         let sig = self.cx.tcx.erase_late_bound_regions(&sig);
+        let inputs = if sig.variadic {
+            // Don't include the spoofed `VaList` in the functions list
+            // of inputs.
+            &sig.inputs()[..sig.inputs().len() - 1]
+        } else {
+            &sig.inputs()[..]
+        };
 
-        for (input_ty, input_hir) in sig.inputs().iter().zip(&decl.inputs) {
+        for (input_ty, input_hir) in inputs.iter().zip(&decl.inputs) {
             self.check_type_for_ffi_and_report_errors(input_hir.span, input_ty);
         }
 
diff --git a/src/librustc_mir/borrow_check/nll/type_check/mod.rs b/src/librustc_mir/borrow_check/nll/type_check/mod.rs
index 4202d10aa63..f897795d86f 100644
--- a/src/librustc_mir/borrow_check/nll/type_check/mod.rs
+++ b/src/librustc_mir/borrow_check/nll/type_check/mod.rs
@@ -1602,10 +1602,17 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
         from_hir_call: bool,
     ) {
         debug!("check_call_inputs({:?}, {:?})", sig, args);
-        if args.len() < sig.inputs().len() || (args.len() > sig.inputs().len() && !sig.variadic) {
+        // Do not count the `VaList` argument as a "true" argument to
+        // a C-variadic function.
+        let inputs = if sig.variadic {
+            &sig.inputs()[..sig.inputs().len() - 1]
+        } else {
+            &sig.inputs()[..]
+        };
+        if args.len() < inputs.len() || (args.len() > inputs.len() && !sig.variadic) {
             span_mirbug!(self, term, "call to {:?} with wrong # of args", sig);
         }
-        for (n, (fn_arg, op_arg)) in sig.inputs().iter().zip(args).enumerate() {
+        for (n, (fn_arg, op_arg)) in inputs.iter().zip(args).enumerate() {
             let op_arg_ty = op_arg.ty(mir, self.tcx());
             let category = if from_hir_call {
                 ConstraintCategory::CallArgument
diff --git a/src/librustc_save_analysis/sig.rs b/src/librustc_save_analysis/sig.rs
index 50a335bf908..222c43de3b7 100644
--- a/src/librustc_save_analysis/sig.rs
+++ b/src/librustc_save_analysis/sig.rs
@@ -190,6 +190,7 @@ impl Sig for ast::Ty {
                 Ok(replace_text(nested, text))
             }
             ast::TyKind::Never => Ok(text_sig("!".to_owned())),
+            ast::TyKind::CVarArgs => Ok(text_sig("...".to_owned())),
             ast::TyKind::Tup(ref ts) => {
                 let mut text = "(".to_owned();
                 let mut defs = vec![];
diff --git a/src/librustc_target/abi/call/mod.rs b/src/librustc_target/abi/call/mod.rs
index 411eb192d90..8ada328a158 100644
--- a/src/librustc_target/abi/call/mod.rs
+++ b/src/librustc_target/abi/call/mod.rs
@@ -24,9 +24,17 @@ mod x86_win64;
 mod wasm32;
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum IgnoreMode {
+    /// C-variadic arguments.
+    CVarArgs,
+    /// A zero-sized type.
+    Zst,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub enum PassMode {
-    /// Ignore the argument (useful for empty struct).
-    Ignore,
+    /// Ignore the argument (useful for empty structs and C-variadic args).
+    Ignore(IgnoreMode),
     /// Pass the argument directly.
     Direct(ArgAttributes),
     /// Pass a pair's elements directly in two arguments.
@@ -481,7 +489,10 @@ impl<'a, Ty> ArgType<'a, Ty> {
     }
 
     pub fn is_ignore(&self) -> bool {
-        self.mode == PassMode::Ignore
+        match self.mode {
+            PassMode::Ignore(_) => true,
+            _ => false
+        }
     }
 }
 
diff --git a/src/librustc_target/abi/call/x86.rs b/src/librustc_target/abi/call/x86.rs
index 2e809571ab1..6ca3ce88bd6 100644
--- a/src/librustc_target/abi/call/x86.rs
+++ b/src/librustc_target/abi/call/x86.rs
@@ -88,7 +88,7 @@ pub fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnType<'a, Ty>, flavor: Fla
 
         for arg in &mut fty.args {
             let attrs = match arg.mode {
-                PassMode::Ignore |
+                PassMode::Ignore(_) |
                 PassMode::Indirect(_, None) => continue,
                 PassMode::Direct(ref mut attrs) => attrs,
                 PassMode::Pair(..) |
diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs
index 6f3dc8b969c..1d99584eec4 100644
--- a/src/librustc_typeck/astconv.rs
+++ b/src/librustc_typeck/astconv.rs
@@ -1822,6 +1822,15 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
             hir::TyKind::Err => {
                 tcx.types.err
             }
+            hir::TyKind::CVarArgs(lt) => {
+                let va_list_did = match tcx.lang_items().va_list() {
+                    Some(did) => did,
+                    None => span_bug!(ast_ty.span,
+                                      "`va_list` lang item required for variadics"),
+                };
+                let region = self.ast_region_to_region(&lt, None);
+                tcx.type_of(va_list_did).subst(tcx, &[region.into()])
+            }
         };
 
         self.record_ty(ast_ty.hir_id, result_ty, ast_ty.span);
diff --git a/src/librustc_typeck/check/callee.rs b/src/librustc_typeck/check/callee.rs
index 7bf7d825406..aeb43635eb7 100644
--- a/src/librustc_typeck/check/callee.rs
+++ b/src/librustc_typeck/check/callee.rs
@@ -368,17 +368,27 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
             .0;
         let fn_sig = self.normalize_associated_types_in(call_expr.span, &fn_sig);
 
+        let inputs = if fn_sig.variadic {
+            if fn_sig.inputs().len() > 1 {
+                &fn_sig.inputs()[..fn_sig.inputs().len() - 1]
+            } else {
+                span_bug!(call_expr.span,
+                          "C-variadic functions are only valid with one or more fixed arguments");
+            }
+        } else {
+            &fn_sig.inputs()[..]
+        };
         // Call the generic checker.
         let expected_arg_tys = self.expected_inputs_for_expected_output(
             call_expr.span,
             expected,
             fn_sig.output(),
-            fn_sig.inputs(),
+            inputs,
         );
         self.check_argument_types(
             call_expr.span,
             call_expr.span,
-            fn_sig.inputs(),
+            inputs,
             &expected_arg_tys[..],
             arg_exprs,
             fn_sig.variadic,
diff --git a/src/librustc_typeck/check/intrinsic.rs b/src/librustc_typeck/check/intrinsic.rs
index 3b174b55f2b..924ced2e2a3 100644
--- a/src/librustc_typeck/check/intrinsic.rs
+++ b/src/librustc_typeck/check/intrinsic.rs
@@ -337,7 +337,7 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
             "va_start" | "va_end" => {
                 match mk_va_list_ty() {
                     Some(va_list_ty) => (0, vec![va_list_ty], tcx.mk_unit()),
-                    None => bug!("va_list lang_item must be defined to use va_list intrinsics")
+                    None => bug!("`va_list` language item needed for C-variadic intrinsics")
                 }
             }
 
@@ -364,14 +364,14 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                         };
                         (0, vec![tcx.mk_imm_ref(tcx.mk_region(env_region), va_list_ty)], ret_ty)
                     }
-                    None => bug!("va_list lang_item must be defined to use va_list intrinsics")
+                    None => bug!("`va_list` language item needed for C-variadic intrinsics")
                 }
             }
 
             "va_arg" => {
                 match mk_va_list_ty() {
                     Some(va_list_ty) => (1, vec![va_list_ty], param(0)),
-                    None => bug!("va_list lang_item must be defined to use va_list intrinsics")
+                    None => bug!("`va_list` language item needed for C-variadic intrinsics")
                 }
             }
 
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 72abbae231a..53dcc258c69 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1752,7 +1752,6 @@ impl Clean<Item> for doctree::Function {
 pub struct FnDecl {
     pub inputs: Arguments,
     pub output: FunctionRetTy,
-    pub variadic: bool,
     pub attrs: Attributes,
 }
 
@@ -1831,7 +1830,6 @@ impl<'a, A: Copy> Clean<FnDecl> for (&'a hir::FnDecl, A)
         FnDecl {
             inputs: (&self.0.inputs[..], self.1).clean(cx),
             output: self.0.output.clean(cx),
-            variadic: self.0.variadic,
             attrs: Attributes::default()
         }
     }
@@ -1849,7 +1847,6 @@ impl<'a, 'tcx> Clean<FnDecl> for (DefId, ty::PolyFnSig<'tcx>) {
         FnDecl {
             output: Return(sig.skip_binder().output().clean(cx)),
             attrs: Attributes::default(),
-            variadic: sig.skip_binder().variadic,
             inputs: Arguments {
                 values: sig.skip_binder().inputs().iter().map(|t| {
                     Argument {
@@ -2252,6 +2249,7 @@ pub enum Type {
     Slice(Box<Type>),
     Array(Box<Type>, String),
     Never,
+    CVarArgs,
     Unique(Box<Type>),
     RawPointer(Mutability, Box<Type>),
     BorrowedRef {
@@ -2290,6 +2288,7 @@ pub enum PrimitiveType {
     Reference,
     Fn,
     Never,
+    CVarArgs,
 }
 
 #[derive(Clone, RustcEncodable, RustcDecodable, Copy, Debug)]
@@ -2469,6 +2468,7 @@ impl PrimitiveType {
             Reference => "reference",
             Fn => "fn",
             Never => "never",
+            CVarArgs => "...",
         }
     }
 
@@ -2518,6 +2518,7 @@ impl Clean<Type> for hir::Ty {
 
         match self.node {
             TyKind::Never => Never,
+            TyKind::CVarArgs(_) => CVarArgs,
             TyKind::Ptr(ref m) => RawPointer(m.mutbl.clean(cx), box m.ty.clean(cx)),
             TyKind::Rptr(ref l, ref m) => {
                 let lifetime = if l.is_elided() {
@@ -3654,6 +3655,7 @@ fn build_deref_target_impls(cx: &DocContext<'_, '_, '_>,
             Reference => None,
             Fn => None,
             Never => None,
+            CVarArgs => tcx.lang_items().va_list(),
         };
         if let Some(did) = did {
             if !did.is_local() {
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 4463dad1c8a..d204a179ca6 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -609,6 +609,7 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
             primitive_link(f, PrimitiveType::Array, &format!("; {}]", n))
         }
         clean::Never => primitive_link(f, PrimitiveType::Never, "!"),
+        clean::CVarArgs => primitive_link(f, PrimitiveType::CVarArgs, "..."),
         clean::RawPointer(m, ref t) => {
             match **t {
                 clean::Generic(_) | clean::ResolvedPath {is_generic: true, ..} => {
@@ -834,18 +835,10 @@ impl fmt::Display for clean::FunctionRetTy {
 
 impl fmt::Display for clean::FnDecl {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if self.variadic {
-            if f.alternate() {
-                write!(f, "({args:#}, ...){arrow:#}", args = self.inputs, arrow = self.output)
-            } else {
-                write!(f, "({args}, ...){arrow}", args = self.inputs, arrow = self.output)
-            }
+        if f.alternate() {
+            write!(f, "({args:#}){arrow:#}", args = self.inputs, arrow = self.output)
         } else {
-            if f.alternate() {
-                write!(f, "({args:#}){arrow:#}", args = self.inputs, arrow = self.output)
-            } else {
-                write!(f, "({args}){arrow}", args = self.inputs, arrow = self.output)
-            }
+            write!(f, "({args}){arrow}", args = self.inputs, arrow = self.output)
         }
     }
 }
@@ -907,12 +900,7 @@ impl<'a> fmt::Display for Function<'a> {
             }
         }
 
-        let mut args_plain = format!("({})", args_plain);
-
-        if decl.variadic {
-            args.push_str(",<br> ...");
-            args_plain.push_str(", ...");
-        }
+        let args_plain = format!("({})", args_plain);
 
         let output = if let hir::IsAsync::Async = asyncness {
             Cow::Owned(decl.sugared_async_return_type())
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index 9c4945d74db..5bae00b9cb8 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -1643,6 +1643,8 @@ pub enum TyKind {
     Mac(Mac),
     /// Placeholder for a kind that has failed to be defined.
     Err,
+    /// Placeholder for a `va_list`.
+    CVarArgs,
 }
 
 impl TyKind {
diff --git a/src/libsyntax/mut_visit.rs b/src/libsyntax/mut_visit.rs
index 86849f580d0..8efc4689cac 100644
--- a/src/libsyntax/mut_visit.rs
+++ b/src/libsyntax/mut_visit.rs
@@ -401,7 +401,8 @@ pub fn noop_visit_ty<T: MutVisitor>(ty: &mut P<Ty>, vis: &mut T) {
     let Ty { id, node, span } = ty.deref_mut();
     vis.visit_id(id);
     match node {
-        TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err | TyKind::Never => {}
+        TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err |
+            TyKind::Never | TyKind::CVarArgs => {}
         TyKind::Slice(ty) => vis.visit_ty(ty),
         TyKind::Ptr(mt) => vis.visit_mt(mt),
         TyKind::Rptr(lt, mt) => {
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 7e900dfeb1e..b58091b57da 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -1543,7 +1543,7 @@ impl<'a> Parser<'a> {
                 // definition...
 
                 // We don't allow argument names to be left off in edition 2018.
-                p.parse_arg_general(p.span.rust_2018(), true)
+                p.parse_arg_general(p.span.rust_2018(), true, false)
             })?;
             generics.where_clause = self.parse_where_clause()?;
 
@@ -1613,7 +1613,7 @@ impl<'a> Parser<'a> {
     /// Parses an optional return type `[ -> TY ]` in a function declaration.
     fn parse_ret_ty(&mut self, allow_plus: bool) -> PResult<'a, FunctionRetTy> {
         if self.eat(&token::RArrow) {
-            Ok(FunctionRetTy::Ty(self.parse_ty_common(allow_plus, true)?))
+            Ok(FunctionRetTy::Ty(self.parse_ty_common(allow_plus, true, false)?))
         } else {
             Ok(FunctionRetTy::Default(self.span.shrink_to_lo()))
         }
@@ -1621,7 +1621,7 @@ impl<'a> Parser<'a> {
 
     /// Parses a type.
     pub fn parse_ty(&mut self) -> PResult<'a, P<Ty>> {
-        self.parse_ty_common(true, true)
+        self.parse_ty_common(true, true, false)
     }
 
     /// Parses a type in restricted contexts where `+` is not permitted.
@@ -1631,11 +1631,11 @@ impl<'a> Parser<'a> {
     /// Example 2: `value1 as TYPE + value2`
     ///     `+` is prohibited to avoid interactions with expression grammar.
     fn parse_ty_no_plus(&mut self) -> PResult<'a, P<Ty>> {
-        self.parse_ty_common(false, true)
+        self.parse_ty_common(false, true, false)
     }
 
-    fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool)
-                       -> PResult<'a, P<Ty>> {
+    fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool,
+                       allow_variadic: bool) -> PResult<'a, P<Ty>> {
         maybe_whole!(self, NtTy, |x| x);
 
         let lo = self.span;
@@ -1772,6 +1772,15 @@ impl<'a> Parser<'a> {
                     TyKind::Path(None, path)
                 }
             }
+        } else if self.check(&token::DotDotDot) {
+            if allow_variadic {
+                self.eat(&token::DotDotDot);
+                TyKind::CVarArgs
+            } else {
+                return Err(self.fatal(
+                    "only foreign functions are allowed to be variadic"
+                ));
+            }
         } else {
             let msg = format!("expected type, found {}", self.this_token_descr());
             return Err(self.fatal(&msg));
@@ -1959,7 +1968,8 @@ impl<'a> Parser<'a> {
     }
 
     /// This version of parse arg doesn't necessarily require identifier names.
-    fn parse_arg_general(&mut self, require_name: bool, is_trait_item: bool) -> PResult<'a, Arg> {
+    fn parse_arg_general(&mut self, require_name: bool, is_trait_item: bool,
+                         allow_variadic: bool) -> PResult<'a, Arg> {
         maybe_whole!(self, NtArg, |x| x);
 
         if let Ok(Some(_)) = self.parse_self_arg() {
@@ -2008,12 +2018,12 @@ impl<'a> Parser<'a> {
             }
 
             self.eat_incorrect_doc_comment("a method argument's type");
-            (pat, self.parse_ty()?)
+            (pat, self.parse_ty_common(true, true, allow_variadic)?)
         } else {
             debug!("parse_arg_general ident_to_pat");
             let parser_snapshot_before_ty = self.clone();
             self.eat_incorrect_doc_comment("a method argument's type");
-            let mut ty = self.parse_ty();
+            let mut ty = self.parse_ty_common(true, true, allow_variadic);
             if ty.is_ok() && self.token != token::Comma &&
                self.token != token::CloseDelim(token::Paren) {
                 // This wasn't actually a type, but a pattern looking like a type,
@@ -2032,6 +2042,11 @@ impl<'a> Parser<'a> {
                     (pat, ty)
                 }
                 Err(mut err) => {
+                    // If this is a variadic argument and we hit an error, return the
+                    // error.
+                    if self.token == token::DotDotDot {
+                        return Err(err);
+                    }
                     // Recover from attempting to parse the argument as a type without pattern.
                     err.cancel();
                     mem::replace(self, parser_snapshot_before_ty);
@@ -2068,7 +2083,7 @@ impl<'a> Parser<'a> {
 
     /// Parses a single function argument.
     crate fn parse_arg(&mut self) -> PResult<'a, Arg> {
-        self.parse_arg_general(true, false)
+        self.parse_arg_general(true, false, false)
     }
 
     /// Parses an argument in a lambda header (e.g., `|arg, arg|`).
@@ -2406,7 +2421,7 @@ impl<'a> Parser<'a> {
                 }
                 let span = lo.to(self.prev_span);
                 let output = if self.eat(&token::RArrow) {
-                    Some(self.parse_ty_common(false, false)?)
+                    Some(self.parse_ty_common(false, false, false)?)
                 } else {
                     None
                 };
@@ -6118,44 +6133,38 @@ impl<'a> Parser<'a> {
                 &token::CloseDelim(token::Paren),
                 SeqSep::trailing_allowed(token::Comma),
                 |p| {
-                    if p.token == token::DotDotDot {
-                        p.bump();
-                        variadic = true;
-                        if allow_variadic {
-                            if p.token != token::CloseDelim(token::Paren) {
-                                let span = p.span;
-                                p.span_err(span,
-                                    "`...` must be last in argument list for variadic function");
-                            }
-                            Ok(None)
-                        } else {
-                            let span = p.prev_span;
-                            if p.token == token::CloseDelim(token::Paren) {
-                                // continue parsing to present any further errors
-                                p.struct_span_err(
-                                    span,
-                                    "only foreign functions are allowed to be variadic"
-                                ).emit();
-                                Ok(Some(dummy_arg(span)))
-                           } else {
-                               // this function definition looks beyond recovery, stop parsing
-                                p.span_err(span,
-                                           "only foreign functions are allowed to be variadic");
-                                Ok(None)
-                            }
-                        }
+                    // If the argument is a C-variadic argument we should not
+                    // enforce named arguments.
+                    let enforce_named_args = if p.token == token::DotDotDot {
+                        false
                     } else {
-                        match p.parse_arg_general(named_args, false) {
-                            Ok(arg) => Ok(Some(arg)),
-                            Err(mut e) => {
-                                e.emit();
-                                let lo = p.prev_span;
-                                // Skip every token until next possible arg or end.
-                                p.eat_to_tokens(&[&token::Comma, &token::CloseDelim(token::Paren)]);
-                                // Create a placeholder argument for proper arg count (#34264).
-                                let span = lo.to(p.prev_span);
-                                Ok(Some(dummy_arg(span)))
+                        named_args
+                    };
+                    match p.parse_arg_general(enforce_named_args, false,
+                                              allow_variadic) {
+                        Ok(arg) => {
+                            if let TyKind::CVarArgs = arg.ty.node {
+                                variadic = true;
+                                if p.token != token::CloseDelim(token::Paren) {
+                                    let span = p.span;
+                                    p.span_err(span,
+                                        "`...` must be last in argument list in variadic function");
+                                    Ok(None)
+                                } else {
+                                    Ok(Some(arg))
+                                }
+                            } else {
+                                Ok(Some(arg))
                             }
+                        },
+                        Err(mut e) => {
+                            e.emit();
+                            let lo = p.prev_span;
+                            // Skip every token until next possible arg or end.
+                            p.eat_to_tokens(&[&token::Comma, &token::CloseDelim(token::Paren)]);
+                            // Create a placeholder argument for proper arg count (issue #34264).
+                            let span = lo.to(p.prev_span);
+                            Ok(Some(dummy_arg(span)))
                         }
                     }
                 }
@@ -6389,7 +6398,8 @@ impl<'a> Parser<'a> {
                      abi: Abi)
                      -> PResult<'a, ItemInfo> {
         let (ident, mut generics) = self.parse_fn_header()?;
-        let decl = self.parse_fn_decl(false)?;
+        let allow_variadic = abi == Abi::C && unsafety == Unsafety::Unsafe;
+        let decl = self.parse_fn_decl(allow_variadic)?;
         generics.where_clause = self.parse_where_clause()?;
         let (inner_attrs, body) = self.parse_inner_attrs_and_block()?;
         let header = FnHeader { unsafety, asyncness, constness, abi };
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index dcf9815f6d1..b3964d0ce9c 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -1118,6 +1118,9 @@ impl<'a> State<'a> {
             ast::TyKind::Mac(ref m) => {
                 self.print_mac(m)?;
             }
+            ast::TyKind::CVarArgs => {
+                self.s.word("...")?;
+            }
         }
         self.end()
     }
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index a002394c710..dd9f4f74d9e 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -315,7 +315,7 @@ pub fn walk_ty<'a, V: Visitor<'a>>(visitor: &mut V, typ: &'a Ty) {
             walk_list!(visitor, visit_lifetime, opt_lifetime);
             visitor.visit_ty(&mutable_type.ty)
         }
-        TyKind::Never => {},
+        TyKind::Never | TyKind::CVarArgs => {}
         TyKind::Tup(ref tuple_element_types) => {
             walk_list!(visitor, visit_ty, tuple_element_types);
         }
diff --git a/src/test/codegen/c-variadic.rs b/src/test/codegen/c-variadic.rs
new file mode 100644
index 00000000000..09c18ed90b2
--- /dev/null
+++ b/src/test/codegen/c-variadic.rs
@@ -0,0 +1,69 @@
+// compile-flags: -C no-prepopulate-passes
+
+#![crate_type = "lib"]
+#![feature(c_variadic)]
+#![no_std]
+use core::ffi::VaList;
+
+extern "C" {
+    fn foreign_c_variadic_0(_: i32, ...);
+    fn foreign_c_variadic_1(_: VaList, ...);
+}
+
+pub unsafe extern "C" fn use_foreign_c_variadic_0() {
+    // Ensure that we correctly call foreign C-variadic functions.
+    // CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0)
+    foreign_c_variadic_0(0);
+    // CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42)
+    foreign_c_variadic_0(0, 42i32);
+    // CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42, i32 1024)
+    foreign_c_variadic_0(0, 42i32, 1024i32);
+    // CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42, i32 1024, i32 0)
+    foreign_c_variadic_0(0, 42i32, 1024i32, 0i32);
+}
+
+// Ensure that we do not remove the `va_list` passed to the foreign function when
+// removing the "spoofed" `VaList` that is used by Rust defined C-variadics.
+pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) {
+    // CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap)
+    foreign_c_variadic_1(ap);
+}
+
+pub unsafe extern "C" fn use_foreign_c_variadic_1_1(ap: VaList) {
+    // CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 42)
+    foreign_c_variadic_1(ap, 42i32);
+}
+pub unsafe extern "C" fn use_foreign_c_variadic_1_2(ap: VaList) {
+    // CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 2, i32 42)
+    foreign_c_variadic_1(ap, 2i32, 42i32);
+}
+
+pub unsafe extern "C" fn use_foreign_c_variadic_1_3(ap: VaList) {
+    // CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 2, i32 42, i32 0)
+    foreign_c_variadic_1(ap, 2i32, 42i32, 0i32);
+}
+
+// Ensure that `va_start` and `va_end` are properly injected.
+#[no_mangle]
+pub unsafe extern "C" fn c_variadic(n: i32, mut ap: ...) -> i32 {
+    // CHECK: call void @llvm.va_start
+    let mut sum = 0;
+    for _ in 0..n {
+        sum += ap.arg::<i32>();
+    }
+    sum
+    // CHECK: call void @llvm.va_end
+}
+
+// Ensure that we generate the correct `call` signature when calling a Rust
+// defined C-variadic.
+pub unsafe fn test_c_variadic_call() {
+    // CHECK: call i32 (i32, ...) @c_variadic(i32 0)
+    c_variadic(0);
+    // CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42)
+    c_variadic(0, 42i32);
+    // CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42, i32 1024)
+    c_variadic(0, 42i32, 1024i32);
+    // CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42, i32 1024, i32 0)
+    c_variadic(0, 42i32, 1024i32, 0i32);
+}
diff --git a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs
index d55aac1e40f..96a238afaec 100644
--- a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs
+++ b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs
@@ -18,8 +18,10 @@ macro_rules! continue_if {
 
 unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool {
     let cstr0 = CStr::from_ptr(ptr);
-    let cstr1 = CString::new(val).unwrap();
-    &*cstr1 == cstr0
+    match CString::new(val) {
+        Ok(cstr1) => &*cstr1 == cstr0,
+        Err(_) => false,
+    }
 }
 
 #[no_mangle]
@@ -68,3 +70,24 @@ pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize {
         }
     })
 }
+
+#[no_mangle]
+pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize {
+    continue_if!(ap.arg::<c_int>() == 42);
+    continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello, World!"));
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn check_varargs_1(_: c_int, mut ap: ...) -> usize {
+    continue_if!(ap.arg::<c_double>().floor() == 3.14f64.floor());
+    continue_if!(ap.arg::<c_long>() == 12);
+    continue_if!(ap.arg::<c_char>() == 'A' as c_char);
+    continue_if!(ap.arg::<c_longlong>() == 1);
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn check_varargs_2(_: c_int, mut ap: ...) -> usize {
+    0
+}
diff --git a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c
index 95cf0ef46ca..91b060dce26 100644
--- a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c
+++ b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c
@@ -8,6 +8,9 @@ extern size_t check_list_0(va_list ap);
 extern size_t check_list_1(va_list ap);
 extern size_t check_list_2(va_list ap);
 extern size_t check_list_copy_0(va_list ap);
+extern size_t check_varargs_0(int fixed, ...);
+extern size_t check_varargs_1(int fixed, ...);
+extern size_t check_varargs_2(int fixed, ...);
 
 int test_rust(size_t (*fn)(va_list), ...) {
     size_t ret = 0;
@@ -26,5 +29,12 @@ int main(int argc, char* argv[]) {
     assert(test_rust(check_list_2, 3.14, 12l, 'a', 6.28, "Hello", 42, "World") == 0);
 
     assert(test_rust(check_list_copy_0, 6.28, 16, 'A', "Skip Me!", "Correct") == 0);
+
+    assert(check_varargs_0(0, 42, "Hello, World!") == 0);
+
+    assert(check_varargs_1(0, 3.14, 12l, 'A', 0x1LL) == 0);
+
+    assert(check_varargs_2(0, "All", "of", "these", "are", "ignored", ".") == 0);
+
     return 0;
 }
diff --git a/src/test/rustdoc/variadic.rs b/src/test/rustdoc/variadic.rs
index bd8f1775b3d..5af2aea21fc 100644
--- a/src/test/rustdoc/variadic.rs
+++ b/src/test/rustdoc/variadic.rs
@@ -1,4 +1,4 @@
 extern "C" {
-    // @has variadic/fn.foo.html //pre 'pub unsafe extern "C" fn foo(x: i32, ...)'
+    // @has variadic/fn.foo.html //pre 'pub unsafe extern "C" fn foo(x: i32, _: ...)'
     pub fn foo(x: i32, ...);
 }
diff --git a/src/test/ui/variadic/variadic-ffi.rs b/src/test/ui/c-variadic/variadic-ffi-1.rs
index 61b2ad4bed5..61b2ad4bed5 100644
--- a/src/test/ui/variadic/variadic-ffi.rs
+++ b/src/test/ui/c-variadic/variadic-ffi-1.rs
diff --git a/src/test/ui/variadic/variadic-ffi.stderr b/src/test/ui/c-variadic/variadic-ffi-1.stderr
index 617b1f46ea3..52d7394d6af 100644
--- a/src/test/ui/variadic/variadic-ffi.stderr
+++ b/src/test/ui/c-variadic/variadic-ffi-1.stderr
@@ -1,5 +1,5 @@
 error[E0045]: variadic function must have C or cdecl calling convention
-  --> $DIR/variadic-ffi.rs:5:5
+  --> $DIR/variadic-ffi-1.rs:5:5
    |
 LL |     fn printf(_: *const u8, ...); //~ ERROR: variadic function must have C or cdecl calling
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variadics require C or cdecl calling convention
diff --git a/src/test/ui/variadic/variadic-ffi-2.rs b/src/test/ui/c-variadic/variadic-ffi-2.rs
index 224ac16f458..224ac16f458 100644
--- a/src/test/ui/variadic/variadic-ffi-2.rs
+++ b/src/test/ui/c-variadic/variadic-ffi-2.rs
diff --git a/src/test/ui/variadic/variadic-ffi-2.stderr b/src/test/ui/c-variadic/variadic-ffi-2.stderr
index cb2a9f874b7..cb2a9f874b7 100644
--- a/src/test/ui/variadic/variadic-ffi-2.stderr
+++ b/src/test/ui/c-variadic/variadic-ffi-2.stderr
diff --git a/src/test/ui/variadic/variadic-ffi-3.rs b/src/test/ui/c-variadic/variadic-ffi-3.rs
index 12b3426d9da..c02d1f54e56 100644
--- a/src/test/ui/variadic/variadic-ffi-3.rs
+++ b/src/test/ui/c-variadic/variadic-ffi-3.rs
@@ -14,12 +14,10 @@ fn main() {
         let x: unsafe extern "C" fn(f: isize, x: u8) = foo;
         //~^ ERROR: mismatched types
         //~| expected type `unsafe extern "C" fn(isize, u8)`
-        //~| found type `unsafe extern "C" fn(isize, u8, ...) {foo}`
 
         let y: extern "C" fn(f: isize, x: u8, ...) = bar;
         //~^ ERROR: mismatched types
-        //~| expected type `extern "C" fn(isize, u8, ...)`
-        //~| found type `extern "C" fn(isize, u8) {bar}`
+        //~| expected type `for<'r> extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...)`
 
         foo(1, 2, 3f32); //~ ERROR can't pass `f32` to variadic function
         foo(1, 2, true); //~ ERROR can't pass `bool` to variadic function
diff --git a/src/test/ui/variadic/variadic-ffi-3.stderr b/src/test/ui/c-variadic/variadic-ffi-3.stderr
index 0fecbbf36b8..82e3c6cd06f 100644
--- a/src/test/ui/variadic/variadic-ffi-3.stderr
+++ b/src/test/ui/c-variadic/variadic-ffi-3.stderr
@@ -23,49 +23,49 @@ LL |         let x: unsafe extern "C" fn(f: isize, x: u8) = foo;
    |                                                        ^^^ expected non-variadic fn, found variadic function
    |
    = note: expected type `unsafe extern "C" fn(isize, u8)`
-              found type `unsafe extern "C" fn(isize, u8, ...) {foo}`
+              found type `for<'r> unsafe extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...) {foo}`
 
 error[E0308]: mismatched types
-  --> $DIR/variadic-ffi-3.rs:19:54
+  --> $DIR/variadic-ffi-3.rs:18:54
    |
 LL |         let y: extern "C" fn(f: isize, x: u8, ...) = bar;
    |                                                      ^^^ expected variadic fn, found non-variadic function
    |
-   = note: expected type `extern "C" fn(isize, u8, ...)`
+   = note: expected type `for<'r> extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...)`
               found type `extern "C" fn(isize, u8) {bar}`
 
 error[E0617]: can't pass `f32` to variadic function
-  --> $DIR/variadic-ffi-3.rs:24:19
+  --> $DIR/variadic-ffi-3.rs:22:19
    |
 LL |         foo(1, 2, 3f32); //~ ERROR can't pass `f32` to variadic function
    |                   ^^^^ help: cast the value to `c_double`: `3f32 as c_double`
 
 error[E0617]: can't pass `bool` to variadic function
-  --> $DIR/variadic-ffi-3.rs:25:19
+  --> $DIR/variadic-ffi-3.rs:23:19
    |
 LL |         foo(1, 2, true); //~ ERROR can't pass `bool` to variadic function
    |                   ^^^^ help: cast the value to `c_int`: `true as c_int`
 
 error[E0617]: can't pass `i8` to variadic function
-  --> $DIR/variadic-ffi-3.rs:26:19
+  --> $DIR/variadic-ffi-3.rs:24:19
    |
 LL |         foo(1, 2, 1i8); //~ ERROR can't pass `i8` to variadic function
    |                   ^^^ help: cast the value to `c_int`: `1i8 as c_int`
 
 error[E0617]: can't pass `u8` to variadic function
-  --> $DIR/variadic-ffi-3.rs:27:19
+  --> $DIR/variadic-ffi-3.rs:25:19
    |
 LL |         foo(1, 2, 1u8); //~ ERROR can't pass `u8` to variadic function
    |                   ^^^ help: cast the value to `c_uint`: `1u8 as c_uint`
 
 error[E0617]: can't pass `i16` to variadic function
-  --> $DIR/variadic-ffi-3.rs:28:19
+  --> $DIR/variadic-ffi-3.rs:26:19
    |
 LL |         foo(1, 2, 1i16); //~ ERROR can't pass `i16` to variadic function
    |                   ^^^^ help: cast the value to `c_int`: `1i16 as c_int`
 
 error[E0617]: can't pass `u16` to variadic function
-  --> $DIR/variadic-ffi-3.rs:29:19
+  --> $DIR/variadic-ffi-3.rs:27:19
    |
 LL |         foo(1, 2, 1u16); //~ ERROR can't pass `u16` to variadic function
    |                   ^^^^ help: cast the value to `c_uint`: `1u16 as c_uint`
diff --git a/src/test/ui/c-variadic/variadic-ffi-4.rs b/src/test/ui/c-variadic/variadic-ffi-4.rs
new file mode 100644
index 00000000000..9101be56456
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-4.rs
@@ -0,0 +1,29 @@
+#![crate_type="lib"]
+#![no_std]
+#![feature(c_variadic)]
+
+use core::ffi::VaList;
+
+pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
+    ap //~ ERROR: explicit lifetime required
+}
+
+pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
+    ap //~ ERROR: explicit lifetime required
+}
+
+pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) {
+    let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
+}
+
+pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+    *ap0 = ap1; //~ ERROR: mismatched types
+}
+
+pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+    ap0 = &mut ap1;
+    //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+    //~^^ ERROR: mismatched types
+    //~^^^ ERROR: mismatched types
+    //~^^^^ ERROR: cannot infer an appropriate lifetime
+}
diff --git a/src/test/ui/c-variadic/variadic-ffi-4.stderr b/src/test/ui/c-variadic/variadic-ffi-4.stderr
new file mode 100644
index 00000000000..1d752be065c
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-4.stderr
@@ -0,0 +1,198 @@
+error[E0621]: explicit lifetime required in the type of `ap`
+  --> $DIR/variadic-ffi-4.rs:8:5
+   |
+LL | pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
+   |                                                       --- help: add explicit lifetime `'a` to the type of `ap`: `core::ffi::VaList<'a>`
+LL |     ap //~ ERROR: explicit lifetime required
+   |     ^^ lifetime `'a` required
+
+error[E0621]: explicit lifetime required in the type of `ap`
+  --> $DIR/variadic-ffi-4.rs:12:5
+   |
+LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
+   |                                                   --- help: add explicit lifetime `'static` to the type of `ap`: `core::ffi::VaList<'static>`
+LL |     ap //~ ERROR: explicit lifetime required
+   |     ^^ lifetime `'static` required
+
+error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
+  --> $DIR/variadic-ffi-4.rs:16:28
+   |
+LL |     let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
+   |                            ^^
+   |
+note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 16:21...
+  --> $DIR/variadic-ffi-4.rs:16:21
+   |
+LL |     let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
+   |                     ^^^^^^^^^^^
+   = note: ...so that the expression is assignable:
+           expected core::ffi::VaList<'_>
+              found core::ffi::VaList<'_>
+note: but, the lifetime must be valid for the method call at 16:13...
+  --> $DIR/variadic-ffi-4.rs:16:13
+   |
+LL |     let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
+   |             ^^^^^^^^^^^^^^^^^^^^
+note: ...so type `core::ffi::VaList<'_>` of expression is valid during the expression
+  --> $DIR/variadic-ffi-4.rs:16:13
+   |
+LL |     let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
+   |             ^^^^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/variadic-ffi-4.rs:20:12
+   |
+LL |     *ap0 = ap1; //~ ERROR: mismatched types
+   |            ^^^ lifetime mismatch
+   |
+   = note: expected type `core::ffi::VaList<'_>`
+              found type `core::ffi::VaList<'_>`
+note: the anonymous lifetime #3 defined on the function body at 19:1...
+  --> $DIR/variadic-ffi-4.rs:19:1
+   |
+LL | / pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+LL | |     *ap0 = ap1; //~ ERROR: mismatched types
+LL | | }
+   | |_^
+note: ...does not necessarily outlive the anonymous lifetime #2 defined on the function body at 19:1
+  --> $DIR/variadic-ffi-4.rs:19:1
+   |
+LL | / pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+LL | |     *ap0 = ap1; //~ ERROR: mismatched types
+LL | | }
+   | |_^
+
+error[E0490]: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^
+   |
+note: the type is valid for the anonymous lifetime #1 defined on the function body at 23:1
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+note: but the borrow lasts for the anonymous lifetime #3 defined on the function body at 23:1
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+
+error[E0308]: mismatched types
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^ lifetime mismatch
+   |
+   = note: expected type `&mut core::ffi::VaList<'_>`
+              found type `&mut core::ffi::VaList<'_>`
+note: the anonymous lifetime #3 defined on the function body at 23:1...
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+note: ...does not necessarily outlive the anonymous lifetime #2 defined on the function body at 23:1
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+
+error[E0308]: mismatched types
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^ lifetime mismatch
+   |
+   = note: expected type `&mut core::ffi::VaList<'_>`
+              found type `&mut core::ffi::VaList<'_>`
+note: the anonymous lifetime #2 defined on the function body at 23:1...
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+note: ...does not necessarily outlive the anonymous lifetime #3 defined on the function body at 23:1
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+
+error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^
+   |
+note: first, the lifetime cannot outlive the anonymous lifetime #3 defined on the function body at 23:1...
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+note: ...so that the type `core::ffi::VaList<'_>` is not borrowed for too long
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^
+note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the function body at 23:1...
+  --> $DIR/variadic-ffi-4.rs:23:1
+   |
+LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
+LL | |     ap0 = &mut ap1;
+LL | |     //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
+LL | |     //~^^ ERROR: mismatched types
+LL | |     //~^^^ ERROR: mismatched types
+LL | |     //~^^^^ ERROR: cannot infer an appropriate lifetime
+LL | | }
+   | |_^
+note: ...so that reference does not outlive borrowed content
+  --> $DIR/variadic-ffi-4.rs:24:11
+   |
+LL |     ap0 = &mut ap1;
+   |           ^^^^^^^^
+
+error: aborting due to 8 previous errors
+
+Some errors occurred: E0308, E0490, E0495, E0621.
+For more information about an error, try `rustc --explain E0308`.
diff --git a/src/test/ui/c-variadic/variadic-ffi-5.rs b/src/test/ui/c-variadic/variadic-ffi-5.rs
new file mode 100644
index 00000000000..d96482ff4d1
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-5.rs
@@ -0,0 +1,31 @@
+#![crate_type="lib"]
+#![no_std]
+#![feature(c_variadic)]
+// The tests in this file are similar to that of variadic-ffi-4, but this
+// one enables nll.
+#![feature(nll)]
+
+use core::ffi::VaList;
+
+pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
+    ap //~ ERROR: explicit lifetime required
+}
+
+pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
+    ap //~ ERROR: explicit lifetime required
+}
+
+pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) {
+    let _ = ap.copy(|ap| { ap }); //~ ERROR: lifetime may not live long enough
+}
+
+pub unsafe extern "C" fn no_escape3(_: usize, ap0: &mut VaList, mut ap1: ...) {
+    *ap0 = ap1; //~ ERROR: lifetime may not live long enough
+}
+
+pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+    ap0 = &mut ap1;
+    //~^ ERROR: lifetime may not live long enough
+    //~^^ ERROR: lifetime may not live long enough
+    //~^^^ ERROR: `ap1` does not live long enough
+}
diff --git a/src/test/ui/c-variadic/variadic-ffi-5.stderr b/src/test/ui/c-variadic/variadic-ffi-5.stderr
new file mode 100644
index 00000000000..2d452872baf
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-5.stderr
@@ -0,0 +1,73 @@
+error[E0621]: explicit lifetime required in the type of `ap`
+  --> $DIR/variadic-ffi-5.rs:11:5
+   |
+LL | pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
+   |                                                       --- help: add explicit lifetime `'a` to the type of `ap`: `core::ffi::VaList<'a>`
+LL |     ap //~ ERROR: explicit lifetime required
+   |     ^^ lifetime `'a` required
+
+error[E0621]: explicit lifetime required in the type of `ap`
+  --> $DIR/variadic-ffi-5.rs:15:5
+   |
+LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
+   |                                                   --- help: add explicit lifetime `'static` to the type of `ap`: `core::ffi::VaList<'static>`
+LL |     ap //~ ERROR: explicit lifetime required
+   |     ^^ lifetime `'static` required
+
+error: lifetime may not live long enough
+  --> $DIR/variadic-ffi-5.rs:19:28
+   |
+LL |     let _ = ap.copy(|ap| { ap }); //~ ERROR: lifetime may not live long enough
+   |                      ---   ^^ returning this value requires that `'1` must outlive `'2`
+   |                      | |
+   |                      | return type of closure is core::ffi::VaList<'2>
+   |                      has type `core::ffi::VaList<'1>`
+
+error: lifetime may not live long enough
+  --> $DIR/variadic-ffi-5.rs:23:5
+   |
+LL | pub unsafe extern "C" fn no_escape3(_: usize, ap0: &mut VaList, mut ap1: ...) {
+   |                                               ---               ------- has type `core::ffi::VaList<'1>`
+   |                                               |
+   |                                               has type `&mut core::ffi::VaList<'2>`
+LL |     *ap0 = ap1; //~ ERROR: lifetime may not live long enough
+   |     ^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
+
+error: lifetime may not live long enough
+  --> $DIR/variadic-ffi-5.rs:27:5
+   |
+LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+   |                                               -------               ------- has type `core::ffi::VaList<'2>`
+   |                                               |
+   |                                               has type `&mut core::ffi::VaList<'1>`
+LL |     ap0 = &mut ap1;
+   |     ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
+
+error: lifetime may not live long enough
+  --> $DIR/variadic-ffi-5.rs:27:5
+   |
+LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+   |                                               -------               ------- has type `core::ffi::VaList<'1>`
+   |                                               |
+   |                                               has type `&mut core::ffi::VaList<'2>`
+LL |     ap0 = &mut ap1;
+   |     ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
+
+error[E0597]: `ap1` does not live long enough
+  --> $DIR/variadic-ffi-5.rs:27:11
+   |
+LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
+   |                                                        - let's call the lifetime of this reference `'1`
+LL |     ap0 = &mut ap1;
+   |     ------^^^^^^^^
+   |     |     |
+   |     |     borrowed value does not live long enough
+   |     assignment requires that `ap1` is borrowed for `'1`
+...
+LL | }
+   |  - `ap1` dropped here while still borrowed
+
+error: aborting due to 7 previous errors
+
+Some errors occurred: E0597, E0621.
+For more information about an error, try `rustc --explain E0597`.
diff --git a/src/test/ui/c-variadic/variadic-ffi-6.rs b/src/test/ui/c-variadic/variadic-ffi-6.rs
new file mode 100644
index 00000000000..9b5293fad3b
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-6.rs
@@ -0,0 +1,12 @@
+#![crate_type="lib"]
+
+pub unsafe extern "C" fn use_vararg_lifetime(
+    x: usize,
+    y: ...
+) -> &usize { //~ ERROR missing lifetime specifier
+    &0
+}
+
+pub unsafe extern "C" fn use_normal_arg_lifetime(x: &usize, y: ...) -> &usize { // OK
+    x
+}
diff --git a/src/test/ui/c-variadic/variadic-ffi-6.stderr b/src/test/ui/c-variadic/variadic-ffi-6.stderr
new file mode 100644
index 00000000000..76bd18959a5
--- /dev/null
+++ b/src/test/ui/c-variadic/variadic-ffi-6.stderr
@@ -0,0 +1,11 @@
+error[E0106]: missing lifetime specifier
+  --> $DIR/variadic-ffi-6.rs:7:6
+   |
+LL | ) -> &usize { //~ ERROR missing lifetime specifier
+   |      ^ help: consider giving it an explicit bounded or 'static lifetime: `&'static`
+   |
+   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0106`.
diff --git a/src/test/ui/error-codes/E0617.rs b/src/test/ui/error-codes/E0617.rs
index 9eed1225ab8..51f13c7dbd5 100644
--- a/src/test/ui/error-codes/E0617.rs
+++ b/src/test/ui/error-codes/E0617.rs
@@ -22,7 +22,7 @@ fn main() {
         //~^ ERROR can't pass `u16` to variadic function
         //~| HELP cast the value to `c_uint`
         printf(::std::ptr::null(), printf);
-        //~^ ERROR can't pass `unsafe extern "C" fn(*const i8, ...) {printf}` to variadic function
-        //~| HELP cast the value to `unsafe extern "C" fn(*const i8, ...)`
+        //~^ ERROR can't pass `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...) {printf}` to variadic function
+        //~| HELP cast the value to `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...)`
     }
 }
diff --git a/src/test/ui/error-codes/E0617.stderr b/src/test/ui/error-codes/E0617.stderr
index 486ca1fa92f..8387d5c7e93 100644
--- a/src/test/ui/error-codes/E0617.stderr
+++ b/src/test/ui/error-codes/E0617.stderr
@@ -28,15 +28,15 @@ error[E0617]: can't pass `u16` to variadic function
 LL |         printf(::std::ptr::null(), 0u16);
    |                                    ^^^^ help: cast the value to `c_uint`: `0u16 as c_uint`
 
-error[E0617]: can't pass `unsafe extern "C" fn(*const i8, ...) {printf}` to variadic function
+error[E0617]: can't pass `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...) {printf}` to variadic function
   --> $DIR/E0617.rs:24:36
    |
 LL |         printf(::std::ptr::null(), printf);
    |                                    ^^^^^^
-help: cast the value to `unsafe extern "C" fn(*const i8, ...)`
+help: cast the value to `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...)`
    |
-LL |         printf(::std::ptr::null(), printf as unsafe extern "C" fn(*const i8, ...));
-   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         printf(::std::ptr::null(), printf as for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...));
+   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/parser/recover-enum2.stderr b/src/test/ui/parser/recover-enum2.stderr
index 2473420a779..b308e644ad9 100644
--- a/src/test/ui/parser/recover-enum2.stderr
+++ b/src/test/ui/parser/recover-enum2.stderr
@@ -10,11 +10,11 @@ error: expected one of `!`, `(`, `)`, `+`, `,`, `::`, or `<`, found `{`
 LL |             Nope(i32 {}) //~ ERROR: found `{`
    |                      ^ expected one of 7 possible tokens here
 
-error: expected one of `!`, `&&`, `&`, `(`, `)`, `*`, `+`, `,`, `::`, `<`, `?`, `[`, `_`, `crate`, `dyn`, `extern`, `fn`, `for`, `impl`, `pub`, `unsafe`, `}`, or lifetime, found `{`
+error: expected one of `!`, `&&`, `&`, `(`, `)`, `*`, `+`, `,`, `...`, `::`, `<`, `?`, `[`, `_`, `crate`, `dyn`, `extern`, `fn`, `for`, `impl`, `pub`, `unsafe`, `}`, or lifetime, found `{`
   --> $DIR/recover-enum2.rs:27:22
    |
 LL |             Nope(i32 {}) //~ ERROR: found `{`
-   |                      ^ expected one of 23 possible tokens here
+   |                      ^ expected one of 24 possible tokens here
 
 error: expected expression, found reserved identifier `_`
   --> $DIR/recover-enum2.rs:32:22