diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index 9721f66f7999e..41c0ac413ff5f 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -284,7 +284,9 @@ struct WireTypePack { static const char name##_symbol[] = #name; \ static const ::emscripten::internal::symbol_registrar name##_registrar -class val { +class unique_val; +class val; +class base_val { public: // missing operators: // * ~ - + ++ -- @@ -296,103 +298,32 @@ class val { // exposing void, comma, and conditional is unnecessary // same with: = += -= *= /= %= <<= >>= >>>= &= ^= |= - static val array() { - return val(internal::_emval_new_array()); - } + static unique_val array(); template - static val array(Iter begin, Iter end) { -#if __cplusplus >= 202002L - if constexpr (std::contiguous_iterator && - internal::typeSupportsMemoryView< - typename std::iterator_traits::value_type>()) { - val view{ typed_memory_view(std::distance(begin, end), std::to_address(begin)) }; - return val(internal::_emval_new_array_from_memory_view(view.as_handle())); - } - // For numeric arrays, following codes are unreachable and the compiler - // will do 'dead code elimination'. - // Others fallback old way. -#endif - val new_array = array(); - for (auto it = begin; it != end; ++it) { - new_array.call("push", *it); - } - return new_array; - } + static unique_val array(Iter begin, Iter end); template - static val array(const std::vector& vec) { - if constexpr (internal::typeSupportsMemoryView()) { - // for numeric types, pass memory view and copy in JS side one-off - val view{ typed_memory_view(vec.size(), vec.data()) }; - return val(internal::_emval_new_array_from_memory_view(view.as_handle())); - } else { - return array(vec.begin(), vec.end()); - } - } - - static val object() { - return val(internal::_emval_new_object()); - } + static unique_val array(const std::vector& vec); - static val u8string(const char* s) { - return val(internal::_emval_new_u8string(s)); - } + static unique_val object(); - static val u16string(const char16_t* s) { - return val(internal::_emval_new_u16string(s)); - } + static unique_val u8string(const char* s); - static val undefined() { - return val(EM_VAL(internal::_EMVAL_UNDEFINED)); - } - - static val null() { - return val(EM_VAL(internal::_EMVAL_NULL)); - } - - static val take_ownership(EM_VAL e) { - return val(e); - } - - static val global(const char* name = 0) { - return val(internal::_emval_get_global(name)); - } - - static val module_property(const char* name) { - return val(internal::_emval_get_module_property(name)); - } - - template - explicit val(T&& value) { - using namespace internal; + static unique_val u16string(const char16_t* s); - WireTypePack argv(std::forward(value)); - new (this) val(_emval_take_value(internal::TypeID::get(), argv)); - } + static unique_val undefined(); - val() : val(EM_VAL(internal::_EMVAL_UNDEFINED)) {} + static unique_val null(); - explicit val(const char* v) - : val(internal::_emval_new_cstring(v)) - {} + static unique_val take_ownership(EM_VAL e); - // Note: unlike other constructors, this doesn't use as_handle() because - // it just moves a value and doesn't need to go via incref/decref. - // This means it's safe to move values across threads - an error will - // only arise if you access or free it from the wrong thread later. - val(val&& v) : handle(v.handle), thread(v.thread) { - v.handle = 0; - } + static unique_val global(const char* name = 0); - val(const val& v) : val(v.as_handle()) { - if (uses_ref_count()) { - internal::_emval_incref(handle); - } - } + static unique_val module_property(const char* name); - ~val() { - if (uses_ref_count()) { + ~base_val() { + if (uses_refcount()) { internal::_emval_decref(as_handle()); handle = 0; } @@ -412,20 +343,7 @@ class val { return taken; } - val& operator=(val&& v) & { - val tmp(std::move(v)); - this->~val(); - new (this) val(std::move(tmp)); - return *this; - } - - val& operator=(const val& v) & { - return *this = val(v); - } - - bool hasOwnProperty(const char* key) const { - return val::global("Object")["prototype"]["hasOwnProperty"].call("call", *this, val(key)); - } + bool hasOwnProperty(const char* key) const; bool isNull() const { return as_handle() == EM_VAL(internal::_EMVAL_NULL); @@ -451,39 +369,37 @@ class val { return internal::_emval_is_string(as_handle()); } - bool isArray() const { - return instanceof(global("Array")); - } + bool isArray() const; - bool equals(const val& v) const { + bool equals(const base_val& v) const { return internal::_emval_equals(as_handle(), v.as_handle()); } - bool operator==(const val& v) const { + bool operator==(const base_val& v) const { return equals(v); } - bool operator!=(const val& v) const { + bool operator!=(const base_val& v) const { return !equals(v); } - bool strictlyEquals(const val& v) const { + bool strictlyEquals(const base_val& v) const { return internal::_emval_strictly_equals(as_handle(), v.as_handle()); } - bool operator>(const val& v) const { + bool operator>(const base_val& v) const { return internal::_emval_greater_than(as_handle(), v.as_handle()); } - bool operator>=(const val& v) const { + bool operator>=(const base_val& v) const { return (*this > v) || (*this == v); } - bool operator<(const val& v) const { + bool operator<(const base_val& v) const { return internal::_emval_less_than(as_handle(), v.as_handle()); } - bool operator<=(const val& v) const { + bool operator<=(const base_val& v) const { return (*this < v) || (*this == v); } @@ -492,9 +408,7 @@ class val { } template - val operator[](const T& key) const { - return val(internal::_emval_get_property(as_handle(), val_ref(key).as_handle())); - } + unique_val operator[](const T& key) const; template void set(const K& key, const V& value) { @@ -507,18 +421,10 @@ class val { } template - val new_(Args&&... args) const { - using namespace internal; - - return internalCall(_emval_call, std::forward(args)...); - } + unique_val new_(Args&&... args) const; template - val operator()(Args&&... args) const { - using namespace internal; - - return internalCall(_emval_call, std::forward(args)...); - } + unique_val operator()(Args&&... args) const; template ReturnValue call(const char* name, Args&&... args) const { @@ -593,23 +499,19 @@ class val { } // Prefer calling val::typeOf() over val::typeof(), since this form works in both C++11 and GNU++11 build modes. "typeof" is a reserved word in GNU++11 extensions. - val typeOf() const { - return val(internal::_emval_typeof(as_handle())); - } + unique_val typeOf() const; // If code is not being compiled with GNU extensions enabled, typeof() is a valid identifier, so support that as a member function. #if __is_identifier(typeof) [[deprecated("Use typeOf() instead.")]] - val typeof() const { - return typeOf(); - } + unique_val typeof() const; #endif - bool instanceof(const val& v) const { + bool instanceof(const base_val& v) const { return internal::_emval_instanceof(as_handle(), v.as_handle()); } - bool in(const val& v) const { + bool in(const base_val& v) const { return internal::_emval_in(as_handle(), v.as_handle()); } @@ -617,9 +519,7 @@ class val { internal::_emval_throw(as_handle()); } - val await() const { - return val(internal::_emval_await(as_handle())); - } + unique_val await() const; struct iterator; @@ -629,26 +529,47 @@ class val { #if __cplusplus >= 202002L class awaiter; - awaiter operator co_await() const; + awaiter operator co_await() const&; + + class awaiter; + awaiter operator co_await() &&; class promise_type; #endif -private: +protected: // takes ownership, assumes handle already incref'd and lives on the same thread - explicit val(EM_VAL handle) + explicit base_val(EM_VAL handle) : handle(handle), thread(pthread_self()) {} + base_val(EM_VAL handle, pthread_t thread) + : handle(handle), thread(thread) + {} + + // Note: unlike other constructors, this doesn't use as_handle() because + // it just moves a value and doesn't need to go via incref/decref. + // This means it's safe to move values across threads - an error will + // only arise if you access or free it from the wrong thread later. + explicit base_val(base_val&& v) : handle(v.handle), thread(v.thread) { + v.handle = 0; + } + + void move_assignment(base_val&& v) & { + EM_VAL new_handle = v.handle; + pthread_t new_thread = v.thread; + v.handle = 0; + this->release_ownership(); + handle = new_handle; + thread = new_thread; + } + // Whether this value is a uses incref/decref (true) or is a special reserved // value (false). - bool uses_ref_count() const { + bool uses_refcount() const { return handle > reinterpret_cast(internal::_EMVAL_LAST_RESERVED_HANDLE); } - template - friend val internal::wrapped_extend(const std::string& , const val& ); - template Ret internalCall(Implementation impl, Args&&... args) const { using namespace internal; @@ -665,8 +586,14 @@ class val { } template - val val_ref(const T& v) const { - return val(v); + unique_val val_ref(const T& v) const; + + const base_val& val_ref(const base_val& v) const { + return v; + } + + const unique_val& val_ref(const unique_val& v) const { + return v; } const val& val_ref(const val& v) const { @@ -675,15 +602,233 @@ class val { pthread_t thread; EM_VAL handle; +}; + +class unique_val : public base_val { + public: + unique_val() : base_val(EM_VAL(internal::_EMVAL_UNDEFINED)) {} + + // takes ownership, assumes handle already incref'd and lives on the same thread + explicit unique_val(EM_VAL handle) : base_val(handle) {} + + explicit unique_val(const char* v) + : base_val(internal::_emval_new_cstring(v)) + {} + + template + explicit unique_val(T&& value); + + // unique_val doesn't allow copy, as that would require incref/decref. + unique_val(const unique_val& v) = delete; + unique_val& operator=(const unique_val& v) = delete; + + unique_val(val&& v); + unique_val(unique_val&& v) : base_val(std::move(v)) {} + + unique_val& operator=(unique_val&& v) & { + move_assignment(std::move(v)); + return *this; + } + + unique_val& operator=(val&& v) &; +}; + +class val : public base_val { + public: + val() : base_val(EM_VAL(internal::_EMVAL_UNDEFINED)) {} + + // takes ownership, assumes handle already incref'd and lives on the same thread + explicit val(EM_VAL handle) : base_val(handle) {} + + explicit val(const char* v) + : base_val(internal::_emval_new_cstring(v)) + {} + + // Note: unlike other constructors, this doesn't use as_handle() because + // it just moves a value and doesn't need to go via incref/decref. + // This means it's safe to move values across threads - an error will + // only arise if you access or free it from the wrong thread later. + val(unique_val&& v) : base_val(std::move(v)) { } + val(val&& v) : base_val(std::move(v)) { } + + val(const val& v) : base_val(v.as_handle()) { + if (uses_refcount()) { + internal::_emval_incref(handle); + } + } + + val(const base_val& v) : base_val(v.as_handle()) { + if (uses_refcount()) { + internal::_emval_incref(handle); + } + } + + template + explicit val(T&& value) : val() { + using namespace internal; + + WireTypePack argv(std::forward(value)); + new (this) val(_emval_take_value(internal::TypeID::get(), argv)); + } + + val& operator=(base_val&& v) & { + move_assignment(std::move(v)); + return *this; + } + + val& operator=(const val& v) & { + if (uses_refcount()) { + internal::_emval_decref(as_handle()); + } + handle = v.as_handle(); + thread = v.thread; + if (uses_refcount()) { + internal::_emval_incref(handle); + } + return *this; + } + + template + friend val internal::wrapped_extend(const std::string& , const val& ); friend struct internal::BindingType; }; -struct val::iterator { +inline unique_val::unique_val(val&& v) : base_val(0) { + move_assignment(std::move(v)); +} + +template +inline unique_val::unique_val(T&& value) : unique_val() { + using namespace internal; + + WireTypePack argv(std::forward(value)); + new (this) unique_val(std::move(_emval_take_value(internal::TypeID::get(), argv))); +} + +inline unique_val& unique_val::operator=(val&& v) & { + move_assignment(std::move(v)); + return *this; +} + +inline unique_val base_val::array() { + return unique_val(internal::_emval_new_array()); +} + +template +inline unique_val base_val::array(Iter begin, Iter end) { +#if __cplusplus >= 202002L + if constexpr (std::contiguous_iterator && + internal::typeSupportsMemoryView< + typename std::iterator_traits::value_type>()) { + unique_val view{ typed_memory_view(std::distance(begin, end), std::to_address(begin)) }; + return unique_val(internal::_emval_new_array_from_memory_view(view.as_handle())); + } + // For numeric arrays, following codes are unreachable and the compiler + // will do 'dead code elimination'. + // Others fallback old way. +#endif + unique_val new_array = array(); + for (auto it = begin; it != end; ++it) { + new_array.call("push", *it); + } + return std::move(new_array); +} + +template +inline unique_val base_val::array(const std::vector& vec) { + if constexpr (internal::typeSupportsMemoryView()) { + // for numeric types, pass memory view and copy in JS side one-off + unique_val view{ typed_memory_view(vec.size(), vec.data()) }; + return unique_val(internal::_emval_new_array_from_memory_view(view.as_handle())); + } else { + return array(vec.begin(), vec.end()); + } +} + +inline unique_val base_val::object() { + return unique_val(internal::_emval_new_object()); +} + +inline unique_val base_val::u8string(const char* s) { + return unique_val(internal::_emval_new_u8string(s)); +} + +inline unique_val base_val::u16string(const char16_t* s) { + return unique_val(internal::_emval_new_u16string(s)); +} + +inline unique_val base_val::undefined() { + return unique_val(EM_VAL(internal::_EMVAL_UNDEFINED)); +} + +inline unique_val base_val::null() { + return unique_val(EM_VAL(internal::_EMVAL_NULL)); +} + +inline unique_val base_val::take_ownership(EM_VAL e) { + return unique_val(e); +} + +inline unique_val base_val::global(const char* name) { + return unique_val(internal::_emval_get_global(name)); +} + +inline unique_val base_val::module_property(const char* name) { + return unique_val(internal::_emval_get_module_property(name)); +} + +inline bool base_val::hasOwnProperty(const char* key) const { + return base_val::global("Object")["prototype"]["hasOwnProperty"].call("call", val(*this), val(key)); +} + +inline bool base_val::isArray() const { + return instanceof(global("Array")); +} + +template +inline unique_val base_val::operator[](const T& key) const { + return unique_val(internal::_emval_get_property(as_handle(), val_ref(key).as_handle())); +} + +template +inline unique_val base_val::new_(Args&&... args) const { + using namespace internal; + + return internalCall(_emval_call, std::forward(args)...); +} + +template +inline unique_val base_val::operator()(Args&&... args) const { + using namespace internal; + + return internalCall(_emval_call, std::forward(args)...); +} + +inline unique_val base_val::typeOf() const { + return val(internal::_emval_typeof(as_handle())); +} + +#if __is_identifier(typeof) +inline unique_val base_val::typeof() const { + return typeOf(); +} +#endif + +inline unique_val base_val::await() const { + return unique_val(internal::_emval_await(as_handle())); +} + +template +inline unique_val base_val::val_ref(const T& v) const { + return unique_val(v); +} + +struct base_val::iterator { iterator() = delete; // Make sure iterator is only moveable, not copyable as it represents a mutable state. iterator(iterator&&) = default; - iterator(const val& v) : iter(internal::_emval_iter_begin(v.as_handle())) { + iterator(const base_val& v) : iter(internal::_emval_iter_begin(v.as_handle())) { this->operator++(); } val&& operator*() { return std::move(cur_value); } @@ -696,7 +841,7 @@ struct val::iterator { val cur_value; }; -inline val::iterator val::begin() const { +inline base_val::iterator base_val::begin() const { return iterator(*this); } @@ -705,20 +850,22 @@ inline val::iterator val::begin() const { // to drive the argument of the `co_await` operator (regardless // of the type of the parent coroutine). // This one is used for Promises represented by the `val` type. -class val::awaiter { +class base_val::awaiter { // State machine holding awaiter's current state. One of: // - initially created with promise // - waiting with a given coroutine handle // - completed with a result - std::variant, val> state; + std::variant, unique_val> state; constexpr static std::size_t STATE_PROMISE = 0; constexpr static std::size_t STATE_CORO = 1; constexpr static std::size_t STATE_RESULT = 2; public: + awaiter(unique_val promise) + : state(std::in_place_index, std::move(promise)) {} awaiter(const val& promise) - : state(std::in_place_index, promise) {} + : state(std::in_place_index, val(promise)) {} // just in case, ensure nobody moves / copies this type around awaiter(awaiter&&) = delete; @@ -746,15 +893,21 @@ class val::awaiter { val await_resume() { return std::move(std::get(state)); } }; -inline val::awaiter val::operator co_await() const { - return {*this}; +inline val::awaiter base_val::operator co_await() const& { + return {val(*this)}; +} + +inline val::awaiter base_val::operator co_await() && { + unique_val tmp; + tmp.move_assignment(std::move(*this)); + return {std::move(tmp)}; } // `promise_type` is a well-known subtype with well-known method names // that compiler uses to drive the coroutine itself // (`T::promise_type` is used for any coroutine with declared return type `T`). -class val::promise_type { - val promise, resolve, reject_with_current_exception; +class base_val::promise_type { + unique_val promise, resolve, reject_with_current_exception; public: // Create a `new Promise` and store it alongside the `resolve` and `reject` @@ -762,13 +915,13 @@ class val::promise_type { promise_type() { EM_VAL resolve_handle; EM_VAL reject_handle; - promise = val(internal::_emval_coro_make_promise(&resolve_handle, &reject_handle)); - resolve = val(resolve_handle); - reject_with_current_exception = val(reject_handle); + promise = unique_val(internal::_emval_coro_make_promise(&resolve_handle, &reject_handle)); + resolve = unique_val(resolve_handle); + reject_with_current_exception = unique_val(reject_handle); } // Return the stored promise as the actual return value of the coroutine. - val get_return_object() { return promise; } + unique_val get_return_object() { return std::move(promise); } // For similarity with JS async functions, our coroutines are eagerly evaluated. auto initial_suspend() noexcept { return std::suspend_never{}; } @@ -813,7 +966,7 @@ struct BindingType::value && // reference count. static WireType toWireType(const val& v) { EM_VAL handle = v.as_handle(); - if (v.uses_ref_count()) { + if (v.uses_refcount()) { _emval_incref(handle); } return handle; diff --git a/test/test_core.py b/test/test_core.py index 8711e968dcadc..fb5e87304f4f1 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7470,7 +7470,7 @@ def test_embind_val_assignment(self): @node_pthreads def test_embind_val_cross_thread(self): - self.emcc_args += ['--bind'] + self.emcc_args += ['--bind', '-g'] create_file('test_embind_val_cross_thread.cpp', r''' #include #include