From 990a4ac73f4fea7f114c8940794a79c33b35edaa Mon Sep 17 00:00:00 2001 From: "Benjamin Thomas (Aviansie Ben)" Date: Tue, 5 Nov 2019 15:57:19 +0000 Subject: [PATCH] Add some basic Doxygen comments for major APIs This PR adds some basic Doxygen comments to describe some of the most important public APIs that consumers of winter might use. --- src/environment.hpp | 19 ++++ src/func.hpp | 124 +++++++++++++++++++++++- src/memory.hpp | 205 +++++++++++++++++++++++++++++++++++++++- src/module.hpp | 223 +++++++++++++++++++++++++++++++++++++++++--- src/type.hpp | 147 ++++++++++++++++++++++++++--- 5 files changed, 685 insertions(+), 33 deletions(-) diff --git a/src/environment.hpp b/src/environment.hpp index 272574d..83b6772 100644 --- a/src/environment.hpp +++ b/src/environment.hpp @@ -23,10 +23,29 @@ namespace winter { +/** + * \brief Represents a single WebAssembly sandboxed environment. + * + * Each Environment represents a completely isolated WebAssembly sandbox. Each sandbox operates + * entirely independently from all others. Because of this, there are several restrictions on + * sharing between WebAssembly code instantiated in different sandboxes: + * + * - WebAssembly code from one sandbox cannot directly call WebAssembly code in a different sandbox + * - References can only be used in the WebAssembly sandbox in which they were created + * - Linear memory cannot be shared between WebAssembly code in different sandboxes, even if it is + * marked as shared + */ class Environment { TypeTable _types; public: + /** + * \brief Gets the TypeTable used by WebAssembly code in this sandbox. + */ const TypeTable& types() const { return _types; } + + /** + * \brief Gets the TypeTable used by WebAssembly code in this sandbox. + */ TypeTable& types() { return _types; } }; diff --git a/src/func.hpp b/src/func.hpp index 9a0445d..888dbb5 100644 --- a/src/func.hpp +++ b/src/func.hpp @@ -44,16 +44,25 @@ class LinkedFunc; using JitFunction = uint32_t (*)(LinkedFuncInternal* func); +/** + * \brief Represents a stream of WebAssembly instructions. + */ class InstructionStream { std::vector _stream; public: explicit InstructionStream(std::vector&& stream) : _stream(std::move(stream)) {} + /** + * \brief Gets the size (in bytes) of this instruction stream. + */ size_t size() const { return _stream.size(); } friend class InstructionCursor; }; +/** + * \brief Represents a cursor for reading from an InstructionStream. + */ class InstructionCursor { const InstructionStream* _stream; const uint8_t* _cursor; @@ -66,8 +75,14 @@ class InstructionCursor { _cursor = _stream->_stream.data() + off; } + /** + * \brief Gets the cursor's current offset (in bytes) in the instruction stream. + */ size_t offset() const { return _cursor - _stream->_stream.data(); } + /** + * \brief Jumps this cursor the specified number of bytes forward or backward. + */ void jump_relative(ssize_t off) { WASSERT(off >= begin() - _cursor, "Instruction cursor out-of-bounds"); WASSERT(off <= end() - _cursor, "Instruction cursor out-of-bounds"); @@ -75,6 +90,9 @@ class InstructionCursor { _cursor += off; } + /** + * \brief Reads the next byte at this cursor as a uint8_t. + */ uint8_t read_u8() { WASSERT(_cursor != end(), "Instruction cursor out-of-bounds"); @@ -82,16 +100,49 @@ class InstructionCursor { } }; +/** + * \brief Represents a WebAssembly function which has not yet been created. + * + * There are two different kinds of AbstractFunc: + * + * - An AbstractFunc with is_import set to false represents a WebAssembly function which will be + * created when instantiating a module. + * - An AbstractFunc with is_import set to true represents a function which will be imported from + * another module. + * + * \see AbstractModule + * \see UnlinkedFunc + * \see LinkedFunc + */ struct AbstractFunc { - bool is_import; - + bool is_import; ///< Whether this function will be imported from another module. + + /** + * \brief The debug name of this function, or empty string if no debug name was provided. + * + * For functions which will be imported from another module, i.e. have is_import set to true, + * this value will not be used and the debug name will be read from the function which ends up + * being linked into this slot. In that case, this field will be the empty string. + */ std::string debug_name; + + /** + * \brief The WebAssembly instructions constituting this function. + * + * For functions which will be imported from another module, i.e. have is_import set to true, + * this field will not be populated and will instead be set to nullptr. + */ std::shared_ptr instrs; - FuncSig sig; + FuncSig sig; ///< The signature of this function. AbstractFunc(bool is_import, std::string debug_name, std::shared_ptr instrs, FuncSig sig) : is_import(is_import), debug_name(std::move(debug_name)), instrs(std::move(instrs)), sig(std::move(sig)) {} + /** + * \brief Creates an AbstractFunc for a function which will be imported from another module. + * + * \param[in] sig the function signature that the function being imported must have. + */ static AbstractFunc for_import(FuncSig sig) { return AbstractFunc(true, "", nullptr, std::move(sig)); } @@ -105,6 +156,12 @@ struct UnlinkedFuncInternal { }; static_assert(std::is_standard_layout::value, "UnlinkedFuncInternal must be standard layout"); +/** + * \brief Represents a WebAssembly function which has been partially instantiated. + * + * \see AbstractFunc + * \see LinkedFunc + */ class UnlinkedFunc { UnlinkedFuncInternal _internal; std::string _debug_name; @@ -119,13 +176,43 @@ class UnlinkedFunc { UnlinkedFunc& operator=(const UnlinkedFunc&) = delete; UnlinkedFunc& operator=(UnlinkedFunc&&) = delete; public: + /** + * \brief Gets a pointer to the internal information structure for this function. + * + * The internal data structure contains basic information about this function that must be + * accessible to JITted code for performance reasons. This information is represented in a + * standard-layout struct so that it can be read/written without needing to call C++ code. + * + * \warning This is not considered to be part of the public API of winter and is only for + * internal use by the VM. The layout of the internal struct is not stable and should + * not be relied upon. + */ UnlinkedFuncInternal* internal() { return &_internal; } + /** + * \brief Gets the function signature for calling this function. + */ const FuncSig& signature() const { return *_internal.sig; } + + /** + * \brief Gets the debug name for this function or an empty string if no name was provided. + */ const std::string& debug_name() const { return _debug_name; } + + /** + * \brief Gets the instruction stream for the WebAssembly instructions in this function. + */ std::shared_ptr instrs() const { return _instrs; } + /** + * \brief Creates an UnlinkedFunc from an AbstractFunc. + * + * This function creates an unlinked instance of the given WebAssembly function. Any linked + * versions of the returned function can only be called from the context of the provided + * WebAssembly sandbox. + */ static std::shared_ptr instantiate(const AbstractFunc& func, Environment& env); + static std::shared_ptr create_mock(const FuncSig* sig) { auto func = std::shared_ptr(new UnlinkedFunc()); @@ -143,6 +230,12 @@ struct LinkedFuncInternal { }; static_assert(std::is_standard_layout::value, "LinkedFuncInternal must be standard layout"); +/** + * \brief Represents a WebAssembly function which is part of a fully instantiated module. + * + * \see UnlinkedFunc + * \see ModuleInstance + */ class LinkedFunc { LinkedFuncInternal _internal; std::shared_ptr _unlinked; @@ -157,12 +250,37 @@ class LinkedFunc { LinkedFunc& operator=(const LinkedFunc&) = delete; LinkedFunc& operator=(LinkedFunc&&) = delete; public: + /** + * \brief Gets a pointer to the internal information structure for this function. + * + * The internal data structure contains basic information about this function that must be + * accessible to JITted code for performance reasons. This information is represented in a + * standard-layout struct so that it can be read/written without needing to call C++ code. + * + * \warning This is not considered to be part of the public API of winter and is only for + * internal use by the VM. The layout of the internal struct is not stable and should + * not be relied upon. + */ LinkedFuncInternal* internal() { return &_internal; } + /** + * \brief Gets the UnlinkedFunc that this function was created from. + */ UnlinkedFunc& unlinked() const { return *_unlinked; } + + /** + * \brief Gets the module instance that this function is part of. + */ ModuleInstance& module() const { return *_module; } + /** + * \brief Creates a new LinkedFunc from an UnlinkedFunc. + * + * This function creates a linked version of the provided UnlinkedFunc using the provided + * ModuleInstance for linking. + */ static std::unique_ptr instantiate(std::shared_ptr unlinked, ModuleInstance* module); + static std::unique_ptr create_mock(const FuncSig* sig) { auto func = std::unique_ptr(new LinkedFunc()); auto unlinked = UnlinkedFunc::create_mock(sig); diff --git a/src/memory.hpp b/src/memory.hpp index 4e6124b..dc70de6 100644 --- a/src/memory.hpp +++ b/src/memory.hpp @@ -45,22 +45,51 @@ static_assert(std::is_standard_layout::value, "NumPages must be transp static_assert(sizeof(NumPages) == sizeof(size_t), "NumPages must be transparent"); static_assert(alignof(NumPages) == alignof(size_t), "NumPages must be transparent"); +/** + * \brief A sentinel value used to represent that the maximum capacity of a memory is unbounded. + */ constexpr NumPages WASM_UNLIMITED_PAGES = NumPages(static_cast(-1)); + +/** + * \brief A sentinel value returned if a linear memory could not be grown. + */ constexpr NumPages WASM_ALLOCATE_FAILURE = NumPages(static_cast(-1)); class Memory; +/** + * \brief Represents the parameters of a Memory that has not yet been created. + * + * There are two different types of AbstractMemory: + * + * - An AbstractMemory with is_import set to false represents a memory which will be created as part + * of instantiating a WebAssembly module. + * - An AbstractMemory with is_import set to true represents the parameters of a memory which will + * be linked from another module. + * + * \see Memory + */ struct AbstractMemory { - bool is_import; + bool is_import; ///< Whether this linear memory will be imported from another module. - bool is_shared; - NumPages initial_pages = NumPages(0); - NumPages max_pages = WASM_UNLIMITED_PAGES; + bool is_shared; ///< Whether this linear memory will be shared between agents. + NumPages initial_pages = NumPages(0); ///< The initial size of this linear memory (in WASM pages). + NumPages max_pages = WASM_UNLIMITED_PAGES; ///< The maximum capacity of this linear memory (in WASM pages). AbstractMemory(bool is_import, bool is_shared, NumPages initial_pages, NumPages max_pages) : is_import(is_import), is_shared(is_shared), initial_pages(initial_pages), max_pages(max_pages) {} - static AbstractMemory for_import(bool is_shared, NumPages initial_pages, NumPages max_pages) { + /** + * \brief Creates an abstract memory representing a memory that will be imported from another + * module. + * + * \param[in] is_shared Whether the memory being imported will be shared between agents. + * \param[in] initial_pages The minimum initial size of the imported memory (in WASM pages) that + * the imported memory must have. + * \param[in] max_pages The maximum capacity of the imported memory (in WASM pages) that the + * imported memory must not exceed. + */ + static AbstractMemory for_import(bool is_shared, NumPages initial_pages = NumPages(0), NumPages max_pages = WASM_UNLIMITED_PAGES) { return AbstractMemory(true, is_shared, initial_pages, max_pages); } }; @@ -85,6 +114,14 @@ struct MemoryInternal { }; static_assert(std::is_standard_layout::value, "MemoryInternal must be standard layout"); +/** + * \brief A WebAssembly linear memory. + * + * \warning For unshared linear memories, many operations produce undefined behaviour when executed + * while a WebAssembly agent is running which could access the memory, except from within + * the context of a host call on such an agent. Only shared memories are safe to access + * while WebAssembly code is running. + */ class Memory { MemoryInternal _internal; NumPages _initial_pages; @@ -117,37 +154,151 @@ class Memory { Memory& operator=(const Memory&) = delete; Memory& operator=(Memory&&) = delete; + /** + * \brief Gets a pointer to the internal information structure for this linear memory. + * + * The internal data structure contains basic information about this linear memory that must be + * accessible to JITted code for performance reasons. This information is represented in a + * standard-layout struct so that it can be read/written without needing to call C++ code. + * + * \warning This is not considered to be part of the public API of winter and is only for + * internal use by the VM. The layout of the internal struct is not stable and should + * not be relied upon. + */ MemoryInternal* internal() { return &_internal; } + /** + * \brief Gets the current size (in bytes) of this linear memory. + * + * The size of a linear memory can only ever be increased and can never decrease. + */ size_t size() const { return _internal.size; } + + /** + * \brief Gets the current size (in WASM pages) of this linear memory. + * + * The size of a linear memory can only ever be increased and can never decrease. + */ NumPages size_pages() const { return NumPages(size() >> WASM_PAGE_SHIFT); } + + /** + * \brief Gets the initial size (in WASM pages) of this linear memory. + */ NumPages initial_size_pages() const { return _initial_pages; } + /** + * \brief Gets the current capacity (in WASM pages) of this linear memory. + * + * The current capacity of a linear memory represents the maximum size to which the linear + * memory can be grown without needing to allocate any new memory. + */ NumPages current_capacity_pages() const { return _internal.current_capacity_pages; } + + /** + * \brief Gets the maximum capacity (in WASM pages) of this linear memory. + */ NumPages max_capacity_pages() const { return _internal.max_capacity_pages; } + /** + * \brief Checks whether this linear memory will never be reallocated. + * + * This method checks if the current capacity of this linear memory is equal to its maximum + * capacity, meaning that the backing memory will never be reallocated. + */ bool is_at_max_capacity() const { return current_capacity_pages() == max_capacity_pages(); } + + /** + * \brief Checks whether this linear memory can be shared between agents. + */ bool is_shared() const { return (_internal.flags & MEMORY_FLAG_SHARED) != 0; } + /** + * \brief Grows this linear memory by the given number of WASM pages. + * + * This method increases the size of this linear memory by the given number of WASM pages. + * + * If `size_pages() + new_pages` exceeds `max_capacity_pages()`, then the size of this linear + * memory will not be grown and WASM_ALLOCATE_FAILURE will be returned. + * + * If `size_pages() + new_pages` exceeds `current_capacity_pages()`, then the backing memory + * will be grown. This invalidates all pointers returned from Memory::data and Memory::ptr_to on + * this linear memory. This is guaranteed never to happen on shared memories. + * + * \param[in] new_pages the number of new WASM pages that should be allocated. + * \return the previous size of this linear memory in WASM pages or WASM_ALLOCATE_FAILURE. + */ NumPages grow(NumPages new_pages); + /** + * \brief Checks whether a given address is valid in this linear memory. + * + * This method determines whether a load or store of the given size at the given address would + * be valid for this linear memory. Since a linear memory can only be grown and never shrunk, if + * this method returns true for a given address and size, it is guaranteed to always return true + * in the future for the same linear memory. + * + * \param[in] addr the address which should be checked. + * \param[in] size the number of bytes after the given address that must be accessible. + */ bool is_valid_address(wptr_t addr, size_t size) const { size_t addr_s = static_cast(addr); return (addr_s + size) >= addr_s && (addr_s + size) <= this->size(); } + /** + * \brief Gets a pointer to an address in this linear memory. + * + * \warning Use of this function is strongly discouraged, as it can be quite difficult to use + * correctly. All pointers returned by this method are invalidated whenever a call to + * Memory::grow causes the backing memory to be reallocated. + * + * \param[in] addr the address to get a pointer to. + * \param[in] size the size (in bytes) of the region that should be accessible by this pointer. + */ void* ptr_to(wptr_t addr, size_t size) { WASSERT(is_valid_address(addr, size), "Out-of-bounds address passed to Memory::ptr_to"); return static_cast(static_cast(_internal.start) + static_cast(addr)); } + + /** + * \brief Gets a pointer to an address in this linear memory. + * + * \warning Use of this function is strongly discouraged, as it can be quite difficult to use + * correctly. All pointers returned by this method are invalidated whenever a call to + * Memory::grow causes the backing memory to be reallocated. + * + * \param[in] addr the address to get a pointer to. + * \param[in] size the size (in bytes) of the region that should be accessible by this pointer. + */ const void* ptr_to(wptr_t addr, size_t size) const { WASSERT(is_valid_address(addr, size), "Out-of-bounds address passed to Memory::ptr_to"); return static_cast(static_cast(_internal.start) + static_cast(addr)); } + /** + * \brief Gets a pointer to the start of the backing memory for this linear memory. + * + * \warning The pointer returned by this method is invalidated whenever a call to Memory::grow + * causes the backing memory to be reallocated. + */ void* data() { return _internal.start; } + + /** + * \brief Gets a pointer to the start of the backing memory for this linear memory. + * + * \warning The pointer returned by this method is invalidated whenever a call to Memory::grow + * causes the backing memory to be reallocated. + */ const void* data() const { return _internal.start; } + /** + * \brief Attempts to read a number of bytes from this linear memory. + * + * \param[in] buf a pointer to a buffer to which data should be read. + * \param[in] addr the address in this linear memory to start reading from. + * \param[in] size the number of bytes to read. + * \return true if the read was successful, false if the provided address was out-of-bounds. + */ bool load(void* buf, wptr_t addr, size_t size) const { if (is_valid_address(addr, size)) { std::memcpy(buf, ptr_to(addr, size), size); @@ -157,12 +308,32 @@ class Memory { } } + /** + * \brief Attempts to read a value from this linear memory. + * + * \warning This method is only intended to read values of a type that WebAssembly can write to + * linear memory in a single instruction (i.e. i32, i64, f32, and f64). There is no + * guarantee that the layout of any other type will match between the sandboxed + * WebAssembly code and the host environment, even if such a type is standard layout. + * + * \param[in] buf a pointer to which the value will be read. + * \param[in] addr the address in this linear memory from which the value should be read. + * \return true if the read was successful, false if the provided address was out-of-bounds. + */ template bool load(T* buf, wptr_t addr) const { static_assert(std::is_standard_layout::value, "Only standard layout types can be in WebAssembly linear memory"); return load(static_cast(buf), addr, sizeof(T)); } + /** + * \brief Attempts to write a number of bytes to this linear memory. + * + * \param[in] buf a pointer to a buffer containing the data to be written. + * \param[in] addr the address in this linear memory to start writing to. + * \param[in] size the number of bytes to write. + * \return true if the write was successful, false if the provided address was out-of-bounds. + */ bool store(const void* buf, wptr_t addr, size_t size) { if (is_valid_address(addr, size)) { std::memcpy(ptr_to(addr, size), buf, size); @@ -172,16 +343,40 @@ class Memory { } } + /** + * \brief Attempts to write a value to this linear memory. + * + * \warning This method is only intended to write values of a type that WebAssembly can read + * from linear memory in a single instruction (i.e. i32, i64, f32, and f64). There is + * no guarantee that the layout of any other type will match between the sandboxed + * WebAssembly code and the host environment, even if such a type is standard layout. + * + * \param[in] buf a pointer to the value to be written. + * \param[in] addr the address in this linear memory to which the value should be written. + * \return true if the write was successful, false if the provided address was out-of-bounds. + */ template bool store(const T* buf, wptr_t addr) { static_assert(std::is_standard_layout::value, "Only standard layout types can be in WebAssembly linear memory"); return store(static_cast(buf), addr, sizeof(T)); } + /** + * \brief Creates a new shared linear memory. + * + * \param[in] min the initial size of the linear memory (in WASM pages). + * \param[in] max the maximum capacity of the linear memory (in WASM pages). + */ static std::shared_ptr create_shared(NumPages min, NumPages max) { return std::shared_ptr(new Memory(AbstractMemory(false, true, min, max))); } + /** + * \brief Creates a new unshared linear memory. + * + * \param[in] min the initial size of the linear memory (in WASM pages). + * \param[in] max the maximum capacity of the linear memory (in WASM pages). + */ static std::shared_ptr create_unshared(NumPages min, NumPages max) { return std::shared_ptr(new Memory(AbstractMemory(false, false, min, max))); } diff --git a/src/module.hpp b/src/module.hpp index a09c76b..a7398c0 100644 --- a/src/module.hpp +++ b/src/module.hpp @@ -31,32 +31,53 @@ namespace winter { +/** + * \brief Represents a type of export or import in a WebAssembly module. + */ enum class ExportType : uint8_t { - Func = 0x00, - Table = 0x01, - Memory = 0x02, - Global = 0x03 + Func = 0x00, ///< An import/export for a function. + Table = 0x01, ///< An import/export for a WebAssembly table. + Memory = 0x02, ///< An import/export for a WebAssembly linear memory. + Global = 0x03 ///< An import/export for a WebAssembly global. }; +/** + * \brief Represents an export in a WebAssembly module. + */ struct Export { - std::string name; - ExportType type; - size_t idx; + std::string name; ///< The name of the export. + ExportType type; ///< The type of object being exported. + size_t idx; ///< The index of the exported object in the module's relevant table. Export(std::string name, ExportType type, size_t idx) : name(std::move(name)), type(type), idx(idx) {} }; +/** + * \brief Represents an import in a WebAssembly module. + */ struct Import { - std::string module; - std::string name; - ExportType type; + std::string module; ///< The name of the module to import from. + std::string name; ///< The name of the export that should be imported. + ExportType type; ///< The type of object to be imported. + + /** + * \brief The index into which the imported object should be placed in this module's relevant + * table. + */ size_t idx; Import(std::string module, std::string name, ExportType type, size_t idx) : module(std::move(module)), name(std::move(name)), type(type), idx(idx) {} }; +/** + * \brief Represents a module which has been type-checked but for which no runtime resources have + * been allocated. + * + * \see Module + * \see ModuleInstance + */ class AbstractModule { std::vector _imports; std::vector _exports; @@ -64,19 +85,62 @@ class AbstractModule { std::vector _memories; std::vector _funcs; public: + /** + * \brief Gets a list of imports in this module. + */ std::vector& imports() { return _imports; } + + /** + * \brief Gets a list of imports in this module. + */ const std::vector& imports() const { return _imports; } + /** + * \brief Gets a list of exports in this module. + */ std::vector& exports() { return _exports; } + + /** + * \brief Gets a list of exports in this module. + */ const std::vector& exports() const { return _exports; } + /** + * \brief Gets a list of linear memories in this module. + */ std::vector& memories() { return _memories; } + + /** + * \brief Gets a list of linear memories in this module. + */ const std::vector& memories() const { return _memories; } + /** + * \brief Gets a list of functions in this module. + */ std::vector& funcs() { return _funcs; } + + /** + * \brief Gets a list of functions in this module. + */ const std::vector& funcs() const { return _funcs; } }; +/** + * \brief Represents a module which has been type-checked and partially instantiated. + * + * Multiple module instances created from the same Module will share certain runtime structures + * between each other: + * + * - Shared linear memories + * - JIT-compiled code and other metadata for functions + * + * Partially instantiating a module will only create runtime data structures for such shared + * resources. + * + * \see AbstractModule + * \see ModuleInstance + */ class Module { std::vector _imports; std::vector _exports; @@ -92,20 +156,46 @@ class Module { Module() {} Module(const AbstractModule& abstract, Environment& env); + /** + * \brief Gets the WebAssembly environment this module can be used in. + */ Environment& env() const { return *_env; } + /** + * \brief Gets a list of unresolved imports in this module. + */ const std::vector& imports() const { return _imports; } + + /** + * \brief Adds a new unresolved import to this module. + */ void add_import(Import i) { _imports.emplace_back(i); } + /** + * \brief Gets a list of exports in this module. + */ const std::vector& exports() const { return _exports; } - std::vector& exports() { return _exports; } + + /** + * \brief Adds a new export to this module. + */ void add_export(Export e) { _exports.emplace_back(e); } + /** + * \brief Gets a list of linear memories in or imported by this module. + */ const std::vector& memories() const { return _memories; } + + /** + * \brief Adds a new linear memory to this module. + * + * If the linear memory being added is marked as being shared, memory for it will be allocated + * immediately. Otherwise, no actual memory will be allocated until the module is instantiated. + */ void add_memory(AbstractMemory mem) { if (mem.is_shared && !mem.is_import) { _shared_memories.emplace_back(new Memory(mem)); @@ -116,11 +206,32 @@ class Module { _memories.emplace_back(mem); } + /** + * \brief Gets a list of functions in or imported by this module. + * + * For functions being imported into this module, slots in this list will be set to nullptr as + * placeholders. + */ const std::vector>& funcs() const { return _funcs; } + + /** + * \brief Gets a list of function signatures for functions imported by this module. + * + * For functions in this module that are not imports, slots in this list will be set to nullptr. + */ + const std::vector import_func_sigs() const { return _import_func_sigs; } + + /** + * \brief Adds a new function to this module. + */ void add_func(std::shared_ptr func) { _import_func_sigs.emplace_back(nullptr); _funcs.emplace_back(func); } + + /** + * \brief Adds a new placeholder function to this module to be used for an imported function. + */ void add_imported_func(FuncSig* sig) { _import_func_sigs.emplace_back(sig); _funcs.emplace_back(nullptr); @@ -129,14 +240,32 @@ class Module { friend class ModuleInstance; }; +/** + * \brief Represents an object which can be imported as a WebAssembly module. + */ class ImportModule { public: virtual ~ImportModule() {} + /** + * \brief Finds the function with a given name in this module or returns nullptr. + * \throws LinkError if the export with the given name is not a function. + */ virtual LinkedFunc* find_func(const Import& import) const = 0; + + /** + * \brief Finds the linear memory with a given name in this module or returns nullptr. + * \throws LinkError if the export with the given name is not a linear memory. + */ virtual std::shared_ptr find_memory(const Import& import) const = 0; }; +/** + * \brief Represents a WebAssembly module which is a combination of other modules. + * + * When searching for exports, modules will be searched in the order in which they appear in the + * list of modules. The first module which returns a valid export with the given name will be used. + */ class ImportMultiModule : public ImportModule { std::vector _modules; public: @@ -146,24 +275,44 @@ class ImportMultiModule : public ImportModule { std::shared_ptr find_memory(const Import& import) const override; }; +/** + * \brief Represents the set of WebAssembly modules presented to a module at link time. + */ class ImportEnvironment { std::map _modules; public: + /** + * \brief Adds a new module to the list of modules that are visible in this environment. + * + * \note If a module with the given name already exists, it will be overwritten by the new + * module. To combine multiple modules into one, consider using ImportMultiModule. + */ void add_module(std::string name, ImportModule* module) { - _modules.emplace(name, module); + _modules[name] = module; } + /** + * \brief Finds the module matching an import or returns nullptr if no such module exists. + */ ImportModule* find_module(const Import& import) const { auto it = _modules.find(import.module); return it != _modules.end() ? it->second : nullptr; } + /** + * \brief Finds the function matching an import or returns nullptr. + * \throws LinkError if the export matching the given import is not a function. + */ LinkedFunc* find_func(const Import& import) const { auto module = find_module(import); return module ? module->find_func(import) : nullptr; } + /** + * \brief Finds the linear memory matching an import or returns nullptr. + * \throws LinkError if the export matching the given import is not a linear memory. + */ std::shared_ptr find_memory(const Import& import) const { auto module = find_module(import); return module ? module->find_memory(import) : nullptr; @@ -188,6 +337,11 @@ struct ModuleInstanceInternal { }; static_assert(std::is_standard_layout::value, "ModuleInstanceInternal must be standard layout"); +/** + * \brief Represents a fully instantiated WebAssembly module which is ready for execution. + * + * \see Module + */ class ModuleInstance : public ImportModule { ModuleInstanceInternal _internal; @@ -209,24 +363,69 @@ class ModuleInstance : public ImportModule { ModuleInstance& operator=(const ModuleInstance&) = delete; ModuleInstance& operator=(ModuleInstance&&) = delete; public: + /** + * \brief Gets a pointer to the internal information structure for this module instance. + * + * The internal data structure contains basic information about this module that must be + * accessible to JITted code for performance reasons. This information is represented in a + * standard-layout struct so that it can be read/written without needing to call C++ code. + * + * \warning This is not considered to be part of the public API of winter and is only for + * internal use by the VM. The layout of the internal struct is not stable and should + * not be relied upon. + */ ModuleInstanceInternal* internal() { return &_internal; } + /** + * \brief Gets the WebAssembly environment this module instance can be used in. + */ Environment& env() const { return *_env; } + /** + * \brief Gets a list of exports provided by this module instance. + */ const std::vector& exports() const { return _exports; } + /** + * \brief Gets a list of functions defined in or imported by this module instance. + */ const std::vector& funcs() const { return _funcs; } + + /** + * \brief Gets a list of linear memories defined in or imported by this module instance. + */ const std::vector>& memories() const { return _memories; } + /** + * \brief Finds an exported item in this module instance corresponding to the given import. + * + * This function finds an item exported by this module instance that corresponds in name and + * export type to the provided import. If no such export exists, nullptr is returned instead. + */ const Export* find_export(const Import& import) const; LinkedFunc* find_func(const Import& import) const override; std::shared_ptr find_memory(const Import& import) const override; + /** + * \brief Fully instantiates and links a partially instantiated module. + * + * \warning All of the modules in the provided ImportEnvironment *must* belong to the same + * Environment as the given Module. Additionally, it is the responsibility of the + * caller to ensure that ModuleInstance objects are destroyed in the reverse order in + * which they were created to ensure that no ModuleInstance outlives a ModuleInstance + * that imports it. Failure to uphold these guarantees results in undefined behaviour. + * + * \throws LinkError if one of the imports in the module to instantiate cannot be satisfied in + * the provided ImportEnvironment. + */ static std::unique_ptr instantiate( const Module& module, const ImportEnvironment& imports ); }; +/** + * \brief Thrown when an error occurs when linking a WebAssembly module. + */ class LinkError : public std::exception { Import _import; const std::string _msg; diff --git a/src/type.hpp b/src/type.hpp index 7121caf..a824534 100644 --- a/src/type.hpp +++ b/src/type.hpp @@ -30,49 +30,120 @@ namespace winter { struct FuncSig; +/** + * \brief Represents a primitive WebAssembly type. + * + * This enumeration represents the "primitive" part of a WebAssembly type. Namely, the part of a + * type that is not dynamically controlled by WebAssembly code. For instance, the primitive i32 type + * can be represented solely as a primitive type, since the WebAssembly code does not have any + * control over the semantics of that type. + */ enum class PrimitiveValueType : uint8_t { - I32 = 0x7f, - I64 = 0x7e, - F32 = 0x7d, - F64 = 0x7c, - FuncRef = 0x70 + I32 = 0x7f, ///< The WebAssembly i32 type. + I64 = 0x7e, ///< The WebAssembly i64 type. + F32 = 0x7d, ///< The WebAssembly f32 type. + F64 = 0x7c, ///< The WebAssembly f64 type. + FuncRef = 0x70 ///< A WebAssembly funcref type. }; +/** + * \brief Represents a full WebAssembly type. + */ struct ValueType { - PrimitiveValueType type; + PrimitiveValueType type; ///< The primitive part of this WebAssembly type. + + /** + * \brief A field whose interpretation depends on ValueType::type. + * + * This field contains the dynamically-controlled part of a WebAssembly type which cannot be + * represented as a simple enumeration constant. The interpretation of this field's value + * depends on the value of ValueType::type: + * + * - PrimitiveValueType::I32: this field should be nullptr. + * - PrimitiveValueType::I64: this field should be nullptr. + * - PrimitiveValueType::F32: this field should be nullptr. + * - PrimitiveValueType::F64: this field should be nullptr. + * - PrimitiveValueType::FuncRef: this field should contain a pointer to a FuncSig representing + * the signature of the typed function reference, or nullptr for an untyped function + * reference. + */ const void* extra; constexpr bool operator==(const ValueType& other) const { return type == other.type && extra == other.extra; } + /** + * \brief Gets the function signature of a function type. + * + * This function gets the function signature of a type whose primitive part is + * PrimitiveValueType::FuncRef. It is invalid to call this function on a type whose primitive + * part does not represent a function reference. + * + * \return a pointer to the function signature of a typed function reference. For an untyped + * function reference (i.e. the funcref type), returns nullptr instead. + */ const FuncSig* func_sig() const { WASSERT(type == PrimitiveValueType::FuncRef, "Called ValueType::func_sig() on non-function type"); return static_cast(extra); } + /** + * \brief Determines whether two types are compatible for assignment. + * + * This functions checks whether a value of type src can be put into a variable of type dest. + * + * \param[in] dest the type of the variable being assigned to. + * \param[in] src the type of the value being written. + * \return true if the assignment is valid, false otherwise. + */ static bool is_assignable_to(const ValueType& dest, const ValueType& src); + /** + * \brief Creates the primitive WebAssembly i32 type. + */ static constexpr ValueType i32() { return { PrimitiveValueType::I32, nullptr }; } + /** + * \brief Creates the primitive WebAssembly i64 type. + */ static constexpr ValueType i64() { return { PrimitiveValueType::I64, nullptr }; } + /** + * \brief Creates the primitive WebAssembly f32 type. + */ static constexpr ValueType f32() { return { PrimitiveValueType::F32, nullptr }; } + /** + * \brief Creates the primitive WebAssembly f64 type. + */ static constexpr ValueType f64() { return { PrimitiveValueType::F64, nullptr }; } + /** + * \brief Creates a WebAssembly type for a function reference. + * + * \warning The function signature being passed in must be a pointer as returned from + * TypeTable::sig for type equality checking to work correctly. Do not pass a pointer + * to a function signature which is not part of a TypeTable. + * + * \param[in] sig the function signature of a typed function reference. nullptr to create an + * untyped function reference. + */ static constexpr ValueType func_ref(const FuncSig* sig = nullptr) { return { PrimitiveValueType::FuncRef, sig }; } + /** + * \brief Returns a primitive WebAssembly type corresponding to the given C++ type. + */ template static constexpr ValueType for_type() = delete; }; @@ -108,21 +179,57 @@ constexpr ValueType ValueType::for_type() { return ValueType::f64(); } +/** + * \brief Represents an untagged WebAssembly value. + * + * Such a value has no associated type information, so it is necessary to know the type from some + * other source in order to know which of the fields of this union should be read/written. + */ union Value { - uint32_t i32; - uint64_t i64; - float f32; - float f64; - void* ref; + uint32_t i32; ///< This value as a WebAssembly i32. + uint64_t i64; ///< This value as a WebAssembly i64. + float f32; ///< This value as a WebAssembly f32. + float f64; ///< This value as a WebAssembly f64. + void* ref; ///< This value as a WebAssembly reference. }; +/** + * \brief Represents a type-tagged WebAssembly value. + * + * Such a value includes type information alongside it that dictates what WebAssembly type the value + * in question represents, so no other source of information is needed to determine how to interpret + * the data. + */ struct TypedValue { - ValueType type; - Value val; + ValueType type; ///< The type of this WebAssembly value + Value val; ///< The actual WebAssembly value itself }; +/** + * \brief Represents the signature of a WebAssembly function. + * + * Note that after module instantiation, FuncSigs will be deduplicated within the context of a + * single winter::Environment. This means that function signatures can usually be checked for + * equality by simply comparing pointer values rather than having to examine the types in the + * signature itself. + */ struct FuncSig { + /** + * \brief The types of WebAssembly values returned by this function. + * + * For functions returning multiple WebAssembly values, the types will be in the order they + * would be written in the textual WebAssembly representation. In other words, the values will + * be pushed onto the stack in the same order as this list of types. + */ std::vector return_types; + + /** + * \brief The types of WebAssembly values taken by this function as parameters. + * + * For functions having multiple parameters, the types will be in the order they would be + * written in the textual WebAssembly representation. In other words, the values will be popped + * from the stack in the reverse of the order of this list of types. + */ std::vector param_types; FuncSig() {} @@ -130,13 +237,27 @@ struct FuncSig { : return_types(std::move(return_types)), param_types(std::move(param_types)) {} }; +/** + * \brief A table of deduplicated WebAssembly type information. + * + * This table is designed to deduplicate dynamic parts of WebAssembly types. Any two types returned + * from the same TypeTable can be checked for equality by simply comparing pointers. + */ class TypeTable { std::vector> _sigs; public: + /** + * \brief Deduplicates a function signature, returning a reference to the canonical version. + */ const FuncSig& sig(FuncSig sig) { return this->sig(std::move(sig.return_types), std::move(sig.param_types)); } + + /** + * \brief Creates or finds a function signature with the given return and parameter types, + * returning a reference to the canonical version. + */ const FuncSig& sig(std::vector return_types, std::vector param_types); };