diff --git a/clang/include/clang/Driver/Multilib2.h b/clang/include/clang/Driver/Multilib2.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Driver/Multilib2.h @@ -0,0 +1,40 @@ +//===- Multilib2.h ----------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_DRIVER_MULTILIB2_H +#define LLVM_CLANG_DRIVER_MULTILIB2_H + +#include "clang/Basic/LLVM.h" +#include + +namespace clang { +namespace driver { + +class Multilib2 { +public: + using const_iterator = std::vector, std::vector>>::const_iterator; + + Multilib2(); + + size_t size() const { return Multilibs.size(); } + const_iterator begin() const { return Multilibs.begin(); } + const_iterator end() const { return Multilibs.end(); } + + bool parse(StringRef, raw_ostream &Err); + + std::pair select(ArrayRef Arguments) const; + +private: + std::vector, std::vector>> Multilibs; + std::vector, std::vector>> RegexAttributes; +}; + +} // namespace driver +} // namespace clang + +#endif // LLVM_CLANG_DRIVER_MULTILIB2_H diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -22,6 +22,7 @@ DriverOptions.cpp Job.cpp Multilib.cpp + Multilib2.cpp OffloadBundler.cpp OptionUtils.cpp Phases.cpp diff --git a/clang/lib/Driver/Multilib2.cpp b/clang/lib/Driver/Multilib2.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Driver/Multilib2.cpp @@ -0,0 +1,289 @@ +//===- Multilib2.cpp - Multilib2 Implementation -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ToolChains/BareMetal.h" +#include "ToolChains/Clang.h" +#include "clang/Driver/Multilib2.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include + +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/TargetParser/Host.h" + +using namespace clang; +using namespace driver; + +Multilib2::Multilib2() {} + +bool Multilib2::parse(StringRef Input, raw_ostream &Err) { + std::vector, std::vector>> MultilibList; + std::vector, std::vector>> RegexAttributesList; + + + llvm::SourceMgr SM; + llvm::yaml::Stream Stream(Input, SM); + llvm::yaml::document_iterator DocumentIterator = Stream.begin(); + + // There has to be at least one document available. + if (DocumentIterator == Stream.end()) + return false; + + llvm::yaml::Node *DocumentRoot = DocumentIterator->getRoot(); + if (!DocumentRoot) { + // TODO report error + return false; + } + + auto *TopMappings = dyn_cast(DocumentRoot); + if (!TopMappings) { + // TODO report error + return false; + } + + for (auto &TopMapping : *TopMappings) { + // The keys should be strings, which represent a source-file path. + auto *TopKey = dyn_cast(TopMapping.getKey()); + if (!TopKey) { + // TODO report error + return false; + } + + if (TopKey->getRawValue() == "variants") { + auto Variants = dyn_cast(TopMapping.getValue()); + if (!Variants) { + // TODO report error + return false; + } + for (auto &Variant : *Variants) { + std::string Path; + std::vector Args, Attrs; + + llvm::yaml::MappingNode *VariantMappings = dyn_cast(&Variant); + if (!VariantMappings) { + // TODO report error + return false; + } + for (auto &VariantMapping : *VariantMappings) { + auto *VariantKey = dyn_cast(VariantMapping.getKey()); + if (!VariantKey) { + // TODO report error + return false; + } + if (VariantKey->getRawValue() == "path") { + auto *VariantValue = dyn_cast(VariantMapping.getValue()); + if (!VariantValue) { + // TODO report error + return false; + } + SmallString<32> PathStorage; + Path = VariantValue->getValue(PathStorage).str(); + } + if (VariantKey->getRawValue() == "args") { + auto Arguments = dyn_cast(VariantMapping.getValue()); + if (!Arguments) { + // TODO report error + return false; + } + for (auto &Argument : *Arguments) { + auto *ArgumentValue = dyn_cast(&Argument); + if (!ArgumentValue) { + // TODO report error + return false; + } + SmallString<32> ArgumentStorage; + Args.push_back(ArgumentValue->getValue(ArgumentStorage).str()); + } + } + if (VariantKey->getRawValue() == "attrs") { + auto Attributes = dyn_cast(VariantMapping.getValue()); + if (!Attributes) { + // TODO report error + return false; + } + for (auto &Attribute : *Attributes) { + auto *AttributeValue = dyn_cast(&Attribute); + if (!AttributeValue) { + // TODO report error + return false; + } + SmallString<32> AttributeStorage; + Attrs.push_back(AttributeValue->getValue(AttributeStorage).str()); + } + } + } + if (StringRef(Path).starts_with("/")) { + Err << "paths must be relative. \"" << Path << "\" starts with \"/\"\n"; + return false; + } + if (Path == ".") { + Path = ""; + } else { + Path = "/" + Path; + } + // TODO: add test that breaks if no sorting is done. + std::sort(Attrs.begin(), Attrs.end()); + MultilibList.push_back(std::tuple, std::vector>(Path, Args, Attrs)); + } + } else if (TopKey->getRawValue() == "arguments") { + auto Arguments = dyn_cast(TopMapping.getValue()); + if (!Arguments) { + // TODO report error + return false; + } + for (auto &Argument : *Arguments) { + std::string Regex; + std::vector MatchAttrs, NoMatchAttrs; + + llvm::yaml::MappingNode *ArgumentMappings = dyn_cast(&Argument); + if (!ArgumentMappings) { + // TODO report error + return false; + } + for (auto &ArgumentMapping : *ArgumentMappings) { + auto *ArgumentKey = dyn_cast(ArgumentMapping.getKey()); + if (!ArgumentKey) { + // TODO report error + return false; + } + if (ArgumentKey->getRawValue() == "regex") { + auto *ArgumentValue = dyn_cast(ArgumentMapping.getValue()); + if (!ArgumentValue) { + // TODO report error + return false; + } + SmallString<32> PathStorage; + Regex = ArgumentValue->getValue(PathStorage).str(); + } + if (ArgumentKey->getRawValue() == "matchAttrs") { + auto Arguments = dyn_cast(ArgumentMapping.getValue()); + if (!Arguments) { + // TODO report error + return false; + } + for (auto &Argument : *Arguments) { + auto *ArgumentValue = dyn_cast(&Argument); + if (!ArgumentValue) { + // TODO report error + return false; + } + SmallString<32> ArgumentStorage; + MatchAttrs.push_back(ArgumentValue->getValue(ArgumentStorage).str()); + } + } + if (ArgumentKey->getRawValue() == "noMatchAttrs") { + auto Attributes = dyn_cast(ArgumentMapping.getValue()); + if (!Attributes) { + // TODO report error + return false; + } + for (auto &Attribute : *Attributes) { + auto *AttributeValue = dyn_cast(&Attribute); + if (!AttributeValue) { + // TODO report error + return false; + } + SmallString<32> AttributeStorage; + NoMatchAttrs.push_back(AttributeValue->getValue(AttributeStorage).str()); + } + } + } + RegexAttributesList.push_back(std::tuple, std::vector>(Regex, MatchAttrs, NoMatchAttrs)); + } + } + } + + Multilibs.swap(MultilibList); + RegexAttributes.swap(RegexAttributesList); + + return true; +} + +static std::vector normalizeArgs(ArrayRef ArgsWithoutFile) { + const char *ExecutableName = "/no/such/clang"; + const char *TestFileName = "test.c"; + std::vector Args = {ExecutableName, TestFileName}; + for (const char *A : ArgsWithoutFile) { + if (A != StringRef("-###")) { + Args.push_back(A); + } + } + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + struct TestDiagnosticConsumer : public DiagnosticConsumer {}; + IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + InMemoryFileSystem->addFile(TestFileName, time_t(), llvm::MemoryBuffer::getMemBuffer("")); + DiagnosticsEngine Diags(DiagID, &*DiagOpts, new TestDiagnosticConsumer); + Driver D(ExecutableName, llvm::sys::getDefaultTargetTriple(), Diags, "clang LLVM compiler", InMemoryFileSystem); + std::unique_ptr C(D.BuildCompilation(Args)); + if (C->getJobs().empty()) { + return std::vector(); + } + llvm::opt::ArgStringList NormalisedArgStrings = C->getJobs().begin()->getArguments(); + + unsigned MissingArgIndex = 0, MissingArgCount = 0; + llvm::opt::InputArgList ParsedArgs = getDriverOptTable().ParseArgs(NormalisedArgStrings, MissingArgIndex, MissingArgCount); + + std::vector Result; + for (const llvm::opt::Arg *A : ParsedArgs) { + Result.push_back(A->getAsString(ParsedArgs)); + } + return Result; +} + +static std::vector argsToAttributes(ArrayRef Arguments, const std::vector, std::vector>> &RegexAttributesList) { + std::set Result; + for (const std::tuple, std::vector> &RegexAttributes : RegexAttributesList) { + const std::regex Regex(std::get<0>(RegexAttributes)); + const std::vector &MatchAttrs = std::get<1>(RegexAttributes); + const std::vector &NoMatchAttrs = std::get<2>(RegexAttributes); + bool Match = false; + for (const std::string &Argument : Arguments) { + if (std::regex_match(Argument, Regex)) { + Match = true; + break; + } + } + if (Match) { + Result.insert(MatchAttrs.begin(), MatchAttrs.end()); + } else { + Result.insert(NoMatchAttrs.begin(), NoMatchAttrs.end()); + } + } + return std::vector(Result.begin(), Result.end()); +} + +std::pair Multilib2::select(ArrayRef Arguments) const { + std::pair Result(false, ""); + std::vector NormalizedArguments(normalizeArgs(Arguments)); + if (NormalizedArguments.empty()) { + return std::pair(false, ""); + } + std::vector Attrs = argsToAttributes(NormalizedArguments, RegexAttributes); + for (const std::tuple, std::vector> &M : Multilibs) { + const std::vector &MAttrs = std::get<2>(M); + if (std::includes(Attrs.begin(), Attrs.end(), MAttrs.begin(), MAttrs.end())) { + Result = std::pair(true, std::get<0>(M)); + } + } + return Result; +} diff --git a/clang/lib/Driver/ToolChains/BareMetal.cpp b/clang/lib/Driver/ToolChains/BareMetal.cpp --- a/clang/lib/Driver/ToolChains/BareMetal.cpp +++ b/clang/lib/Driver/ToolChains/BareMetal.cpp @@ -16,6 +16,7 @@ #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Multilib2.h" #include "clang/Driver/Options.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/Path.h" @@ -28,6 +29,54 @@ using namespace clang::driver::tools; using namespace clang::driver::toolchains; +static std::string stringRemove(std::string S, StringRef Remove) { + for (;;) { + std::string::size_type i = S.find(Remove); + if (i == std::string::npos) + break; + S.erase(i, Remove.size()); + } + return S; +} + +/// To facilitate multilib, normalise the triple, discarding +/// information that can be specified via other flags. +static std::string simplifyTripleName(llvm::Triple Triple) { + Triple.setArchName(llvm::Triple::getArchTypePrefix(Triple.getArch())); + + if (Triple.getEnvironment() == llvm::Triple::EABIHF) { + Triple.setEnvironment(llvm::Triple::EABI); + } + + std::string Result = Triple.normalize(); + + return stringRemove(Result, "-unknown"); +} + +static std::string computeBaseSysRoot(const Driver &D, const llvm::Triple& Triple) { + if (!D.SysRoot.empty()) + return D.SysRoot; + + SmallString<128> SysRootDir(D.Dir); + llvm::sys::path::append(SysRootDir, "../lib/clang-runtimes"); + + // Try using the --target argument verbatim + SmallString<128> SysRootWithTarget(SysRootDir); + llvm::sys::path::append(SysRootWithTarget, D.getTargetTriple()); + if (D.getVFS().exists(SysRootWithTarget)) { + return std::string(SysRootWithTarget); + } + + SmallString<128> SysRootWithSimplifiedTarget(SysRootDir); + llvm::sys::path::append(SysRootWithSimplifiedTarget, simplifyTripleName(Triple)); + if (D.getVFS().exists(SysRootWithSimplifiedTarget)) { + return std::string(SysRootWithSimplifiedTarget); + } + + // Fall back to using the --target argument verbatim + return std::string(SysRootWithTarget); +} + static Multilib makeMultilib(StringRef commonSuffix) { return Multilib(commonSuffix, commonSuffix, commonSuffix); } @@ -91,6 +140,62 @@ return false; } +static std::string makeMultilibFilePath(const Driver &D, const llvm::Triple &TargetTriple) { + const std::string BaseSysRoot = computeBaseSysRoot(D, TargetTriple); + llvm::SmallString<128> Result(BaseSysRoot); + llvm::sys::path::append(Result, "multilib.yaml"); + return std::string(Result); +} + +static bool readMultilibFile(const Driver &D, const llvm::Twine &MultilibFileName, Multilib2 &Multilibs) { + llvm::ErrorOr> MB = D.getVFS().getBufferForFile(MultilibFileName); + return MB && Multilibs.parse(MB.get()->getBuffer(), llvm::errs()); +} + +/* +Terms: +* User sysroot: the sysroot specified by the user with --sysroot. +* Base sysroot: the user sysroot, or a value chosen automatically according to + the target. +* Final sysroot: if using multilib, a subdirectory of the base sysroot + containing include and lib directories; otherwise the same as the base + sysroot. +* isysroot: Like sysroot, except for include directories. + +For each of lib (--sysroot) and include (-isysroot, falling back to --sysroot): +1. Has the user specified a sysroot? If so, use that as the base sysroot + Else use try using the target verbatim as the base sysroot. Does it exist? If so, use it. + Else use the normalised, simplified target as the base sysroot. +2. With the base sysroot chosen, multilib can be picked. + Does the base sysroot contain clang.multilib? If so, load it and use it. + Otherwise fall back to RISC-V's multilib implementation if appropriate, + otherwise no multilib. +*/ +static bool findArmMultilibs(const Driver &D, const llvm::Triple &TargetTriple, + const ArgList &Args, + DetectedMultilibs &Result) { + Multilib2 M; + + if (!readMultilibFile(D, makeMultilibFilePath(D, TargetTriple), M)) { + return false; + } + + std::vector ArgumentStrings; + for (const llvm::opt::Arg *A : Args) { + ArgumentStrings.push_back(A->getAsString(Args)); + } + + std::vector ArgumentCStrings; + for (const std::string &S : ArgumentStrings) { + ArgumentCStrings.push_back(S.c_str()); + } + + std::pair SuccessAndPath = M.select(ArgumentCStrings); + StringRef Path = SuccessAndPath.second; + Result.SelectedMultilib = Multilib(Path, Path, Path); + return SuccessAndPath.first; +} + BareMetal::BareMetal(const Driver &D, const llvm::Triple &Triple, const ArgList &Args) : ToolChain(D, Triple, Args) { @@ -161,6 +266,11 @@ SelectedMultilib = Result.SelectedMultilib; Multilibs = Result.Multilibs; } + } else if (isARMBareMetal(Triple) || isAArch64BareMetal(Triple)) { + if (findArmMultilibs(D, Triple, Args, Result)) { + SelectedMultilib = Result.SelectedMultilib; + Multilibs = Result.Multilibs; + } } } @@ -174,15 +284,7 @@ } std::string BareMetal::computeSysRoot() const { - if (!getDriver().SysRoot.empty()) - return getDriver().SysRoot + SelectedMultilib.osSuffix(); - - SmallString<128> SysRootDir; - llvm::sys::path::append(SysRootDir, getDriver().Dir, "../lib/clang-runtimes", - getDriver().getTargetTriple()); - - SysRootDir += SelectedMultilib.osSuffix(); - return std::string(SysRootDir); + return computeBaseSysRoot(getDriver(), getTriple()) + SelectedMultilib.osSuffix(); } void BareMetal::AddClangSystemIncludeArgs(const ArgList &DriverArgs, diff --git a/clang/test/Driver/Inputs/baremetal_multilib/arm-none-eabi/multilib.yaml b/clang/test/Driver/Inputs/baremetal_multilib/arm-none-eabi/multilib.yaml new file mode 100644 --- /dev/null +++ b/clang/test/Driver/Inputs/baremetal_multilib/arm-none-eabi/multilib.yaml @@ -0,0 +1,101 @@ +# This file is in two parts: +# 1. A list of library variants. +# 2. A mapping from command line arguments to attributes. + +# How does clang use this file? +# 1. If the ToolChain class for the architecture supports this form of +# multilibit then it loads file if present in sysroot. +# 2. Generate clang -cc1 arguments for the user provided arguments. +# 3. Compare the arguments against each regular expression and store +# associated attributes if there's a match. +# 4. Find the last library variant whose attributes are a subset of the +# attributes derived from the user provided arguments. +# 5. Use the path for the library variant as the sysroot. + +# The first section of the file is the list of library variants. +# A library is considered compatible if the attributes (see below) +# derived from its arguments are a subset of the attributes derived +# from the arguments provided by the user. +# If multiple libraries are deemed compatible then the one that appears +# last in the list wins. + +variants: +- path: thumb/v6-m/nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv6m] + attrs: [thumb, soft, v6m] +- path: thumb/v7-m/nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv7m+nofp] + attrs: [thumb, soft, v7m] +- path: thumb/v7e-m/nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv7em, -mfpu=none] + attrs: [thumb, soft, v7em] +- path: thumb/v8-m.main/nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8m.main+nofp] + attrs: [thumb, soft, v8m.main] +- path: thumb/v8.1-m.main/nofp/nomve + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8.1m.main+nofp+nomve] + attrs: [thumb, soft, v8.1m.main] +- path: thumb/v7e-m/fpv4_sp_d16 + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv7em, -mfpu=fpv4-sp-d16] + attrs: [thumb, hard, v7em, vfp2sp] +- path: thumb/v7e-m/fpv5_d16 + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv7em, -mfpu=fpv5-d16] + attrs: [thumb, hard, v7em, fp-armv8d16sp] +- path: thumb/v8-m.main/fp + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8m.main+fp] + attrs: [thumb, hard, v8m.main] +- path: thumb/v8.1-m.main/fp + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8.1m.main+fp] + attrs: [thumb, hard, v8.1m.main, vfp2sp] +- path: thumb/v8.1-m.main/nofp/mve + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8.1m.main+nofp+mve] + attrs: [thumb, hard, v8.1m.main, mve] + + +# The second section of the file is a map from clang -cc1 command line +# options to attributes. These are the options printed out if you run +# clang with "-###" +# The regex must match a whole logical argument string +# e.g. "-target-cpu generic" or "-msoft-float". +# "matchAttrs" attributes will be added if an argument matches, while +# "noMatchAttrs" attributes will be added otherwise. +arguments: +- regex: -triple aarch64.* + matchAttrs: [aarch64] +- regex: -triple thumb.* + matchAttrs: [thumb] +- regex: -mfloat-abi soft + matchAttrs: [soft] +- regex: -mfloat-abi hard + matchAttrs: [hard] +# Floating point registers are enabled by default. +# If the feature isn't disabled then we know it's enabled. +- regex: -target-feature -fpregs + noMatchAttrs: [fpregs] +- regex: -target-feature \+vfp2sp + matchAttrs: [vfp2sp] +# Used to distinguish "-march=armv7em -mfpu=fpv4-sp-d16" and +# "-march=armv7em -mfpu=fpv5-d16" +- regex: -target-feature \+fp-armv8d16sp + matchAttrs: [fp-armv8d16sp] +- regex: -target-feature \+mve + matchAttrs: [mve] +- regex: -triple .*v6m-.* + matchAttrs: [v6m] +- regex: -triple .*v7m-.* + matchAttrs: [v7m, v6m] +- regex: -triple .*v7em-.* + matchAttrs: [v7em, v7m, v6m] +- regex: -triple .*v8m\.base-.* + matchAttrs: [v8m.base, v6m] +- regex: -triple .*v8m\.main-.* + matchAttrs: [v8m.main, v7em, v7m, v6m] +# Match v8.1m and later. We assume that v8.2m (if/when it exists) will +# be backwards compatible with v8.1m. +# The alternative is to not recognise later versions, and require that +# this multilib spec is updated before it can be used with newer +# architecture versions. +- regex: -triple .*v8\.[0-9]+m\.base-.* + matchAttrs: [v8m.base, v6m] +- regex: -triple .*v8\.[0-9]+m\.main-.* + matchAttrs: [v8.1m.main, v8m.main, v7em, v7m, v6m] diff --git a/clang/test/Driver/arm-compiler-rt.c b/clang/test/Driver/arm-compiler-rt.c --- a/clang/test/Driver/arm-compiler-rt.c +++ b/clang/test/Driver/arm-compiler-rt.c @@ -1,4 +1,5 @@ // RUN: %clang -target arm-none-eabi \ +// RUN: --sysroot=%S/Inputs/resource_dir_with_arch_subdir \ // RUN: -resource-dir=%S/Inputs/resource_dir_with_arch_subdir \ // RUN: -rtlib=compiler-rt -### %s 2>&1 \ // RUN: | FileCheck %s -check-prefix ARM-EABI diff --git a/clang/test/Driver/baremetal-multilib.cpp b/clang/test/Driver/baremetal-multilib.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Driver/baremetal-multilib.cpp @@ -0,0 +1,21 @@ +// REQUIRES: shell +// UNSUPPORTED: system-windows + +// RUN: rm -rf %T/baremetal_multilib +// RUN: mkdir -p %T/baremetal_multilib/bin +// RUN: mkdir -p %T/baremetal_multilib/lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/fp/lib +// RUN: touch %T/baremetal_multilib/lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/fp/lib/libclang_rt.builtins.a +// RUN: ln -s %clang %T/baremetal_multilib/bin/clang +// RUN: ln -s %S/Inputs/baremetal_multilib/arm-none-eabi/multilib.yaml %T/baremetal_multilib/lib/clang-runtimes/arm-none-eabi/multilib.yaml + +// RUN: %T/baremetal_multilib/bin/clang -no-canonical-prefixes %s -### -o %t.o 2>&1 \ +// RUN: --target=thumbv8m.main-none-eabihf --sysroot= \ +// RUN: | FileCheck --check-prefix=CHECK-V6M-C %s +// CHECK-V6M-C: "{{.*}}clang{{.*}}" "-cc1" "-triple" "thumbv8m.main-none-unknown-eabihf" +// CHECK-V6M-C-SAME: "-internal-isystem" "{{.*}}/baremetal_multilib/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/fp/include/c++/v1" +// CHECK-V6M-C-SAME: "-internal-isystem" "{{.*}}/baremetal_multilib/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/fp/include" +// CHECK-V6M-C-SAME: "-x" "c++" "{{.*}}/baremetal-multilib.cpp" +// CHECK-V6M-C-NEXT: "{{[^"]*}}ld{{(\.(lld|bfd|gold))?}}{{(\.exe)?}}" "{{.*}}.o" "-Bstatic" +// CHECK-V6M-C-SAME: "-L{{.*}}/baremetal_multilib/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/fp/lib" +// CHECK-V6M-C-SAME: "-lc" "-lm" "-lclang_rt.builtins" +// CHECK-V6M-C-SAME: "-o" "{{.*}}.o" diff --git a/clang/test/Driver/baremetal.cpp b/clang/test/Driver/baremetal.cpp --- a/clang/test/Driver/baremetal.cpp +++ b/clang/test/Driver/baremetal.cpp @@ -118,9 +118,9 @@ // Verify that the bare metal driver does not include any host system paths: // CHECK-AARCH64-NO-HOST-INC: InstalledDir: [[INSTALLEDDIR:.+]] // CHECK-AARCH64-NO-HOST-INC: "-resource-dir" "[[RESOURCE:[^"]+]]" -// CHECK-AARCH64-NO-HOST-INC-SAME: "-internal-isystem" "[[INSTALLEDDIR]]{{[/\\]+}}..{{[/\\]+}}lib{{[/\\]+}}clang-runtimes{{[/\\]+}}aarch64-none-elf{{[/\\]+}}include{{[/\\]+}}c++{{[/\\]+}}v1" +// CHECK-AARCH64-NO-HOST-INC-SAME: "-internal-isystem" "[[INSTALLEDDIR]]{{[/\\]+}}..{{[/\\]+}}lib{{[/\\]+}}clang-runtimes{{[/\\]+}}aarch64-none-elf{{[/\\]+.*}}include{{[/\\]+}}c++{{[/\\]+}}v1" // CHECK-AARCH64-NO-HOST-INC-SAME: "-internal-isystem" "[[RESOURCE]]{{[/\\]+}}include" -// CHECK-AARCH64-NO-HOST-INC-SAME: "-internal-isystem" "[[INSTALLEDDIR]]{{[/\\]+}}..{{[/\\]+}}lib{{[/\\]+}}clang-runtimes{{[/\\]+}}aarch64-none-elf{{[/\\]+}}include" +// CHECK-AARCH64-NO-HOST-INC-SAME: "-internal-isystem" "[[INSTALLEDDIR]]{{[/\\]+}}..{{[/\\]+}}lib{{[/\\]+}}clang-runtimes{{[/\\]+}}aarch64-none-elf{{[/\\]+.*}}include" // RUN: %clang %s -### --target=riscv64-unknown-elf -o %t.out -L some/directory/user/asked/for \ // RUN: --sysroot=%S/Inputs/basic_riscv64_tree/riscv64-unknown-elf 2>&1 \ diff --git a/clang/test/Driver/print-libgcc-file-name-clangrt.c b/clang/test/Driver/print-libgcc-file-name-clangrt.c --- a/clang/test/Driver/print-libgcc-file-name-clangrt.c +++ b/clang/test/Driver/print-libgcc-file-name-clangrt.c @@ -45,6 +45,7 @@ // RUN: %clang -rtlib=compiler-rt -print-libgcc-file-name 2>&1 \ // RUN: --target=armv7m-none-eabi \ +// RUN: --sysroot=%S/Inputs/resource_dir_with_arch_subdir \ // RUN: -resource-dir=%S/Inputs/resource_dir_with_arch_subdir \ // RUN: | FileCheck --check-prefix=CHECK-CLANGRT-ARM-BAREMETAL %s // CHECK-CLANGRT-ARM-BAREMETAL: libclang_rt.builtins-armv7m.a diff --git a/clang/unittests/Driver/CMakeLists.txt b/clang/unittests/Driver/CMakeLists.txt --- a/clang/unittests/Driver/CMakeLists.txt +++ b/clang/unittests/Driver/CMakeLists.txt @@ -12,6 +12,7 @@ ToolChainTest.cpp ModuleCacheTest.cpp MultilibTest.cpp + Multilib2Test.cpp SanitizerArgsTest.cpp ) diff --git a/clang/unittests/Driver/Multilib2Test.cpp b/clang/unittests/Driver/Multilib2Test.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Driver/Multilib2Test.cpp @@ -0,0 +1,281 @@ +//===- unittests/Driver/Multilib2Test.cpp --- Multilib2 tests ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/Multilib2.h" +#include "llvm/ADT/ArrayRef.h" +#include "gtest/gtest.h" + +using namespace clang::driver; +using namespace clang; + +TEST(Multilib2Test, Parse) { + std::string O; + llvm::raw_string_ostream OS(O); + + Multilib2 M; + EXPECT_FALSE(M.parse(R"( +variants: +- path: /abc +)", OS)); + EXPECT_TRUE(StringRef(O).contains("paths must be relative")) << O; + + EXPECT_TRUE(M.parse(R"( +variants: +- path: . +)", OS)); + EXPECT_EQ(1U, M.size()); + EXPECT_EQ("", std::get<0>(*M.begin())); + + EXPECT_TRUE(M.parse(R"( +variants: +- path: abc +)", OS)); + EXPECT_EQ(1U, M.size()); + EXPECT_EQ("/abc", std::get<0>(*M.begin())); + + EXPECT_TRUE(M.parse(R"( +variants: +- path: pqr + args: [-mfloat-abi=soft] +)", OS)); + EXPECT_EQ(1U, M.size()); + EXPECT_EQ("/pqr", std::get<0>(*M.begin())); + EXPECT_EQ(std::vector({"-mfloat-abi=soft"}), std::get<1>(*M.begin())); + + EXPECT_TRUE(M.parse(R"( +variants: +- path: pqr + args: [-mfloat-abi=soft, -fno-exceptions] +)", OS)); + EXPECT_EQ(1U, M.size()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft", "-fno-exceptions"}), std::get<1>(*M.begin())); + + EXPECT_TRUE(M.parse(R"( +variants: +- path: a +- path: b +)", OS)); + EXPECT_EQ(2U, M.size()); +} + +TEST(Multilib2Test, SelectSoft) { + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(R"( +variants: +- path: s + attrs: [softabi] +arguments: +- regex: -mfloat-abi soft + matchAttrs: [softabi] +- regex: -mfloat-abi hard + matchAttrs: [hardabi] +- regex: -msoft-float + noMatchAttrs: [hasfp] +)", OS)); + EXPECT_TRUE(M.select({"--target=arm-none-eabi"}).first); + EXPECT_TRUE(M.select({"--target=arm-none-eabi", "-mfloat-abi=soft"}).first); + EXPECT_TRUE(M.select({"--target=arm-none-eabi", "-mfloat-abi=softfp"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabi", "-mfloat-abi=hard"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabihf"}).first); +} + +TEST(Multilib2Test, SelectSoftFP) { + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(R"( +variants: +- path: f + attrs: [softabi, hasfp] +arguments: +- regex: -mfloat-abi soft + matchAttrs: [softabi] +- regex: -mfloat-abi hard + matchAttrs: [hardabi] +- regex: -msoft-float + noMatchAttrs: [hasfp] +)", OS)); + EXPECT_FALSE(M.select({"--target=arm-none-eabi", "-mfloat-abi=soft"}).first); + EXPECT_TRUE(M.select({"--target=arm-none-eabi", "-mfloat-abi=softfp"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabi", "-mfloat-abi=hard"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabihf"}).first); +} + +TEST(Multilib2Test, SelectHard) { + // If hard float is all that's available then select that only if compiling with hard float. + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(R"( +variants: +- path: h + attrs: [hardabi, hasfp] +arguments: +- regex: -mfloat-abi soft + matchAttrs: [softabi] +- regex: -mfloat-abi hard + matchAttrs: [hardabi] +- regex: -msoft-float + noMatchAttrs: [hasfp] +)", OS)); + EXPECT_FALSE(M.select({"--target=arm-none-eabi"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabi", "-mfloat-abi=soft"}).first); + EXPECT_FALSE(M.select({"--target=arm-none-eabi", "-mfloat-abi=softfp"}).first); + EXPECT_TRUE(M.select({"--target=arm-none-eabi", "-mfloat-abi=hard"}).first); + EXPECT_TRUE(M.select({"--target=arm-none-eabihf"}).first); +} + +TEST(Multilib2Test, SelectFloatABI) { + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(R"( +variants: +- path: s + attrs: [softabi] +- path: f + attrs: [softabi, hasfp] +- path: h + attrs: [hardabi, hasfp] +arguments: +- regex: -mfloat-abi soft + matchAttrs: [softabi] +- regex: -mfloat-abi hard + matchAttrs: [hardabi] +- regex: -msoft-float + noMatchAttrs: [hasfp] +)", OS)); + EXPECT_EQ("/f", M.select({"--target=arm-none-eabi"}).second); + EXPECT_EQ("/s", M.select({"--target=arm-none-eabi", "-mfloat-abi=soft"}).second); + EXPECT_EQ("/f", M.select({"--target=arm-none-eabi", "-mfloat-abi=softfp"}).second); + EXPECT_EQ("/h", M.select({"--target=arm-none-eabi", "-mfloat-abi=hard"}).second); + EXPECT_EQ("/h", M.select({"--target=arm-none-eabihf"}).second); +} + +TEST(Multilib2Test, SelectFloatABIReversed) { + // If soft is specified after softfp then softfp will never be + // selected because soft is compatible with softfp and last wins. + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(R"( +variants: +- path: h + attrs: [hardabi, hasfp] +- path: f + attrs: [softabi, hasfp] +- path: s + attrs: [softabi] +arguments: +- regex: -mfloat-abi soft + matchAttrs: [softabi] +- regex: -mfloat-abi hard + matchAttrs: [hardabi] +- regex: -msoft-float + noMatchAttrs: [hasfp] +)", OS)); + EXPECT_EQ("/s", M.select({"--target=arm-none-eabi"}).second); + EXPECT_EQ("/s", M.select({"--target=arm-none-eabi", "-mfloat-abi=soft"}).second); + EXPECT_EQ("/s", M.select({"--target=arm-none-eabi", "-mfloat-abi=softfp"}).second); + EXPECT_EQ("/h", M.select({"--target=arm-none-eabi", "-mfloat-abi=hard"}).second); + EXPECT_EQ("/h", M.select({"--target=arm-none-eabihf"}).second); +} + +TEST(Multilib2Test, SelectMClass) { + const StringRef MultilibSpec = R"( +variants: +- path: armv6m_soft_nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv6m] + attrs: [thumb, soft, v6m] +- path: armv7m_soft_nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv7m+nofp] + attrs: [thumb, soft, v7m] +- path: armv7em_soft_nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv7em, -mfpu=none] + attrs: [thumb, soft, v7em] +- path: armv8m.main_soft_nofp + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8m.main+nofp] + attrs: [thumb, soft, v8m.main] +- path: armv8.1m.main_soft_nofp_nomve + args: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8.1m.main+nofp+nomve] + attrs: [thumb, soft, v8.1m.main] +- path: armv7em_hard_fpv4_sp_d16 + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv7em, -mfpu=fpv4-sp-d16] + attrs: [thumb, hard, v7em, vfp2sp] +- path: armv7em_hard_fpv5_d16 + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv7em, -mfpu=fpv5-d16] + attrs: [thumb, hard, v7em, fp-armv8d16sp] +- path: armv8m.main_hard_fp + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8m.main+fp] + attrs: [thumb, hard, v8m.main] +- path: armv8.1m.main_hard_fp + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8.1m.main+fp] + attrs: [thumb, hard, v8.1m.main, vfp2sp] +- path: armv8.1m.main_hard_nofp_mve + args: [--target=arm-none-eabi, -mfloat-abi=hard, -march=armv8.1m.main+nofp+mve] + attrs: [thumb, hard, v8.1m.main, mve] +arguments: +- regex: -triple aarch64.* + matchAttrs: [aarch64] +- regex: -triple thumb.* + matchAttrs: [thumb] +- regex: -mfloat-abi soft + matchAttrs: [soft] +- regex: -mfloat-abi hard + matchAttrs: [hard] +- regex: -target-feature -fpregs + noMatchAttrs: [fpregs] +- regex: -target-feature \+vfp2sp + matchAttrs: [vfp2sp] +- regex: -target-feature \+fp-armv8d16sp + matchAttrs: [fp-armv8d16sp] +- regex: -target-feature \+mve + matchAttrs: [mve] +- regex: -triple .*v6m-.* + matchAttrs: [v6m] +- regex: -triple .*v7m-.* + matchAttrs: [v7m, v6m] +- regex: -triple .*v7em-.* + matchAttrs: [v7em, v7m, v6m] +- regex: -triple .*v8m\.base-.* + matchAttrs: [v8m.base, v6m] +- regex: -triple .*v8m\.main-.* + matchAttrs: [v8m.main, v7em, v7m, v6m] +- regex: -triple .*v8\.[0-9]+m\.base-.* + matchAttrs: [v8m.base, v6m] +- regex: -triple .*v8\.[0-9]+m\.main-.* + matchAttrs: [v8.1m.main, v8m.main, v7em, v7m, v6m] +)"; + + std::string O; + llvm::raw_string_ostream OS(O); + Multilib2 M; + ASSERT_TRUE(M.parse(MultilibSpec, OS)); + + EXPECT_EQ((std::pair(true, "/armv6m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=soft", "-march=armv6m"})); + EXPECT_EQ((std::pair(true, "/armv7m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=soft", "-march=armv7m+nofp"})); + EXPECT_EQ((std::pair(true, "/armv7em_soft_nofp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=soft", "-march=armv7em", "-mfpu=none"})); + EXPECT_EQ((std::pair(true, "/armv8m.main_soft_nofp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=soft", "-march=armv8m.main+nofp"})); + EXPECT_EQ((std::pair(true, "/armv8.1m.main_soft_nofp_nomve")), M.select({"--target=arm-none-eabi", "-mfloat-abi=soft", "-march=armv8.1m.main+nofp+nomve"})); + EXPECT_EQ((std::pair(true, "/armv7em_hard_fpv4_sp_d16")), M.select({"--target=arm-none-eabi", "-mfloat-abi=hard", "-march=armv7em", "-mfpu=fpv4-sp-d16"})); + EXPECT_EQ((std::pair(true, "/armv7em_hard_fpv5_d16")), M.select({"--target=arm-none-eabi", "-mfloat-abi=hard", "-march=armv7em", "-mfpu=fpv5-d16"})); + EXPECT_EQ((std::pair(true, "/armv8m.main_hard_fp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=hard", "-march=armv8m.main+fp"})); + EXPECT_EQ((std::pair(true, "/armv8.1m.main_hard_fp")), M.select({"--target=arm-none-eabi", "-mfloat-abi=hard", "-march=armv8.1m.main+fp"})); + EXPECT_EQ((std::pair(true, "/armv8.1m.main_hard_nofp_mve")), M.select({"--target=arm-none-eabi", "-mfloat-abi=hard", "-march=armv8.1m.main+nofp+mve"})); + EXPECT_EQ((std::pair(true, "/armv6m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mcpu=cortex-m0"})); + EXPECT_EQ((std::pair(true, "/armv6m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mcpu=cortex-m0+"})); + EXPECT_EQ((std::pair(true, "/armv6m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mcpu=cortex-m1"})); + EXPECT_EQ((std::pair(true, "/armv7m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mcpu=cortex-m3"})); + EXPECT_EQ((std::pair(true, "/armv7em_hard_fpv4_sp_d16")), M.select({"--target=arm-none-eabihf", "-mcpu=cortex-m4"})); + EXPECT_EQ((std::pair(true, "/armv7em_hard_fpv5_d16")), M.select({"--target=arm-none-eabihf", "-mcpu=cortex-m7"})); + EXPECT_EQ((std::pair(true, "/armv6m_soft_nofp")), M.select({"--target=arm-none-eabi", "-mcpu=cortex-m23"})); + EXPECT_EQ((std::pair(true, "/armv8m.main_hard_fp")), M.select({"--target=arm-none-eabihf", "-mcpu=cortex-m33"})); + EXPECT_EQ((std::pair(true, "/armv8.1m.main_hard_nofp_mve")), M.select({"--target=arm-none-eabihf", "-mcpu=cortex-m55"})); +}