From e6095b028a6d34c78eb2803ec2d11650f9a925ce Mon Sep 17 00:00:00 2001 From: Tzvetan Mikov Date: Sun, 29 Dec 2024 18:46:16 -0800 Subject: [PATCH] [SH] Changes to compile with wasi-sdk Wasi-sdk is much less Unix-like than the environment provided by Emscripten and requires more profound changes for support. Unix mman emulation requires: - `-lwasi-emulated-mman` Unfortunately JS and C++ exceptions are both a mess. Wasi-sdk [does not support C++ exceptions yet](https://github.com/WebAssembly/wasi-sdk/tree/a4d918fa119c9beb712bf08bbb8fa9996aab0a71?tab=readme-ov-file#notable-limitations). We can live with that, since Hermes for the most part compiles with exceptions disabled anyway. However JSI relies on exceptions and it is part of the compiled library. To get things working one needs to rely on the following hack (which isn't included in this commit): ```c++ extern "C" void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) { llvh::report_fatal_error("C++ exceptions not supported on Wasi"); } extern "C" void* __cxa_allocate_exception(size_t) { llvh::report_fatal_error("C++ exceptions not supported on Wasi"); } ``` This causes any JSI exception to simply abort. JS exceptions work fine with the bytecode interpreter, however code produced by the *native backend* relies on `setjmp/longjmp`. Alas, `setjmp/longjmp` are not well supported by Wasm. This fact was hidden by Emscripten, which used its own JS shim and exceptions to implement them. That, however, does not work with standalone WASI. According to the [Wasi-sdk documentation](https://github.com/WebAssembly/wasi-sdk/blob/a4d918fa119c9beb712bf08bbb8fa9996aab0a71/SetjmpLongjmp.md), `setjmp/longjmp` can be supported, using the Wasm exception handling proposal. This requires: - `-mllvm -wasm-enable-sjlj` - `-lsetjmp` I was able to successfully compile the shermes-generated code to a Wasm binary with the following flags, however I was **not** able to find a single standalone runtime that was able to run it out of the box! Wasmtime and Wasmer refused to load the module with the error "exceptions proposal not enabled". Iwasm failed to load it some error about Wasm operand stack. I am sure that it is possible to either find or configure a Wasm runtime to support the exceptions proposal, but since none of the major runtimes support if out of the box, I consider it useless for now. Sadly. I am sure v8 supports it, but what's the point of using v8 for this? First, v8 already supports JS, and second, Emscripten has that covered already. There is an experimental followup commit which turns setjmp() into noop and longjmp() into abort(). That at least allows natively compiled JS to run, until the first exception... --- cmake/modules/Hermes.cmake | 2 + external/dtoa/locks.cpp | 10 + external/llvh/CMakeLists.txt | 4 +- external/llvh/cmake/config-ix.cmake | 1 + .../llvh/include/llvh/Config/config.h.cmake | 3 + external/llvh/include/llvh/Support/Atomic.h | 16 + .../llvh/include/llvh/Support/FileSystem.h | 10 +- external/llvh/include/llvh/Support/Program.h | 2 +- external/llvh/lib/Support/Host.cpp | 3 + external/llvh/lib/Support/Path.cpp | 3 + external/llvh/lib/Support/Process.cpp | 3 + external/llvh/lib/Support/Program.cpp | 3 + external/llvh/lib/Support/Signals.cpp | 3 + external/llvh/lib/Support/Wasi/Host.inc | 68 ++ external/llvh/lib/Support/Wasi/Path.inc | 903 ++++++++++++++++++ external/llvh/lib/Support/Wasi/Process.inc | 421 ++++++++ external/llvh/lib/Support/Wasi/Program.inc | 203 ++++ external/llvh/lib/Support/Wasi/README.txt | 5 + external/llvh/lib/Support/Wasi/Signals.inc | 55 ++ external/llvh/lib/Support/Wasi/Unix.h | 105 ++ include/hermes/BCGen/HBC/BCProvider.h | 1 + include/hermes/Support/FakeThreads.h | 242 +++++ include/hermes/VM/GCConcurrency.h | 91 +- include/hermes/VM/JSLib/DateCache.h | 5 + .../hermes/VM/Profiler/SamplingProfilerDefs.h | 2 +- include/hermes/VM/TimeLimitMonitor.h | 1 + .../VM/instrumentation/StatSamplingThread.h | 1 + lib/Support/CMakeLists.txt | 1 + lib/Support/OSCompatPosix.cpp | 2 +- lib/Support/OSCompatWasi.cpp | 276 ++++++ lib/Support/SerialExecutor.cpp | 6 + lib/Support/StackExecutor.cpp | 6 +- lib/VM/Instrumentation/ProcessStats.cpp | 2 +- lib/VM/Runtime.cpp | 2 + 34 files changed, 2369 insertions(+), 92 deletions(-) create mode 100644 external/llvh/lib/Support/Wasi/Host.inc create mode 100644 external/llvh/lib/Support/Wasi/Path.inc create mode 100644 external/llvh/lib/Support/Wasi/Process.inc create mode 100644 external/llvh/lib/Support/Wasi/Program.inc create mode 100644 external/llvh/lib/Support/Wasi/README.txt create mode 100644 external/llvh/lib/Support/Wasi/Signals.inc create mode 100644 external/llvh/lib/Support/Wasi/Unix.h create mode 100644 include/hermes/Support/FakeThreads.h create mode 100644 lib/Support/OSCompatWasi.cpp 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) {