Skip to content

Commit 84962cb

Browse files
committed
[embind] Add pointer policies for val's call, operator(), and new_.
After #24175, pointer policies were required in more places, but there was no way to pass the policy into val's `call`, `operator()`, and `new_` methods and use pointers. All of these methods take variadic template arguments already which made passing separate variadic policy arguments challenging. I used some C++14/17 tricks to separate the regular arguments and policy arguments.
1 parent aaab470 commit 84962cb

File tree

7 files changed

+104
-7
lines changed

7 files changed

+104
-7
lines changed

system/include/emscripten/val.h

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ class EMBIND_VISIBILITY_DEFAULT val {
345345
explicit val(T&& value, Policies...) {
346346
using namespace internal;
347347

348-
new (this) val(internalCall<EM_INVOKER_KIND::CAST, WithPolicies<Policies...>, val>(nullptr, nullptr, std::forward<T>(value)));
348+
new (this) val(internalCallWithPolicy<EM_INVOKER_KIND::CAST, WithPolicies<Policies...>, val>(nullptr, nullptr, std::forward<T>(value)));
349349
}
350350

351351
val() : val(EM_VAL(internal::_EMVAL_UNDEFINED)) {}
@@ -492,28 +492,28 @@ class EMBIND_VISIBILITY_DEFAULT val {
492492
val new_(Args&&... args) const {
493493
using namespace internal;
494494

495-
return internalCall<EM_INVOKER_KIND::CONSTRUCTOR, WithPolicies<>, val>(as_handle(), nullptr, std::forward<Args>(args)...);
495+
return internalCall<EM_INVOKER_KIND::CONSTRUCTOR, val>(as_handle(), nullptr, std::forward<Args>(args)...);
496496
}
497497

498498
template<typename... Args>
499499
val operator()(Args&&... args) const {
500500
using namespace internal;
501501

502-
return internalCall<EM_INVOKER_KIND::FUNCTION, WithPolicies<>, val>(as_handle(), nullptr, std::forward<Args>(args)...);
502+
return internalCall<EM_INVOKER_KIND::FUNCTION, val>(as_handle(), nullptr, std::forward<Args>(args)...);
503503
}
504504

505505
template<typename ReturnValue, typename... Args>
506506
ReturnValue call(const char* name, Args&&... args) const {
507507
using namespace internal;
508508

509-
return internalCall<EM_INVOKER_KIND::METHOD, WithPolicies<>, ReturnValue>(as_handle(), name, std::forward<Args>(args)...);
509+
return internalCall<EM_INVOKER_KIND::METHOD, ReturnValue>(as_handle(), name, std::forward<Args>(args)...);
510510
}
511511

512512
template<typename T, typename ...Policies>
513513
T as(Policies...) const {
514514
using namespace internal;
515515

516-
return internalCall<EM_INVOKER_KIND::CAST, WithPolicies<Policies...>, T>(as_handle(), nullptr, *this);
516+
return internalCallWithPolicy<EM_INVOKER_KIND::CAST, WithPolicies<Policies...>, T>(as_handle(), nullptr, *this);
517517
}
518518

519519
// 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.
@@ -573,8 +573,27 @@ class EMBIND_VISIBILITY_DEFAULT val {
573573
template<typename WrapperType>
574574
friend val internal::wrapped_extend(const std::string& , const val& );
575575

576-
template<internal::EM_INVOKER_KIND Kind, typename Policy, typename Ret, typename... Args>
576+
template<internal::EM_INVOKER_KIND Kind, typename Ret, typename... Args>
577577
static Ret internalCall(EM_VAL handle, const char *methodName, Args&&... args) {
578+
using namespace internal;
579+
#if __cplusplus >= 201703L
580+
using Policy = WithPolicies<FilterTypes<isPolicy, Args...>>;
581+
auto filteredArgs = Filter<isNotPolicy>(args...);
582+
return std::apply(
583+
[&](auto&&... filteredArgs) {
584+
return internalCallWithPolicy<Kind, Policy, Ret>(handle, methodName, std::forward<decltype(filteredArgs)>(filteredArgs)...);
585+
},
586+
filteredArgs
587+
);
588+
#else
589+
// When std::apply is not available allow pointers by default. std::apply
590+
// could be polyfilled, but it requires a lot of code.
591+
return internalCallWithPolicy<Kind, WithPolicies<allow_raw_pointers>, Ret>(handle, methodName, std::forward<decltype(args)>(args)...);
592+
#endif
593+
}
594+
595+
template<internal::EM_INVOKER_KIND Kind, typename Policy, typename Ret, typename... Args>
596+
static Ret internalCallWithPolicy(EM_VAL handle, const char *methodName, Args&&... args) {
578597
static_assert(!std::is_lvalue_reference<Ret>::value,
579598
"Cannot create a lvalue reference out of a JS value.");
580599

system/include/emscripten/wire.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ struct WithPolicies {
265265
};
266266
};
267267

268+
template<typename... Policies>
269+
struct WithPolicies<std::tuple<Policies...>> : WithPolicies<Policies...> {};
270+
268271
// BindingType<T>
269272

270273
// The second typename is an unused stub so it's possible to
@@ -672,6 +675,34 @@ using isAsync = disjunction<std::is_same<async, Policies>...>;
672675
template<typename... Policies>
673676
using isNonnullReturn = disjunction<std::is_same<nonnull<ret_val>, Policies>...>;
674677

678+
// Build a tuple type that contains all the types where the predicate is true.
679+
// e.g. FilterTypes<std::is_integral, int, char, float> would return std::tuple<int, char>.
680+
template <template <class> class Predicate, class... T>
681+
using FilterTypes = decltype(std::tuple_cat(
682+
std::declval<
683+
typename std::conditional<
684+
Predicate<T>::value,
685+
std::tuple<T>,
686+
std::tuple<>
687+
>::type
688+
>()...
689+
));
690+
691+
#if __cplusplus >= 201402L
692+
// Build a tuple that contains all the args where the predicate is true.
693+
template<template <class> class Predicate, typename... Args>
694+
auto Filter(Args&&... args) {
695+
return std::tuple_cat(
696+
std::get<Predicate<typename std::decay_t<Args>>::value ? 0 : 1>(
697+
std::make_tuple(
698+
[](auto&& arg) { return std::forward_as_tuple(std::forward<decltype(arg)>(arg)); },
699+
[](auto&&) { return std::tuple<>(); }
700+
)
701+
)(std::forward<Args>(args))...
702+
);
703+
}
704+
#endif
705+
675706
} // namespace internal
676707

677708
} // namespace emscripten
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <emscripten/bind.h>
2+
#include <emscripten/val.h>
3+
int main() {
4+
void* x = nullptr;
5+
emscripten::val::global().call<void>("test", x);
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <emscripten/bind.h>
2+
#include <emscripten/val.h>
3+
int main() {
4+
void* x = nullptr;
5+
emscripten::val::global()("test", x);
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <emscripten/bind.h>
2+
#include <emscripten/val.h>
3+
int main() {
4+
void* x = nullptr;
5+
emscripten::val::global().new_(x);
6+
}

test/embind/test_val.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ EMSCRIPTEN_BINDINGS(test_bindings) {
4141

4242
int main() {
4343
printf("start\n");
44+
Dummy *dummy;
4445

4546
test("val array()");
4647
val::global().set("a", val::array());
@@ -408,6 +409,14 @@ int main() {
408409
ensure_js("a instanceof A");
409410
ensure_js("a.arg1 == 2");
410411
ensure_js("a.arg2 == 'b'");
412+
#if __cplusplus >= 201703L
413+
dummy = new Dummy();
414+
val::global().set("a", val::global("A").new_(dummy, val("b"), allow_raw_pointers()));
415+
ensure_js("a instanceof A");
416+
ensure_js("a.arg1 instanceof Module.Dummy");
417+
ensure_js("a.arg2 == 'b'");
418+
delete dummy;
419+
#endif
411420

412421
test("template<typename T> val operator[](const T& key)");
413422
EM_ASM(
@@ -457,6 +466,11 @@ int main() {
457466
);
458467
ensure(val::global("f1")(val(2),val("3")).as<int>() == 2);
459468
ensure(val::global("f2")(val(2),val("3")).as<string>() == "3");
469+
#if __cplusplus >= 201703L
470+
dummy = new Dummy();
471+
ensure(val::global("f1")(dummy, 42, allow_raw_pointers()).as<Dummy*>(allow_raw_pointers()) == dummy);
472+
delete dummy;
473+
#endif
460474

461475
test("template<typename ReturnValue, typename... Args> ReturnValue call(const char* name, Args&&... args)");
462476
EM_ASM(
@@ -473,6 +487,18 @@ int main() {
473487
c = new C;
474488
);
475489
ensure(val::global("c").call<int>("method", val(2)) == 2);
490+
#if __cplusplus >= 201703L
491+
dummy = new Dummy();
492+
EM_ASM(
493+
globalThis.C = function() {
494+
this.method = function(arg) { this.obj = arg; };
495+
};
496+
c = new C;
497+
);
498+
val::global("c").call<void>("method", dummy, allow_raw_pointers());
499+
ensure_js("c.obj instanceof Module.Dummy");
500+
delete dummy;
501+
#endif
476502

477503
test("template<typename T, typename ...Policies> T as(Policies...)");
478504
EM_ASM(
@@ -677,7 +703,7 @@ int main() {
677703
ensure_js("a == '😃 = \U0001F603 is :-D'");
678704

679705
test("val set() with policy");
680-
Dummy *dummy = new Dummy();
706+
dummy = new Dummy();
681707
val::global().set("a", dummy, allow_raw_pointers());
682708
ensure_js("a instanceof Module.Dummy");
683709
val::global().set("a", val::null());

test/test_other.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3332,6 +3332,9 @@ def test_embind_closure_no_dynamic_execution(self):
33323332
'val_1': ['embind/test_embind_no_raw_pointers_val_1.cpp'],
33333333
'val_2': ['embind/test_embind_no_raw_pointers_val_2.cpp'],
33343334
'val_3': ['embind/test_embind_no_raw_pointers_val_3.cpp'],
3335+
'val_invoke': ['embind/test_embind_no_raw_pointers_val_invoke.cpp'],
3336+
'val_call': ['embind/test_embind_no_raw_pointers_val_call.cpp'],
3337+
'val_new': ['embind/test_embind_no_raw_pointers_val_new.cpp'],
33353338
})
33363339
def test_embind_no_raw_pointers(self, filename):
33373340
stderr = self.expect_fail([EMCC, '-lembind', test_file(filename)])

0 commit comments

Comments
 (0)