diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md
index d774e465118b3..5eea9c8687900 100644
--- a/src/doc/rustc/src/command-line-arguments.md
+++ b/src/doc/rustc/src/command-line-arguments.md
@@ -304,3 +304,10 @@ to customize the output:
 
 Note that it is invalid to combine the `--json` argument with the `--color`
 argument, and it is required to combine `--json` with `--error-format=json`.
+
+## `@path`: load command-line flags from a path
+
+If you specify `@path` on the command-line, then it will open `path` and read
+command line options from it. These options are one per line; a blank line indicates
+an empty option. The file can use Unix or Windows style line endings, and must be
+encoded as UTF-8.
diff --git a/src/librustc_driver/args.rs b/src/librustc_driver/args.rs
new file mode 100644
index 0000000000000..0906d358badd4
--- /dev/null
+++ b/src/librustc_driver/args.rs
@@ -0,0 +1,53 @@
+use std::error;
+use std::fmt;
+use std::fs;
+use std::io;
+use std::str;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+static USED_ARGSFILE_FEATURE: AtomicBool = AtomicBool::new(false);
+
+pub fn used_unstable_argsfile() -> bool {
+    USED_ARGSFILE_FEATURE.load(Ordering::Relaxed)
+}
+
+pub fn arg_expand(arg: String) -> Result<Vec<String>, Error> {
+    if arg.starts_with("@") {
+        let path = &arg[1..];
+        let file = match fs::read_to_string(path) {
+            Ok(file) => {
+                USED_ARGSFILE_FEATURE.store(true, Ordering::Relaxed);
+                file
+            }
+            Err(ref err) if err.kind() == io::ErrorKind::InvalidData => {
+                return Err(Error::Utf8Error(Some(path.to_string())));
+            }
+            Err(err) => return Err(Error::IOError(path.to_string(), err)),
+        };
+        Ok(file.lines().map(ToString::to_string).collect())
+    } else {
+        Ok(vec![arg])
+    }
+}
+
+#[derive(Debug)]
+pub enum Error {
+    Utf8Error(Option<String>),
+    IOError(String, io::Error),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
+            Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {}", path),
+            Error::IOError(path, err) => write!(fmt, "IO Error: {}: {}", path, err),
+        }
+    }
+}
+
+impl error::Error for Error {
+    fn description(&self) -> &'static str {
+        "argument error"
+    }
+}
diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs
index fdd0773b73ae2..2cec404c3d7f6 100644
--- a/src/librustc_driver/lib.rs
+++ b/src/librustc_driver/lib.rs
@@ -66,6 +66,7 @@ use syntax::symbol::sym;
 use syntax_pos::{DUMMY_SP, MultiSpan, FileName};
 
 pub mod pretty;
+mod args;
 
 /// Exit status code used for successful compilation and help output.
 pub const EXIT_SUCCESS: i32 = 0;
@@ -139,14 +140,22 @@ impl Callbacks for TimePassesCallbacks {
 // See comments on CompilerCalls below for details about the callbacks argument.
 // The FileLoader provides a way to load files from sources other than the file system.
 pub fn run_compiler(
-    args: &[String],
+    at_args: &[String],
     callbacks: &mut (dyn Callbacks + Send),
     file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
     emitter: Option<Box<dyn Write + Send>>
 ) -> interface::Result<()> {
+    let mut args = Vec::new();
+    for arg in at_args {
+        match args::arg_expand(arg.clone()) {
+            Ok(arg) => args.extend(arg),
+            Err(err) => early_error(ErrorOutputType::default(),
+                &format!("Failed to load argument file: {}", err)),
+        }
+    }
     let diagnostic_output = emitter.map(|emitter| DiagnosticOutput::Raw(emitter))
                                    .unwrap_or(DiagnosticOutput::Default);
-    let matches = match handle_options(args) {
+    let matches = match handle_options(&args) {
         Some(matches) => matches,
         None => return Ok(()),
     };
@@ -777,13 +786,19 @@ fn usage(verbose: bool, include_unstable_options: bool) {
     } else {
         "\n    --help -v           Print the full set of options rustc accepts"
     };
-    println!("{}\nAdditional help:
+    let at_path = if verbose && nightly_options::is_nightly_build() {
+        "    @path               Read newline separated options from `path`\n"
+    } else {
+        ""
+    };
+    println!("{options}{at_path}\nAdditional help:
     -C help             Print codegen options
     -W help             \
-              Print 'lint' options and default settings{}{}\n",
-             options.usage(message),
-             nightly_help,
-             verbose_help);
+              Print 'lint' options and default settings{nightly}{verbose}\n",
+             options = options.usage(message),
+             at_path = at_path,
+             nightly = nightly_help,
+             verbose = verbose_help);
 }
 
 fn print_wall_help() {
@@ -1008,6 +1023,12 @@ pub fn handle_options(args: &[String]) -> Option<getopts::Matches> {
     //   (unstable option being used on stable)
     nightly_options::check_nightly_options(&matches, &config::rustc_optgroups());
 
+    // Late check to see if @file was used without unstable options enabled
+    if crate::args::used_unstable_argsfile() && !nightly_options::is_unstable_enabled(&matches) {
+        early_error(ErrorOutputType::default(),
+            "@path is unstable - use -Z unstable-options to enable its use");
+    }
+
     if matches.opt_present("h") || matches.opt_present("help") {
         // Only show unstable options in --help if we accept unstable options.
         usage(matches.opt_present("verbose"), nightly_options::is_unstable_enabled(&matches));
@@ -1188,7 +1209,7 @@ pub fn main() {
     let result = report_ices_to_stderr_if_any(|| {
         let args = env::args_os().enumerate()
             .map(|(i, arg)| arg.into_string().unwrap_or_else(|arg| {
-                early_error(ErrorOutputType::default(),
+                    early_error(ErrorOutputType::default(),
                             &format!("Argument {} is not valid Unicode: {:?}", i, arg))
             }))
             .collect::<Vec<_>>();
diff --git a/src/test/ui/commandline-argfile-badutf8.args b/src/test/ui/commandline-argfile-badutf8.args
new file mode 100644
index 0000000000000..c070b0c2400d8
--- /dev/null
+++ b/src/test/ui/commandline-argfile-badutf8.args
@@ -0,0 +1,2 @@
+--cfg
+unbroken�
\ No newline at end of file
diff --git a/src/test/ui/commandline-argfile-badutf8.rs b/src/test/ui/commandline-argfile-badutf8.rs
new file mode 100644
index 0000000000000..161715685b57f
--- /dev/null
+++ b/src/test/ui/commandline-argfile-badutf8.rs
@@ -0,0 +1,13 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// build-fail
+// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/src/test/ui/commandline-argfile-badutf8.stderr b/src/test/ui/commandline-argfile-badutf8.stderr
new file mode 100644
index 0000000000000..9af6fc0a518df
--- /dev/null
+++ b/src/test/ui/commandline-argfile-badutf8.stderr
@@ -0,0 +1,2 @@
+error: Failed to load argument file: Utf8 error in $DIR/commandline-argfile-badutf8.args
+
diff --git a/src/test/ui/commandline-argfile-missing.rs b/src/test/ui/commandline-argfile-missing.rs
new file mode 100644
index 0000000000000..a29b4ab062de3
--- /dev/null
+++ b/src/test/ui/commandline-argfile-missing.rs
@@ -0,0 +1,16 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// ignore-tidy-linelength
+// build-fail
+// normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+// normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/src/test/ui/commandline-argfile-missing.stderr b/src/test/ui/commandline-argfile-missing.stderr
new file mode 100644
index 0000000000000..179ad83100419
--- /dev/null
+++ b/src/test/ui/commandline-argfile-missing.stderr
@@ -0,0 +1,2 @@
+error: Failed to load argument file: IO Error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
diff --git a/src/test/ui/commandline-argfile.args b/src/test/ui/commandline-argfile.args
new file mode 100644
index 0000000000000..972938bf6c8dd
--- /dev/null
+++ b/src/test/ui/commandline-argfile.args
@@ -0,0 +1,2 @@
+--cfg
+unbroken
\ No newline at end of file
diff --git a/src/test/ui/commandline-argfile.rs b/src/test/ui/commandline-argfile.rs
new file mode 100644
index 0000000000000..fc1ba0c8d677d
--- /dev/null
+++ b/src/test/ui/commandline-argfile.rs
@@ -0,0 +1,13 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// build-pass
+// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}