diff --git a/cmake/modules/Hermes.cmake b/cmake/modules/Hermes.cmake index 739f6293a94..d8532200b3e 100644 --- a/cmake/modules/Hermes.cmake +++ b/cmake/modules/Hermes.cmake @@ -57,6 +57,8 @@ else (WIN32) else () set(LLVM_HAVE_LINK_VERSION_SCRIPT 1) endif () + elseif (WASI) + # Do nothing for WASI. It will be detected at compile time. else (FUCHSIA OR UNIX) MESSAGE(SEND_ERROR "Unable to determine platform") endif (FUCHSIA OR UNIX) diff --git a/external/dtoa/locks.cpp b/external/dtoa/locks.cpp index 9f74de8f65d..7a6968b655e 100644 --- a/external/dtoa/locks.cpp +++ b/external/dtoa/locks.cpp @@ -1,3 +1,6 @@ +#include "llvh/Config/llvm-config.h" + +#if LLVM_ENABLE_THREADS #include #include @@ -15,3 +18,10 @@ extern "C" void FREE_DTOA_LOCK(int n) { assert(n == 1 && "only dtoa lock 1 is supported"); mutex.unlock(); } +#else +extern "C" void ACQUIRE_DTOA_LOCK(int) { +} + +extern "C" void FREE_DTOA_LOCK(int) { +} +#endif diff --git a/external/llvh/CMakeLists.txt b/external/llvh/CMakeLists.txt index 5944b9c0e84..594426ab3f7 100644 --- a/external/llvh/CMakeLists.txt +++ b/external/llvh/CMakeLists.txt @@ -11,7 +11,9 @@ set(PACKAGE_NAME "LLVH") set(PACKAGE_STRING "LLVH 8.0.0svn") set(PACKAGE_VERSION "8.0.0svn") -set(LLVM_ENABLE_THREADS 1) +if ("${LLVM_ENABLE_THREADS}" STREQUAL "") + set(LLVM_ENABLE_THREADS 1) +endif () if (LLVH_REVERSE_ITERATION) set(LLVM_ENABLE_REVERSE_ITERATION 1) endif () diff --git a/external/llvh/cmake/config-ix.cmake b/external/llvh/cmake/config-ix.cmake index efefa216d9d..b67ec2b70b7 100644 --- a/external/llvh/cmake/config-ix.cmake +++ b/external/llvh/cmake/config-ix.cmake @@ -213,6 +213,7 @@ check_symbol_exists(strerror string.h HAVE_STRERROR) check_symbol_exists(strerror_r string.h HAVE_STRERROR_R) check_symbol_exists(strerror_s string.h HAVE_DECL_STRERROR_S) check_symbol_exists(setenv stdlib.h HAVE_SETENV) +check_symbol_exists(tzset time.h HAVE_TZSET) if( PURE_WINDOWS ) check_symbol_exists(_chsize_s io.h HAVE__CHSIZE_S) diff --git a/external/llvh/include/llvh/Config/config.h.cmake b/external/llvh/include/llvh/Config/config.h.cmake index 7502f6c57f2..52ca9ec2e95 100644 --- a/external/llvh/include/llvh/Config/config.h.cmake +++ b/external/llvh/include/llvh/Config/config.h.cmake @@ -166,6 +166,9 @@ /* Define to 1 if you have the `setenv' function. */ #cmakedefine HAVE_SETENV ${HAVE_SETENV} +/* Define to 1 if you have the `tzset' function. */ +#cmakedefine HAVE_TZSET ${HAVE_TZSET} + /* Define to 1 if you have the `sched_getaffinity' function. */ #cmakedefine HAVE_SCHED_GETAFFINITY ${HAVE_SCHED_GETAFFINITY} diff --git a/external/llvh/include/llvh/Support/Atomic.h b/external/llvh/include/llvh/Support/Atomic.h index e8c6d5c6b19..0376708eb4f 100644 --- a/external/llvh/include/llvh/Support/Atomic.h +++ b/external/llvh/include/llvh/Support/Atomic.h @@ -40,4 +40,20 @@ namespace llvh { } } +#ifdef __GNUC__ + +namespace llvh::sys { + inline void MemoryFence() { + __sync_synchronize(); + } + inline cas_flag CompareAndSwap(volatile cas_flag* ptr, + cas_flag new_value, + cas_flag old_value) { + return __sync_val_compare_and_swap(ptr, old_value, new_value); + } + +} + +#endif + #endif diff --git a/external/llvh/include/llvh/Support/FileSystem.h b/external/llvh/include/llvh/Support/FileSystem.h index 8d43963e608..606103fca6b 100644 --- a/external/llvh/include/llvh/Support/FileSystem.h +++ b/external/llvh/include/llvh/Support/FileSystem.h @@ -157,7 +157,7 @@ class UniqueID { /// represents the information provided by Windows FileFirstFile/FindNextFile. class basic_file_status { protected: - #if defined(LLVM_ON_UNIX) + #if defined(LLVM_ON_UNIX) || defined(__wasi__) time_t fs_st_atime = 0; time_t fs_st_mtime = 0; uid_t fs_st_uid = 0; @@ -179,7 +179,7 @@ class basic_file_status { explicit basic_file_status(file_type Type) : Type(Type) {} - #if defined(LLVM_ON_UNIX) + #if defined(LLVM_ON_UNIX) || defined(__wasi__) basic_file_status(file_type Type, perms Perms, time_t ATime, time_t MTime, uid_t UID, gid_t GID, off_t Size) : fs_st_atime(ATime), fs_st_mtime(MTime), fs_st_uid(UID), fs_st_gid(GID), @@ -202,7 +202,7 @@ class basic_file_status { TimePoint<> getLastAccessedTime() const; TimePoint<> getLastModificationTime() const; - #if defined(LLVM_ON_UNIX) + #if defined(LLVM_ON_UNIX) || defined(__wasi__) uint32_t getUser() const { return fs_st_uid; } uint32_t getGroup() const { return fs_st_gid; } uint64_t getSize() const { return fs_st_size; } @@ -229,7 +229,7 @@ class basic_file_status { class file_status : public basic_file_status { friend bool equivalent(file_status A, file_status B); - #if defined(LLVM_ON_UNIX) + #if defined(LLVM_ON_UNIX) || defined(__wasi__) dev_t fs_st_dev = 0; nlink_t fs_st_nlinks = 0; ino_t fs_st_ino = 0; @@ -245,7 +245,7 @@ class file_status : public basic_file_status { explicit file_status(file_type Type) : basic_file_status(Type) {} - #if defined(LLVM_ON_UNIX) + #if defined(LLVM_ON_UNIX) || defined(__wasi__) file_status(file_type Type, perms Perms, dev_t Dev, nlink_t Links, ino_t Ino, time_t ATime, time_t MTime, uid_t UID, gid_t GID, off_t Size) : basic_file_status(Type, Perms, ATime, MTime, UID, GID, Size), diff --git a/external/llvh/include/llvh/Support/Program.h b/external/llvh/include/llvh/Support/Program.h index d68225bbee3..5e6a338007e 100644 --- a/external/llvh/include/llvh/Support/Program.h +++ b/external/llvh/include/llvh/Support/Program.h @@ -26,7 +26,7 @@ namespace sys { /// This is the OS-specific separator for PATH like environment variables: // a colon on Unix or a semicolon on Windows. -#if defined(LLVM_ON_UNIX) +#if defined(LLVM_ON_UNIX) || defined(__wasi__) const char EnvPathSeparator = ':'; #elif defined (_WIN32) const char EnvPathSeparator = ';'; diff --git a/external/llvh/lib/Support/Host.cpp b/external/llvh/lib/Support/Host.cpp index eff32e235d9..cb8dc992bbf 100644 --- a/external/llvh/lib/Support/Host.cpp +++ b/external/llvh/lib/Support/Host.cpp @@ -33,6 +33,9 @@ #ifdef _WIN32 #include "Windows/Host.inc" #endif +#ifdef __wasi__ +#include "Wasi/Host.inc" +#endif #ifdef _MSC_VER #include #endif diff --git a/external/llvh/lib/Support/Path.cpp b/external/llvh/lib/Support/Path.cpp index 43988d3ed5e..8a44d49ad32 100644 --- a/external/llvh/lib/Support/Path.cpp +++ b/external/llvh/lib/Support/Path.cpp @@ -1113,6 +1113,9 @@ ErrorOr getPermissions(const Twine &Path) { #if defined(_WIN32) #include "Windows/Path.inc" #endif +#ifdef __wasi__ +#include "Wasi/Path.inc" +#endif namespace llvh { namespace sys { diff --git a/external/llvh/lib/Support/Process.cpp b/external/llvh/lib/Support/Process.cpp index bc6bf24c646..36811bcd1c2 100644 --- a/external/llvh/lib/Support/Process.cpp +++ b/external/llvh/lib/Support/Process.cpp @@ -96,3 +96,6 @@ bool Process::AreCoreFilesPrevented() { return coreFilesPrevented; } #ifdef _WIN32 #include "Windows/Process.inc" #endif +#ifdef __wasi__ +#include "Wasi/Process.inc" +#endif diff --git a/external/llvh/lib/Support/Program.cpp b/external/llvh/lib/Support/Program.cpp index b5bf65883e0..519e9437da0 100644 --- a/external/llvh/lib/Support/Program.cpp +++ b/external/llvh/lib/Support/Program.cpp @@ -81,3 +81,6 @@ bool sys::commandLineFitsWithinSystemLimits(StringRef Program, #ifdef _WIN32 #include "Windows/Program.inc" #endif +#ifdef __wasi__ +#include "Wasi/Program.inc" +#endif diff --git a/external/llvh/lib/Support/Signals.cpp b/external/llvh/lib/Support/Signals.cpp index 483d0c95c53..ac5bad13dc1 100644 --- a/external/llvh/lib/Support/Signals.cpp +++ b/external/llvh/lib/Support/Signals.cpp @@ -246,3 +246,6 @@ static bool printSymbolizedStackTrace(StringRef Argv0, void **StackTrace, #ifdef _WIN32 #include "Windows/Signals.inc" #endif +#ifdef __wasi__ +#include "Wasi/Signals.inc" +#endif diff --git a/external/llvh/lib/Support/Wasi/Host.inc b/external/llvh/lib/Support/Wasi/Host.inc new file mode 100644 index 00000000000..4cc81d714c8 --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Host.inc @@ -0,0 +1,68 @@ +//===- llvm/Support/Unix/Host.inc -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the UNIX Host support. +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +//=== WARNING: Implementation here must contain only generic UNIX code that +//=== is guaranteed to work on *all* UNIX variants. +//===----------------------------------------------------------------------===// + +#include "Unix.h" +#include "llvh/ADT/StringRef.h" +#include "llvh/Config/config.h" +#include +#include +#include + +using namespace llvh; + +static std::string getOSVersion() { + struct utsname info; + + if (uname(&info)) + return ""; + + return info.release; +} + +static std::string updateTripleOSVersion(std::string TargetTripleString) { + // On darwin, we want to update the version to match that of the target. + std::string::size_type DarwinDashIdx = TargetTripleString.find("-darwin"); + if (DarwinDashIdx != std::string::npos) { + TargetTripleString.resize(DarwinDashIdx + strlen("-darwin")); + TargetTripleString += getOSVersion(); + return TargetTripleString; + } + std::string::size_type MacOSDashIdx = TargetTripleString.find("-macos"); + if (MacOSDashIdx != std::string::npos) { + TargetTripleString.resize(MacOSDashIdx); + // Reset the OS to darwin as the OS version from `uname` doesn't use the + // macOS version scheme. + TargetTripleString += "-darwin"; + TargetTripleString += getOSVersion(); + } + return TargetTripleString; +} + +std::string sys::getDefaultTargetTriple() { + std::string TargetTripleString = + updateTripleOSVersion(LLVM_DEFAULT_TARGET_TRIPLE); + + // Override the default target with an environment variable named by + // LLVM_TARGET_TRIPLE_ENV. +#if defined(LLVM_TARGET_TRIPLE_ENV) + if (const char *EnvTriple = std::getenv(LLVM_TARGET_TRIPLE_ENV)) + TargetTripleString = EnvTriple; +#endif + + return TargetTripleString; +} diff --git a/external/llvh/lib/Support/Wasi/Path.inc b/external/llvh/lib/Support/Wasi/Path.inc new file mode 100644 index 00000000000..a15cb5d3889 --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Path.inc @@ -0,0 +1,903 @@ +//===- llvm/Support/Unix/Path.inc - Unix Path Implementation ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Unix specific implementation of the Path API. +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +//=== WARNING: Implementation here must contain only generic UNIX code that +//=== is guaranteed to work on *all* UNIX variants. +//===----------------------------------------------------------------------===// + +#include "Unix.h" +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_MMAN_H +#include +#endif + +#include + +#ifdef __APPLE__ +#include +#include +#endif + +// Both stdio.h and cstdio are included via different paths and +// stdcxx's cstdio doesn't include stdio.h, so it doesn't #undef the macros +// either. +#undef ferror +#undef feof + +// For GNU Hurd +#if defined(__GNU__) && !defined(PATH_MAX) +# define PATH_MAX 4096 +#endif + +#include +#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__FreeBSD__) && \ + !defined(__linux__) +#include +#define STATVFS statvfs +#define FSTATVFS fstatvfs +#define STATVFS_F_FRSIZE(vfs) vfs.f_frsize +#else +#if defined(__OpenBSD__) || defined(__FreeBSD__) +#include +#include +#elif defined(__linux__) +#if defined(HAVE_LINUX_MAGIC_H) +#include +#else +#if defined(HAVE_LINUX_NFS_FS_H) +#include +#endif +#if defined(HAVE_LINUX_SMB_H) +#include +#endif +#endif +#include +#else +#include +#endif +#define STATVFS statfs +#define FSTATVFS fstatfs +#define STATVFS_F_FRSIZE(vfs) static_cast(vfs.f_bsize) +#endif + +#if defined(__NetBSD__) || defined(__EMSCRIPTEN__) +#define STATVFS_F_FLAG(vfs) (vfs).f_flag +#else +#define STATVFS_F_FLAG(vfs) (vfs).f_flags +#endif + +using namespace llvh; + +namespace llvh { +namespace sys { +namespace fs { + +const file_t kInvalidFile = -1; + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__minix) || defined(__FreeBSD_kernel__) || defined(__linux__) || \ + defined(__CYGWIN__) || defined(__DragonFly__) || defined(_AIX) +static int +test_dir(char ret[PATH_MAX], const char *dir, const char *bin) +{ + struct stat sb; + char fullpath[PATH_MAX]; + + snprintf(fullpath, PATH_MAX, "%s/%s", dir, bin); + if (!realpath(fullpath, ret)) + return 1; + if (stat(fullpath, &sb) != 0) + return 1; + + return 0; +} + +static char * +getprogpath(char ret[PATH_MAX], const char *bin) +{ + char *pv, *s, *t; + + /* First approach: absolute path. */ + if (bin[0] == '/') { + if (test_dir(ret, "/", bin) == 0) + return ret; + return nullptr; + } + + /* Second approach: relative path. */ + if (strchr(bin, '/')) { + char cwd[PATH_MAX]; + if (!getcwd(cwd, PATH_MAX)) + return nullptr; + if (test_dir(ret, cwd, bin) == 0) + return ret; + return nullptr; + } + + /* Third approach: $PATH */ + if ((pv = getenv("PATH")) == nullptr) + return nullptr; + s = pv = strdup(pv); + if (!pv) + return nullptr; + while ((t = strsep(&s, ":")) != nullptr) { + if (test_dir(ret, t, bin) == 0) { + free(pv); + return ret; + } + } + free(pv); + return nullptr; +} +#endif // __FreeBSD__ || __NetBSD__ || __FreeBSD_kernel__ + +/// GetMainExecutable - Return the path to the main executable, given the +/// value of argv[0] from program startup. +std::string getMainExecutable(const char *argv0, void *MainAddr) { + report_fatal_error("getMainExecutable() is not implemented on WASI"); +} + +TimePoint<> basic_file_status::getLastAccessedTime() const { + return toTimePoint(fs_st_atime); +} + +TimePoint<> basic_file_status::getLastModificationTime() const { + return toTimePoint(fs_st_mtime); +} + +UniqueID file_status::getUniqueID() const { + return UniqueID(fs_st_dev, fs_st_ino); +} + +uint32_t file_status::getLinkCount() const { + return fs_st_nlinks; +} + +ErrorOr disk_space(const Twine &Path) { + struct STATVFS Vfs; + if (::STATVFS(Path.str().c_str(), &Vfs)) + return std::error_code(errno, std::generic_category()); + auto FrSize = STATVFS_F_FRSIZE(Vfs); + space_info SpaceInfo; + SpaceInfo.capacity = static_cast(Vfs.f_blocks) * FrSize; + SpaceInfo.free = static_cast(Vfs.f_bfree) * FrSize; + SpaceInfo.available = static_cast(Vfs.f_bavail) * FrSize; + return SpaceInfo; +} + +std::error_code current_path(SmallVectorImpl &result) { + result.clear(); + + const char *pwd = ::getenv("PWD"); + llvh::sys::fs::file_status PWDStatus, DotStatus; + if (pwd && llvh::sys::path::is_absolute(pwd) && + !llvh::sys::fs::status(pwd, PWDStatus) && + !llvh::sys::fs::status(".", DotStatus) && + PWDStatus.getUniqueID() == DotStatus.getUniqueID()) { + result.append(pwd, pwd + strlen(pwd)); + return std::error_code(); + } + +#ifdef MAXPATHLEN + result.reserve(MAXPATHLEN); +#else +// For GNU Hurd + result.reserve(1024); +#endif + + while (true) { + if (::getcwd(result.data(), result.capacity()) == nullptr) { + // See if there was a real error. + if (errno != ENOMEM) + return std::error_code(errno, std::generic_category()); + // Otherwise there just wasn't enough space. + result.reserve(result.capacity() * 2); + } else + break; + } + + result.set_size(strlen(result.data())); + return std::error_code(); +} + +std::error_code set_current_path(const Twine &path) { + SmallString<128> path_storage; + StringRef p = path.toNullTerminatedStringRef(path_storage); + + if (::chdir(p.begin()) == -1) + return std::error_code(errno, std::generic_category()); + + return std::error_code(); +} + +std::error_code create_directory(const Twine &path, bool IgnoreExisting, + perms Perms) { + SmallString<128> path_storage; + StringRef p = path.toNullTerminatedStringRef(path_storage); + + if (::mkdir(p.begin(), Perms) == -1) { + if (errno != EEXIST || !IgnoreExisting) + return std::error_code(errno, std::generic_category()); + } + + return std::error_code(); +} + +// Note that we are using symbolic link because hard links are not supported by +// all filesystems (SMB doesn't). +std::error_code create_link(const Twine &to, const Twine &from) { + // Get arguments. + SmallString<128> from_storage; + SmallString<128> to_storage; + StringRef f = from.toNullTerminatedStringRef(from_storage); + StringRef t = to.toNullTerminatedStringRef(to_storage); + + if (::symlink(t.begin(), f.begin()) == -1) + return std::error_code(errno, std::generic_category()); + + return std::error_code(); +} + +std::error_code create_hard_link(const Twine &to, const Twine &from) { + // Get arguments. + SmallString<128> from_storage; + SmallString<128> to_storage; + StringRef f = from.toNullTerminatedStringRef(from_storage); + StringRef t = to.toNullTerminatedStringRef(to_storage); + + if (::link(t.begin(), f.begin()) == -1) + return std::error_code(errno, std::generic_category()); + + return std::error_code(); +} + +std::error_code remove(const Twine &path, bool IgnoreNonExisting) { + SmallString<128> path_storage; + StringRef p = path.toNullTerminatedStringRef(path_storage); + + struct stat buf; + if (lstat(p.begin(), &buf) != 0) { + if (errno != ENOENT || !IgnoreNonExisting) + return std::error_code(errno, std::generic_category()); + return std::error_code(); + } + + // Note: this check catches strange situations. In all cases, LLVM should + // only be involved in the creation and deletion of regular files. This + // check ensures that what we're trying to erase is a regular file. It + // effectively prevents LLVM from erasing things like /dev/null, any block + // special file, or other things that aren't "regular" files. + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode) && !S_ISLNK(buf.st_mode)) + return make_error_code(errc::operation_not_permitted); + + if (::remove(p.begin()) == -1) { + if (errno != ENOENT || !IgnoreNonExisting) + return std::error_code(errno, std::generic_category()); + } + + return std::error_code(); +} + +static bool is_local_impl(struct STATVFS &Vfs) { +#if defined(__linux__) +#ifndef NFS_SUPER_MAGIC +#define NFS_SUPER_MAGIC 0x6969 +#endif +#ifndef SMB_SUPER_MAGIC +#define SMB_SUPER_MAGIC 0x517B +#endif +#ifndef CIFS_MAGIC_NUMBER +#define CIFS_MAGIC_NUMBER 0xFF534D42 +#endif + switch ((uint32_t)Vfs.f_type) { + case NFS_SUPER_MAGIC: + case SMB_SUPER_MAGIC: + case CIFS_MAGIC_NUMBER: + return false; + default: + return true; + } +#elif defined(__CYGWIN__) + // Cygwin doesn't expose this information; would need to use Win32 API. + return false; +#elif defined(__Fuchsia__) + // Fuchsia doesn't yet support remote filesystem mounts. + return true; +#elif defined(__HAIKU__) + // Haiku doesn't expose this information. + return false; +#elif defined(__sun) + // statvfs::f_basetype contains a null-terminated FSType name of the mounted target + StringRef fstype(Vfs.f_basetype); + // NFS is the only non-local fstype?? + return !fstype.equals("nfs"); +#elif defined(__wasm__) + return false; +#else + return !!(STATVFS_F_FLAG(Vfs) & MNT_LOCAL); +#endif +} + +std::error_code is_local(const Twine &Path, bool &Result) { + struct STATVFS Vfs; + if (::STATVFS(Path.str().c_str(), &Vfs)) + return std::error_code(errno, std::generic_category()); + + Result = is_local_impl(Vfs); + return std::error_code(); +} + +std::error_code is_local(int FD, bool &Result) { + struct STATVFS Vfs; + if (::FSTATVFS(FD, &Vfs)) + return std::error_code(errno, std::generic_category()); + + Result = is_local_impl(Vfs); + return std::error_code(); +} + +std::error_code rename(const Twine &from, const Twine &to) { + // Get arguments. + SmallString<128> from_storage; + SmallString<128> to_storage; + StringRef f = from.toNullTerminatedStringRef(from_storage); + StringRef t = to.toNullTerminatedStringRef(to_storage); + + if (::rename(f.begin(), t.begin()) == -1) + return std::error_code(errno, std::generic_category()); + + return std::error_code(); +} + +std::error_code resize_file(int FD, uint64_t Size) { +#if defined(HAVE_POSIX_FALLOCATE) + // If we have posix_fallocate use it. Unlike ftruncate it always allocates + // space, so we get an error if the disk is full. + if (int Err = ::posix_fallocate(FD, 0, Size)) { + if (Err != EINVAL && Err != EOPNOTSUPP) + return std::error_code(Err, std::generic_category()); + } +#endif + // Use ftruncate as a fallback. It may or may not allocate space. At least on + // OS X with HFS+ it does. + if (::ftruncate(FD, Size) == -1) + return std::error_code(errno, std::generic_category()); + + return std::error_code(); +} + +static int convertAccessMode(AccessMode Mode) { + switch (Mode) { + case AccessMode::Exist: + return F_OK; + case AccessMode::Write: + return W_OK; + case AccessMode::Execute: + return R_OK | X_OK; // scripts also need R_OK. + } + llvm_unreachable("invalid enum"); +} + +std::error_code access(const Twine &Path, AccessMode Mode) { + SmallString<128> PathStorage; + StringRef P = Path.toNullTerminatedStringRef(PathStorage); + + if (::access(P.begin(), convertAccessMode(Mode)) == -1) + return std::error_code(errno, std::generic_category()); + + if (Mode == AccessMode::Execute) { + // Don't say that directories are executable. + struct stat buf; + if (0 != stat(P.begin(), &buf)) + return errc::permission_denied; + if (!S_ISREG(buf.st_mode)) + return errc::permission_denied; + } + + return std::error_code(); +} + +bool can_execute(const Twine &Path) { + return !access(Path, AccessMode::Execute); +} + +bool equivalent(file_status A, file_status B) { + assert(status_known(A) && status_known(B)); + return A.fs_st_dev == B.fs_st_dev && + A.fs_st_ino == B.fs_st_ino; +} + +std::error_code equivalent(const Twine &A, const Twine &B, bool &result) { + file_status fsA, fsB; + if (std::error_code ec = status(A, fsA)) + return ec; + if (std::error_code ec = status(B, fsB)) + return ec; + result = equivalent(fsA, fsB); + return std::error_code(); +} + +static void expandTildeExpr(SmallVectorImpl &Path) { +} + +static file_type typeForMode(mode_t Mode) { + if (S_ISDIR(Mode)) + return file_type::directory_file; + else if (S_ISREG(Mode)) + return file_type::regular_file; + else if (S_ISBLK(Mode)) + return file_type::block_file; + else if (S_ISCHR(Mode)) + return file_type::character_file; + else if (S_ISFIFO(Mode)) + return file_type::fifo_file; + else if (S_ISSOCK(Mode)) + return file_type::socket_file; + else if (S_ISLNK(Mode)) + return file_type::symlink_file; + return file_type::type_unknown; +} + +static std::error_code fillStatus(int StatRet, const struct stat &Status, + file_status &Result) { + if (StatRet != 0) { + std::error_code EC(errno, std::generic_category()); + if (EC == errc::no_such_file_or_directory) + Result = file_status(file_type::file_not_found); + else + Result = file_status(file_type::status_error); + return EC; + } + + perms Perms = static_cast(Status.st_mode) & all_perms; + Result = file_status(typeForMode(Status.st_mode), Perms, Status.st_dev, + Status.st_nlink, Status.st_ino, Status.st_atime, + Status.st_mtime, Status.st_uid, Status.st_gid, + Status.st_size); + + return std::error_code(); +} + +std::error_code status(const Twine &Path, file_status &Result, bool Follow) { + SmallString<128> PathStorage; + StringRef P = Path.toNullTerminatedStringRef(PathStorage); + + struct stat Status; + int StatRet = (Follow ? ::stat : ::lstat)(P.begin(), &Status); + return fillStatus(StatRet, Status, Result); +} + +std::error_code status(int FD, file_status &Result) { + struct stat Status; + int StatRet = ::fstat(FD, &Status); + return fillStatus(StatRet, Status, Result); +} + +std::error_code setPermissions(const Twine &Path, perms Permissions) { + SmallString<128> PathStorage; + StringRef P = Path.toNullTerminatedStringRef(PathStorage); + + if (::chmod(P.begin(), Permissions)) + return std::error_code(errno, std::generic_category()); + return std::error_code(); +} + +std::error_code setLastAccessAndModificationTime(int FD, TimePoint<> AccessTime, + TimePoint<> ModificationTime) { +#if defined(HAVE_FUTIMENS) + timespec Times[2]; + Times[0] = sys::toTimeSpec(AccessTime); + Times[1] = sys::toTimeSpec(ModificationTime); + if (::futimens(FD, Times)) + return std::error_code(errno, std::generic_category()); + return std::error_code(); +#elif defined(HAVE_FUTIMES) + timeval Times[2]; + Times[0] = sys::toTimeVal( + std::chrono::time_point_cast(AccessTime)); + Times[1] = + sys::toTimeVal(std::chrono::time_point_cast( + ModificationTime)); + if (::futimes(FD, Times)) + return std::error_code(errno, std::generic_category()); + return std::error_code(); +#else +#warning Missing futimes() and futimens() + return make_error_code(errc::function_not_supported); +#endif +} + +std::error_code mapped_file_region::init(int FD, uint64_t Offset, + mapmode Mode) { + assert(Size != 0); + return std::error_code(ENOSYS, std::generic_category()); +} + +mapped_file_region::mapped_file_region(int fd, mapmode mode, size_t length, + uint64_t offset, std::error_code &ec) + : Size(length), Mapping(), Mode(mode) { + (void)Mode; + ec = init(fd, offset, mode); + if (ec) + Mapping = nullptr; +} + +mapped_file_region::~mapped_file_region() { + assert(!Mapping && "Invalid state, since mmap() is not supported"); +} + +size_t mapped_file_region::size() const { + assert(Mapping && "Mapping failed but used anyway!"); + return Size; +} + +char *mapped_file_region::data() const { + assert(Mapping && "Mapping failed but used anyway!"); + return reinterpret_cast(Mapping); +} + +const char *mapped_file_region::const_data() const { + assert(Mapping && "Mapping failed but used anyway!"); + return reinterpret_cast(Mapping); +} + +int mapped_file_region::alignment() { + return Process::getPageSize(); +} + +std::error_code detail::directory_iterator_construct(detail::DirIterState &it, + StringRef path, + bool follow_symlinks) { + SmallString<128> path_null(path); + DIR *directory = ::opendir(path_null.c_str()); + if (!directory) + return std::error_code(errno, std::generic_category()); + + it.IterationHandle = reinterpret_cast(directory); + // Add something for replace_filename to replace. + path::append(path_null, "."); + it.CurrentEntry = directory_entry(path_null.str(), follow_symlinks); + return directory_iterator_increment(it); +} + +std::error_code detail::directory_iterator_destruct(detail::DirIterState &it) { + if (it.IterationHandle) + ::closedir(reinterpret_cast(it.IterationHandle)); + it.IterationHandle = 0; + it.CurrentEntry = directory_entry(); + return std::error_code(); +} + +static file_type direntType(dirent* Entry) { + // Most platforms provide the file type in the dirent: Linux/BSD/Mac. + // The DTTOIF macro lets us reuse our status -> type conversion. +#if defined(_DIRENT_HAVE_D_TYPE) && defined(DTTOIF) + return typeForMode(DTTOIF(Entry->d_type)); +#else + // Other platforms such as Solaris require a stat() to get the type. + return file_type::type_unknown; +#endif +} + +std::error_code detail::directory_iterator_increment(detail::DirIterState &It) { + errno = 0; + dirent *CurDir = ::readdir(reinterpret_cast(It.IterationHandle)); + if (CurDir == nullptr && errno != 0) { + return std::error_code(errno, std::generic_category()); + } else if (CurDir != nullptr) { + StringRef Name(CurDir->d_name); + if ((Name.size() == 1 && Name[0] == '.') || + (Name.size() == 2 && Name[0] == '.' && Name[1] == '.')) + return directory_iterator_increment(It); + It.CurrentEntry.replace_filename(Name, direntType(CurDir)); + } else + return directory_iterator_destruct(It); + + return std::error_code(); +} + +ErrorOr directory_entry::status() const { + file_status s; + if (auto EC = fs::status(Path, s, FollowSymlinks)) + return EC; + return s; +} + +#if !defined(F_GETPATH) +static bool hasProcSelfFD() { + // If we have a /proc filesystem mounted, we can quickly establish the + // real name of the file with readlink + static const bool Result = (::access("/proc/self/fd", R_OK) == 0); + return Result; +} +#endif + +static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags, + FileAccess Access) { + int Result = 0; + if (Access == FA_Read) + Result |= O_RDONLY; + else if (Access == FA_Write) + Result |= O_WRONLY; + else if (Access == (FA_Read | FA_Write)) + Result |= O_RDWR; + + // This is for compatibility with old code that assumed F_Append implied + // would open an existing file. See Windows/Path.inc for a longer comment. + if (Flags & F_Append) + Disp = CD_OpenAlways; + + if (Disp == CD_CreateNew) { + Result |= O_CREAT; // Create if it doesn't exist. + Result |= O_EXCL; // Fail if it does. + } else if (Disp == CD_CreateAlways) { + Result |= O_CREAT; // Create if it doesn't exist. + Result |= O_TRUNC; // Truncate if it does. + } else if (Disp == CD_OpenAlways) { + Result |= O_CREAT; // Create if it doesn't exist. + } else if (Disp == CD_OpenExisting) { + // Nothing special, just don't add O_CREAT and we get these semantics. + } + + if (Flags & F_Append) + Result |= O_APPEND; + +#ifdef O_CLOEXEC + if (!(Flags & OF_ChildInherit)) + Result |= O_CLOEXEC; +#endif + + return Result; +} + +std::error_code openFile(const Twine &Name, int &ResultFD, + CreationDisposition Disp, FileAccess Access, + OpenFlags Flags, unsigned Mode) { + int OpenFlags = nativeOpenFlags(Disp, Flags, Access); + + SmallString<128> Storage; + StringRef P = Name.toNullTerminatedStringRef(Storage); + // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal + // when open is overloaded, such as in Bionic. + auto Open = [&]() { return ::open(P.begin(), OpenFlags, Mode); }; + if ((ResultFD = sys::RetryAfterSignal(-1, Open)) < 0) + return std::error_code(errno, std::generic_category()); +#ifndef O_CLOEXEC + if (!(Flags & OF_ChildInherit)) { + int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC); + (void)r; + assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed"); + } +#endif + return std::error_code(); +} + +Expected openNativeFile(const Twine &Name, CreationDisposition Disp, + FileAccess Access, OpenFlags Flags, + unsigned Mode) { + + int FD; + std::error_code EC = openFile(Name, FD, Disp, Access, Flags, Mode); + if (EC) + return errorCodeToError(EC); + return FD; +} + +std::error_code openFileForRead(const Twine &Name, int &ResultFD, + OpenFlags Flags, + SmallVectorImpl *RealPath) { + std::error_code EC = + openFile(Name, ResultFD, CD_OpenExisting, FA_Read, Flags, 0666); + if (EC) + return EC; + + // Attempt to get the real name of the file, if the user asked + if(!RealPath) + return std::error_code(); + RealPath->clear(); +#if defined(F_GETPATH) + // When F_GETPATH is availble, it is the quickest way to get + // the real path name. + char Buffer[MAXPATHLEN]; + if (::fcntl(ResultFD, F_GETPATH, Buffer) != -1) + RealPath->append(Buffer, Buffer + strlen(Buffer)); +#else + char Buffer[PATH_MAX]; + if (hasProcSelfFD()) { + char ProcPath[64]; + snprintf(ProcPath, sizeof(ProcPath), "/proc/self/fd/%d", ResultFD); + ssize_t CharCount = ::readlink(ProcPath, Buffer, sizeof(Buffer)); + if (CharCount > 0) + RealPath->append(Buffer, Buffer + CharCount); + } else { + SmallString<128> Storage; + StringRef P = Name.toNullTerminatedStringRef(Storage); + + // Use ::realpath to get the real path name + if (::realpath(P.begin(), Buffer) != nullptr) + RealPath->append(Buffer, Buffer + strlen(Buffer)); + } +#endif + return std::error_code(); +} + +Expected openNativeFileForRead(const Twine &Name, OpenFlags Flags, + SmallVectorImpl *RealPath) { + file_t ResultFD; + std::error_code EC = openFileForRead(Name, ResultFD, Flags, RealPath); + if (EC) + return errorCodeToError(EC); + return ResultFD; +} + +void closeFile(file_t &F) { + ::close(F); + F = kInvalidFile; +} + +template +static std::error_code remove_directories_impl(const T &Entry, + bool IgnoreErrors) { + std::error_code EC; + directory_iterator Begin(Entry, EC, false); + directory_iterator End; + while (Begin != End) { + auto &Item = *Begin; + ErrorOr st = Item.status(); + if (!st && !IgnoreErrors) + return st.getError(); + + if (is_directory(*st)) { + EC = remove_directories_impl(Item, IgnoreErrors); + if (EC && !IgnoreErrors) + return EC; + } + + EC = fs::remove(Item.path(), true); + if (EC && !IgnoreErrors) + return EC; + + Begin.increment(EC); + if (EC && !IgnoreErrors) + return EC; + } + return std::error_code(); +} + +std::error_code remove_directories(const Twine &path, bool IgnoreErrors) { + auto EC = remove_directories_impl(path, IgnoreErrors); + if (EC && !IgnoreErrors) + return EC; + EC = fs::remove(path, true); + if (EC && !IgnoreErrors) + return EC; + return std::error_code(); +} + +std::error_code real_path(const Twine &path, SmallVectorImpl &dest, + bool expand_tilde) { + dest.clear(); + if (path.isTriviallyEmpty()) + return std::error_code(); + + if (expand_tilde) { + SmallString<128> Storage; + path.toVector(Storage); + expandTildeExpr(Storage); + return real_path(Storage, dest, false); + } + + SmallString<128> Storage; + StringRef P = path.toNullTerminatedStringRef(Storage); + char Buffer[PATH_MAX]; + if (::realpath(P.begin(), Buffer) == nullptr) + return std::error_code(errno, std::generic_category()); + dest.append(Buffer, Buffer + strlen(Buffer)); + return std::error_code(); +} + +} // end namespace fs + +namespace path { + +bool home_directory(SmallVectorImpl &result) { + char *RequestedDir = getenv("HOME"); + if (!RequestedDir) + return false; + + result.clear(); + result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); + return true; +} + +static bool getDarwinConfDir(bool TempDir, SmallVectorImpl &Result) { + #if defined(_CS_DARWIN_USER_TEMP_DIR) && defined(_CS_DARWIN_USER_CACHE_DIR) + // On Darwin, use DARWIN_USER_TEMP_DIR or DARWIN_USER_CACHE_DIR. + // macros defined in on darwin >= 9 + int ConfName = TempDir ? _CS_DARWIN_USER_TEMP_DIR + : _CS_DARWIN_USER_CACHE_DIR; + size_t ConfLen = confstr(ConfName, nullptr, 0); + if (ConfLen > 0) { + do { + Result.resize(ConfLen); + ConfLen = confstr(ConfName, Result.data(), Result.size()); + } while (ConfLen > 0 && ConfLen != Result.size()); + + if (ConfLen > 0) { + assert(Result.back() == 0); + Result.pop_back(); + return true; + } + + Result.clear(); + } + #endif + return false; +} + +static const char *getEnvTempDir() { + // Check whether the temporary directory is specified by an environment + // variable. + const char *EnvironmentVariables[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; + for (const char *Env : EnvironmentVariables) { + if (const char *Dir = std::getenv(Env)) + return Dir; + } + + return nullptr; +} + +static const char *getDefaultTempDir(bool ErasedOnReboot) { +#ifdef P_tmpdir + if ((bool)P_tmpdir) + return P_tmpdir; +#endif + + if (ErasedOnReboot) + return "/tmp"; + return "/var/tmp"; +} + +void system_temp_directory(bool ErasedOnReboot, SmallVectorImpl &Result) { + Result.clear(); + + if (ErasedOnReboot) { + // There is no env variable for the cache directory. + if (const char *RequestedDir = getEnvTempDir()) { + Result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); + return; + } + } + + if (getDarwinConfDir(ErasedOnReboot, Result)) + return; + + const char *RequestedDir = getDefaultTempDir(ErasedOnReboot); + Result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); +} + +} // end namespace path + +} // end namespace sys +} // end namespace llvh diff --git a/external/llvh/lib/Support/Wasi/Process.inc b/external/llvh/lib/Support/Wasi/Process.inc new file mode 100644 index 00000000000..c466821e3a0 --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Process.inc @@ -0,0 +1,421 @@ +//===- Unix/Process.cpp - Unix Process Implementation --------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides the generic Unix implementation of the Process class. +// +//===----------------------------------------------------------------------===// + +#include "Unix.h" +#include "llvh/ADT/Hashing.h" +#include "llvh/ADT/StringRef.h" +#include "llvh/Config/config.h" +#include "llvh/Support/ManagedStatic.h" +#include "llvh/Support/Mutex.h" +#include "llvh/Support/MutexGuard.h" +#if HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SIGNAL_H +#include +#endif +// DragonFlyBSD, and OpenBSD have deprecated for +// instead. Unix.h includes this for us already. +#if defined(HAVE_MALLOC_H) && !defined(__DragonFly__) && \ + !defined(__OpenBSD__) +#include +#endif +#if defined(HAVE_MALLCTL) +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_TERMIOS_H +# include +#endif +#if defined(__APPLE__) +#include +#endif + +//===----------------------------------------------------------------------===// +//=== WARNING: Implementation here must contain only generic UNIX code that +//=== is guaranteed to work on *all* UNIX variants. +//===----------------------------------------------------------------------===// + +using namespace llvh; +using namespace sys; + +static std::pair getRUsageTimes() { +#if defined(HAVE_GETRUSAGE) + struct rusage RU; + ::getrusage(RUSAGE_SELF, &RU); + return { toDuration(RU.ru_utime), toDuration(RU.ru_stime) }; +#else +#warning Cannot get usage times on this platform + return { std::chrono::microseconds::zero(), std::chrono::microseconds::zero() }; +#endif +} + +// On Cygwin, getpagesize() returns 64k(AllocationGranularity) and +// offset in mmap(3) should be aligned to the AllocationGranularity. +unsigned Process::getPageSize() { +#if defined(HAVE_GETPAGESIZE) + static const int page_size = ::getpagesize(); +#elif defined(HAVE_SYSCONF) + static long page_size = ::sysconf(_SC_PAGE_SIZE); +#else +#error Cannot get the page size on this machine +#endif + return static_cast(page_size); +} + +size_t Process::GetMallocUsage() { +#if defined(HAVE_MALLINFO) + struct mallinfo mi; + mi = ::mallinfo(); + return mi.uordblks; +#elif defined(HAVE_MALLOC_ZONE_STATISTICS) && defined(HAVE_MALLOC_MALLOC_H) + malloc_statistics_t Stats; + malloc_zone_statistics(malloc_default_zone(), &Stats); + return Stats.size_in_use; // darwin +#elif defined(HAVE_MALLCTL) + size_t alloc, sz; + sz = sizeof(size_t); + if (mallctl("stats.allocated", &alloc, &sz, NULL, 0) == 0) + return alloc; + return 0; +#elif defined(HAVE_SBRK) + // Note this is only an approximation and more closely resembles + // the value returned by mallinfo in the arena field. + static char *StartOfMemory = reinterpret_cast(::sbrk(0)); + char *EndOfMemory = (char*)sbrk(0); + if (EndOfMemory != ((char*)-1) && StartOfMemory != ((char*)-1)) + return EndOfMemory - StartOfMemory; + return 0; +#else +#warning Cannot get malloc info on this platform + return 0; +#endif +} + +void Process::GetTimeUsage(TimePoint<> &elapsed, std::chrono::nanoseconds &user_time, + std::chrono::nanoseconds &sys_time) { + elapsed = std::chrono::system_clock::now(); + std::tie(user_time, sys_time) = getRUsageTimes(); +} + +#if defined(HAVE_MACH_MACH_H) && !defined(__GNU__) +#include +#endif + +// Some LLVM programs such as bugpoint produce core files as a normal part of +// their operation. To prevent the disk from filling up, this function +// does what's necessary to prevent their generation. +void Process::PreventCoreFiles() { +#if HAVE_SETRLIMIT + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + setrlimit(RLIMIT_CORE, &rlim); +#endif + +#if defined(HAVE_MACH_MACH_H) && !defined(__GNU__) && !(defined(TARGET_OS_TV) && TARGET_OS_TV) + // Disable crash reporting on Mac OS X 10.0-10.4 + + // get information about the original set of exception ports for the task + mach_msg_type_number_t Count = 0; + exception_mask_t OriginalMasks[EXC_TYPES_COUNT]; + exception_port_t OriginalPorts[EXC_TYPES_COUNT]; + exception_behavior_t OriginalBehaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t OriginalFlavors[EXC_TYPES_COUNT]; + kern_return_t err = + task_get_exception_ports(mach_task_self(), EXC_MASK_ALL, OriginalMasks, + &Count, OriginalPorts, OriginalBehaviors, + OriginalFlavors); + if (err == KERN_SUCCESS) { + // replace each with MACH_PORT_NULL. + for (unsigned i = 0; i != Count; ++i) + task_set_exception_ports(mach_task_self(), OriginalMasks[i], + MACH_PORT_NULL, OriginalBehaviors[i], + OriginalFlavors[i]); + } + + // Disable crash reporting on Mac OS X 10.5 + signal(SIGABRT, _exit); + signal(SIGILL, _exit); + signal(SIGFPE, _exit); + signal(SIGSEGV, _exit); + signal(SIGBUS, _exit); +#endif + + coreFilesPrevented = true; +} + +Optional Process::GetEnv(StringRef Name) { + std::string NameStr = Name.str(); + const char *Val = ::getenv(NameStr.c_str()); + if (!Val) + return None; + return std::string(Val); +} + +namespace { +class FDCloser { +public: + FDCloser(int &FD) : FD(FD), KeepOpen(false) {} + void keepOpen() { KeepOpen = true; } + ~FDCloser() { + if (!KeepOpen && FD >= 0) + ::close(FD); + } + +private: + FDCloser(const FDCloser &) = delete; + void operator=(const FDCloser &) = delete; + + int &FD; + bool KeepOpen; +}; +} + +std::error_code Process::FixupStandardFileDescriptors() { + return std::error_code(); +} + +std::error_code Process::SafelyCloseFileDescriptor(int FD) { +#if LLVM_ENABLE_THREADS + if (int EC = pthread_sigmask(SIG_SETMASK, &FullSet, &SavedSet)) + return std::error_code(EC, std::generic_category()); +#endif + // Attempt to close the file descriptor. + // We need to save the error, if one occurs, because our subsequent call to + // pthread_sigmask might tamper with errno. + int ErrnoFromClose = 0; + if (::close(FD) < 0) + ErrnoFromClose = errno; + // Restore the signal mask back to what we saved earlier. + int EC = 0; +#if LLVM_ENABLE_THREADS + EC = pthread_sigmask(SIG_SETMASK, &SavedSet, nullptr); +#endif + // The error code from close takes precedence over the one from + // pthread_sigmask. + if (ErrnoFromClose) + return std::error_code(ErrnoFromClose, std::generic_category()); + return std::error_code(EC, std::generic_category()); +} + +bool Process::StandardInIsUserInput() { + return FileDescriptorIsDisplayed(STDIN_FILENO); +} + +bool Process::StandardOutIsDisplayed() { + return FileDescriptorIsDisplayed(STDOUT_FILENO); +} + +bool Process::StandardErrIsDisplayed() { + return FileDescriptorIsDisplayed(STDERR_FILENO); +} + +bool Process::FileDescriptorIsDisplayed(int fd) { +#if HAVE_ISATTY + return isatty(fd); +#else + // If we don't have isatty, just return false. + return false; +#endif +} + +static unsigned getColumns(int FileID) { + // If COLUMNS is defined in the environment, wrap to that many columns. + if (const char *ColumnsStr = std::getenv("COLUMNS")) { + int Columns = std::atoi(ColumnsStr); + if (Columns > 0) + return Columns; + } + + unsigned Columns = 0; + +#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_TERMIOS_H) + // Try to determine the width of the terminal. + struct winsize ws; + if (ioctl(FileID, TIOCGWINSZ, &ws) == 0) + Columns = ws.ws_col; +#endif + + return Columns; +} + +unsigned Process::StandardOutColumns() { + if (!StandardOutIsDisplayed()) + return 0; + + return getColumns(1); +} + +unsigned Process::StandardErrColumns() { + if (!StandardErrIsDisplayed()) + return 0; + + return getColumns(2); +} + +#ifdef HAVE_TERMINFO +// We manually declare these extern functions because finding the correct +// headers from various terminfo, curses, or other sources is harder than +// writing their specs down. +extern "C" int setupterm(char *term, int filedes, int *errret); +extern "C" struct term *set_curterm(struct term *termp); +extern "C" int del_curterm(struct term *termp); +extern "C" int tigetnum(char *capname); +#endif + +#ifdef HAVE_TERMINFO +static ManagedStatic TermColorMutex; +#endif + +static bool terminalHasColors(int fd) { +#ifdef HAVE_TERMINFO + // First, acquire a global lock because these C routines are thread hostile. + MutexGuard G(*TermColorMutex); + + int errret = 0; + if (setupterm(nullptr, fd, &errret) != 0) + // Regardless of why, if we can't get terminfo, we shouldn't try to print + // colors. + return false; + + // Test whether the terminal as set up supports color output. How to do this + // isn't entirely obvious. We can use the curses routine 'has_colors' but it + // would be nice to avoid a dependency on curses proper when we can make do + // with a minimal terminfo parsing library. Also, we don't really care whether + // the terminal supports the curses-specific color changing routines, merely + // if it will interpret ANSI color escape codes in a reasonable way. Thus, the + // strategy here is just to query the baseline colors capability and if it + // supports colors at all to assume it will translate the escape codes into + // whatever range of colors it does support. We can add more detailed tests + // here if users report them as necessary. + // + // The 'tigetnum' routine returns -2 or -1 on errors, and might return 0 if + // the terminfo says that no colors are supported. + bool HasColors = tigetnum(const_cast("colors")) > 0; + + // Now extract the structure allocated by setupterm and free its memory + // through a really silly dance. + struct term *termp = set_curterm(nullptr); + (void)del_curterm(termp); // Drop any errors here. + + // Return true if we found a color capabilities for the current terminal. + if (HasColors) + return true; +#else + // When the terminfo database is not available, check if the current terminal + // is one of terminals that are known to support ANSI color escape codes. + if (const char *TermStr = std::getenv("TERM")) { + return StringSwitch(TermStr) + .Case("ansi", true) + .Case("cygwin", true) + .Case("linux", true) + .StartsWith("screen", true) + .StartsWith("xterm", true) + .StartsWith("vt100", true) + .StartsWith("rxvt", true) + .EndsWith("color", true) + .Default(false); + } +#endif + + // Otherwise, be conservative. + return false; +} + +bool Process::FileDescriptorHasColors(int fd) { + // A file descriptor has colors if it is displayed and the terminal has + // colors. + return FileDescriptorIsDisplayed(fd) && terminalHasColors(fd); +} + +bool Process::StandardOutHasColors() { + return FileDescriptorHasColors(STDOUT_FILENO); +} + +bool Process::StandardErrHasColors() { + return FileDescriptorHasColors(STDERR_FILENO); +} + +void Process::UseANSIEscapeCodes(bool /*enable*/) { + // No effect. +} + +bool Process::ColorNeedsFlush() { + // No, we use ANSI escape sequences. + return false; +} + +const char *Process::OutputColor(char code, bool bold, bool bg) { + return colorcodes[bg?1:0][bold?1:0][code&7]; +} + +const char *Process::OutputBold(bool bg) { + return "\033[1m"; +} + +const char *Process::OutputReverse() { + return "\033[7m"; +} + +const char *Process::ResetColor() { + return "\033[0m"; +} + +#if !HAVE_DECL_ARC4RANDOM +static unsigned GetRandomNumberSeed() { + // Attempt to get the initial seed from /dev/urandom, if possible. + int urandomFD = open("/dev/urandom", O_RDONLY); + + if (urandomFD != -1) { + unsigned seed; + // Don't use a buffered read to avoid reading more data + // from /dev/urandom than we need. + int count = read(urandomFD, (void *)&seed, sizeof(seed)); + + close(urandomFD); + + // Return the seed if the read was successful. + if (count == sizeof(seed)) + return seed; + } + + // Otherwise, swizzle the current time and the process ID to form a reasonable + // seed. + const auto Now = std::chrono::high_resolution_clock::now(); + return hash_combine(Now.time_since_epoch().count(), ::getpid()); +} +#endif + +unsigned llvh::sys::Process::GetRandomNumber() { +#if HAVE_DECL_ARC4RANDOM + return arc4random(); +#else + static int x = (static_cast(::srand(GetRandomNumberSeed())), 0); + (void)x; + return ::rand(); +#endif +} diff --git a/external/llvh/lib/Support/Wasi/Program.inc b/external/llvh/lib/Support/Wasi/Program.inc new file mode 100644 index 00000000000..ff4cc1a63e3 --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Program.inc @@ -0,0 +1,203 @@ +//===- llvm/Support/Unix/Program.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Unix specific portion of the Program class. +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +//=== WARNING: Implementation here must contain only generic UNIX code that +//=== is guaranteed to work on *all* UNIX variants. +//===----------------------------------------------------------------------===// + +#include "Unix.h" +#include "llvh/ADT/StringExtras.h" +#include "llvh/Config/config.h" +#include "llvh/Support/Compiler.h" +#include "llvh/Support/Errc.h" +#include "llvh/Support/FileSystem.h" +#include "llvh/Support/Path.h" +#include "llvh/Support/StringSaver.h" +#include "llvh/Support/raw_ostream.h" +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_RESOURCE_H +#include +#endif +#if HAVE_SIGNAL_H +#include +#endif +#if HAVE_FCNTL_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#if defined(__APPLE__) +#include +#endif +#ifdef HAVE_POSIX_SPAWN +#include + +#if defined(__APPLE__) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#define USE_NSGETENVIRON 1 +#else +#define USE_NSGETENVIRON 0 +#endif + +#if !USE_NSGETENVIRON + extern char **environ; +#else +#include // _NSGetEnviron +#endif +#endif + +namespace llvh { + +using namespace sys; + +ProcessInfo::ProcessInfo() : Pid(0), ReturnCode(0) {} + +ErrorOr sys::findProgramByName(StringRef Name, + ArrayRef Paths) { + assert(!Name.empty() && "Must have a name!"); + // Use the given path verbatim if it contains any slashes; this matches + // the behavior of sh(1) and friends. + if (Name.find('/') != StringRef::npos) + return std::string(Name); + + SmallVector EnvironmentPaths; + if (Paths.empty()) + if (const char *PathEnv = std::getenv("PATH")) { + SplitString(PathEnv, EnvironmentPaths, ":"); + Paths = EnvironmentPaths; + } + + for (auto Path : Paths) { + if (Path.empty()) + continue; + + // Check to see if this first directory contains the executable... + SmallString<128> FilePath(Path); + sys::path::append(FilePath, Name); + if (sys::fs::can_execute(FilePath.c_str())) + return std::string(FilePath.str()); // Found the executable! + } + return errc::no_such_file_or_directory; +} + +#ifdef HAVE_POSIX_SPAWN +static bool RedirectIO_PS(const std::string *Path, int FD, std::string *ErrMsg, + posix_spawn_file_actions_t *FileActions) { + if (!Path) // Noop + return false; + const char *File; + if (Path->empty()) + // Redirect empty paths to /dev/null + File = "/dev/null"; + else + File = Path->c_str(); + + if (int Err = posix_spawn_file_actions_addopen( + FileActions, FD, File, + FD == 0 ? O_RDONLY : O_WRONLY | O_CREAT, 0666)) + return MakeErrMsg(ErrMsg, "Cannot dup2", Err); + return false; +} +#endif + +} + +static bool Execute(ProcessInfo &PI, StringRef Program, + ArrayRef Args, Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit, std::string *ErrMsg) { + if (ErrMsg) + *ErrMsg = std::string("Execute() is not supported on Wasi"); + return false; +} + +namespace llvh { + +ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait, + bool WaitUntilTerminates, std::string *ErrMsg) { + if (ErrMsg) + *ErrMsg = std::string("Wait() is not supported on Wasi"); + return {}; +} + +std::error_code sys::ChangeStdinToBinary() { + // Do nothing, as Unix doesn't differentiate between text and binary. + return std::error_code(); +} + +std::error_code sys::ChangeStdoutToBinary() { + // Do nothing, as Unix doesn't differentiate between text and binary. + return std::error_code(); +} + +std::error_code +llvh::sys::writeFileWithEncoding(StringRef FileName, StringRef Contents, + WindowsEncodingMethod Encoding /*unused*/) { + std::error_code EC; + llvh::raw_fd_ostream OS(FileName, EC, llvh::sys::fs::OpenFlags::F_Text); + + if (EC) + return EC; + + OS << Contents; + + if (OS.has_error()) + return make_error_code(errc::io_error); + + return EC; +} + +bool llvh::sys::commandLineFitsWithinSystemLimits(StringRef Program, + ArrayRef Args) { + static long ArgMax = sysconf(_SC_ARG_MAX); + // POSIX requires that _POSIX_ARG_MAX is 4096, which is the lowest possible + // value for ARG_MAX on a POSIX compliant system. + static long ArgMin = _POSIX_ARG_MAX; + + // This the same baseline used by xargs. + long EffectiveArgMax = 128 * 1024; + + if (EffectiveArgMax > ArgMax) + EffectiveArgMax = ArgMax; + else if (EffectiveArgMax < ArgMin) + EffectiveArgMax = ArgMin; + + // System says no practical limit. + if (ArgMax == -1) + return true; + + // Conservatively account for space required by environment variables. + long HalfArgMax = EffectiveArgMax / 2; + + size_t ArgLength = Program.size() + 1; + for (StringRef Arg : Args) { + // Ensure that we do not exceed the MAX_ARG_STRLEN constant on Linux, which + // does not have a constant unlike what the man pages would have you + // believe. Since this limit is pretty high, perform the check + // unconditionally rather than trying to be aggressive and limiting it to + // Linux only. + if (Arg.size() >= (32 * 4096)) + return false; + + ArgLength += Arg.size() + 1; + if (ArgLength > size_t(HalfArgMax)) { + return false; + } + } + + return true; +} +} diff --git a/external/llvh/lib/Support/Wasi/README.txt b/external/llvh/lib/Support/Wasi/README.txt new file mode 100644 index 00000000000..e4daab7b51d --- /dev/null +++ b/external/llvh/lib/Support/Wasi/README.txt @@ -0,0 +1,5 @@ +llvm/lib/Support/Wasi README +=========================== + +This directory provides implementations of the lib/System classes for +WASI. diff --git a/external/llvh/lib/Support/Wasi/Signals.inc b/external/llvh/lib/Support/Wasi/Signals.inc new file mode 100644 index 00000000000..a7933be13cf --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Signals.inc @@ -0,0 +1,55 @@ +//===- Signals.cpp - Generic Unix Signals Implementation -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines some helpful functions for dealing with the possibility of +// Unix signals occurring while your program is running. +// +//===----------------------------------------------------------------------===// +// +// This file is extremely careful to only do signal-safe things while in a +// signal handler. In particular, memory allocation and acquiring a mutex +// while in a signal handler should never occur. ManagedStatic isn't usable from +// a signal handler for 2 reasons: +// +// 1. Creating a new one allocates. +// 2. The signal handler could fire while llvm_shutdown is being processed, in +// which case the ManagedStatic is in an unknown state because it could +// already have been destroyed, or be in the process of being destroyed. +// +// Modifying the behavior of the signal handlers (such as registering new ones) +// can acquire a mutex, but all this guarantees is that the signal handler +// behavior is only modified by one thread at a time. A signal handler can still +// fire while this occurs! +// +// Adding work to a signal handler requires lock-freedom (and assume atomics are +// always lock-free) because the signal handler could fire while new work is +// being added. +// +//===----------------------------------------------------------------------===// + +void llvh::sys::AddSignalHandler(sys::SignalHandlerCallback FnPtr, + void *Cookie) { // Signal-safe. + (void)insertSignalHandler; +} + +void llvh::sys::RunInterruptHandlers() { +} + +void llvh::sys::PrintStackTraceOnErrorSignal(StringRef Argv0, + bool DisableCrashReporting) { +} + +/// This platform does not have dl_iterate_phdr, so we do not yet know how to +/// find all loaded DSOs. +static bool findModulesAndOffsets(void **StackTrace, int Depth, + const char **Modules, intptr_t *Offsets, + const char *MainExecutableName, + StringSaver &StrPool) { + return false; +} diff --git a/external/llvh/lib/Support/Wasi/Unix.h b/external/llvh/lib/Support/Wasi/Unix.h new file mode 100644 index 00000000000..8176d82dc5d --- /dev/null +++ b/external/llvh/lib/Support/Wasi/Unix.h @@ -0,0 +1,105 @@ +//===- llvm/Support/Unix/Unix.h - Common Unix Include File -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines things specific to Unix implementations. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_SUPPORT_UNIX_UNIX_H +#define LLVM_LIB_SUPPORT_UNIX_UNIX_H + +//===----------------------------------------------------------------------===// +//=== WARNING: Implementation here must contain only generic UNIX code that +//=== is guaranteed to work on all UNIX variants. +//===----------------------------------------------------------------------===// + +#include "llvh/Config/config.h" // Get autoconf configuration settings +#include "llvh/Support/Chrono.h" +#include "llvh/Support/Errno.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +# include +#endif +#include + +#ifdef HAVE_DLFCN_H +# include +#endif + +#ifdef HAVE_FCNTL_H +# include +#endif + +/// This function builds an error message into \p ErrMsg using the \p prefix +/// string and the Unix error number given by \p errnum. If errnum is -1, the +/// default then the value of errno is used. +/// Make an error message +/// +/// If the error number can be converted to a string, it will be +/// separated from prefix by ": ". +static inline bool MakeErrMsg( + std::string* ErrMsg, const std::string& prefix, int errnum = -1) { + if (!ErrMsg) + return true; + if (errnum == -1) + errnum = errno; + *ErrMsg = prefix + ": " + llvh::sys::StrError(errnum); + return true; +} + +namespace llvh { +namespace sys { + +/// Convert a struct timeval to a duration. Note that timeval can be used both +/// as a time point and a duration. Be sure to check what the input represents. +inline std::chrono::microseconds toDuration(const struct timeval &TV) { + return std::chrono::seconds(TV.tv_sec) + + std::chrono::microseconds(TV.tv_usec); +} + +/// Convert a time point to struct timespec. +inline struct timespec toTimeSpec(TimePoint<> TP) { + using namespace std::chrono; + + struct timespec RetVal; + RetVal.tv_sec = toTimeT(TP); + RetVal.tv_nsec = (TP.time_since_epoch() % seconds(1)).count(); + return RetVal; +} + +/// Convert a time point to struct timeval. +inline struct timeval toTimeVal(TimePoint TP) { + using namespace std::chrono; + + struct timeval RetVal; + RetVal.tv_sec = toTimeT(TP); + RetVal.tv_usec = (TP.time_since_epoch() % seconds(1)).count(); + return RetVal; +} + +} // namespace sys +} // namespace llvh + +#endif diff --git a/include/hermes/BCGen/HBC/BCProvider.h b/include/hermes/BCGen/HBC/BCProvider.h index 369a7b64fa9..26c41c1cef4 100644 --- a/include/hermes/BCGen/HBC/BCProvider.h +++ b/include/hermes/BCGen/HBC/BCProvider.h @@ -15,6 +15,7 @@ #include "hermes/SourceMap/SourceMapGenerator.h" #include "hermes/Support/BigIntSupport.h" #include "hermes/Support/Buffer.h" +#include "hermes/Support/FakeThreads.h" #include "hermes/Support/OSCompat.h" #include "hermes/Support/PageAccessTracker.h" #include "hermes/Support/StringTableEntry.h" diff --git a/include/hermes/Support/FakeThreads.h b/include/hermes/Support/FakeThreads.h new file mode 100644 index 00000000000..a282c8e5db1 --- /dev/null +++ b/include/hermes/Support/FakeThreads.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifndef HERMES_SUPPORT_FAKETHREADS_H +#define HERMES_SUPPORT_FAKETHREADS_H + +#include "llvh/Support/ErrorHandling.h" + +#include +#include +#include + +namespace hermes { + +/// FakeAtomic has the same API as std::atomic, but ignores the memory order +/// argument and always accesses data non-atomically. +/// Used when the GC doesn't require atomicity. +/// NOTE: This differs from std::atomic where it doesn't have default memory +/// orders, since we want all atomic operations to be very explicit with their +/// requirements. Also don't define operator T for the same reason. +template +class FakeAtomic final { + public: + constexpr FakeAtomic() : data_{} {} + constexpr FakeAtomic(T desired) : data_{desired} {} + + T load(std::memory_order order) const { + (void)order; + return data_; + } + + void store(T desired, std::memory_order order) { + (void)order; + data_ = desired; + } + + T fetch_add(T arg, std::memory_order order) { + (void)order; + const T oldData = data_; + data_ += arg; + return oldData; + } + + T exchange(T arg, std::memory_order order) { + (void)order; + const T oldData = data_; + data_ = arg; + return oldData; + } + + T fetch_sub(T arg, std::memory_order order) { + (void)order; + const T oldData = data_; + data_ -= arg; + return oldData; + } + + /// Use store explicitly instead. + FakeAtomic &operator=(const FakeAtomic &) = delete; + + private: + T data_; +}; + +/// A FakeMutex has the same API as a std::mutex but does nothing. +/// It pretends to always be locked for convenience of asserts that need to work +/// in both concurrent code and non-concurrent code. +class FakeMutex { + public: + explicit FakeMutex() = default; + + operator bool() const { + return true; + } + + uint32_t depth() const { + return 1; + } + + void lock() {} + bool try_lock() { + return true; + } + void unlock() {} +}; + +} // namespace hermes + +#if defined(__wasi__) && !LLVM_ENABLE_THREADS +// Provide fake replacement for std::thread under Wasi to make compilation +// possible. + +namespace std { + +using recursive_mutex = hermes::FakeMutex; +using mutex = hermes::FakeMutex; + +template +class unique_lock { + mutex_type *m_; + + public: + explicit unique_lock(mutex_type &m) : m_(&m) {} + unique_lock(mutex_type &m, std::adopt_lock_t) : m_(&m) {} + + mutex_type *release() noexcept { + mutex_type *res = m_; + m_ = nullptr; + return res; + } + + void lock() {} + void unlock() {} +}; + +enum class cv_status { no_timeout, timeout }; + +class condition_variable { + public: + void notify_all() noexcept {} + void notify_one() noexcept {} + + template + std::cv_status wait_until( + std::unique_lock &, + const std::chrono::time_point &) { + return cv_status::no_timeout; + } + + template + std::cv_status wait_for( + std::unique_lock &, + const std::chrono::duration &) { + return cv_status::no_timeout; + } + + void wait(std::unique_lock &) {} + + template + void wait(std::unique_lock &, Predicate pred) { + if (!pred()) { + llvh::report_fatal_error( + "waiting in std::condition_variable not supported under Wasi"); + } + } +}; + +class condition_variable_any { + public: + void notify_all() noexcept {} + void notify_one() noexcept {} + + template + std::cv_status wait_until( + Lock &, + const std::chrono::time_point &) { + return cv_status::no_timeout; + } + + template + std::cv_status wait_for(Lock &, const std::chrono::duration &) { + return cv_status::no_timeout; + } + + template + void wait(Lock &, Predicate pred) { + if (!pred()) { + llvh::report_fatal_error( + "waiting in std::condition_variable not supported under Wasi"); + } + } +}; + +template +class future { + public: + T get() { + llvh::report_fatal_error("std::future not supported under Wasi"); + } +}; + +template +class promise { + public: + future get_future() { + return future(); + } + void set_value(const R &value) { + llvh::report_fatal_error("std::promise not supported under Wasi"); + } +}; + +template <> +class promise { + public: + future get_future() { + return future(); + } + void set_value() {} +}; + +class thread { + public: + class id { + public: + bool operator==(const id &other) const noexcept { + return true; + } + }; + + thread() noexcept {} + + template + explicit thread(F &&f, Args &&...args) { + llvh::report_fatal_error("std::thread not supported under Wasi"); + } + + id get_id() const noexcept { + return id(); + } + + void join() {} + bool joinable() const noexcept { + return false; + } +}; + +namespace this_thread { +inline thread::id get_id() noexcept { + return thread::id(); +} +} // namespace this_thread + +} // namespace std + +#endif + +#endif diff --git a/include/hermes/VM/GCConcurrency.h b/include/hermes/VM/GCConcurrency.h index a582531928d..7091fd432ce 100644 --- a/include/hermes/VM/GCConcurrency.h +++ b/include/hermes/VM/GCConcurrency.h @@ -8,6 +8,8 @@ #ifndef HERMES_VM_GCCONCURRENCY_H #define HERMES_VM_GCCONCURRENCY_H +#include "hermes/Support/FakeThreads.h" + #include #include #include @@ -32,62 +34,6 @@ static constexpr bool kConcurrentGC = namespace impl { -/// FakeAtomic has the same API as std::atomic, but ignores the memory order -/// argument and always accesses data non-atomically. -/// Used when the GC doesn't require atomicity. -/// In the JS VM, there is currently only one mutator thread and at most one GC -/// thread. The GC thread will not do any modifications to these atomics, and -/// will only read them. Therefore it is typically safe for the mutator to use -/// relaxed reads. Writes will typically require std::memory_order_release or -/// stricter to make sure the GC sees the writes which occur before the atomic -/// write. -/// NOTE: This differs from std::atomic where it doesn't have default memory -/// orders, since we want all atomic operations to be very explicit with their -/// requirements. Also don't define operator T for the same reason. -template -class FakeAtomic final { - public: - constexpr FakeAtomic() : data_{} {} - constexpr FakeAtomic(T desired) : data_{desired} {} - - T load(std::memory_order order) const { - (void)order; - return data_; - } - - void store(T desired, std::memory_order order) { - (void)order; - data_ = desired; - } - - T fetch_add(T arg, std::memory_order order) { - (void)order; - const T oldData = data_; - data_ += arg; - return oldData; - } - - T exchange(T arg, std::memory_order order) { - (void)order; - const T oldData = data_; - data_ = arg; - return oldData; - } - - T fetch_sub(T arg, std::memory_order order) { - (void)order; - const T oldData = data_; - data_ -= arg; - return oldData; - } - - /// Use store explicitly instead. - FakeAtomic &operator=(const FakeAtomic &) = delete; - - private: - T data_; -}; - /// A DebugMutex wraps a std::recursive_mutex and also tracks which thread /// currently has the mutex locked. Only available in debug modes. class DebugMutex { @@ -135,34 +81,19 @@ class DebugMutex { uint32_t depth_{0}; }; -/// A FakeMutex has the same API as a std::mutex but does nothing. -/// It pretends to always be locked for convenience of asserts that need to work -/// in both concurrent code and non-concurrent code. -class FakeMutex { - public: - explicit FakeMutex() = default; - - operator bool() const { - return true; - } - - uint32_t depth() const { - return 1; - } - - void lock() {} - bool try_lock() { - return true; - } - void unlock() {} -}; - } // namespace impl // Only these typedefs should be used by the rest of the VM. + +// In the JS VM, there is currently only one mutator thread and at most one GC +// thread. The GC thread will not do any modifications to these atomics, and +// will only read them. Therefore it is typically safe for the mutator to use +// relaxed reads. Writes will typically require std::memory_order_release or +// stricter to make sure the GC sees the writes which occur before the atomic +// write. template using AtomicIfConcurrentGC = typename std:: - conditional, impl::FakeAtomic>::type; + conditional, FakeAtomic>::type; using Mutex = std::conditional< kConcurrentGC, @@ -172,7 +103,7 @@ using Mutex = std::conditional< std::recursive_mutex #endif , - impl::FakeMutex>::type; + FakeMutex>::type; } // namespace vm } // namespace hermes diff --git a/include/hermes/VM/JSLib/DateCache.h b/include/hermes/VM/JSLib/DateCache.h index 84fefc90a73..b09f22899be 100644 --- a/include/hermes/VM/JSLib/DateCache.h +++ b/include/hermes/VM/JSLib/DateCache.h @@ -10,6 +10,8 @@ #include "hermes/VM/JSLib/DateUtil.h" +#include "llvh/Config/config.h" + namespace hermes { namespace vm { @@ -93,7 +95,10 @@ class LocalTimeOffsetCache { /// Reset the standard local time offset and the DST cache. void reset() { + // Wasi doesn't provide tzset(). + #ifdef HAVE_TZSET ::tzset(); + #endif ltza_ = localTZA(); caches_.fill(DSTCacheEntry{}); candidate_ = caches_.data(); diff --git a/include/hermes/VM/Profiler/SamplingProfilerDefs.h b/include/hermes/VM/Profiler/SamplingProfilerDefs.h index 9d9fe9f7679..827e6085c0c 100644 --- a/include/hermes/VM/Profiler/SamplingProfilerDefs.h +++ b/include/hermes/VM/Profiler/SamplingProfilerDefs.h @@ -8,7 +8,7 @@ #ifndef HERMES_VM_PROFILER_SAMPLINGPROFILERDEFS_H #define HERMES_VM_PROFILER_SAMPLINGPROFILERDEFS_H -#if defined(__EMSCRIPTEN__) +#if defined(__wasm__) #define HERMESVM_SAMPLING_PROFILER_AVAILABLE 0 #else // !defined(__EMSCRIPTEN__) diff --git a/include/hermes/VM/TimeLimitMonitor.h b/include/hermes/VM/TimeLimitMonitor.h index 12d353915d6..7b50f079035 100644 --- a/include/hermes/VM/TimeLimitMonitor.h +++ b/include/hermes/VM/TimeLimitMonitor.h @@ -9,6 +9,7 @@ #define HERMES_VM_TIMELIMITMONITOR_H #include "llvh/ADT/DenseMap.h" +#include "hermes/Support/FakeThreads.h" #include #include diff --git a/include/hermes/VM/instrumentation/StatSamplingThread.h b/include/hermes/VM/instrumentation/StatSamplingThread.h index 857838fd6e1..ca652395bdf 100644 --- a/include/hermes/VM/instrumentation/StatSamplingThread.h +++ b/include/hermes/VM/instrumentation/StatSamplingThread.h @@ -8,6 +8,7 @@ #ifndef HERMES_VM_SAMPLINGTHREAD_H #define HERMES_VM_SAMPLINGTHREAD_H +#include "hermes/Support/FakeThreads.h" #include "hermes/VM/instrumentation/ProcessStats.h" #include diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 42b14fa9786..a6bd8a89145 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -28,6 +28,7 @@ add_hermes_library(hermesSupport MD5.cpp OSCompatCommon.cpp OSCompatEmscripten.cpp + OSCompatWasi.cpp OSCompatPosix.cpp OSCompatWindows.cpp PageAccessTrackerPosix.cpp diff --git a/lib/Support/OSCompatPosix.cpp b/lib/Support/OSCompatPosix.cpp index 21877af9527..79a046e7a88 100644 --- a/lib/Support/OSCompatPosix.cpp +++ b/lib/Support/OSCompatPosix.cpp @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -#if !defined(_WINDOWS) && !defined(__EMSCRIPTEN__) +#if !defined(_WINDOWS) && !defined(__wasm__) #include "hermes/Support/Compiler.h" #include "hermes/Support/ErrorHandling.h" diff --git a/lib/Support/OSCompatWasi.cpp b/lib/Support/OSCompatWasi.cpp new file mode 100644 index 00000000000..7a8354ccad0 --- /dev/null +++ b/lib/Support/OSCompatWasi.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef __wasi__ + +#define _WASI_EMULATED_MMAN +// -lwasi-emulated-mman + +#include "hermes/Support/ErrorHandling.h" +#include "hermes/Support/OSCompat.h" + +#include +#include + +#include + +#include +#include + +#include "llvh/Support/raw_ostream.h" + +namespace hermes { +namespace oscompat { + +#ifndef NDEBUG +static size_t testPgSz = 0; + +void set_test_page_size(size_t pageSz) { + testPgSz = pageSz; +} + +void reset_test_page_size() { + testPgSz = 0; +} +#endif + +static inline size_t page_size_real() { + return getpagesize(); +} + +size_t page_size() { +#ifndef NDEBUG + if (testPgSz != 0) { + return testPgSz; + } +#endif + return page_size_real(); +} + +#ifndef NDEBUG +static constexpr size_t unsetVMAllocLimit = std::numeric_limits::max(); +static size_t totalVMAllocLimit = unsetVMAllocLimit; + +void set_test_vm_allocate_limit(size_t totSz) { + totalVMAllocLimit = totSz; +} + +void unset_test_vm_allocate_limit() { + totalVMAllocLimit = unsetVMAllocLimit; +} +#endif // !NDEBUG + +static llvh::ErrorOr vm_allocate_impl(size_t sz) { +#ifndef NDEBUG + if (LLVM_UNLIKELY(sz > totalVMAllocLimit)) { + return make_error_code(OOMError::TestVMLimitReached); + } else if (LLVM_UNLIKELY(totalVMAllocLimit != unsetVMAllocLimit)) { + totalVMAllocLimit -= sz; + } +#endif // !NDEBUG + + void *result = mmap( + nullptr, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (result == MAP_FAILED) { + // Since mmap is a POSIX API, even on MacOS, errno should use the POSIX + // generic_category. + return std::error_code(errno, std::generic_category()); + } + return result; +} + +llvh::ErrorOr vm_allocate(size_t sz, void * /* hint */) { + assert(sz % page_size() == 0); +#ifndef NDEBUG + if (testPgSz != 0 && testPgSz > static_cast(page_size_real())) { + return vm_allocate_aligned(sz, testPgSz); + } +#endif // !NDEBUG + return vm_allocate_impl(sz); +} + +llvh::ErrorOr +vm_allocate_aligned(size_t sz, size_t alignment, void * /* hint */) { + assert(sz > 0 && sz % page_size() == 0); + assert(alignment > 0 && alignment % page_size() == 0); + // Ensure the alignment is a power of two as this is required by + // aligned_alloc. + assert(llvh::isPowerOf2_64(alignment)); + + // Emscripten does not support partial munmap, so use aligned_alloc to obtain + // an aligned region and then memset it to zero. + auto *p = aligned_alloc(alignment, sz); + if (!p) + return std::error_code(errno, std::generic_category()); + memset(p, 0, sz); + return p; +} + +void vm_free(void *p, size_t sz) { + auto ret = munmap(p, sz); + + assert(!ret && "Failed to free memory region."); + (void)ret; + +#ifndef NDEBUG + if (LLVM_UNLIKELY(totalVMAllocLimit != unsetVMAllocLimit) && p) { + totalVMAllocLimit += sz; + } +#endif +} + +void vm_free_aligned(void *p, size_t sz) { + free(p); +} + +/// Define a no-op implementation of the reserve/commit APIs that just call +/// through to regular allocations. +llvh::ErrorOr +vm_reserve_aligned(size_t sz, size_t alignment, void *hint) { + return vm_allocate_aligned(sz, alignment, hint); +} +void vm_release_aligned(void *p, size_t sz) { + vm_free_aligned(p, sz); +} +llvh::ErrorOr vm_commit(void *p, size_t sz) { + return p; +} +void vm_uncommit(void *p, size_t sz) {} + +void vm_hugepage(void *p, size_t sz) { + assert( + reinterpret_cast(p) % page_size() == 0 && + "Precondition: pointer is page-aligned."); +} + +void vm_unused(void *p, size_t sz) { +#ifndef NDEBUG + const size_t PS = page_size(); + assert( + reinterpret_cast(p) % PS == 0 && + "Precondition: pointer is page-aligned."); +#endif +} + +void vm_prefetch(void *p, size_t sz) { + assert( + reinterpret_cast(p) % page_size() == 0 && + "Precondition: pointer is page-aligned."); +} + +void vm_name(void *p, size_t sz, const char *name) {} + +bool vm_protect(void *p, size_t sz, ProtectMode mode) { + auto prot = PROT_NONE; + if (mode == ProtectMode::ReadWrite) { + prot = PROT_WRITE | PROT_READ; + } + int err = mprotect(p, sz, prot); + return err != -1; +} + +bool vm_madvise(void *p, size_t sz, MAdvice advice) { +#ifndef NDEBUG + const size_t PS = page_size(); + assert( + reinterpret_cast(p) % PS == 0 && + "Precondition: pointer is page-aligned."); +#endif + return true; +} + +llvh::ErrorOr vm_footprint(char *start, char *end) { + return std::error_code(errno, std::generic_category()); +} + +int pages_in_ram(const void *p, size_t sz, llvh::SmallVectorImpl *runs) { + return -1; +} + +uint64_t peak_rss() { + return 0; +} + +uint64_t current_rss() { + return 0; +} + +uint64_t current_private_dirty() { + return 0; +} + +std::vector get_vm_protect_modes(const void *p, size_t sz) { + return std::vector{}; +} + +bool num_context_switches(long &voluntary, long &involuntary) { + voluntary = involuntary = -1; + return false; +} + +uint64_t global_thread_id() { + return 0; +} + +namespace detail { + +std::pair thread_stack_bounds_impl() { + return {0, 0}; +} + +} // namespace detail + +void set_thread_name(const char *name) { + // Intentionally does nothing +} + +// Platform-specific implementations of thread_cpu_time + +std::chrono::microseconds thread_cpu_time() { + using namespace std::chrono; + return microseconds(0); +} + +// Platform-specific implementations of thread_page_fault_count + +bool thread_page_fault_count(int64_t *outMinorFaults, int64_t *outMajorFaults) { + return false; +} + +std::string thread_name() { + return ""; +} + +std::vector sched_getaffinity() { + // Not yet supported. + return std::vector(); +} + +int sched_getcpu() { + // Not yet supported. + return -1; +} + +bool set_env(const char *name, const char *value) { + // Enforce the contract of this function that value must not be empty + assert(*value != '\0' && "value cannot be empty string"); + return setenv(name, value, 1) == 0; +} + +bool unset_env(const char *name) { + return unsetenv(name) == 0; +} + +/*static*/ +void *SigAltStackLeakSuppressor::stackRoot_{nullptr}; + +SigAltStackLeakSuppressor::~SigAltStackLeakSuppressor() {} + +} // namespace oscompat +} // namespace hermes + +#endif // __EMSCRIPTEN__ diff --git a/lib/Support/SerialExecutor.cpp b/lib/Support/SerialExecutor.cpp index 5b473dcfb81..889b909ff24 100644 --- a/lib/Support/SerialExecutor.cpp +++ b/lib/Support/SerialExecutor.cpp @@ -5,6 +5,10 @@ * LICENSE file in the root directory of this source tree. */ +#include "llvh/Config/llvm-config.h" + +#if LLVM_ENABLE_THREADS + #include #include @@ -114,3 +118,5 @@ void *SerialExecutor::threadMain(void *p) { } } // namespace hermes + +#endif diff --git a/lib/Support/StackExecutor.cpp b/lib/Support/StackExecutor.cpp index 86bfb99823b..ac26d85e556 100644 --- a/lib/Support/StackExecutor.cpp +++ b/lib/Support/StackExecutor.cpp @@ -7,6 +7,8 @@ #include "hermes/Support/StackExecutor.h" +#include "llvh/Config/llvm-config.h" + #if defined(HERMES_USE_BOOST_CONTEXT) && HERMES_USE_BOOST_CONTEXT #include "llvh/Support/Debug.h" #include "llvh/Support/raw_ostream.h" @@ -80,10 +82,10 @@ void executeInStack(StackExecutor &exec, void *arg, void (*func)(void *arg)) { } // namespace hermes -#elif defined(__wasm__) && !defined(__EMSCRIPTEN_THREADS__) +#elif !LLVM_ENABLE_THREADS namespace hermes { -#pragma message("Warning: StackExecutor is no-op in Wasm without threads!") +#pragma message("Warning: StackExecutor is no-op without threads!") class StackExecutor { public: diff --git a/lib/VM/Instrumentation/ProcessStats.cpp b/lib/VM/Instrumentation/ProcessStats.cpp index eef6b8ca1d0..f8c78c059eb 100644 --- a/lib/VM/Instrumentation/ProcessStats.cpp +++ b/lib/VM/Instrumentation/ProcessStats.cpp @@ -69,7 +69,7 @@ ProcessStats::Info getProcessStatSnapshot() { const size_t PS = getpagesize(); rss *= PS / 1024; va *= PS / 1024; -#elif defined(__EMSCRIPTEN__) +#elif defined(__wasm__) rss = va = 0; #else #error "Unsupported platform" diff --git a/lib/VM/Runtime.cpp b/lib/VM/Runtime.cpp index bacf34984e7..2f1a3a63e52 100644 --- a/lib/VM/Runtime.cpp +++ b/lib/VM/Runtime.cpp @@ -105,6 +105,7 @@ static const Predefined::Str fixedPropCacheNames[(size_t)PropCacheID::_COUNT] = } // namespace +#ifdef HERMESVM_RUNTIME_ON_STACK // Minidumps include stack memory, not heap memory. If we want to be // able to inspect the Runtime object in a minidump, we can do that by // arranging for it to be allocated on a stack. No existing stack is @@ -148,6 +149,7 @@ class Runtime::StackRuntime { Runtime *runtime_{nullptr}; std::thread thread_; }; +#endif /* static */ std::shared_ptr Runtime::create(const RuntimeConfig &runtimeConfig) {