| // WebAssembly C++ API |
| |
| #ifndef __WASM_HH |
| #define __WASM_HH |
| |
| #include <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <memory> |
| #include <limits> |
| #include <string> |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Auxiliaries |
| |
| // Machine types |
| |
| static_assert(sizeof(float) == sizeof(int32_t), "incompatible float type"); |
| static_assert(sizeof(double) == sizeof(int64_t), "incompatible double type"); |
| static_assert(sizeof(intptr_t) == sizeof(int32_t) || |
| sizeof(intptr_t) == sizeof(int64_t), "incompatible pointer type"); |
| |
| using byte_t = char; |
| using float32_t = float; |
| using float64_t = double; |
| |
| |
| namespace wasm { |
| |
| // Vectors |
| |
| template<class T> |
| class vec { |
| static const size_t invalid_size = SIZE_MAX; |
| |
| size_t size_; |
| std::unique_ptr<T[]> data_; |
| |
| #ifdef WASM_API_DEBUG |
| void make_data(); |
| void free_data(); |
| #else |
| void make_data() {} |
| void free_data() {} |
| #endif |
| |
| vec(size_t size) : vec(size, size ? new(std::nothrow) T[size] : nullptr) { |
| make_data(); |
| } |
| |
| vec(size_t size, T* data) : size_(size), data_(data) { |
| assert(!!size_ == !!data_ || size_ == invalid_size); |
| } |
| |
| public: |
| using elem_type = T; |
| |
| vec(vec<T>&& that) : vec(that.size_, that.data_.release()) {} |
| |
| ~vec() { |
| free_data(); |
| } |
| |
| operator bool() const { |
| return bool(size_ != invalid_size); |
| } |
| |
| auto size() const -> size_t { |
| return size_; |
| } |
| |
| auto get() const -> const T* { |
| return data_.get(); |
| } |
| |
| auto get() -> T* { |
| return data_.get(); |
| } |
| |
| auto release() -> T* { |
| return data_.release(); |
| } |
| |
| void reset() { |
| free_data(); |
| size_ = invalid_size; |
| data_.reset(); |
| } |
| |
| void reset(vec& that) { |
| free_data(); |
| size_ = that.size_; |
| data_.reset(that.data_.release()); |
| } |
| |
| auto operator=(vec&& that) -> vec& { |
| reset(that); |
| return *this; |
| } |
| |
| auto operator[](size_t i) -> T& { |
| assert(i < size_); |
| return data_[i]; |
| } |
| |
| auto operator[](size_t i) const -> const T& { |
| assert(i < size_); |
| return data_[i]; |
| } |
| |
| auto copy() const -> vec { |
| auto v = vec(size_); |
| if (v) for (size_t i = 0; i < size_; i++) v.data_[i] = data_[i]; |
| return v; |
| } |
| |
| // TODO: This can't be used for e.g. vec<Val> |
| auto deep_copy() const -> vec { |
| auto v = vec(size_); |
| if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]->copy(); |
| return v; |
| } |
| |
| static auto make_uninitialized(size_t size = 0) -> vec { |
| return vec(size); |
| } |
| |
| static auto make(size_t size, T init[]) -> vec { |
| auto v = vec(size); |
| if (v) for (size_t i = 0; i < size; ++i) v.data_[i] = std::move(init[i]); |
| return v; |
| } |
| |
| static auto make(std::string s) -> vec<char> { |
| auto v = vec(s.length() + 1); |
| if (v) std::strcpy(v.get(), s.data()); |
| return v; |
| } |
| |
| // TODO(mvsc): MVSC requires this special case: |
| static auto make() -> vec { |
| return vec(0); |
| } |
| |
| template<class... Ts> |
| static auto make(Ts&&... args) -> vec { |
| T data[] = { std::move(args)... }; |
| return make(sizeof...(Ts), data); |
| } |
| |
| static auto adopt(size_t size, T data[]) -> vec { |
| return vec(size, data); |
| } |
| |
| static auto invalid() -> vec { |
| return vec(invalid_size, nullptr); |
| } |
| }; |
| |
| |
| // Ownership |
| |
| template<class T> using own = std::unique_ptr<T>; |
| template<class T> using ownvec = vec<own<T>>; |
| |
| template<class T> |
| auto make_own(T* x) -> own<T> { return own<T>(x); } |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Runtime Environment |
| |
| // Configuration |
| |
| class Config { |
| public: |
| Config() = delete; |
| ~Config(); |
| void operator delete(void*); |
| |
| static auto make() -> own<Config>; |
| |
| // Implementations may provide custom methods for manipulating Configs. |
| }; |
| |
| |
| // Engine |
| |
| class Engine { |
| public: |
| Engine() = delete; |
| ~Engine(); |
| void operator delete(void*); |
| |
| static auto make(own<Config>&& = Config::make()) -> own<Engine>; |
| }; |
| |
| |
| // Store |
| |
| class Store { |
| public: |
| Store() = delete; |
| ~Store(); |
| void operator delete(void*); |
| |
| static auto make(Engine*) -> own<Store>; |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Type Representations |
| |
| // Type attributes |
| |
| enum Mutability : uint8_t { CONST, VAR }; |
| |
| struct Limits { |
| uint32_t min; |
| uint32_t max; |
| |
| Limits(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) : |
| min(min), max(max) {} |
| }; |
| |
| |
| // Value Types |
| |
| enum ValKind : uint8_t { |
| I32, I64, F32, F64, |
| ANYREF = 128, FUNCREF, |
| }; |
| |
| inline bool is_num(ValKind k) { return k < ANYREF; } |
| inline bool is_ref(ValKind k) { return k >= ANYREF; } |
| |
| |
| class ValType { |
| public: |
| ValType() = delete; |
| ~ValType(); |
| void operator delete(void*); |
| |
| static auto make(ValKind) -> own<ValType>; |
| auto copy() const -> own<ValType>; |
| |
| auto kind() const -> ValKind; |
| auto is_num() const -> bool { return wasm::is_num(kind()); } |
| auto is_ref() const -> bool { return wasm::is_ref(kind()); } |
| }; |
| |
| |
| // External Types |
| |
| enum ExternKind : uint8_t { |
| EXTERN_FUNC, EXTERN_GLOBAL, EXTERN_TABLE, EXTERN_MEMORY |
| }; |
| |
| class FuncType; |
| class GlobalType; |
| class TableType; |
| class MemoryType; |
| |
| class ExternType { |
| public: |
| ExternType() = delete; |
| ~ExternType(); |
| void operator delete(void*); |
| |
| auto copy() const-> own<ExternType>; |
| |
| auto kind() const -> ExternKind; |
| |
| auto func() -> FuncType*; |
| auto global() -> GlobalType*; |
| auto table() -> TableType*; |
| auto memory() -> MemoryType*; |
| |
| auto func() const -> const FuncType*; |
| auto global() const -> const GlobalType*; |
| auto table() const -> const TableType*; |
| auto memory() const -> const MemoryType*; |
| }; |
| |
| |
| // Function Types |
| |
| class FuncType : public ExternType { |
| public: |
| FuncType() = delete; |
| ~FuncType(); |
| |
| static auto make( |
| ownvec<ValType>&& params = ownvec<ValType>::make(), |
| ownvec<ValType>&& results = ownvec<ValType>::make() |
| ) -> own<FuncType>; |
| |
| auto copy() const -> own<FuncType>; |
| |
| auto params() const -> const ownvec<ValType>&; |
| auto results() const -> const ownvec<ValType>&; |
| }; |
| |
| |
| // Global Types |
| |
| class GlobalType : public ExternType { |
| public: |
| GlobalType() = delete; |
| ~GlobalType(); |
| |
| static auto make(own<ValType>&&, Mutability) -> own<GlobalType>; |
| auto copy() const -> own<GlobalType>; |
| |
| auto content() const -> const ValType*; |
| auto mutability() const -> Mutability; |
| }; |
| |
| |
| // Table Types |
| |
| class TableType : public ExternType { |
| public: |
| TableType() = delete; |
| ~TableType(); |
| |
| static auto make(own<ValType>&&, Limits) -> own<TableType>; |
| auto copy() const -> own<TableType>; |
| |
| auto element() const -> const ValType*; |
| auto limits() const -> const Limits&; |
| }; |
| |
| |
| // Memory Types |
| |
| class MemoryType : public ExternType { |
| public: |
| MemoryType() = delete; |
| ~MemoryType(); |
| |
| static auto make(Limits) -> own<MemoryType>; |
| auto copy() const -> own<MemoryType>; |
| |
| auto limits() const -> const Limits&; |
| }; |
| |
| |
| // Import Types |
| |
| using Name = vec<byte_t>; |
| |
| class ImportType { |
| public: |
| ImportType() = delete; |
| ~ImportType(); |
| void operator delete(void*); |
| |
| static auto make(Name&& module, Name&& name, own<ExternType>&&) -> |
| own<ImportType>; |
| auto copy() const -> own<ImportType>; |
| |
| auto module() const -> const Name&; |
| auto name() const -> const Name&; |
| auto type() const -> const ExternType*; |
| }; |
| |
| |
| // Export Types |
| |
| class ExportType { |
| public: |
| ExportType() = delete; |
| ~ExportType(); |
| void operator delete(void*); |
| |
| static auto make(Name&&, own<ExternType>&&) -> own<ExportType>; |
| auto copy() const -> own<ExportType>; |
| |
| auto name() const -> const Name&; |
| auto type() const -> const ExternType*; |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Runtime Objects |
| |
| // References |
| |
| class Ref { |
| public: |
| Ref() = delete; |
| ~Ref(); |
| void operator delete(void*); |
| |
| auto copy() const -> own<Ref>; |
| auto same(const Ref*) const -> bool; |
| |
| auto get_host_info() const -> void*; |
| void set_host_info(void* info, void (*finalizer)(void*) = nullptr); |
| }; |
| |
| |
| // Values |
| |
| class Val { |
| ValKind kind_; |
| union impl { |
| int32_t i32; |
| int64_t i64; |
| float32_t f32; |
| float64_t f64; |
| Ref* ref; |
| } impl_; |
| |
| Val(ValKind kind, impl impl) : kind_(kind), impl_(impl) {} |
| |
| public: |
| Val() : kind_(ANYREF) { impl_.ref = nullptr; } |
| Val(int32_t i) : kind_(I32) { impl_.i32 = i; } |
| Val(int64_t i) : kind_(I64) { impl_.i64 = i; } |
| Val(float32_t z) : kind_(F32) { impl_.f32 = z; } |
| Val(float64_t z) : kind_(F64) { impl_.f64 = z; } |
| Val(own<Ref>&& r) : kind_(ANYREF) { impl_.ref = r.release(); } |
| |
| Val(Val&& that) : kind_(that.kind_), impl_(that.impl_) { |
| if (is_ref()) that.impl_.ref = nullptr; |
| } |
| |
| ~Val() { |
| reset(); |
| } |
| |
| auto is_num() const -> bool { return wasm::is_num(kind_); } |
| auto is_ref() const -> bool { return wasm::is_ref(kind_); } |
| |
| static auto i32(int32_t x) -> Val { return Val(x); } |
| static auto i64(int64_t x) -> Val { return Val(x); } |
| static auto f32(float32_t x) -> Val { return Val(x); } |
| static auto f64(float64_t x) -> Val { return Val(x); } |
| static auto ref(own<Ref>&& x) -> Val { return Val(std::move(x)); } |
| template<class T> inline static auto make(T x) -> Val; |
| template<class T> inline static auto make(own<T>&& x) -> Val; |
| |
| void reset() { |
| if (is_ref() && impl_.ref) { |
| delete impl_.ref; |
| impl_.ref = nullptr; |
| } |
| } |
| |
| void reset(Val& that) { |
| reset(); |
| kind_ = that.kind_; |
| impl_ = that.impl_; |
| if (is_ref()) that.impl_.ref = nullptr; |
| } |
| |
| auto operator=(Val&& that) -> Val& { |
| reset(that); |
| return *this; |
| } |
| |
| auto kind() const -> ValKind { return kind_; } |
| auto i32() const -> int32_t { assert(kind_ == I32); return impl_.i32; } |
| auto i64() const -> int64_t { assert(kind_ == I64); return impl_.i64; } |
| auto f32() const -> float32_t { assert(kind_ == F32); return impl_.f32; } |
| auto f64() const -> float64_t { assert(kind_ == F64); return impl_.f64; } |
| auto ref() const -> Ref* { assert(is_ref()); return impl_.ref; } |
| template<class T> inline auto get() const -> T; |
| |
| auto release_ref() -> own<Ref> { |
| assert(is_ref()); |
| auto ref = impl_.ref; |
| impl_.ref = nullptr; |
| return own<Ref>(ref); |
| } |
| |
| auto copy() const -> Val { |
| if (is_ref() && impl_.ref != nullptr) { |
| // TODO(mvsc): MVSC cannot handle this: |
| // impl impl = {.ref = impl_.ref->copy().release()}; |
| impl impl; |
| impl.ref = impl_.ref->copy().release(); |
| return Val(kind_, impl); |
| } else { |
| return Val(kind_, impl_); |
| } |
| } |
| }; |
| |
| |
| template<> inline auto Val::make<int32_t>(int32_t x) -> Val { return Val(x); } |
| template<> inline auto Val::make<int64_t>(int64_t x) -> Val { return Val(x); } |
| template<> inline auto Val::make<float32_t>(float32_t x) -> Val { return Val(x); } |
| template<> inline auto Val::make<float64_t>(float64_t x) -> Val { return Val(x); } |
| template<> inline auto Val::make<Ref>(own<Ref>&& x) -> Val { |
| return Val(std::move(x)); |
| } |
| |
| template<> inline auto Val::make<uint32_t>(uint32_t x) -> Val { |
| return Val(static_cast<int32_t>(x)); |
| } |
| template<> inline auto Val::make<uint64_t>(uint64_t x) -> Val { |
| return Val(static_cast<int64_t>(x)); |
| } |
| |
| template<> inline auto Val::get<int32_t>() const -> int32_t { return i32(); } |
| template<> inline auto Val::get<int64_t>() const -> int64_t { return i64(); } |
| template<> inline auto Val::get<float32_t>() const -> float32_t { return f32(); } |
| template<> inline auto Val::get<float64_t>() const -> float64_t { return f64(); } |
| template<> inline auto Val::get<Ref*>() const -> Ref* { return ref(); } |
| |
| template<> inline auto Val::get<uint32_t>() const -> uint32_t { |
| return static_cast<uint32_t>(i32()); |
| } |
| template<> inline auto Val::get<uint64_t>() const -> uint64_t { |
| return static_cast<uint64_t>(i64()); |
| } |
| |
| |
| // Traps |
| |
| using Message = vec<byte_t>; // null terminated |
| |
| class Instance; |
| |
| class Frame { |
| public: |
| Frame() = delete; |
| ~Frame(); |
| void operator delete(void*); |
| |
| auto copy() const -> own<Frame>; |
| |
| auto instance() const -> Instance*; |
| auto func_index() const -> uint32_t; |
| auto func_offset() const -> size_t; |
| auto module_offset() const -> size_t; |
| }; |
| |
| class Trap : public Ref { |
| public: |
| Trap() = delete; |
| ~Trap(); |
| |
| static auto make(Store*, const Message& msg) -> own<Trap>; |
| auto copy() const -> own<Trap>; |
| |
| auto message() const -> Message; |
| auto origin() const -> own<Frame>; // may be null |
| auto trace() const -> ownvec<Frame>; // may be empty, origin first |
| }; |
| |
| |
| // Shared objects |
| |
| template<class T> |
| class Shared { |
| public: |
| Shared() = delete; |
| ~Shared(); |
| void operator delete(void*); |
| }; |
| |
| |
| // Modules |
| |
| class Module : public Ref { |
| public: |
| Module() = delete; |
| ~Module(); |
| |
| static auto validate(Store*, const vec<byte_t>& binary) -> bool; |
| static auto make(Store*, const vec<byte_t>& binary) -> own<Module>; |
| auto copy() const -> own<Module>; |
| |
| auto imports() const -> ownvec<ImportType>; |
| auto exports() const -> ownvec<ExportType>; |
| |
| auto share() const -> own<Shared<Module>>; |
| static auto obtain(Store*, const Shared<Module>*) -> own<Module>; |
| |
| auto serialize() const -> vec<byte_t>; |
| static auto deserialize(Store*, const vec<byte_t>&) -> own<Module>; |
| }; |
| |
| |
| // Foreign Objects |
| |
| class Foreign : public Ref { |
| public: |
| Foreign() = delete; |
| ~Foreign(); |
| |
| static auto make(Store*) -> own<Foreign>; |
| auto copy() const -> own<Foreign>; |
| }; |
| |
| |
| // Externals |
| |
| class Func; |
| class Global; |
| class Table; |
| class Memory; |
| |
| class Extern : public Ref { |
| public: |
| Extern() = delete; |
| ~Extern(); |
| |
| auto copy() const -> own<Extern>; |
| |
| auto kind() const -> ExternKind; |
| auto type() const -> own<ExternType>; |
| |
| auto func() -> Func*; |
| auto global() -> Global*; |
| auto table() -> Table*; |
| auto memory() -> Memory*; |
| |
| auto func() const -> const Func*; |
| auto global() const -> const Global*; |
| auto table() const -> const Table*; |
| auto memory() const -> const Memory*; |
| }; |
| |
| |
| // Function Instances |
| |
| class Func : public Extern { |
| public: |
| Func() = delete; |
| ~Func(); |
| |
| using callback = auto (*)(const Val[], Val[]) -> own<Trap>; |
| using callback_with_env = auto (*)(void*, const Val[], Val[]) -> own<Trap>; |
| |
| static auto make(Store*, const FuncType*, callback) -> own<Func>; |
| static auto make(Store*, const FuncType*, callback_with_env, |
| void*, void (*finalizer)(void*) = nullptr) -> own<Func>; |
| auto copy() const -> own<Func>; |
| |
| auto type() const -> own<FuncType>; |
| auto param_arity() const -> size_t; |
| auto result_arity() const -> size_t; |
| |
| auto call(const Val[] = nullptr, Val[] = nullptr) const -> own<Trap>; |
| }; |
| |
| |
| // Global Instances |
| |
| class Global : public Extern { |
| public: |
| Global() = delete; |
| ~Global(); |
| |
| static auto make(Store*, const GlobalType*, const Val&) -> own<Global>; |
| auto copy() const -> own<Global>; |
| |
| auto type() const -> own<GlobalType>; |
| auto get() const -> Val; |
| void set(const Val&); |
| }; |
| |
| |
| // Table Instances |
| |
| class Table : public Extern { |
| public: |
| Table() = delete; |
| ~Table(); |
| |
| using size_t = uint32_t; |
| |
| static auto make( |
| Store*, const TableType*, const Ref* init = nullptr) -> own<Table>; |
| auto copy() const -> own<Table>; |
| |
| auto type() const -> own<TableType>; |
| auto get(size_t index) const -> own<Ref>; |
| auto set(size_t index, const Ref*) -> bool; |
| auto size() const -> size_t; |
| auto grow(size_t delta, const Ref* init = nullptr) -> bool; |
| }; |
| |
| |
| // Memory Instances |
| |
| class Memory : public Extern { |
| public: |
| Memory() = delete; |
| ~Memory(); |
| |
| static auto make(Store*, const MemoryType*) -> own<Memory>; |
| auto copy() const -> own<Memory>; |
| |
| using pages_t = uint32_t; |
| |
| static const size_t page_size = 0x10000; |
| |
| auto type() const -> own<MemoryType>; |
| auto data() const -> byte_t*; |
| auto data_size() const -> size_t; |
| auto size() const -> pages_t; |
| auto grow(pages_t delta) -> bool; |
| }; |
| |
| |
| // Module Instances |
| |
| class Instance : public Ref { |
| public: |
| Instance() = delete; |
| ~Instance(); |
| |
| static auto make( |
| Store*, const Module*, const Extern* const[], own<Trap>* = nullptr |
| ) -> own<Instance>; |
| auto copy() const -> own<Instance>; |
| |
| auto exports() const -> ownvec<Extern>; |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| } // namespace wasm |
| |
| #endif // #ifdef __WASM_HH |