From 7e4e9fdb8ff5ce21b541f1d2df9b9215fc3757d9 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Wed, 5 Apr 2023 01:14:43 +0330 Subject: [PATCH] LibWasm: Start implementing WASI This commit starts adding support for WASI, along with the framework to implement all the functions (though only a couple are currently implemented). --- AK/Debug.h.in | 4 + Meta/CMake/all_the_debug_macros.cmake | 1 + .../LibWasm/AbstractMachine/AbstractMachine.h | 2 +- Userland/Libraries/LibWasm/CMakeLists.txt | 1 + Userland/Libraries/LibWasm/Forward.h | 5 + Userland/Libraries/LibWasm/WASI/Wasi.cpp | 1017 +++++++++++++++++ Userland/Libraries/LibWasm/Wasi.h | 943 +++++++++++++++ 7 files changed, 1972 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibWasm/WASI/Wasi.cpp create mode 100644 Userland/Libraries/LibWasm/Wasi.h diff --git a/AK/Debug.h.in b/AK/Debug.h.in index 08f8ec17d1..3aa7149ad2 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -498,6 +498,10 @@ # cmakedefine01 UTF8_DEBUG #endif +#ifndef WASI_DEBUG +# cmakedefine01 WASI_DEBUG +#endif + #ifndef WASM_BINPARSER_DEBUG # cmakedefine01 WASM_BINPARSER_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index a44e5b13e3..45cddb1b15 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -212,6 +212,7 @@ set(VOLATILE_PAGE_RANGES_DEBUG ON) set(VRA_DEBUG ON) set(WAITBLOCK_DEBUG ON) set(WAITQUEUE_DEBUG ON) +set(WASI_DEBUG ON) set(WASM_BINPARSER_DEBUG ON) set(WASM_TRACE_DEBUG ON) set(WASM_VALIDATOR_DEBUG ON) diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 4c8ca6261f..dac570e96f 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -121,7 +121,7 @@ public: ALWAYS_INLINE Value& operator=(Value const& value) = default; template - ALWAYS_INLINE Optional to() + ALWAYS_INLINE Optional to() const { Optional result; m_value.visit( diff --git a/Userland/Libraries/LibWasm/CMakeLists.txt b/Userland/Libraries/LibWasm/CMakeLists.txt index 5b78f92da6..729156278c 100644 --- a/Userland/Libraries/LibWasm/CMakeLists.txt +++ b/Userland/Libraries/LibWasm/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES AbstractMachine/Validator.cpp Parser/Parser.cpp Printer/Printer.cpp + WASI/Wasi.cpp ) serenity_lib(LibWasm wasm) diff --git a/Userland/Libraries/LibWasm/Forward.h b/Userland/Libraries/LibWasm/Forward.h index 4bb2c85f84..84006190ef 100644 --- a/Userland/Libraries/LibWasm/Forward.h +++ b/Userland/Libraries/LibWasm/Forward.h @@ -11,5 +11,10 @@ namespace Wasm { class AbstractMachine; class Validator; struct ValidationError; +struct Interpreter; + +namespace Wasi { +struct Implementation; +} } diff --git a/Userland/Libraries/LibWasm/WASI/Wasi.cpp b/Userland/Libraries/LibWasm/WASI/Wasi.cpp new file mode 100644 index 0000000000..22cc5b369a --- /dev/null +++ b/Userland/Libraries/LibWasm/WASI/Wasi.cpp @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Wasm::Wasi::ABI { + +template +Wasm::Value CompatibleValue::to_wasm_value() const +{ + return Wasm::Value(value); +} + +template +T deserialize(CompatibleValue const& data) +{ + return deserialize(Array { ReadonlyBytes { &data.value, sizeof(data.value) } }); +} + +template +void serialize(T const& value, Array bytes) +{ + if constexpr (IsEnum) + return serialize(to_underlying(value), move(bytes)); + else if constexpr (IsIntegral) + ReadonlyBytes { &value, sizeof(value) }.copy_to(bytes[0]); + else if constexpr (IsSpecializationOf) + return serialize(value.value(), move(bytes)); + else + return value.serialize_into(move(bytes)); +} + +template +T deserialize(Array const& bytes) +{ + if constexpr (IsEnum) { + return static_cast(deserialize>(bytes)); + } else if constexpr (IsIntegral) { + T value; + ByteReader::load(bytes[0].data(), value); + return value; + } else if constexpr (IsSpecializationOf) { + return deserialize>(bytes); + } else { + return T::read_from(bytes); + } +} + +template +CompatibleValue to_compatible_value(Wasm::Value const& value) +{ + using Type = typename ToCompatibleValue::Type; + // Note: the type can't be something else, we've already checked before through the function type's runtime checker. + auto converted_value = *value.template to(); + return { .value = converted_value }; +} + +} + +namespace Wasm::Wasi { + +void ArgsSizes::serialize_into(Array bytes) const +{ + ABI::serialize(count, Array { bytes[0] }); + ABI::serialize(size, Array { bytes[1] }); +} + +void EnvironSizes::serialize_into(Array bytes) const +{ + ABI::serialize(count, Array { bytes[0] }); + ABI::serialize(size, Array { bytes[1] }); +} + +void SockRecvResult::serialize_into(Array bytes) const +{ + ABI::serialize(size, Array { bytes[0] }); + ABI::serialize(roflags, Array { bytes[1] }); +} + +void ROFlags::serialize_into(Array bytes) const +{ + ABI::serialize(data, Array { bytes[0] }); +} + +template +void LittleEndian::serialize_into(Array bytes) const +{ + ABI::serialize(m_value, move(bytes)); +} + +template +LittleEndian LittleEndian::read_from(Array const& bytes) +{ + auto swapped = ABI::deserialize(bytes); + return bit_cast>(swapped); +} + +Rights Rights::read_from(Array const& bytes) +{ + Rights rights { .data = 0 }; + bytes[0].copy_to(rights.data.bytes()); + return rights; +} + +void Rights::serialize_into(Array bytes) const +{ + data.bytes().copy_to(bytes[0]); +} + +void FDFlags::serialize_into(Array bytes) const +{ + ReadonlyBytes { &data, sizeof(data) }.copy_to(bytes[0]); +} + +FDFlags FDFlags::read_from(Array const& bytes) +{ + FDFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +FSTFlags FSTFlags::read_from(Array const& bytes) +{ + FSTFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +OFlags OFlags::read_from(Array const& bytes) +{ + OFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +SDFlags SDFlags::read_from(Array const& bytes) +{ + SDFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +void FDStat::serialize_into(Array bytes) const +{ + auto data = bytes[0]; + ABI::serialize(fs_filetype, Array { data.slice(offsetof(FDStat, fs_filetype), sizeof(fs_filetype)) }); + ABI::serialize(fs_flags, Array { data.slice(offsetof(FDStat, fs_flags), sizeof(fs_flags)) }); + ABI::serialize(fs_rights_base, Array { data.slice(offsetof(FDStat, fs_rights_base), sizeof(fs_rights_base)) }); + ABI::serialize(fs_rights_inheriting, Array { data.slice(offsetof(FDStat, fs_rights_inheriting), sizeof(fs_rights_inheriting)) }); +} + +void PreStat::serialize_into(Array bytes) const +{ + auto data = bytes[0]; + ABI::serialize(tag, Array { data.slice(0, sizeof(tag)) }); + if (tag == 0) + ABI::serialize(dir, Array { data.slice(offsetof(PreStat, dir), sizeof(dir)) }); + else + VERIFY_NOT_REACHED(); +} + +void PreStatDir::serialize_into(Array bytes) const +{ + ABI::serialize(pr_name_len, move(bytes)); +} + +void FileStat::serialize_into(Array bytes) const +{ + auto data = bytes[0]; + ABI::serialize(dev, Array { data.slice(0, sizeof(dev)) }); + ABI::serialize(ino, Array { data.slice(offsetof(FileStat, ino), sizeof(ino)) }); + ABI::serialize(filetype, Array { data.slice(offsetof(FileStat, filetype), sizeof(filetype)) }); + ABI::serialize(nlink, Array { data.slice(offsetof(FileStat, nlink), sizeof(nlink)) }); + ABI::serialize(size, Array { data.slice(offsetof(FileStat, size), sizeof(size)) }); + ABI::serialize(atim, Array { data.slice(offsetof(FileStat, atim), sizeof(atim)) }); + ABI::serialize(mtim, Array { data.slice(offsetof(FileStat, mtim), sizeof(mtim)) }); + ABI::serialize(ctim, Array { data.slice(offsetof(FileStat, ctim), sizeof(ctim)) }); +} + +RIFlags RIFlags::read_from(Array const& bytes) +{ + RIFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +LookupFlags LookupFlags::read_from(Array const& bytes) +{ + LookupFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +CIOVec CIOVec::read_from(Array const& bytes) +{ + return CIOVec { + .buf = ABI::deserialize(Array { bytes[0].slice(offsetof(CIOVec, buf), sizeof(buf)) }), + .buf_len = ABI::deserialize(Array { bytes[0].slice(offsetof(CIOVec, buf_len), sizeof(buf_len)) }), + }; +} + +IOVec IOVec::read_from(Array const& bytes) +{ + return IOVec { + .buf = ABI::deserialize(Array { bytes[0].slice(offsetof(IOVec, buf), sizeof(buf)) }), + .buf_len = ABI::deserialize(Array { bytes[0].slice(offsetof(IOVec, buf_len), sizeof(buf_len)) }), + }; +} + +template +ErrorOr> copy_typed_array(Configuration& configuration, Pointer source, Size count) +{ + Vector values; + TRY(values.try_ensure_capacity(count)); + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + UnderlyingPointerType address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) { + return Error::from_errno(ENOBUFS); + } + + for (Size i = 0; i < count; i += 1) { + values.unchecked_append(T::read_from(Array { ReadonlyBytes { memory->data().bytes().slice(address, size) } })); + address += size; + } + + return values; +} + +template +ErrorOr copy_typed_value_to(Configuration& configuration, T const& value, Pointer destination) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + UnderlyingPointerType address = destination.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + size) { + return Error::from_errno(ENOBUFS); + } + + ABI::serialize(value, Array { Bytes { memory->data().bytes().slice(address, size) } }); + return {}; +} + +template +ErrorOr> slice_typed_memory(Configuration& configuration, Pointer source, Size count) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + auto address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) + return Error::from_errno(ENOBUFS); + + auto untyped_slice = memory->data().bytes().slice(address, size * count); + return Span(untyped_slice.data(), count); +} + +template +ErrorOr> slice_typed_memory(Configuration& configuration, ConstPointer source, Size count) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + auto address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) + return Error::from_errno(ENOBUFS); + + auto untyped_slice = memory->data().bytes().slice(address, size * count); + return Span(untyped_slice.data(), count); +} + +static ErrorOr copy_string_including_terminating_null(Configuration& configuration, StringView string, Pointer target) +{ + auto slice = TRY(slice_typed_memory(configuration, target, string.bytes().size() + 1)); + string.bytes().copy_to(slice); + slice[string.bytes().size()] = 0; + return slice.size(); +} + +static ErrorOr copy_string_excluding_terminating_null(Configuration& configuration, StringView string, Pointer target, Size target_length) +{ + auto byte_count = min(string.bytes().size(), target_length); + auto slice = TRY(slice_typed_memory(configuration, target, byte_count)); + string.bytes().copy_trimmed_to(slice); + return byte_count; +} + +static Errno errno_value_from_errno(int value); + +Vector const& Implementation::arguments() const +{ + if (!cache.cached_arguments.has_value()) { + cache.cached_arguments.lazy_emplace([&] { + if (provide_arguments) + return provide_arguments(); + return Vector {}; + }); + } + + return *cache.cached_arguments; +} + +Vector const& Implementation::environment() const +{ + if (!cache.cached_environment.has_value()) { + cache.cached_environment.lazy_emplace([&] { + if (provide_environment) + return provide_environment(); + return Vector {}; + }); + } + + return *cache.cached_environment; +} + +Vector const& Implementation::preopened_directories() const +{ + if (!cache.cached_preopened_directories.has_value()) { + cache.cached_preopened_directories.lazy_emplace([&] { + if (provide_preopened_directories) + return provide_preopened_directories(); + return Vector {}; + }); + } + + return *cache.cached_preopened_directories; +} + +Implementation::Descriptor Implementation::map_fd(FD fd) +{ + u32 fd_value = fd.value(); + if (auto* value = m_fd_map.find(fd_value)) + return value->downcast(); + + return UnmappedDescriptor(fd_value); +} + +ErrorOr> Implementation::impl$args_get(Configuration& configuration, Pointer> argv, Pointer argv_buf) +{ + UnderlyingPointerType raw_argv_buffer = argv_buf.value(); + UnderlyingPointerType raw_argv = argv.value(); + + for (auto& entry : arguments()) { + auto ptr = Pointer { raw_argv_buffer }; + auto byte_count = TRY(copy_string_including_terminating_null(configuration, entry.bytes_as_string_view(), ptr)); + raw_argv_buffer += byte_count; + + TRY(copy_typed_value_to(configuration, ptr, Pointer> { raw_argv })); + raw_argv += sizeof(ptr); + } + + return Result {}; +} + +ErrorOr> Implementation::impl$args_sizes_get(Configuration&) +{ + size_t count = 0; + size_t total_size = 0; + for (auto& entry : arguments()) { + count += 1; + total_size += entry.bytes().size() + 1; // 1 extra byte for terminating null. + } + + return Result(ArgsSizes { + count, + total_size, + }); +} + +ErrorOr> Implementation::impl$environ_get(Configuration& configuration, Pointer> environ, Pointer environ_buf) +{ + UnderlyingPointerType raw_environ_buffer = environ_buf.value(); + UnderlyingPointerType raw_environ = environ.value(); + + for (auto& entry : environment()) { + auto ptr = Pointer { raw_environ_buffer }; + auto byte_count = TRY(copy_string_including_terminating_null(configuration, entry.bytes_as_string_view(), ptr)); + raw_environ_buffer += byte_count; + + TRY(copy_typed_value_to(configuration, ptr, Pointer> { raw_environ })); + raw_environ += sizeof(ptr); + } + + return Result {}; +} + +ErrorOr> Implementation::impl$environ_sizes_get(Configuration&) +{ + size_t count = 0; + size_t total_size = 0; + for (auto& entry : environment()) { + count += 1; + total_size += entry.bytes().size() + 1; // 1 extra byte for terminating null. + } + + return Result(EnvironSizes { + count, + total_size, + }); +} + +ErrorOr> Implementation::impl$proc_exit(Configuration&, ExitCode exit_code) +{ + exit(exit_code); +} + +ErrorOr> Implementation::impl$fd_close(Configuration&, FD fd) +{ + return map_fd(fd).visit( + [&](u32 fd) -> Result { + if (close(bit_cast(fd)) != 0) + return errno_value_from_errno(errno); + return {}; + }, + [&](PreopenedDirectoryDescriptor) -> Result { + return errno_value_from_errno(EISDIR); + }, + [&](UnmappedDescriptor) -> Result { + return errno_value_from_errno(EBADF); + }); +} + +ErrorOr> Implementation::impl$fd_write(Configuration& configuration, FD fd, Pointer iovs, Size iovs_len) +{ + auto mapped_fd = map_fd(fd); + if (!mapped_fd.has()) + return errno_value_from_errno(EBADF); + + u32 fd_value = mapped_fd.get(); + Size bytes_written = 0; + for (auto& iovec : TRY(copy_typed_array(configuration, iovs, iovs_len))) { + auto slice = TRY(slice_typed_memory(configuration, iovec.buf, iovec.buf_len)); + auto result = write(fd_value, slice.data(), slice.size()); + if (result < 0) + return errno_value_from_errno(errno); + bytes_written += static_cast(result); + } + return bytes_written; +} + +ErrorOr> Implementation::impl$fd_prestat_get(Configuration&, FD fd) +{ + auto& paths = preopened_directories(); + return map_fd(fd).visit( + [&](UnmappedDescriptor unmapped_fd) -> Result { + // Map the new fd to the next available directory. + if (m_first_unmapped_preopened_directory_index >= paths.size()) + return errno_value_from_errno(EBADF); + + auto index = m_first_unmapped_preopened_directory_index++; + m_fd_map.insert(unmapped_fd.value(), PreopenedDirectoryDescriptor(index)); + return PreStat { + .tag = 0, + .dir = PreStatDir { + .pr_name_len = paths[index].mapped_path.string().bytes().size(), + }, + }; + }, + [&](u32) -> Result { + return errno_value_from_errno(EBADF); + }, + [&](PreopenedDirectoryDescriptor fd) -> Result { + return PreStat { + .tag = 0, + .dir = PreStatDir { + .pr_name_len = paths[fd.value()].mapped_path.string().bytes().size(), + }, + }; + }); +} + +ErrorOr> Implementation::impl$fd_prestat_dir_name(Configuration& configuration, FD fd, Pointer path, Size path_len) +{ + auto mapped_fd = map_fd(fd); + if (!mapped_fd.has()) + return errno_value_from_errno(EBADF); + + auto& entry = preopened_directories()[mapped_fd.get().value()]; + auto byte_count = TRY(copy_string_excluding_terminating_null(configuration, entry.mapped_path.string().view(), path, path_len)); + if (byte_count < path_len.value()) + return errno_value_from_errno(ENOBUFS); + + return Result {}; +} + +ErrorOr> Implementation::impl$path_filestat_get(Configuration& configuration, FD fd, LookupFlags flags, ConstPointer path, Size path_len) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has()) { + auto& entry = preopened_directories()[mapped_fd.get().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + int options = 0; + if (!flags.bits.symlink_follow) + options |= AT_SYMLINK_NOFOLLOW; + + auto slice = TRY(slice_typed_memory(configuration, path, path_len)); + auto null_terminated_string = DeprecatedString::copy(slice); + + struct stat stat_buf; + if (fstatat(dir_fd, null_terminated_string.characters(), &stat_buf, options) < 0) + return errno_value_from_errno(errno); + + constexpr auto file_type_of = [](struct stat const& buf) { + if (S_ISDIR(buf.st_mode)) + return FileType::Directory; + if (S_ISCHR(buf.st_mode)) + return FileType::CharacterDevice; + if (S_ISBLK(buf.st_mode)) + return FileType::BlockDevice; + if (S_ISREG(buf.st_mode)) + return FileType::RegularFile; + if (S_ISFIFO(buf.st_mode)) + return FileType::Unknown; // no Pipe? :yakfused: + if (S_ISLNK(buf.st_mode)) + return FileType::SymbolicLink; + if (S_ISSOCK(buf.st_mode)) + return FileType::SocketDGram; // :shrug: + return FileType::Unknown; + }; + + return Result(FileStat { + .dev = stat_buf.st_dev, + .ino = stat_buf.st_ino, + .filetype = file_type_of(stat_buf), + .nlink = stat_buf.st_nlink, + .size = stat_buf.st_size, + .atim = stat_buf.st_atime, + .mtim = stat_buf.st_mtime, + .ctim = stat_buf.st_ctime, + }); +} + +ErrorOr> Implementation::impl$path_create_directory(Configuration& configuration, FD fd, Pointer path, Size path_len) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has()) { + auto& entry = preopened_directories()[mapped_fd.get().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + auto slice = TRY(slice_typed_memory(configuration, path, path_len)); + auto null_terminated_string = DeprecatedString::copy(slice); + + if (mkdirat(dir_fd, null_terminated_string.characters(), 0755) < 0) + return errno_value_from_errno(errno); + + return Result {}; +} + +ErrorOr> Implementation::impl$path_open(Configuration& configuration, FD fd, LookupFlags lookup_flags, Pointer path, Size path_len, OFlags o_flags, Rights, Rights, FDFlags fd_flags) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has()) { + auto& entry = preopened_directories()[mapped_fd.get().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + // FIXME: What should we do with dsync/rsync? + + int open_flags = 0; + if (fd_flags.bits.append) + open_flags |= O_APPEND; + if (fd_flags.bits.nonblock) + open_flags |= O_NONBLOCK; + if (fd_flags.bits.sync) + open_flags |= O_SYNC; + + if (o_flags.bits.trunc) + open_flags |= O_TRUNC; + if (o_flags.bits.creat) + open_flags |= O_CREAT; + if (o_flags.bits.directory) + open_flags |= O_DIRECTORY; + if (o_flags.bits.excl) + open_flags |= O_EXCL; + + if (!lookup_flags.bits.symlink_follow) + open_flags |= O_NOFOLLOW; + + auto path_data = TRY(slice_typed_memory(configuration, path, path_len)); + auto path_string = DeprecatedString::copy(path_data); + + int opened_fd = openat(dir_fd, path_string.characters(), open_flags, 0644); + if (opened_fd < 0) + return errno_value_from_errno(errno); + + // FIXME: Implement Rights and RightsInheriting. + + return FD(opened_fd); +} + +ErrorOr> Implementation::impl$clock_time_get(Configuration&, ClockID id, Timestamp precision) +{ + constexpr u64 nanoseconds_in_millisecond = 1000'000ull; + constexpr u64 nanoseconds_in_second = 1000'000'000ull; + + clockid_t clock_id; + switch (id) { + case ClockID::Realtime: + if (precision >= nanoseconds_in_millisecond) + clock_id = CLOCK_REALTIME_COARSE; + else + clock_id = CLOCK_REALTIME; + break; + case ClockID::Monotonic: + if (precision >= nanoseconds_in_millisecond) + clock_id = CLOCK_MONOTONIC_COARSE; + else + clock_id = CLOCK_MONOTONIC; + break; + case ClockID::ProcessCPUTimeID: + case ClockID::ThreadCPUTimeID: + return Errno::NoSys; + break; + } + + struct timespec ts; + if (clock_gettime(clock_id, &ts) < 0) + return errno_value_from_errno(errno); + + return Result { static_cast(ts.tv_sec) * nanoseconds_in_second + static_cast(ts.tv_nsec) }; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +ErrorOr> Implementation::impl$clock_res_get(Configuration&, ClockID id) +{ + return Errno::NoSys; +} +ErrorOr> Implementation::impl$fd_advise(Configuration&, FD, FileSize offset, FileSize len, Advice) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_allocate(Configuration&, FD, FileSize offset, FileSize len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_datasync(Configuration&, FD) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_fdstat_get(Configuration&, FD) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_fdstat_set_flags(Configuration&, FD, FDFlags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_fdstat_set_rights(Configuration&, FD, Rights fs_rights_base, Rights fs_rights_inheriting) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_filestat_get(Configuration&, FD) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_filestat_set_size(Configuration&, FD, FileSize) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_filestat_set_times(Configuration&, FD, Timestamp atim, Timestamp mtim, FSTFlags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_pread(Configuration&, FD, Pointer iovs, Size iovs_len, FileSize offset) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_pwrite(Configuration&, FD, Pointer iovs, Size iovs_len, FileSize offset) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_read(Configuration&, FD, Pointer iovs, Size iovs_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_readdir(Configuration&, FD, Pointer buf, Size buf_len, DirCookie cookie) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_renumber(Configuration&, FD from, FD to) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_seek(Configuration&, FD, FileDelta offset, Whence whence) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_sync(Configuration&, FD) { return Errno::NoSys; } +ErrorOr> Implementation::impl$fd_tell(Configuration&, FD) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_filestat_set_times(Configuration&, FD, LookupFlags, Pointer path, Size path_len, Timestamp atim, Timestamp mtim, FSTFlags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_link(Configuration&, FD, LookupFlags, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_readlink(Configuration&, FD, LookupFlags, Pointer path, Size path_len, Pointer buf, Size buf_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_remove_directory(Configuration&, FD, Pointer path, Size path_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_rename(Configuration&, FD, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_symlink(Configuration&, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$path_unlink_file(Configuration&, FD, Pointer path, Size path_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$poll_oneoff(Configuration&, ConstPointer in, Pointer out, Size nsubscriptions) { return Errno::NoSys; } +ErrorOr> Implementation::impl$proc_raise(Configuration&, Signal) { return Errno::NoSys; } +ErrorOr> Implementation::impl$sched_yield(Configuration&) { return Errno::NoSys; } +ErrorOr> Implementation::impl$random_get(Configuration&, Pointer buf, Size buf_len) { return Errno::NoSys; } +ErrorOr> Implementation::impl$sock_accept(Configuration&, FD fd, FDFlags fd_flags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$sock_recv(Configuration&, FD fd, Pointer ri_data, Size ri_data_len, RIFlags ri_flags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$sock_send(Configuration&, FD fd, Pointer si_data, Size si_data_len, SIFlags si_flags) { return Errno::NoSys; } +ErrorOr> Implementation::impl$sock_shutdown(Configuration&, FD fd, SDFlags how) { return Errno::NoSys; } + +#pragma GCC diagnostic pop + +template +static Array address_spans(Span values, Configuration& configuration) +{ + Array result; + auto memory = configuration.store().get(MemoryAddress { 0 })->data().span(); + for (size_t i = 0; i < N; ++i) + result[i] = memory.slice(*values[i].to()); + return result; +} + +#define ENUMERATE_FUNCTION_NAMES(M) \ + M(args_get) \ + M(args_sizes_get) \ + M(environ_get) \ + M(environ_sizes_get) \ + M(clock_res_get) \ + M(clock_time_get) \ + M(fd_advise) \ + M(fd_allocate) \ + M(fd_close) \ + M(fd_datasync) \ + M(fd_fdstat_get) \ + M(fd_fdstat_set_flags) \ + M(fd_fdstat_set_rights) \ + M(fd_filestat_get) \ + M(fd_filestat_set_size) \ + M(fd_filestat_set_times) \ + M(fd_pread) \ + M(fd_prestat_get) \ + M(fd_prestat_dir_name) \ + M(fd_pwrite) \ + M(fd_read) \ + M(fd_readdir) \ + M(fd_renumber) \ + M(fd_seek) \ + M(fd_sync) \ + M(fd_tell) \ + M(fd_write) \ + M(path_create_directory) \ + M(path_filestat_get) \ + M(path_filestat_set_times) \ + M(path_link) \ + M(path_open) \ + M(path_readlink) \ + M(path_remove_directory) \ + M(path_rename) \ + M(path_symlink) \ + M(path_unlink_file) \ + M(poll_oneoff) \ + M(proc_exit) \ + M(proc_raise) \ + M(sched_yield) \ + M(random_get) \ + M(sock_accept) \ + M(sock_recv) \ + M(sock_send) \ + M(sock_shutdown) + +struct Names { +#define NAME(x) FlyString x; + ENUMERATE_FUNCTION_NAMES(NAME) +#undef NAME + + static ErrorOr construct() + { + return Names { +#define NAME(x) .x = TRY(FlyString::from_utf8(#x##sv)), + ENUMERATE_FUNCTION_NAMES(NAME) +#undef NAME + }; + } +}; + +ErrorOr Implementation::function_by_name(StringView name) +{ + auto name_for_comparison = TRY(FlyString::from_utf8(name)); + static auto names = TRY(Names::construct()); + +#define IMPL(x) \ + if (name_for_comparison == names.x) \ + return invocation_of<&Implementation::impl$##x>(#x##sv); + + ENUMERATE_FUNCTION_NAMES(IMPL) + +#undef IMPL + + return Error::from_string_literal("No such host function"); +} + +namespace ABI { + +template +auto CompatibleValueType = IsOneOf + ? Wasm::ValueType(Wasm::ValueType::I32) + : Wasm::ValueType(Wasm::ValueType::I64); + +template> (Implementation::*impl)(Configuration&, Args...)> +struct InvocationOf { + HostFunction operator()(Implementation& self, StringView function_name) + { + Vector arguments_types { CompatibleValueType::Type>... }; + if constexpr (!IsVoid) { + if constexpr (requires { declval(); }) { + for_each_type([&](TypeWrapper) { + arguments_types.append(CompatibleValueType>::Type>); + }); + } else { + arguments_types.append(CompatibleValueType>::Type>); + } + } + + return HostFunction( + [&self, function_name](Configuration& configuration, Vector& arguments) -> Wasm::Result { + Tuple args = [&](IndexSequence) + { + return Tuple { ABI::deserialize(ABI::to_compatible_value(arguments[Is]))... }; + } + .template operator()(MakeIndexSequence()); + + auto result = args.apply_as_args([&](auto&&... impl_args) { return (self.*impl)(configuration, impl_args...); }); + dbgln_if(WASI_DEBUG, "WASI: Called {}", function_name); + + if (result.is_error()) + return Wasm::Trap { DeprecatedString::formatted("Invalid call to {}() = {}", function_name, result.error()) }; + + auto value = result.release_value(); + if (value.is_error()) + return Wasm::Result { Vector { Value { ValueType(ValueType::I32), static_cast(to_underlying(value.error().value())) } } }; + + if constexpr (!IsVoid) { + // Return values are passed as pointers, after the arguments + if constexpr (requires { &R::serialize_into; }) { + constexpr auto ResultCount = [](void(R::*)(Array) const) { return N; } + (&R::serialize_into); + ABI::serialize(*value.result(), address_spans(arguments.span().slice(sizeof...(Args)), configuration)); + } else { + ABI::serialize(*value.result(), address_spans<1>(arguments.span().slice(sizeof...(Args)), configuration)); + } + } + // Return value is errno, we have nothing to return. + return Wasm::Result { Vector { Value(ValueType(ValueType::I32), 0ull) } }; + }, + FunctionType { + move(arguments_types), + { ValueType(ValueType::I32) }, + }); + } +}; + +}; + +Errno errno_value_from_errno(int value) +{ + switch (value) { +#ifdef ESUCCESS + case ESUCCESS: + return Errno::Success; +#endif + case E2BIG: + return Errno::TooBig; + case EACCES: + return Errno::Access; + case EADDRINUSE: + return Errno::AddressInUse; + case EADDRNOTAVAIL: + return Errno::AddressNotAvailable; + case EAFNOSUPPORT: + return Errno::AFNotSupported; + case EAGAIN: + return Errno::Again; + case EALREADY: + return Errno::Already; + case EBADF: + return Errno::BadF; + case EBUSY: + return Errno::Busy; + case ECANCELED: + return Errno::Canceled; + case ECHILD: + return Errno::Child; + case ECONNABORTED: + return Errno::ConnectionAborted; + case ECONNREFUSED: + return Errno::ConnectionRefused; + case ECONNRESET: + return Errno::ConnectionReset; + case EDEADLK: + return Errno::Deadlock; + case EDESTADDRREQ: + return Errno::DestinationAddressRequired; + case EDOM: + return Errno::Domain; + case EEXIST: + return Errno::Exist; + case EFAULT: + return Errno::Fault; + case EFBIG: + return Errno::FBig; + case EHOSTUNREACH: + return Errno::HostUnreachable; + case EILSEQ: + return Errno::IllegalSequence; + case EINPROGRESS: + return Errno::InProgress; + case EINTR: + return Errno::Interrupted; + case EINVAL: + return Errno::Invalid; + case EIO: + return Errno::IO; + case EISCONN: + return Errno::IsConnected; + case EISDIR: + return Errno::IsDirectory; + case ELOOP: + return Errno::Loop; + case EMFILE: + return Errno::MFile; + case EMLINK: + return Errno::MLink; + case EMSGSIZE: + return Errno::MessageSize; + case ENAMETOOLONG: + return Errno::NameTooLong; + case ENETDOWN: + return Errno::NetworkDown; + case ENETRESET: + return Errno::NetworkReset; + case ENETUNREACH: + return Errno::NetworkUnreachable; + case ENFILE: + return Errno::NFile; + case ENOBUFS: + return Errno::NoBufferSpace; + case ENODEV: + return Errno::NoDevice; + case ENOENT: + return Errno::NoEntry; + case ENOEXEC: + return Errno::NoExec; + case ENOLCK: + return Errno::NoLock; + case ENOMEM: + return Errno::NoMemory; + case ENOPROTOOPT: + return Errno::NoProtocolOption; + case ENOSPC: + return Errno::NoSpace; + case ENOSYS: + return Errno::NoSys; + case ENOTCONN: + return Errno::NotConnected; + case ENOTDIR: + return Errno::NotDirectory; + case ENOTEMPTY: + return Errno::NotEmpty; + case ENOTRECOVERABLE: + return Errno::NotRecoverable; + case ENOTSOCK: + return Errno::NotSocket; + case ENOTSUP: + return Errno::NotSupported; + case ENOTTY: + return Errno::NoTTY; + case ENXIO: + return Errno::NXIO; + case EOVERFLOW: + return Errno::Overflow; + case EPERM: + return Errno::Permission; + case EPIPE: + return Errno::Pipe; + case EPROTO: + return Errno::Protocol; + case EPROTONOSUPPORT: + return Errno::ProtocolNotSupported; + case EPROTOTYPE: + return Errno::ProtocolType; + case ERANGE: + return Errno::Range; + case ESPIPE: + return Errno::SPipe; + case ESRCH: + return Errno::SRCH; + case ESTALE: + return Errno::Stale; + case ETIMEDOUT: + return Errno::TimedOut; + case ETXTBSY: + return Errno::TextBusy; + case EXDEV: + return Errno::XDev; + default: + return Errno::Invalid; + } +} +} diff --git a/Userland/Libraries/LibWasm/Wasi.h b/Userland/Libraries/LibWasm/Wasi.h new file mode 100644 index 0000000000..0dedce568d --- /dev/null +++ b/Userland/Libraries/LibWasm/Wasi.h @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Wasm::Wasi::ABI { + +// NOTE: The "real" ABI used in the wild is described by [api.h from libc-bottom-half](https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h) +// This is *not* the same ABI as the one described in the WASI spec, nor is it the same ABI as api.h on wasi-libc/master. +// The highlights of the ABI are: +// - (most) structs are passed as pointers to heap. +// - arrays are fat pointers splat across two arguments +// - return object locations are also passed as arguments, the number of arguments depends on the return type itself: +// - ArgsSizes / EnvironSizes / the return type of sock_recv use two arguments +// - everything else is passed like a normal struct + +template +struct InvocationOf { + HostFunction operator()(Implementation&, StringView name); +}; + +template +void serialize(T const&, Array); + +template +T deserialize(Array const&); + +template +struct ToCompatibleValue { + using Type = void; +}; + +template +struct CompatibleValue { + typename ToCompatibleValue::Type value; + + Wasm::Value to_wasm_value() const; +}; + +template +CompatibleValue to_compatible_value(Wasm::Value const&); + +template +T deserialize(CompatibleValue const&); + +} + +namespace Wasm::Wasi { + +// NOTE: This is a copy of LittleEndian from Endian.h, +// we can't use those because they have a packed attribute, and depend on it; +// but we want proper alignment on these types. +template +class alignas(T) LittleEndian { +public: + constexpr LittleEndian() = default; + + constexpr LittleEndian(T value) + : m_value(AK::convert_between_host_and_little_endian(value)) + { + } + + constexpr operator T() const { return AK::convert_between_host_and_little_endian(m_value); } + constexpr T value() const { return AK::convert_between_host_and_little_endian(m_value); } + + LittleEndian& operator+=(T other) + { + m_value = AK::convert_between_host_and_little_endian(AK::convert_between_host_and_little_endian(m_value) + other); + return *this; + } + + // This returns the internal representation. In this case, that is the value stored in little endian format. + constexpr Bytes bytes() { return Bytes { &m_value, sizeof(m_value) }; } + constexpr ReadonlyBytes bytes() const { return ReadonlyBytes { &m_value, sizeof(m_value) }; } + + void serialize_into(Array bytes) const; + static LittleEndian read_from(Array const& bytes); + +private: + T m_value { 0 }; +}; + +using Size = LittleEndian; +using FileSize = LittleEndian; +using Timestamp = LittleEndian; + +namespace Detail { +template +struct __Pointer_tag; +template +struct __ConstPointer_tag; +} + +// NOTE: Might need to be updated if WASI ever supports memory64. +using UnderlyingPointerType = u32; + +template +using Pointer = DistinctNumeric, Detail::__Pointer_tag, AK::DistinctNumericFeature::Comparison>; +template +using ConstPointer = DistinctNumeric, Detail::__ConstPointer_tag, AK::DistinctNumericFeature::Comparison>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L70 +enum class ClockID : u32 { + Realtime, + Monotonic, + ProcessCPUTimeID, + ThreadCPUTimeID, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L105 +enum class Errno : u16 { + Success, + TooBig, + Access, + AddressInUse, + AddressNotAvailable, + AFNotSupported, + Again, + Already, + BadF, + BadMessage, + Busy, + Canceled, + Child, + ConnectionAborted, + ConnectionRefused, + ConnectionReset, + Deadlock, + DestinationAddressRequired, + Domain, + DQuot, // Reserved, Unused. + Exist, + Fault, + FBig, + HostUnreachable, + IdentifierRemoved, + IllegalSequence, + InProgress, + Interrupted, + Invalid, + IO, + IsConnected, + IsDirectory, + Loop, + MFile, + MLink, + MessageSize, + MultiHop, // Reserved, Unused. + NameTooLong, + NetworkDown, + NetworkReset, + NetworkUnreachable, + NFile, + NoBufferSpace, + NoDevice, + NoEntry, + NoExec, + NoLock, + NoLink, + NoMemory, + NoMessage, + NoProtocolOption, + NoSpace, + NoSys, + NotConnected, + NotDirectory, + NotEmpty, + NotRecoverable, + NotSocket, + NotSupported, + NoTTY, + NXIO, + Overflow, + OwnerDead, + Permission, + Pipe, + Protocol, + ProtocolNotSupported, + ProtocolType, + Range, + ReadOnlyFS, + SPipe, + SRCH, + Stale, + TimedOut, + TextBusy, + XDev, + NotCapable, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L498 +struct Rights { + using CompatibleType = u64; + + struct Bits { + bool fd_datasync : 1; + bool fd_read : 1; + bool fd_seek : 1; + bool fd_fdstat_set_flags : 1; + bool fd_sync : 1; + bool fd_tell : 1; + bool fd_write : 1; + bool fd_advise : 1; + bool fd_allocate : 1; + bool path_create_directory : 1; + bool path_create_file : 1; + bool path_link_source : 1; + bool path_link_target : 1; + bool path_open : 1; + bool fd_readdir : 1; + bool path_readlink : 1; + bool path_rename_source : 1; + bool path_rename_target : 1; + bool path_filestat_get : 1; + bool path_filestat_set_size : 1; + bool path_filestat_set_times : 1; + bool fd_filestat_get : 1; + bool fd_filestat_set_size : 1; + bool fd_filestat_set_times : 1; + bool path_symlink : 1; + bool path_remove_directory : 1; + bool path_unlink_file : 1; + bool poll_fd_readwrite : 1; + bool sock_shutdown : 1; + bool sock_accept : 1; + + u64 _unused : 34; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static Rights read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L663 +using FD = DistinctNumeric, struct __FD_tag, AK::DistinctNumericFeature::Comparison>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L671 +struct IOVec { + Pointer buf; + Size buf_len; + + static IOVec read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L692 +struct CIOVec { + ConstPointer buf; + Size buf_len; + + static CIOVec read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L713 +using FileDelta = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L721 +enum class Whence : u8 { + Set, + Cur, + End, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L746 +using DirCookie = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L754 +using DirNameLen = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L762 +using INode = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L770 +enum class FileType : u8 { + Unknown, + BlockDevice, + CharacterDevice, + Directory, + RegularFile, + SocketDGram, + SocketStream, + SymbolicLink, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L818 +struct DirEnt { + DirCookie d_next; + INode d_ino; + DirNameLen d_namlen; + FileType d_type; + u8 _padding[3] { 0 }; // Not part of the API, but the struct is required to be 24 bytes - even though it has no explicit padding. +}; +static_assert(sizeof(DirEnt) == 24); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L851 +enum class Advice : u8 { + Normal, + Sequential, + Random, + WillNeed, + DontNeed, + NoReuse, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L889 +struct FDFlags { + using CompatibleType = u16; + + struct Bits { + bool append : 1; + bool dsync : 1; + bool nonblock : 1; + bool rsync : 1; + bool sync : 1; + u16 _unused : 11; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static FDFlags read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L924 +struct FDStat { + FileType fs_filetype; + FDFlags fs_flags; + Rights fs_rights_base; + Rights fs_rights_inheriting; + + void serialize_into(Array bytes) const; +}; +static_assert(sizeof(FDStat) == 24); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L959 +using Device = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L967 +struct FSTFlags { + using CompatibleType = u16; + + struct Bits { + bool atim : 1; + bool atim_now : 1; + bool mtim : 1; + bool mtim_now : 1; + u16 _unused : 12; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static FSTFlags read_from(Array const& bytes); +}; +static_assert(sizeof(FSTFlags) == 2); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L995 +struct LookupFlags { + using CompatibleType = u32; + + struct Bits { + bool symlink_follow : 1; + u32 _unused : 31; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static LookupFlags read_from(Array const& bytes); +}; +static_assert(sizeof(LookupFlags) == 4); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1008 +struct OFlags { + using CompatibleType = u16; + + struct Bits { + bool creat : 1; + bool directory : 1; + bool excl : 1; + bool trunc : 1; + + u16 _unused : 12; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + static OFlags read_from(Array const& bytes); +}; +static_assert(sizeof(OFlags) == 2); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1036 +using LinkCount = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1044 +struct FileStat { + Device dev; + INode ino; + FileType filetype; + LinkCount nlink; + FileSize size; + Timestamp atim; + Timestamp mtim; + Timestamp ctim; + + void serialize_into(Array bytes) const; +}; +static_assert(sizeof(FileStat) == 64); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1102 +using UserData = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1110 +enum class EventType : u8 { + Clock, + FDRead, + FDWrite, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1137 +struct EventRWFlags { + using CompatibleType = u16; + + struct Bits { + bool fd_readwrite_hangup : 1; + + u16 _unused : 15; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static EventRWFlags read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1151 +struct EventFDReadWrite { + FileSize nbytes; + EventRWFlags flags; + + void serialize_into(Array bytes) const; + static EventFDReadWrite read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1186 +struct Event { + UserData userdata; + Errno errno_; + EventType type; + EventFDReadWrite fd_readwrite; + + void serialize_into(Array bytes) const; + static Event read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1220 +struct SubClockFlags { + using CompatibleType = u16; + + struct Bits { + bool subscription_clock_abstime : 1; + + u16 _unused : 15; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static SubClockFlags read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1237 +struct SubscriptionClock { + ClockID id; + Timestamp timeout; + Timestamp precision; + SubClockFlags flags; + + void serialize_into(Array bytes) const; + static SubscriptionClock read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1272 +struct SubscriptionFDReadWrite { + FD file_descriptor; + + void serialize_into(Array bytes) const; + static SubscriptionFDReadWrite read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1287 +struct SubscriptionU { + u8 tag; + union { + SubscriptionClock clock; + SubscriptionFDReadWrite fd_read; + SubscriptionFDReadWrite fd_write; + }; + + void serialize_into(Array bytes) const; + static SubscriptionU read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1306 +struct Subscription { + UserData userdata; + SubscriptionU u; + + void serialize_into(Array bytes) const; + static Subscription read_from(Array const& bytes); +}; +static_assert(sizeof(Subscription) == 48); +static_assert(alignof(Subscription) == 8); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1334 +using ExitCode = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1342 +enum class Signal : u8 { + None, + HUP, + INT, + QUIT, + ILL, + TRAP, + ABRT, + BUS, + FPE, + KILL, + USR1, + SEGV, + USR2, + PIPE, + ALRM, + TERM, + CHLD, + CONT, + STOP, + TSTP, + TTIN, + TTOU, + URG, + XCPU, + XFSZ, + VTALRM, + PROF, + WINCH, + POLL, + PWR, + SYS, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1536 +struct RIFlags { + using CompatibleType = u16; + + struct Bits { + bool recv_peek : 1; + bool recv_waitall : 1; + + u16 _unused : 14; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + static RIFlags read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1554 +struct ROFlags { + using CompatibleType = u16; + + struct Bits { + bool recv_data_truncated : 1; + + u16 _unused : 15; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1568 +using SIFlags = LittleEndian; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1576 +struct SDFlags { + using CompatibleType = u8; + + struct Bits { + bool rd : 1; + bool wr : 1; + + u8 _unused : 6; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian data; + }; + + void serialize_into(Array bytes) const; + static SDFlags read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1594 +enum class PreOpenType : u8 { + Dir, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1607 +struct PreStatDir { + Size pr_name_len; + + void serialize_into(Array bytes) const; + static PreStatDir read_from(Array const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1636 +struct PreStat { + u8 tag; + union { + PreStatDir dir; + }; + + void serialize_into(Array bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1676 +struct ArgsSizes { + Size count; + Size size; + + using SerializationComponents = TypeList; + + void serialize_into(Array bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1708 +struct EnvironSizes { + Size count; + Size size; + + using SerializationComponents = TypeList; + + void serialize_into(Array bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L2664 +struct SockRecvResult { + Size size; + ROFlags roflags; + + using SerializationComponents = TypeList; + + void serialize_into(Array bytes) const; +}; + +template +struct Result { + Result(TResult&& result) + : bits {} + , tag(0) + { + new (&bits) TResult(move(result)); + } + + Result(Errno&& error) + : bits {} + , tag(1) + { + new (&bits) Errno(error); + } + + Optional result() const + { + if (tag == 0) + return *bit_cast(&bits[0]); + return {}; + } + + Optional error() const + { + if (tag == 1) + return *bit_cast(&bits[0]); + return {}; + } + + bool is_error() const { return tag == 1; } + + template + Errno serialize_into(Array&& spans) const + { + if (tag == 1) + return error().value(); + + ABI::serialize(*result(), move(spans)); + return Errno::Success; + } + +private: + alignas(max(alignof(TResult), alignof(Errno))) u8 bits[max(sizeof(TResult), sizeof(Errno))]; + LittleEndian tag; +}; + +template +struct Result { + Result() + : error_bits {} + , tag(0) + { + } + + Result(Errno&& error) + : error_bits {} + , tag(1) + { + new (&error_bits) Errno(error); + } + + Optional result() const + { + if (tag == 0) + return { Empty {} }; + return {}; + } + Optional error() const + { + if (tag == 1) + return *bit_cast(&error_bits[0]); + return {}; + } + bool is_error() const { return tag == 1; } + +private: + alignas(Errno) u8 error_bits[sizeof(Errno)]; + LittleEndian tag; +}; + +struct Implementation { + struct MappedPath { + LexicalPath host_path; + LexicalPath mapped_path; + mutable Optional opened_fd {}; + }; + + struct Details { + Function()> provide_arguments; + Function()> provide_environment; + Function()> provide_preopened_directories; + }; + + explicit Implementation(Details&& details) + : provide_arguments(move(details.provide_arguments)) + , provide_environment(move(details.provide_environment)) + , provide_preopened_directories(move(details.provide_preopened_directories)) + { + // Map all of std{in,out,err} by default. + m_fd_map.insert(0, 0); + m_fd_map.insert(1, 1); + m_fd_map.insert(2, 2); + } + + ErrorOr function_by_name(StringView); + +private: + template + HostFunction invocation_of(StringView name) { return ABI::InvocationOf {}(*this, name); } + + ErrorOr> impl$args_get(Configuration&, Pointer> argv, Pointer argv_buf); + ErrorOr> impl$args_sizes_get(Configuration&); + ErrorOr> impl$environ_get(Configuration&, Pointer> environ, Pointer environ_buf); + ErrorOr> impl$environ_sizes_get(Configuration&); + ErrorOr> impl$clock_res_get(Configuration&, ClockID id); + ErrorOr> impl$clock_time_get(Configuration&, ClockID id, Timestamp precision); + ErrorOr> impl$fd_advise(Configuration&, FD, FileSize offset, FileSize len, Advice); + ErrorOr> impl$fd_allocate(Configuration&, FD, FileSize offset, FileSize len); + ErrorOr> impl$fd_close(Configuration&, FD); + ErrorOr> impl$fd_datasync(Configuration&, FD); + ErrorOr> impl$fd_fdstat_get(Configuration&, FD); + ErrorOr> impl$fd_fdstat_set_flags(Configuration&, FD, FDFlags); + ErrorOr> impl$fd_fdstat_set_rights(Configuration&, FD, Rights fs_rights_base, Rights fs_rights_inheriting); + ErrorOr> impl$fd_filestat_get(Configuration&, FD); + ErrorOr> impl$fd_filestat_set_size(Configuration&, FD, FileSize); + ErrorOr> impl$fd_filestat_set_times(Configuration&, FD, Timestamp atim, Timestamp mtim, FSTFlags); + ErrorOr> impl$fd_pread(Configuration&, FD, Pointer iovs, Size iovs_len, FileSize offset); + ErrorOr> impl$fd_prestat_get(Configuration&, FD); + ErrorOr> impl$fd_prestat_dir_name(Configuration&, FD, Pointer path, Size path_len); + ErrorOr> impl$fd_pwrite(Configuration&, FD, Pointer iovs, Size iovs_len, FileSize offset); + ErrorOr> impl$fd_read(Configuration&, FD, Pointer iovs, Size iovs_len); + ErrorOr> impl$fd_readdir(Configuration&, FD, Pointer buf, Size buf_len, DirCookie cookie); + ErrorOr> impl$fd_renumber(Configuration&, FD from, FD to); + ErrorOr> impl$fd_seek(Configuration&, FD, FileDelta offset, Whence whence); + ErrorOr> impl$fd_sync(Configuration&, FD); + ErrorOr> impl$fd_tell(Configuration&, FD); + ErrorOr> impl$fd_write(Configuration&, FD, Pointer iovs, Size iovs_len); + ErrorOr> impl$path_create_directory(Configuration&, FD, Pointer path, Size path_len); + ErrorOr> impl$path_filestat_get(Configuration&, FD, LookupFlags, ConstPointer path, Size path_len); + ErrorOr> impl$path_filestat_set_times(Configuration&, FD, LookupFlags, Pointer path, Size path_len, Timestamp atim, Timestamp mtim, FSTFlags); + ErrorOr> impl$path_link(Configuration&, FD, LookupFlags, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len); + ErrorOr> impl$path_open(Configuration&, FD, LookupFlags, Pointer path, Size path_len, OFlags, Rights fs_rights_base, Rights fs_rights_inheriting, FDFlags fd_flags); + ErrorOr> impl$path_readlink(Configuration&, FD, LookupFlags, Pointer path, Size path_len, Pointer buf, Size buf_len); + ErrorOr> impl$path_remove_directory(Configuration&, FD, Pointer path, Size path_len); + ErrorOr> impl$path_rename(Configuration&, FD, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len); + ErrorOr> impl$path_symlink(Configuration&, Pointer old_path, Size old_path_len, FD, Pointer new_path, Size new_path_len); + ErrorOr> impl$path_unlink_file(Configuration&, FD, Pointer path, Size path_len); + ErrorOr> impl$poll_oneoff(Configuration&, ConstPointer in, Pointer out, Size nsubscriptions); + ErrorOr> impl$proc_exit(Configuration&, ExitCode); // Note: noreturn. + ErrorOr> impl$proc_raise(Configuration&, Signal); + ErrorOr> impl$sched_yield(Configuration&); + ErrorOr> impl$random_get(Configuration&, Pointer buf, Size buf_len); + ErrorOr> impl$sock_accept(Configuration&, FD fd, FDFlags fd_flags); + ErrorOr> impl$sock_recv(Configuration&, FD fd, Pointer ri_data, Size ri_data_len, RIFlags ri_flags); + ErrorOr> impl$sock_send(Configuration&, FD fd, Pointer si_data, Size ri_data_len, SIFlags si_flags); + ErrorOr> impl$sock_shutdown(Configuration&, FD fd, SDFlags how); + + Vector const& arguments() const; + Vector const& environment() const; + Vector const& preopened_directories() const; + + using PreopenedDirectoryDescriptor = DistinctNumeric, struct PreopenedDirectoryDescriptor_tag, AK::DistinctNumericFeature::Comparison, AK::DistinctNumericFeature::CastToUnderlying, AK::DistinctNumericFeature::Increment>; + using UnmappedDescriptor = DistinctNumeric, struct UnmappedDescriptor_tag, AK::DistinctNumericFeature::Comparison, AK::DistinctNumericFeature::CastToUnderlying>; + using MappedDescriptor = Variant; + using Descriptor = Variant; + + Descriptor map_fd(FD); + +public: + Function()> provide_arguments; + Function()> provide_environment; + Function()> provide_preopened_directories; + +private: + struct Cache { + Optional> cached_arguments; + Optional> cached_environment; + Optional> cached_preopened_directories; + }; + + mutable Cache cache {}; + + RedBlackTree m_fd_map; + size_t m_first_unmapped_preopened_directory_index { 0 }; +}; + +#undef IMPL + +} + +namespace Wasm::Wasi::ABI { + +template +struct ToCompatibleValue> { + using Type = typename ToCompatibleValue::Type; +}; + +template +struct ToCompatibleValue> { + using Type = MakeSigned; +}; + +template +requires(requires { declval(); }) +struct ToCompatibleValue { + using Type = MakeSigned; +}; + +template +struct ToCompatibleValue { + using Type = MakeSigned; +}; + +template +struct ToCompatibleValue { + using Type = MakeSigned>; +}; + +} + +template +struct AK::Formatter> : AK::Formatter { + ErrorOr format(FormatBuilder& builder, Wasm::Wasi::LittleEndian value) + { + return Formatter::format(builder, value.operator T()); + } +};