Просмотр исходного кода

Build the clang::CompilerInvocation in the driver. (#5784)

Add driver flags to specify clang driver arguments.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Richard Smith 9 месяцев назад
Родитель
Сommit
553dd6e531

+ 13 - 0
toolchain/base/BUILD

@@ -47,6 +47,19 @@ cc_test(
     ],
 )
 
+cc_library(
+    name = "clang_invocation",
+    srcs = ["clang_invocation.cpp"],
+    hdrs = ["clang_invocation.h"],
+    deps = [
+        "//common:check",
+        "//toolchain/diagnostics:diagnostic_emitter",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:frontend",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_library(
     name = "fixed_size_value_store",
     hdrs = ["fixed_size_value_store.h"],

+ 113 - 0
toolchain/base/clang_invocation.cpp

@@ -0,0 +1,113 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/base/clang_invocation.h"
+
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/Utils.h"
+
+namespace Carbon {
+
+// The fake file name to use for the synthesized includes file.
+static constexpr const char IncludesFileName[] = "<carbon Cpp imports>";
+
+namespace {
+
+// Used to convert diagnostics from the Clang driver to Carbon diagnostics.
+class ClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer {
+ public:
+  // Creates an instance with the location that triggers calling Clang.
+  // `context` must not be null.
+  explicit ClangDriverDiagnosticConsumer(Diagnostics::NoLocEmitter* emitter)
+      : emitter_(emitter) {}
+
+  // Generates a Carbon warning for each Clang warning and a Carbon error for
+  // each Clang error or fatal.
+  auto HandleDiagnostic(clang::DiagnosticsEngine::Level diag_level,
+                        const clang::Diagnostic& info) -> void override {
+    DiagnosticConsumer::HandleDiagnostic(diag_level, info);
+
+    llvm::SmallString<256> message;
+    info.FormatDiagnostic(message);
+
+    switch (diag_level) {
+      case clang::DiagnosticsEngine::Ignored:
+      case clang::DiagnosticsEngine::Note:
+      case clang::DiagnosticsEngine::Remark: {
+        // TODO: Emit notes and remarks.
+        break;
+      }
+      case clang::DiagnosticsEngine::Warning:
+      case clang::DiagnosticsEngine::Error:
+      case clang::DiagnosticsEngine::Fatal: {
+        CARBON_DIAGNOSTIC(CppInteropDriverWarning, Warning, "{0}", std::string);
+        CARBON_DIAGNOSTIC(CppInteropDriverError, Error, "{0}", std::string);
+        emitter_->Emit(diag_level == clang::DiagnosticsEngine::Warning
+                           ? CppInteropDriverWarning
+                           : CppInteropDriverError,
+                       message.str().str());
+        break;
+      }
+    }
+  }
+
+ private:
+  // Diagnostic emitter. Note that driver diagnostics don't have meaningful
+  // locations attached.
+  Diagnostics::NoLocEmitter* emitter_;
+};
+
+}  // namespace
+
+static auto BuildClangInvocationImpl(
+    Diagnostics::NoLocEmitter& emitter,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+    llvm::ArrayRef<std::string> clang_path_and_args)
+    -> std::unique_ptr<clang::CompilerInvocation> {
+  ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
+
+  // The clang driver inconveniently wants an array of `const char*`, so convert
+  // the arguments.
+  llvm::SmallVector<const char*> driver_args(llvm::map_range(
+      clang_path_and_args, [](const std::string& str) { return str.c_str(); }));
+
+  // Add our include file name as the input file, and force it to be interpreted
+  // as C++.
+  driver_args.push_back("-x");
+  driver_args.push_back("c++");
+  driver_args.push_back(IncludesFileName);
+
+  // Build a diagnostics engine. Note that we don't have any diagnostic options
+  // yet; they're produced by running the driver.
+  clang::DiagnosticOptions driver_diag_opts;
+  llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
+      clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
+                                                 &diagnostics_consumer,
+                                                 /*ShouldOwnClient=*/false));
+
+  // Ask the driver to process the arguments and build a corresponding clang
+  // frontend invocation.
+  return clang::createInvocation(driver_args,
+                                 {.Diags = driver_diags, .VFS = fs});
+}
+
+auto BuildClangInvocation(Diagnostics::Consumer& consumer,
+                          llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                          llvm::ArrayRef<std::string> clang_path_and_args)
+    -> std::unique_ptr<clang::CompilerInvocation> {
+  Diagnostics::ErrorTrackingConsumer error_tracker(consumer);
+  Diagnostics::NoLocEmitter emitter(&error_tracker);
+
+  // Forward to the implementation to avoid exposing `import_cpp` outside check.
+  auto invocation = BuildClangInvocationImpl(emitter, fs, clang_path_and_args);
+
+  // If Clang produced an error, throw away its invocation.
+  if (error_tracker.seen_error()) {
+    return nullptr;
+  }
+
+  return invocation;
+}
+
+}  // namespace Carbon

+ 28 - 0
toolchain/base/clang_invocation.h

@@ -0,0 +1,28 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
+#define CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
+
+#include <string>
+
+#include "clang/Frontend/CompilerInvocation.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
+
+namespace Carbon {
+
+// Builds and returns a clang `CompilerInvocation` to use when building code for
+// interop, from a list of clang driver arguments. Emits diagnostics to
+// `consumer` if the arguments are invalid.
+auto BuildClangInvocation(Diagnostics::Consumer& consumer,
+                          llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                          llvm::ArrayRef<std::string> clang_path_and_args)
+    -> std::unique_ptr<clang::CompilerInvocation>;
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_

+ 1 - 0
toolchain/check/BUILD

@@ -208,6 +208,7 @@ cc_library(
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:formatter",
         "//toolchain/sem_ir:typed_insts",
+        "@llvm-project//clang:frontend",
         "@llvm-project//llvm:Support",
     ],
 )

+ 6 - 4
toolchain/check/check.cpp

@@ -13,7 +13,9 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_emitter.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/check/import_cpp.h"
 #include "toolchain/diagnostics/diagnostic.h"
+#include "toolchain/diagnostics/diagnostic_consumer.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/lex/token_kind.h"
 #include "toolchain/parse/node_ids.h"
@@ -390,8 +392,8 @@ auto CheckParseTrees(
     llvm::MutableArrayRef<Unit> units,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    llvm::StringRef clang_path, llvm::StringRef target,
-    const CheckParseTreesOptions& options) -> void {
+    const CheckParseTreesOptions& options,
+    std::shared_ptr<clang::CompilerInvocation> clang_invocation) -> void {
   // UnitAndImports is big due to its SmallVectors, so we default to 0 on the
   // stack.
   llvm::SmallVector<UnitAndImports, 0> unit_infos(
@@ -446,7 +448,7 @@ auto CheckParseTrees(
   for (int check_index = 0;
        check_index < static_cast<int>(ready_to_check.size()); ++check_index) {
     auto* unit_info = ready_to_check[check_index];
-    CheckUnit(unit_info, tree_and_subtrees_getters, fs, clang_path, target,
+    CheckUnit(unit_info, tree_and_subtrees_getters, fs, clang_invocation,
               options.vlog_stream)
         .Run();
     for (auto* incoming_import : unit_info->incoming_imports) {
@@ -495,7 +497,7 @@ auto CheckParseTrees(
     // incomplete imports.
     for (auto& unit_info : unit_infos) {
       if (unit_info.imports_remaining > 0) {
-        CheckUnit(&unit_info, tree_and_subtrees_getters, fs, clang_path, target,
+        CheckUnit(&unit_info, tree_and_subtrees_getters, fs, clang_invocation,
                   options.vlog_stream)
             .Run();
       }

+ 3 - 2
toolchain/check/check.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_CHECK_CHECK_H_
 #define CARBON_TOOLCHAIN_CHECK_CHECK_H_
 
+#include "clang/Frontend/CompilerInvocation.h"
 #include "common/ostream.h"
 #include "toolchain/base/shared_value_stores.h"
 #include "toolchain/base/timings.h"
@@ -75,8 +76,8 @@ auto CheckParseTrees(
     llvm::MutableArrayRef<Unit> units,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    llvm::StringRef clang_path, llvm::StringRef target,
-    const CheckParseTreesOptions& options) -> void;
+    const CheckParseTreesOptions& options,
+    std::shared_ptr<clang::CompilerInvocation> clang_invocation) -> void;
 
 }  // namespace Carbon::Check
 

+ 3 - 4
toolchain/check/check_unit.cpp

@@ -57,7 +57,7 @@ CheckUnit::CheckUnit(
     UnitAndImports* unit_and_imports,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    llvm::StringRef clang_path, llvm::StringRef target,
+    std::shared_ptr<clang::CompilerInvocation> clang_invocation,
     llvm::raw_ostream* vlog_stream)
     : unit_and_imports_(unit_and_imports),
       tree_and_subtrees_getter_(
@@ -65,8 +65,7 @@ CheckUnit::CheckUnit(
               [unit_and_imports->unit->sem_ir->check_ir_id().index]),
       total_ir_count_(tree_and_subtrees_getters.size()),
       fs_(std::move(fs)),
-      clang_path_(clang_path),
-      target_(target),
+      clang_invocation_(std::move(clang_invocation)),
       emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters,
                unit_and_imports_->unit->sem_ir),
       context_(
@@ -158,7 +157,7 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
     CARBON_CHECK(cpp_ast);
     CARBON_CHECK(!cpp_ast->get());
     *cpp_ast =
-        ImportCppFiles(context_, cpp_import_names, fs_, clang_path_, target_);
+        ImportCppFiles(context_, cpp_import_names, fs_, clang_invocation_);
   }
 }
 

+ 3 - 3
toolchain/check/check_unit.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_CHECK_CHECK_UNIT_H_
 #define CARBON_TOOLCHAIN_CHECK_CHECK_UNIT_H_
 
+#include "clang/Frontend/CompilerInvocation.h"
 #include "common/map.h"
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/check/check.h"
@@ -124,7 +125,7 @@ class CheckUnit {
       UnitAndImports* unit_and_imports,
       llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-      llvm::StringRef clang_path, llvm::StringRef target,
+      std::shared_ptr<clang::CompilerInvocation> clang_invocation,
       llvm::raw_ostream* vlog_stream);
 
   // Produces and checks the IR for the provided unit.
@@ -187,8 +188,7 @@ class CheckUnit {
   // The number of IRs being checked in total.
   int total_ir_count_;
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
-  llvm::StringRef clang_path_;
-  llvm::StringRef target_;
+  std::shared_ptr<clang::CompilerInvocation> clang_invocation_;
 
   DiagnosticEmitter emitter_;
   Context context_;

+ 15 - 110
toolchain/check/import_cpp.cpp

@@ -45,9 +45,6 @@
 
 namespace Carbon::Check {
 
-// The fake file name to use for the synthesized includes file.
-static constexpr const char IncludesFileName[] = "<carbon Cpp imports>";
-
 // Generates C++ file contents to #include all requested imports.
 static auto GenerateCppIncludesHeaderCode(
     Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
@@ -101,99 +98,6 @@ static auto AddImportIRInst(Context& context,
 
 namespace {
 
-// Used to convert diagnostics from the Clang driver to Carbon diagnostics.
-class CarbonClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer {
- public:
-  // Creates an instance with the location that triggers calling Clang.
-  // `context` must not be null.
-  explicit CarbonClangDriverDiagnosticConsumer(
-      Diagnostics::NoLocEmitter* emitter)
-      : emitter_(emitter) {}
-
-  // Generates a Carbon warning for each Clang warning and a Carbon error for
-  // each Clang error or fatal.
-  auto HandleDiagnostic(clang::DiagnosticsEngine::Level diag_level,
-                        const clang::Diagnostic& info) -> void override {
-    DiagnosticConsumer::HandleDiagnostic(diag_level, info);
-
-    llvm::SmallString<256> message;
-    info.FormatDiagnostic(message);
-
-    switch (diag_level) {
-      case clang::DiagnosticsEngine::Ignored:
-      case clang::DiagnosticsEngine::Note:
-      case clang::DiagnosticsEngine::Remark: {
-        // TODO: Emit notes and remarks.
-        break;
-      }
-      case clang::DiagnosticsEngine::Warning:
-      case clang::DiagnosticsEngine::Error:
-      case clang::DiagnosticsEngine::Fatal: {
-        CARBON_DIAGNOSTIC(CppInteropDriverWarning, Warning, "{0}", std::string);
-        CARBON_DIAGNOSTIC(CppInteropDriverError, Error, "{0}", std::string);
-        emitter_->Emit(diag_level == clang::DiagnosticsEngine::Warning
-                           ? CppInteropDriverWarning
-                           : CppInteropDriverError,
-                       message.str().str());
-        break;
-      }
-    }
-  }
-
- private:
-  // Diagnostic emitter. Note that driver diagnostics don't have meaningful
-  // locations attached.
-  Diagnostics::NoLocEmitter* emitter_;
-};
-
-}  // namespace
-
-// Builds a clang `CompilerInvocation` describing the options to use to build an
-// imported C++ AST.
-// TODO: Cache the compiler invocation created here and reuse it if building
-// multiple AST units. Consider building the `CompilerInvocation` from the
-// driver and passing it into check. This would also allow us to have a shared
-// set of defaults between the Clang invocation we use for imports and the
-// invocation we use for `carbon clang`.
-static auto BuildCompilerInvocation(
-    Context& context, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    const std::string& clang_path, const std::string& target)
-    -> std::unique_ptr<clang::CompilerInvocation> {
-  Diagnostics::NoLocEmitter emitter(context.emitter());
-  CarbonClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
-
-  const char* driver_args[] = {
-      clang_path.c_str(),
-      // Propagate the target to Clang.
-      "-target",
-      target.c_str(),
-      // Require PIE. Note its default is configurable in Clang.
-      "-fPIE",
-      // Parse as a C++ (not C) header.
-      "-x",
-      "c++",
-      IncludesFileName,
-  };
-
-  // Build a diagnostics engine. Note that we don't have any diagnostic options
-  // yet; they're produced by running the driver.
-  clang::DiagnosticOptions driver_diag_opts;
-  llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
-      clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
-                                                 &diagnostics_consumer,
-                                                 /*ShouldOwnClient=*/false));
-
-  // Ask the driver to process the arguments and build a corresponding clang
-  // frontend invocation.
-  auto invocation =
-      clang::createInvocation(driver_args, {.Diags = driver_diags, .VFS = fs});
-
-  // Emit any queued diagnostics from parsing our driver arguments.
-  return invocation;
-}
-
-namespace {
-
 // Used to convert Clang diagnostics to Carbon diagnostics.
 class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
  public:
@@ -310,16 +214,8 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
 static auto GenerateAst(Context& context,
                         llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
                         llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                        const std::string& clang_path,
-                        const std::string& target)
+                        std::shared_ptr<clang::CompilerInvocation> invocation)
     -> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
-  // Build the options to use to invoke the Clang frontend.
-  std::shared_ptr<clang::CompilerInvocation> invocation =
-      BuildCompilerInvocation(context, fs, clang_path, target);
-  if (!invocation) {
-    return {nullptr, true};
-  }
-
   // Build a diagnostics engine.
   CarbonClangDiagnosticConsumer diagnostics_consumer(&context,
                                                      invocation.get());
@@ -328,11 +224,20 @@ static auto GenerateAst(Context& context,
           *fs, invocation->getDiagnosticOpts(), &diagnostics_consumer,
           /*ShouldOwnClient=*/false));
 
+  // Extract the input from the frontend invocation and make sure it makes
+  // sense.
+  const auto& inputs = invocation->getFrontendOpts().Inputs;
+  CARBON_CHECK(inputs.size() == 1 &&
+               inputs[0].getKind().getLanguage() == clang::Language::CXX &&
+               inputs[0].getKind().getFormat() == clang::InputKind::Source);
+  llvm::StringRef file_name = inputs[0].getFile();
+
   // Remap the imports file name to the corresponding `#include`s.
+  // TODO: Modify the frontend options to specify this memory buffer as input
+  // instead of remapping the file.
   std::string includes = GenerateCppIncludesHeaderCode(context, imports);
-  auto includes_buffer =
-      llvm::MemoryBuffer::getMemBuffer(includes, IncludesFileName);
-  invocation->getPreprocessorOpts().addRemappedFile(IncludesFileName,
+  auto includes_buffer = llvm::MemoryBuffer::getMemBuffer(includes, file_name);
+  invocation->getPreprocessorOpts().addRemappedFile(file_name,
                                                     includes_buffer.get());
 
   // Create the AST unit.
@@ -388,7 +293,7 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
 auto ImportCppFiles(Context& context,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
                     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                    llvm::StringRef clang_path, llvm::StringRef target)
+                    std::shared_ptr<clang::CompilerInvocation> invocation)
     -> std::unique_ptr<clang::ASTUnit> {
   if (imports.empty()) {
     return nullptr;
@@ -404,7 +309,7 @@ auto ImportCppFiles(Context& context,
   auto name_scope_id = AddNamespace(context, package_id, imports);
 
   auto [generated_ast, ast_has_error] =
-      GenerateAst(context, imports, fs, clang_path.str(), target.str());
+      GenerateAst(context, imports, fs, std::move(invocation));
 
   SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
   name_scope.set_is_closed_import(true);

+ 5 - 1
toolchain/check/import_cpp.h

@@ -5,9 +5,13 @@
 #ifndef CARBON_TOOLCHAIN_CHECK_IMPORT_CPP_H_
 #define CARBON_TOOLCHAIN_CHECK_IMPORT_CPP_H_
 
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 
 namespace Carbon::Check {
 
@@ -17,7 +21,7 @@ namespace Carbon::Check {
 auto ImportCppFiles(Context& context,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
                     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                    llvm::StringRef clang_path, llvm::StringRef target)
+                    std::shared_ptr<clang::CompilerInvocation> invocation)
     -> std::unique_ptr<clang::ASTUnit>;
 
 // Looks up the given name in the Clang AST generated when importing C++ code.

+ 0 - 6
toolchain/diagnostics/coverage_test.cpp

@@ -58,12 +58,6 @@ constexpr Kind UntestedKinds[] = {
     // conversion cannot fail. This should be covered once we support `ref`
     // binding syntax.
     Kind::ConversionFailureNonRefToRef,
-
-    // TODO: These are temporarily unreachable because we don't pass invalid
-    // driver options to Clang, but will become reachable once we support
-    // passing custom Clang arguments.
-    Kind::CppInteropDriverError,
-    Kind::CppInteropDriverWarning,
 };
 
 // Looks for diagnostic kinds that aren't covered by a file_test.

+ 1 - 0
toolchain/driver/BUILD

@@ -125,6 +125,7 @@ cc_library(
         "//common:raw_string_ostream",
         "//common:version",
         "//common:vlog",
+        "//toolchain/base:clang_invocation",
         "//toolchain/base:llvm_tools",
         "//toolchain/base:shared_value_stores",
         "//toolchain/base:timings",

+ 54 - 2
toolchain/driver/compile_subcommand.cpp

@@ -15,6 +15,7 @@
 #include "common/vlog.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
+#include "toolchain/base/clang_invocation.h"
 #include "toolchain/base/timings.h"
 #include "toolchain/check/check.h"
 #include "toolchain/codegen/codegen.h"
@@ -62,6 +63,30 @@ compile to machine code.
             &phase);
       });
 
+  b.AddStringOption(
+      {
+          .name = "clang-arg",
+          .value_name = "CLANG-ARG",
+          .help = R"""(
+An argument to pass to the Clang compiler for use when compiling imported C++
+code.
+
+All flags that are accepted by the Clang driver are supported. However, you
+cannot specify arguments that would result in additional compilations being
+performed. Use `carbon clang` instead to compile additional source files.
+)""",
+      },
+      [&](auto& arg_b) { arg_b.Append(&clang_args); });
+
+  b.AddStringPositionalArg(
+      {
+          .name = "CLANG-ARG",
+          .help = R"""(
+Additional Clang arguments. See help for `--clang-arg` for details.
+)""",
+      },
+      [&](auto& arg_b) { arg_b.Append(&clang_args); });
+
   // TODO: Rearrange the code setting this option and two related ones to
   // allow them to reference each other instead of hard-coding their names.
   b.AddStringOption(
@@ -820,6 +845,34 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
     return {.success = false};
   }
 
+  std::shared_ptr<clang::CompilerInvocation> clang_invocation;
+  // Build a clang invocation. We do this regardless of whether we're running
+  // check, because this is essentially performing further option validation,
+  // and we generally validate all options even if we're not using them for the
+  // selected phases of compilation.
+  // TODO: Share any arguments we specify here with the `carbon clang`
+  // subcommand.
+  {
+    llvm::SmallVector<std::string> clang_path_and_args = {
+        driver_env.installation->clang_path(),
+        // Propagate the target to Clang.
+        llvm::formatv("--target={0}", options_.codegen_options.target).str(),
+        // Enable PIE by default, but allow it to be overridden by Clang
+        // arguments. Clang's default is configurable, but we'd like our
+        // defaults to be more stable.
+        // TODO: Decide if we want this.
+        "-fPIE",
+    };
+    for (auto str : options_.clang_args) {
+      clang_path_and_args.push_back(str.str());
+    }
+    clang_invocation = BuildClangInvocation(driver_env.consumer, driver_env.fs,
+                                            clang_path_and_args);
+    if (!clang_invocation) {
+      return {.success = false};
+    }
+  }
+
   // Find the files comprising the prelude if we are importing it.
   // TODO: Replace this with a search for library api files in a
   // package-specific search path based on the library name.
@@ -944,8 +997,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
     }
   }
   Check::CheckParseTrees(check_units, cache.tree_and_subtrees_getters(),
-                         driver_env.fs, driver_env.installation->clang_path(),
-                         options_.codegen_options.target, options);
+                         driver_env.fs, options, clang_invocation);
   CARBON_VLOG_TO(driver_env.vlog_stream,
                  "*** Check::CheckParseTrees done ***\n");
   for (auto& unit : units) {

+ 1 - 0
toolchain/driver/compile_subcommand.h

@@ -41,6 +41,7 @@ struct CompileOptions {
 
   llvm::StringRef output_filename;
   llvm::SmallVector<llvm::StringRef> input_filenames;
+  llvm::SmallVector<llvm::StringRef> clang_args;
 
   bool asm_output = false;
   bool force_obj_output = false;

+ 15 - 0
toolchain/driver/testdata/compile/clang_args_warning.carbon

@@ -0,0 +1,15 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// ARGS: --include-diagnostic-kind compile --clang-arg=-L/usr/lib foo.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/clang_args_warning.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/clang_args_warning.carbon
+// CHECK:STDERR: warning: argument unused during compilation: '-L/usr/lib' [CppInteropDriverWarning]
+// CHECK:STDERR:
+
+// --- foo.carbon

+ 17 - 0
toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon

@@ -0,0 +1,17 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// ARGS: --include-diagnostic-kind compile foo.carbon -- bar.cpp
+//
+// NOAUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon
+// CHECK:STDERR: error: unable to handle compilation, expected exactly one compiler job in ' "{{.*}}/clang" "-cc1" {{.*}} "-x" "c++" "bar.cpp";  "{{.*}}/clang" "-cc1" {{.*}} "-x" "c++" "<carbon Cpp imports>"; ' [CppInteropDriverError]
+// CHECK:STDERR:
+
+// --- foo.carbon
+
+// --- bar.cpp

+ 18 - 0
toolchain/driver/testdata/compile/fail_clang_args.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// ARGS: --include-diagnostic-kind compile --target=x86-pc-linux-gnu --clang-arg=-Wall --clang-arg=-Wextra foo.carbon -- -Wuninitialized -Wno-all -###
+//
+// SET-CAPTURE-CONSOLE-OUTPUT
+// SET-CHECK-SUBSET
+// NOAUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_args.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_args.carbon
+// CHECK:STDERR: clang version {{.*}}
+// CHECK:STDERR: InstalledDir: {{.*}}/toolchain/install/prefix_root/lib/carbon/../../lib/carbon/llvm/bin
+// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix_root/lib/carbon/../../lib/carbon/llvm/bin/clang" "-cc1" {{.*}}"-triple" "x86-pc-linux-gnu" {{.*}}"-fsyntax-only" {{.*}} "-resource-dir" {{.*}} "-Wall" "-Wextra" "-Wuninitialized" "-Wno-all" {{.*}}
+
+// --- foo.carbon

+ 15 - 0
toolchain/driver/testdata/compile/fail_clang_args_error.carbon

@@ -0,0 +1,15 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// ARGS: --include-diagnostic-kind compile --clang-arg=-fclang-flag-that-doesnt-exist foo.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_args_error.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_args_error.carbon
+// CHECK:STDERR: error: unknown argument: '-fclang-flag-that-doesnt-exist' [CppInteropDriverError]
+// CHECK:STDERR:
+
+// --- foo.carbon

+ 1 - 0
toolchain/language_server/BUILD

@@ -35,6 +35,7 @@ cc_library(
         "//common:check",
         "//common:map",
         "//common:raw_string_ostream",
+        "//toolchain/base:clang_invocation",
         "//toolchain/base:shared_value_stores",
         "//toolchain/check",
         "//toolchain/diagnostics:diagnostic_emitter",

+ 10 - 4
toolchain/language_server/context.cpp

@@ -11,10 +11,12 @@
 #include "common/check.h"
 #include "common/raw_string_ostream.h"
 #include "llvm/TargetParser/Host.h"
+#include "toolchain/base/clang_invocation.h"
 #include "toolchain/base/shared_value_stores.h"
 #include "toolchain/check/check.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/diagnostic_consumer.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/lex/lex.h"
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/parse.h"
@@ -157,16 +159,20 @@ auto Context::File::SetText(Context& context, std::optional<int64_t> version,
   auto getter = [this]() -> const Parse::TreeAndSubtrees& {
     return *tree_and_subtrees_;
   };
-  llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
-      new llvm::vfs::InMemoryFileSystem;
+  // TODO: Include any unsaved files as an overlay on the real file system.
+  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs =
+      llvm::vfs::getRealFileSystem();
 
   // TODO: Include the prelude.
   Check::CheckParseTreesOptions check_options;
   check_options.vlog_stream = context.vlog_stream();
+
+  auto clang_invocation =
+      BuildClangInvocation(consumer, fs, {context.installation().clang_path()});
+
   Check::CheckParseTrees(units,
                          llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>(getter),
-                         fs, context.installation().clang_path(),
-                         llvm::sys::getDefaultTargetTriple(), check_options);
+                         fs, check_options, std::move(clang_invocation));
 
   // Note we need to publish diagnostics even when empty.
   // TODO: Consider caching previously published diagnostics and only publishing