blob: a7298a9a71bfb5cc333a9c36d59db605b513053e [file] [log] [blame]
//===--- WebAssembly.cpp - WebAssembly ToolChain Implementation -*- 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
//
//===----------------------------------------------------------------------===//
#include "WebAssembly.h"
#include "CommonArgs.h"
#include "clang/Basic/Version.h"
#include "clang/Config/config.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Option/ArgList.h"
using namespace clang::driver;
using namespace clang::driver::tools;
using namespace clang::driver::toolchains;
using namespace clang;
using namespace llvm::opt;
/// Following the conventions in https://wiki.debian.org/Multiarch/Tuples,
/// we remove the vendor field to form the multiarch triple.
std::string WebAssembly::getMultiarchTriple(const Driver &D,
const llvm::Triple &TargetTriple,
StringRef SysRoot) const {
return (TargetTriple.getArchName() + "-" +
TargetTriple.getOSAndEnvironmentName()).str();
}
std::string wasm::Linker::getLinkerPath(const ArgList &Args) const {
const ToolChain &ToolChain = getToolChain();
if (const Arg* A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
StringRef UseLinker = A->getValue();
if (!UseLinker.empty()) {
if (llvm::sys::path::is_absolute(UseLinker) &&
llvm::sys::fs::can_execute(UseLinker))
return std::string(UseLinker);
// Accept 'lld', and 'ld' as aliases for the default linker
if (UseLinker != "lld" && UseLinker != "ld")
ToolChain.getDriver().Diag(diag::err_drv_invalid_linker_name)
<< A->getAsString(Args);
}
}
return ToolChain.GetProgramPath(ToolChain.getDefaultLinker());
}
void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output,
const InputInfoList &Inputs,
const ArgList &Args,
const char *LinkingOutput) const {
const ToolChain &ToolChain = getToolChain();
const char *Linker = Args.MakeArgString(getLinkerPath(Args));
ArgStringList CmdArgs;
CmdArgs.push_back("-m");
if (ToolChain.getTriple().isArch64Bit())
CmdArgs.push_back("wasm64");
else
CmdArgs.push_back("wasm32");
if (Args.hasArg(options::OPT_s))
CmdArgs.push_back("--strip-all");
Args.AddAllArgs(CmdArgs, options::OPT_L);
Args.AddAllArgs(CmdArgs, options::OPT_u);
ToolChain.AddFilePathLibArgs(Args, CmdArgs);
const char *Crt1 = "crt1.o";
const char *Entry = NULL;
// If crt1-command.o exists, it supports new-style commands, so use it.
// Otherwise, use the old crt1.o. This is a temporary transition measure.
// Once WASI libc no longer needs to support LLVM versions which lack
// support for new-style command, it can make crt1.o the same as
// crt1-command.o. And once LLVM no longer needs to support WASI libc
// versions before that, it can switch to using crt1-command.o.
if (ToolChain.GetFilePath("crt1-command.o") != "crt1-command.o")
Crt1 = "crt1-command.o";
if (const Arg *A = Args.getLastArg(options::OPT_mexec_model_EQ)) {
StringRef CM = A->getValue();
if (CM == "command") {
// Use default values.
} else if (CM == "reactor") {
Crt1 = "crt1-reactor.o";
Entry = "_initialize";
} else {
ToolChain.getDriver().Diag(diag::err_drv_invalid_argument_to_option)
<< CM << A->getOption().getName();
}
}
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nostartfiles))
CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath(Crt1)));
if (Entry) {
CmdArgs.push_back(Args.MakeArgString("--entry"));
CmdArgs.push_back(Args.MakeArgString(Entry));
}
AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
if (ToolChain.ShouldLinkCXXStdlib(Args))
ToolChain.AddCXXStdlibLibArgs(Args, CmdArgs);
if (Args.hasArg(options::OPT_pthread)) {
CmdArgs.push_back("-lpthread");
CmdArgs.push_back("--shared-memory");
}
CmdArgs.push_back("-lc");
AddRunTimeLibs(ToolChain, ToolChain.getDriver(), CmdArgs, Args);
}
CmdArgs.push_back("-o");
CmdArgs.push_back(Output.getFilename());
C.addCommand(std::make_unique<Command>(JA, *this,
ResponseFileSupport::AtFileCurCP(),
Linker, CmdArgs, Inputs, Output));
// When optimizing, if wasm-opt is available, run it.
if (Arg *A = Args.getLastArg(options::OPT_O_Group)) {
auto WasmOptPath = ToolChain.GetProgramPath("wasm-opt");
if (WasmOptPath != "wasm-opt") {
StringRef OOpt = "s";
if (A->getOption().matches(options::OPT_O4) ||
A->getOption().matches(options::OPT_Ofast))
OOpt = "4";
else if (A->getOption().matches(options::OPT_O0))
OOpt = "0";
else if (A->getOption().matches(options::OPT_O))
OOpt = A->getValue();
if (OOpt != "0") {
const char *WasmOpt = Args.MakeArgString(WasmOptPath);
ArgStringList CmdArgs;
CmdArgs.push_back(Output.getFilename());
CmdArgs.push_back(Args.MakeArgString(llvm::Twine("-O") + OOpt));
CmdArgs.push_back("-o");
CmdArgs.push_back(Output.getFilename());
C.addCommand(std::make_unique<Command>(
JA, *this, ResponseFileSupport::AtFileCurCP(), WasmOpt, CmdArgs,
Inputs, Output));
}
}
}
}
/// Given a base library directory, append path components to form the
/// LTO directory.
static std::string AppendLTOLibDir(const std::string &Dir) {
// The version allows the path to be keyed to the specific version of
// LLVM in used, as the bitcode format is not stable.
return Dir + "/llvm-lto/" LLVM_VERSION_STRING;
}
WebAssembly::WebAssembly(const Driver &D, const llvm::Triple &Triple,
const llvm::opt::ArgList &Args)
: ToolChain(D, Triple, Args) {
assert(Triple.isArch32Bit() != Triple.isArch64Bit());
getProgramPaths().push_back(getDriver().getInstalledDir());
auto SysRoot = getDriver().SysRoot;
if (getTriple().getOS() == llvm::Triple::UnknownOS) {
// Theoretically an "unknown" OS should mean no standard libraries, however
// it could also mean that a custom set of libraries is in use, so just add
// /lib to the search path. Disable multiarch in this case, to discourage
// paths containing "unknown" from acquiring meanings.
getFilePaths().push_back(SysRoot + "/lib");
} else {
const std::string MultiarchTriple =
getMultiarchTriple(getDriver(), Triple, SysRoot);
if (D.isUsingLTO()) {
// For LTO, enable use of lto-enabled sysroot libraries too, if available.
// Note that the directory is keyed to the LLVM revision, as LLVM's
// bitcode format is not stable.
auto Dir = AppendLTOLibDir(SysRoot + "/lib/" + MultiarchTriple);
getFilePaths().push_back(Dir);
}
getFilePaths().push_back(SysRoot + "/lib/" + MultiarchTriple);
}
}
bool WebAssembly::IsMathErrnoDefault() const { return false; }
bool WebAssembly::IsObjCNonFragileABIDefault() const { return true; }
bool WebAssembly::UseObjCMixedDispatch() const { return true; }
bool WebAssembly::isPICDefault() const { return false; }
bool WebAssembly::isPIEDefault(const llvm::opt::ArgList &Args) const {
return false;
}
bool WebAssembly::isPICDefaultForced() const { return false; }
bool WebAssembly::IsIntegratedAssemblerDefault() const { return true; }
bool WebAssembly::hasBlocksRuntime() const { return false; }
// TODO: Support profiling.
bool WebAssembly::SupportsProfiling() const { return false; }
bool WebAssembly::HasNativeLLVMSupport() const { return true; }
void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
ArgStringList &CC1Args,
Action::OffloadKind) const {
if (!DriverArgs.hasFlag(clang::driver::options::OPT_fuse_init_array,
options::OPT_fno_use_init_array, true))
CC1Args.push_back("-fno-use-init-array");
// '-pthread' implies atomics, bulk-memory, mutable-globals, and sign-ext
if (DriverArgs.hasFlag(options::OPT_pthread, options::OPT_no_pthread,
false)) {
if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
<< "-mno-atomics";
if (DriverArgs.hasFlag(options::OPT_mno_bulk_memory,
options::OPT_mbulk_memory, false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
<< "-mno-bulk-memory";
if (DriverArgs.hasFlag(options::OPT_mno_mutable_globals,
options::OPT_mmutable_globals, false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
<< "-mno-mutable-globals";
if (DriverArgs.hasFlag(options::OPT_mno_sign_ext, options::OPT_msign_ext,
false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
<< "-mno-sign-ext";
CC1Args.push_back("-target-feature");
CC1Args.push_back("+atomics");
CC1Args.push_back("-target-feature");
CC1Args.push_back("+bulk-memory");
CC1Args.push_back("-target-feature");
CC1Args.push_back("+mutable-globals");
CC1Args.push_back("-target-feature");
CC1Args.push_back("+sign-ext");
}
if (!DriverArgs.hasFlag(options::OPT_mmutable_globals,
options::OPT_mno_mutable_globals, false)) {
// -fPIC implies +mutable-globals because the PIC ABI used by the linker
// depends on importing and exporting mutable globals.
llvm::Reloc::Model RelocationModel;
unsigned PICLevel;
bool IsPIE;
std::tie(RelocationModel, PICLevel, IsPIE) =
ParsePICArgs(*this, DriverArgs);
if (RelocationModel == llvm::Reloc::PIC_) {
if (DriverArgs.hasFlag(options::OPT_mno_mutable_globals,
options::OPT_mmutable_globals, false)) {
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-fPIC"
<< "-mno-mutable-globals";
}
CC1Args.push_back("-target-feature");
CC1Args.push_back("+mutable-globals");
}
}
if (DriverArgs.getLastArg(options::OPT_fwasm_exceptions)) {
// '-fwasm-exceptions' is not compatible with '-mno-exception-handling'
if (DriverArgs.hasFlag(options::OPT_mno_exception_handing,
options::OPT_mexception_handing, false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-fwasm-exceptions"
<< "-mno-exception-handling";
// '-fwasm-exceptions' is not compatible with
// '-mllvm -enable-emscripten-cxx-exceptions'
for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) {
if (StringRef(A->getValue(0)) == "-enable-emscripten-cxx-exceptions")
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-fwasm-exceptions"
<< "-mllvm -enable-emscripten-cxx-exceptions";
}
// '-fwasm-exceptions' implies exception-handling feature
CC1Args.push_back("-target-feature");
CC1Args.push_back("+exception-handling");
// Backend needs -wasm-enable-eh to enable Wasm EH
CC1Args.push_back("-mllvm");
CC1Args.push_back("-wasm-enable-eh");
}
for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) {
StringRef Opt = A->getValue(0);
if (Opt.startswith("-emscripten-cxx-exceptions-allowed")) {
// '-mllvm -emscripten-cxx-exceptions-allowed' should be used with
// '-mllvm -enable-emscripten-cxx-exceptions'
bool EmEHArgExists = false;
for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) {
if (StringRef(A->getValue(0)) == "-enable-emscripten-cxx-exceptions") {
EmEHArgExists = true;
break;
}
}
if (!EmEHArgExists)
getDriver().Diag(diag::err_drv_argument_only_allowed_with)
<< "-mllvm -emscripten-cxx-exceptions-allowed"
<< "-mllvm -enable-emscripten-cxx-exceptions";
// Prevent functions specified in -emscripten-cxx-exceptions-allowed list
// from being inlined before reaching the wasm backend.
StringRef FuncNamesStr = Opt.split('=').second;
SmallVector<StringRef, 4> FuncNames;
FuncNamesStr.split(FuncNames, ',');
for (auto Name : FuncNames) {
CC1Args.push_back("-mllvm");
CC1Args.push_back(DriverArgs.MakeArgString("--force-attribute=" + Name +
":noinline"));
}
}
if (Opt.startswith("-wasm-enable-sjlj")) {
// '-mllvm -wasm-enable-sjlj' is not compatible with
// '-mno-exception-handling'
if (DriverArgs.hasFlag(options::OPT_mno_exception_handing,
options::OPT_mexception_handing, false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-mllvm -wasm-enable-sjlj"
<< "-mno-exception-handling";
// '-mllvm -wasm-enable-sjlj' is not compatible with
// '-mllvm -enable-emscripten-cxx-exceptions'
// because we don't allow Emscripten EH + Wasm SjLj
for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) {
if (StringRef(A->getValue(0)) == "-enable-emscripten-cxx-exceptions")
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-mllvm -wasm-enable-sjlj"
<< "-mllvm -enable-emscripten-cxx-exceptions";
}
// '-mllvm -wasm-enable-sjlj' is not compatible with
// '-mllvm -enable-emscripten-sjlj'
for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) {
if (StringRef(A->getValue(0)) == "-enable-emscripten-sjlj")
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-mllvm -wasm-enable-sjlj"
<< "-mllvm -enable-emscripten-sjlj";
}
// '-mllvm -wasm-enable-sjlj' implies exception-handling feature
CC1Args.push_back("-target-feature");
CC1Args.push_back("+exception-handling");
// Backend needs '-exception-model=wasm' to use Wasm EH instructions
CC1Args.push_back("-exception-model=wasm");
}
}
}
ToolChain::RuntimeLibType WebAssembly::GetDefaultRuntimeLibType() const {
return ToolChain::RLT_CompilerRT;
}
ToolChain::CXXStdlibType
WebAssembly::GetCXXStdlibType(const ArgList &Args) const {
if (Arg *A = Args.getLastArg(options::OPT_stdlib_EQ)) {
StringRef Value = A->getValue();
if (Value != "libc++")
getDriver().Diag(diag::err_drv_invalid_stdlib_name)
<< A->getAsString(Args);
}
return ToolChain::CST_Libcxx;
}
void WebAssembly::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
ArgStringList &CC1Args) const {
if (DriverArgs.hasArg(clang::driver::options::OPT_nostdinc))
return;
const Driver &D = getDriver();
if (!DriverArgs.hasArg(options::OPT_nobuiltininc)) {
SmallString<128> P(D.ResourceDir);
llvm::sys::path::append(P, "include");
addSystemInclude(DriverArgs, CC1Args, P);
}
if (DriverArgs.hasArg(options::OPT_nostdlibinc))
return;
// Check for configure-time C include directories.
StringRef CIncludeDirs(C_INCLUDE_DIRS);
if (CIncludeDirs != "") {
SmallVector<StringRef, 5> dirs;
CIncludeDirs.split(dirs, ":");
for (StringRef dir : dirs) {
StringRef Prefix =
llvm::sys::path::is_absolute(dir) ? "" : StringRef(D.SysRoot);
addExternCSystemInclude(DriverArgs, CC1Args, Prefix + dir);
}
return;
}
if (getTriple().getOS() != llvm::Triple::UnknownOS) {
const std::string MultiarchTriple =
getMultiarchTriple(D, getTriple(), D.SysRoot);
addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include/" + MultiarchTriple);
}
addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include");
}
void WebAssembly::AddClangCXXStdlibIncludeArgs(const ArgList &DriverArgs,
ArgStringList &CC1Args) const {
if (!DriverArgs.hasArg(options::OPT_nostdlibinc) &&
!DriverArgs.hasArg(options::OPT_nostdincxx)) {
if (getTriple().getOS() != llvm::Triple::UnknownOS) {
const std::string MultiarchTriple =
getMultiarchTriple(getDriver(), getTriple(), getDriver().SysRoot);
addSystemInclude(DriverArgs, CC1Args,
getDriver().SysRoot + "/include/" + MultiarchTriple +
"/c++/v1");
}
addSystemInclude(DriverArgs, CC1Args,
getDriver().SysRoot + "/include/c++/v1");
}
}
void WebAssembly::AddCXXStdlibLibArgs(const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) const {
switch (GetCXXStdlibType(Args)) {
case ToolChain::CST_Libcxx:
CmdArgs.push_back("-lc++");
CmdArgs.push_back("-lc++abi");
break;
case ToolChain::CST_Libstdcxx:
llvm_unreachable("invalid stdlib name");
}
}
SanitizerMask WebAssembly::getSupportedSanitizers() const {
SanitizerMask Res = ToolChain::getSupportedSanitizers();
if (getTriple().isOSEmscripten()) {
Res |= SanitizerKind::Vptr | SanitizerKind::Leak | SanitizerKind::Address;
}
return Res;
}
Tool *WebAssembly::buildLinker() const {
return new tools::wasm::Linker(*this);
}