diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst b/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst index b697a996bda38..b08a45b756149 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst @@ -1122,6 +1122,63 @@ The example can be compiled on the Linux/macOS terminal with:: emcc -O2 -Wall -Werror -lembind -o oscillator.html oscillator.cpp +Argument policies +----------------- + +When calling JavaScript functions via ``val``, arguments are converted from C++ to JavaScript. By default, objects are copied. +To control this behavior, you can use argument policies. + +The following policies are supported: + +* :cpp:type:`policy::take_ownership` - Ownership is transferred to JavaScript. The C++ object is moved. +* :cpp:type:`policy::reference` - A reference is passed to JavaScript. The C++ object is not copied or moved. + +Policies can be specified for specific arguments using the ``arg`` template parameter. + +.. code-block:: cpp + + #include + #include + + using namespace emscripten; + + struct MyClass { + MyClass() {} + MyClass(const MyClass&) { printf("copy\n"); } + MyClass(MyClass&&) { printf("move\n"); } + }; + + EMSCRIPTEN_BINDINGS(my_module) { + class_("MyClass"); + } + + int main() { + MyClass obj; + + // Default: copy + val::global("someFunction")(obj); + + // Take ownership: move + val::global("someFunction")(obj, policy::take_ownership>()); + + // Reference: no copy/move + val::global("someFunction")(obj, policy::reference>()); + } + +Policies can also be used with ``val::call``, ``val::new_``, and ``val::set``. + +.. code-block:: cpp + + // Use policy with val::call + val::global().call("someFunction", obj, policy::reference>()); + + // Use policy with val::new_ + val v = val::global("MyClass").new_(obj, policy::take_ownership>()); + + // Use policy with val::set + val::global().set("prop", obj, policy::reference()); + + Built-in type conversions ========================= diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index d7fd17b1844fc..e4981d86cbd63 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -236,20 +236,36 @@ void writeGenericWireType(GenericWireType*& cursor, T wt) { ++cursor; } +template +struct WireTypeSelector { + using Type = OriginalType; + static const OriginalType& convert(const OriginalType& v) { return v; } +}; + +template +struct WireTypeSelector, OriginalType> { + using Type = ArgWithPolicy; + static Type convert(const OriginalType& v) { return Type(v); } +}; + +template inline void writeGenericWireTypes(GenericWireType*&) { } -template +template EMSCRIPTEN_ALWAYS_INLINE void writeGenericWireTypes(GenericWireType*& cursor, First&& first, Rest&&... rest) { - writeGenericWireType(cursor, BindingType::toWireType(std::forward(first), rvp::default_tag{})); - writeGenericWireTypes(cursor, std::forward(rest)...); + using TransformedType = typename Policy::template MapWithPolicies::type; + using Selector = WireTypeSelector; + + writeGenericWireType(cursor, BindingType::toWireType(Selector::convert(std::forward(first)), rvp::default_tag{})); + writeGenericWireTypes(cursor, std::forward(rest)...); } -template +template struct WireTypePack { WireTypePack(Args&&... args) { GenericWireType* cursor = elements.data(); - writeGenericWireTypes(cursor, std::forward(args)...); + writeGenericWireTypes(cursor, std::forward(args)...); } operator EM_VAR_ARGS() const { @@ -483,8 +499,8 @@ class EMBIND_VISIBILITY_DEFAULT val { } template - void set(const K& key, const V& value, Policies... policies) { - internal::_emval_set_property(as_handle(), val_ref(key).as_handle(), val_ref(value, policies...).as_handle()); + void set(K&& key, V&& value, Policies... policies) { + internal::_emval_set_property(as_handle(), val_ref(std::forward(key)).as_handle(), val_ref(std::forward(value), policies...).as_handle()); } template @@ -599,7 +615,7 @@ class EMBIND_VISIBILITY_DEFAULT val { static constexpr typename Policy::template ArgTypeList argTypes; thread_local EM_INVOKER mc = _emval_create_invoker(argTypes.getCount(), argTypes.getTypes(), Kind); - WireTypePack argv(std::forward(args)...); + WireTypePack argv(std::forward(args)...); EM_DESTRUCTORS destructors = nullptr; RetWire result; @@ -624,14 +640,18 @@ class EMBIND_VISIBILITY_DEFAULT val { } template - val val_ref(const T& v, Policies... policies) const { - return val(v, policies...); + val val_ref(T&& v, Policies... policies) const { + return val(std::forward(v), policies...); } const val& val_ref(const val& v) const { return v; } + val& val_ref(val& v) const { + return v; + } + pthread_t thread; EM_VAL handle; diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h index a054476fe1057..1fd5ce79c2149 100644 --- a/system/include/emscripten/wire.h +++ b/system/include/emscripten/wire.h @@ -42,6 +42,12 @@ namespace internal { typedef const void* TYPEID; +template +struct ArgWithPolicy { + const T& value; + ArgWithPolicy(const T& v) : value(v) {} +}; + // We don't need the full std::type_info implementation. We // just need a unique identifier per type and polymorphic type // identification. @@ -155,6 +161,34 @@ template struct TypeID : TypeID { }; +template +struct TypeID> { + static constexpr TYPEID get() { + return TypeID::get(); + } +}; + +template +struct TypeID> { + static constexpr TYPEID get() { + return TypeID>::get(); + } +}; + +template +struct TypeID> { + static constexpr TYPEID get() { + return TypeID>::get(); + } +}; + +template +struct TypeID> { + static constexpr TYPEID get() { + return LightTypeID::get(); + } +}; + // ExecutePolicies<> template @@ -381,6 +415,14 @@ struct BindingType { } }; +template +struct BindingType> { + using WireType = typename BindingType::WireType; + static WireType toWireType(const ArgWithPolicy& v, rvp::default_tag) { + return BindingType::toWireType(v.value, Policy{}); + } +}; + template struct GenericBindingType { typedef typename std::remove_reference::type ActualT; @@ -580,6 +622,34 @@ enum class enum_value_type { string = 2 }; +namespace policy { + +template> +struct take_ownership { + template + struct Transform { + using type = typename std::conditional< + Index == Slot::index, + internal::ArgWithPolicy, + InputType + >::type; + }; +}; + +template> +struct reference { + template + struct Transform { + using type = typename std::conditional< + Index == Slot::index, + internal::ArgWithPolicy, + InputType + >::type; + }; +}; + +} // end namespace policy + namespace internal { template @@ -650,6 +720,16 @@ struct GetReturnValuePolicy using tag = rvp::reference; }; +template +struct isPolicy, Rest...> { + static constexpr bool value = true; +}; + +template +struct isPolicy, Rest...> { + static constexpr bool value = true; +}; + template struct GetReturnValuePolicy { using tag = GetReturnValuePolicy::tag; diff --git a/test/embind/test_val.cpp b/test/embind/test_val.cpp index 44178adbcaf7b..6b0eed32870af 100644 --- a/test/embind/test_val.cpp +++ b/test/embind/test_val.cpp @@ -33,10 +33,22 @@ Dummy * makeDummy() { return new Dummy(); } +struct MoveTester { + static int moves; + static int copies; + static void reset() { moves = 0; copies = 0; } + MoveTester() {} + MoveTester(const MoveTester&) { copies++; } + MoveTester(MoveTester&&) { moves++; } +}; +int MoveTester::moves = 0; +int MoveTester::copies = 0; + EMSCRIPTEN_BINDINGS(test_bindings) { emscripten::class_("Dummy"); emscripten::function("throw_js_error", &throw_js_error); emscripten::function("makeDummy", &makeDummy, emscripten::allow_raw_pointers()); + emscripten::class_("MoveTester"); } int main() { @@ -721,6 +733,111 @@ int main() { globalThis.staticDummy.delete(); ); + test("val::operator() with policies"); + EM_ASM( + globalThis.assertAndDelete = function(arg0) { + assert(arg0); + arg0.delete(); + }; + globalThis.assertArg0 = function(arg0) { + assert(arg0); + }; + globalThis.assertArg1 = function(arg0, arg1) { + assert(arg1); + }; + ); + MoveTester t; + MoveTester::reset(); + val::global("assertAndDelete")(t); // default is copy + ensure(MoveTester::copies == 1); + ensure(MoveTester::moves == 0); + + MoveTester::reset(); + val::global("assertAndDelete")(t, policy::take_ownership>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 1); + + MoveTester::reset(); + val::global("assertAndDelete")(new MoveTester(), policy::take_ownership>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + + MoveTester::reset(); + MoveTester *ptr = new MoveTester(); + val::global("assertArg0")(ptr, policy::reference>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + delete ptr; + + MoveTester::reset(); + val::global("assertArg0")(t, policy::reference>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + + MoveTester::reset(); + val::global("assertArg1")(val::null(), t, policy::reference>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + + test("val::call with policies"); + MoveTester::reset(); + val::global().call("assertAndDelete", t, val::null(), policy::take_ownership>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 1); + + MoveTester::reset(); + val::global().call("assertArg0", t, val::null(), policy::reference>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + + test("val constructor with policies"); + MoveTester::reset(); + val v_owned(t, policy::take_ownership()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 1); + v_owned.call("delete"); + + MoveTester::reset(); + val v_ref(t, policy::reference()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + + test("val::set with policies"); + MoveTester::reset(); + val::global().set("temp", t, policy::take_ownership()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 1); + val::global("temp").call("delete"); + val::global().delete_("temp"); + + MoveTester::reset(); + val::global().set("temp", t, policy::reference()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + val::global().delete_("temp"); + + test("val::new_ with policies"); + EM_ASM( + globalThis.TestClass = class { + constructor(arg0) { + this.arg0 = arg0; + } + delete() { + this.arg0.delete(); + } + }; + ); + MoveTester::reset(); + val testClass_owned = val::global("TestClass").new_(t, policy::take_ownership>()); + testClass_owned.call("delete"); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 1); + + MoveTester::reset(); + val testClass_ref = val::global("TestClass").new_(t, policy::reference>()); + ensure(MoveTester::copies == 0); + ensure(MoveTester::moves == 0); + printf("end\n"); return 0; } diff --git a/test/embind/test_val.out b/test/embind/test_val.out index 91bd25c105e08..8165e0a2cca38 100644 --- a/test/embind/test_val.out +++ b/test/embind/test_val.out @@ -44,4 +44,9 @@ test: val u8string(const char* s)... test: val u16string(const char16_t* s)... test: val set() with policy... test: val as<> with reference... +test: val::operator() with policies... +test: val::call with policies... +test: val constructor with policies... +test: val::set with policies... +test: val::new_ with policies... end