// WebAssembly C++ API #ifndef __WASM_HH #define __WASM_HH #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // 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 vec { static const size_t invalid_size = SIZE_MAX; size_t size_; std::unique_ptr 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&& 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 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 { 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 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 using own = std::unique_ptr; template using ownvec = vec>; template auto make_own(T* x) -> own { return own(x); } /////////////////////////////////////////////////////////////////////////////// // Runtime Environment // Configuration class Config { public: Config() = delete; ~Config(); void operator delete(void*); static auto make() -> own; // Implementations may provide custom methods for manipulating Configs. }; // Engine class Engine { public: Engine() = delete; ~Engine(); void operator delete(void*); static auto make(own&& = Config::make()) -> own; }; // Store class Store { public: Store() = delete; ~Store(); void operator delete(void*); static auto make(Engine*) -> own; }; /////////////////////////////////////////////////////////////////////////////// // 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::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; auto copy() const -> own; 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; 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&& params = ownvec::make(), ownvec&& results = ownvec::make() ) -> own; auto copy() const -> own; auto params() const -> const ownvec&; auto results() const -> const ownvec&; }; // Global Types class GlobalType : public ExternType { public: GlobalType() = delete; ~GlobalType(); static auto make(own&&, Mutability) -> own; auto copy() const -> own; auto content() const -> const ValType*; auto mutability() const -> Mutability; }; // Table Types class TableType : public ExternType { public: TableType() = delete; ~TableType(); static auto make(own&&, Limits) -> own; auto copy() const -> own; 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; auto copy() const -> own; auto limits() const -> const Limits&; }; // Import Types using Name = vec; class ImportType { public: ImportType() = delete; ~ImportType(); void operator delete(void*); static auto make(Name&& module, Name&& name, own&&) -> own; auto copy() const -> own; 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&&) -> own; auto copy() const -> own; 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; 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&& 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&& x) -> Val { return Val(std::move(x)); } template inline static auto make(T x) -> Val; template inline static auto make(own&& 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 inline auto get() const -> T; auto release_ref() -> own { assert(is_ref()); auto ref = impl_.ref; impl_.ref = nullptr; return own(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 x) -> Val { return Val(x); } template<> inline auto Val::make(int64_t x) -> Val { return Val(x); } template<> inline auto Val::make(float32_t x) -> Val { return Val(x); } template<> inline auto Val::make(float64_t x) -> Val { return Val(x); } template<> inline auto Val::make(own&& x) -> Val { return Val(std::move(x)); } template<> inline auto Val::make(uint32_t x) -> Val { return Val(static_cast(x)); } template<> inline auto Val::make(uint64_t x) -> Val { return Val(static_cast(x)); } template<> inline auto Val::get() const -> int32_t { return i32(); } template<> inline auto Val::get() const -> int64_t { return i64(); } template<> inline auto Val::get() const -> float32_t { return f32(); } template<> inline auto Val::get() const -> float64_t { return f64(); } template<> inline auto Val::get() const -> Ref* { return ref(); } template<> inline auto Val::get() const -> uint32_t { return static_cast(i32()); } template<> inline auto Val::get() const -> uint64_t { return static_cast(i64()); } // Traps using Message = vec; // null terminated class Instance; class Frame { public: Frame() = delete; ~Frame(); void operator delete(void*); auto copy() const -> own; 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; auto copy() const -> own; auto message() const -> Message; auto origin() const -> own; // may be null auto trace() const -> ownvec; // may be empty, origin first }; // Shared objects template 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& binary) -> bool; static auto make(Store*, const vec& binary) -> own; auto copy() const -> own; auto imports() const -> ownvec; auto exports() const -> ownvec; auto share() const -> own>; static auto obtain(Store*, const Shared*) -> own; auto serialize() const -> vec; static auto deserialize(Store*, const vec&) -> own; }; // Foreign Objects class Foreign : public Ref { public: Foreign() = delete; ~Foreign(); static auto make(Store*) -> own; auto copy() const -> own; }; // Externals class Func; class Global; class Table; class Memory; class Extern : public Ref { public: Extern() = delete; ~Extern(); auto copy() const -> own; auto kind() const -> ExternKind; auto type() const -> own; 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; using callback_with_env = auto (*)(void*, const Val[], Val[]) -> own; static auto make(Store*, const FuncType*, callback) -> own; static auto make(Store*, const FuncType*, callback_with_env, void*, void (*finalizer)(void*) = nullptr) -> own; auto copy() const -> own; auto type() const -> own; auto param_arity() const -> size_t; auto result_arity() const -> size_t; auto call(const Val[] = nullptr, Val[] = nullptr) const -> own; }; // Global Instances class Global : public Extern { public: Global() = delete; ~Global(); static auto make(Store*, const GlobalType*, const Val&) -> own; auto copy() const -> own; auto type() const -> own; 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; auto copy() const -> own
; auto type() const -> own; auto get(size_t index) const -> own; 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; auto copy() const -> own; using pages_t = uint32_t; static const size_t page_size = 0x10000; auto type() const -> own; 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* = nullptr ) -> own; auto copy() const -> own; auto exports() const -> ownvec; }; /////////////////////////////////////////////////////////////////////////////// } // namespace wasm #endif // #ifdef __WASM_HH