Bug 1192043 - Add support for proxying native calls; r=snorp
authorJim Chen <nchen@mozilla.com>
Tue, 25 Aug 2015 14:52:16 -0400
changeset 259313 450d1f83b00197a99fe33c2802cfddb9d4fb7870
parent 259312 8cd372463964a86fcd88195d9e9eda6000a51ba6
child 259314 419ade49d346f41632f8ad4478edc2b1ecac5825
push id29277
push userryanvm@gmail.com
push dateWed, 26 Aug 2015 18:32:23 +0000
treeherdermozilla-central@fea87cbeaa6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1192043
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1192043 - Add support for proxying native calls; r=snorp If a C++ class implements native calls that all return void, it can choose to have those calls go through a custom proxy function by inheriting from mozilla::jni::UsesNativeCallProxy and override the ProxyNativeCall member. ProxyNativeCall accepts a rvalue reference to a functor object specific to each call, and can alter the calling environment (e.g. dispatch the call to another thread) before issuing the actual native call through the functor's operator(). Any JNI refs contained in the call are automatically turned into global refs so that the call can continue to work outside of the original JNI call. Native call proxy will be used to implement automatic dispatching of native calls from the main thread to run on the Gecko thread.
widget/android/jni/Natives.h
--- a/widget/android/jni/Natives.h
+++ b/widget/android/jni/Natives.h
@@ -1,17 +1,20 @@
 #ifndef mozilla_jni_Natives_h__
 #define mozilla_jni_Natives_h__
 
 #include <jni.h>
 
+#include "mozilla/IndexSequence.h"
 #include "mozilla/Move.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
+#include "mozilla/unused.h"
 #include "mozilla/jni/Accessors.h"
 #include "mozilla/jni/Refs.h"
 #include "mozilla/jni/Types.h"
 #include "mozilla/jni/Utils.h"
 
 namespace mozilla {
 namespace jni {
 
@@ -167,16 +170,240 @@ struct NativePtr<Impl, /* UseWeakPtr = *
             HandleUncaughtException(instance.Env());
             delete ptr;
         }
     }
 };
 
 } // namespace
 
+/**
+ * For C++ classes whose native methods all return void, they can choose to
+ * have the native calls go through a proxy by inheriting from
+ * mozilla::jni::UsesNativeCallProxy, and overriding the OnNativeCall member.
+ * Subsequently, every native call is automatically wrapped in a functor
+ * object, and the object is passed to OnNativeCall. The OnNativeCall
+ * implementation can choose to invoke the call, save it, dispatch it to a
+ * different thread, etc. Each copy of functor may only be invoked once.
+ *
+ * class MyClass : public MyJavaClass::Natives<MyClass>
+ *               , public mozilla::jni::UsesNativeCallProxy
+ * {
+ *     // ...
+ *
+ *     template<class Functor>
+ *     class ProxyRunnable final : public Runnable
+ *     {
+ *         Functor mCall;
+ *     public:
+ *         ProxyRunnable(Functor&& call) : mCall(mozilla::Move(call)) {}
+ *         virtual void run() override { mCall(); }
+ *     };
+ *
+ * public:
+ *     template<class Functor>
+ *     static void OnNativeCall(Functor&& call)
+ *     {
+ *         RunOnAnotherThread(new ProxyRunnable(mozilla::Move(call)));
+ *     }
+ * };
+ */
+
+struct UsesNativeCallProxy
+{
+    template<class Functor>
+    static void OnNativeCall(Functor&& call)
+    {
+        // The default behavior is to invoke the call right away.
+        call();
+    }
+};
+
+namespace {
+
+// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied
+// call may happen outside of the original JNI native call, we must save all
+// JNI ref arguments as global refs to avoid the arguments going out of scope.
+template<typename T>
+struct ProxyArg
+{
+    static_assert(mozilla::IsPod<T>::value, "T must be primitive type");
+
+    // Primitive types can be saved by value.
+    typedef T Type;
+    typedef typename TypeAdapter<T>::JNIType JNIType;
+
+    static void Clear(JNIEnv* env, Type&) {}
+
+    static Type From(JNIEnv* env, JNIType val)
+    {
+        return TypeAdapter<T>::ToNative(env, val);
+    }
+};
+
+template<class T>
+struct ProxyArg<Ref<T>>
+{
+    // Ref types need to be saved by global ref.
+    typedef typename T::GlobalRef Type;
+    typedef typename TypeAdapter<Ref<T>>::JNIType JNIType;
+
+    static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); }
+
+    static Type From(JNIEnv* env, JNIType val)
+    {
+        return Type(env, T::Ref::From(val));
+    }
+};
+
+template<typename T> struct ProxyArg<const T&> : ProxyArg<T> {};
+template<> struct ProxyArg<Param<String>> : ProxyArg<Ref<String>> {};
+
+// ProxyNativeCall implements the functor object that is passed to
+// UsesNativeCallProxy::OnNativeCall
+template<class Impl, class Owner, bool IsStatic,
+         bool HasThisArg /* has instance/class local ref in the call */,
+         typename... Args>
+class ProxyNativeCall
+{
+    template<class T, class I, class A, bool S, bool V>
+    friend class NativeStubImpl;
+
+    // "this arg" refers to the ClassObject::LocalRef (for static methods) or
+    // Owner::LocalRef (for instance methods) that we optionally (as indicated
+    // by HasThisArg) pass into the destination C++ function.
+    typedef typename mozilla::Conditional<IsStatic,
+            ClassObject, Owner>::Type ThisArgClass;
+    typedef typename mozilla::Conditional<IsStatic,
+            jclass, jobject>::Type ThisArgJNIType;
+
+    // Type signature of the destination C++ function, which matches the
+    // Method template parameter in NativeStubImpl::Wrap.
+    typedef typename mozilla::Conditional<IsStatic,
+            typename mozilla::Conditional<HasThisArg,
+                    void (*) (const ClassObject::LocalRef&, Args...),
+                    void (*) (Args...)>::Type,
+            typename mozilla::Conditional<HasThisArg,
+                    void (Impl::*) (const typename Owner::LocalRef&, Args...),
+                    void (Impl::*) (Args...)>::Type>::Type NativeCallType;
+
+    // Destination C++ function.
+    const NativeCallType mNativeCall;
+    // Saved this arg.
+    typename ThisArgClass::GlobalRef mThisArg;
+    // Saved arguments.
+    mozilla::Tuple<typename ProxyArg<Args>::Type...> mArgs;
+
+    ProxyNativeCall(NativeCallType nativeCall,
+                    JNIEnv* env,
+                    ThisArgJNIType thisArg,
+                    typename ProxyArg<Args>::JNIType... args)
+        : mNativeCall(nativeCall)
+        , mThisArg(env, ThisArgClass::Ref::From(thisArg))
+        , mArgs(ProxyArg<Args>::From(env, args)...)
+    {}
+
+    // We cannot use IsStatic and HasThisArg directly (without going through
+    // extra hoops) because GCC complains about invalid overloads, so we use
+    // another pair of template parameters, Static and ThisArg.
+
+    template<bool Static, bool ThisArg, size_t... Indices>
+    typename mozilla::EnableIf<Static && ThisArg, void>::Type
+    Call(const ClassObject::LocalRef& cls, mozilla::IndexSequence<Indices...>)
+    {
+        (*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...);
+    }
+
+    template<bool Static, bool ThisArg, size_t... Indices>
+    typename mozilla::EnableIf<Static && !ThisArg, void>::Type
+    Call(const ClassObject::LocalRef& cls, mozilla::IndexSequence<Indices...>)
+    {
+        (*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+    }
+
+    template<bool Static, bool ThisArg, size_t... Indices>
+    typename mozilla::EnableIf<!Static && ThisArg, void>::Type
+    Call(const typename Owner::LocalRef& inst,
+         mozilla::IndexSequence<Indices...>)
+    {
+        Impl* const impl = NativePtr<Impl>::Get(inst);
+        HandleUncaughtException(inst.Env());
+        (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...);
+    }
+
+    template<bool Static, bool ThisArg, size_t... Indices>
+    typename mozilla::EnableIf<!Static && !ThisArg, void>::Type
+    Call(const typename Owner::LocalRef& inst,
+         mozilla::IndexSequence<Indices...>)
+    {
+        Impl* const impl = NativePtr<Impl>::Get(inst);
+        HandleUncaughtException(inst.Env());
+        (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+    }
+
+    template<size_t... Indices>
+    void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>)
+    {
+        int dummy[] = {
+            (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...
+        };
+        mozilla::unused << dummy;
+    }
+
+public:
+    static const bool isStatic = IsStatic;
+
+    ProxyNativeCall(ProxyNativeCall&&) = default;
+    ProxyNativeCall(const ProxyNativeCall&) = default;
+
+    // Get class ref for static calls or object ref for instance calls.
+    typename ThisArgClass::Param GetThisArg() { return mThisArg; }
+
+    // Return if target is the given function pointer / pointer-to-member.
+    // Because we can only compare pointers of the same type, we use a
+    // templated overload that is chosen only if given a different type of
+    // pointer than our target pointer type.
+    bool IsTarget(NativeCallType call) { return call == mNativeCall; }
+    template<typename T> bool IsTarget(T&&) { return false; }
+
+    void operator()()
+    {
+        JNIEnv* const env = GetEnvForThread();
+        typename ThisArgClass::LocalRef thisArg(env, mThisArg);
+        Call<IsStatic, HasThisArg>(
+                thisArg, typename IndexSequenceFor<Args...>::Type());
+
+        // Clear all saved global refs. We do this after the call is invoked,
+        // and not inside the destructor because we already have a JNIEnv here,
+        // so it's more efficient to clear out the saved args here. The
+        // downside is that the call can only be invoked once.
+        Clear(env, typename IndexSequenceFor<Args...>::Type());
+    }
+};
+
+// We can only use Impl::OnNativeCall when Impl is derived from
+// UsesNativeCallProxy, otherwise it's a compile error. Therefore, the real
+// Dispatch function is conditional on UsesNativeCallProxy being a base class
+// of Impl. Otherwise, the dummy Dispatch function below that is chosen during
+// overload resolution. Because Dispatch is called with an rvalue, the &&
+// version is always chosen before the const& version, if possible.
+
+template<class Impl, class O, bool S, bool V, typename... A>
+typename mozilla::EnableIf<
+        mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value, void>::Type
+Dispatch(ProxyNativeCall<Impl, O, S, V, A...>&& call)
+{
+    Impl::OnNativeCall(mozilla::Move(call));
+}
+
+template<typename T>
+void Dispatch(const T&) {}
+
+} // namespace
+
 template<class Cls, class Impl> class NativeImpl;
 
 namespace detail {
 
 // Wrapper methods that convert arguments from the JNI types to the native
 // types, e.g. from jobject to jni::Object::Ref. For instance methods, the
 // wrapper methods also convert calls to calls on objects.
 //
@@ -198,29 +425,35 @@ class NativeStubImpl<Traits, Impl, jni::
     typedef typename TypeAdapter<ReturnType>::JNIType ReturnJNIType;
 
 public:
     // Instance method
     template<ReturnType (Impl::*Method) (Args...)>
     static ReturnJNIType Wrap(JNIEnv* env, jobject instance,
                               typename TypeAdapter<Args>::JNIType... args)
     {
+        static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
+                      "Native call proxy only supports void return type");
+
         Impl* const impl = NativePtr<Impl>::Get(env, instance);
         if (!impl) {
             return ReturnJNIType();
         }
         return TypeAdapter<ReturnType>::FromNative(env,
                 (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...));
     }
 
     // Instance method with instance reference
     template<ReturnType (Impl::*Method) (const typename Owner::LocalRef&, Args...)>
     static ReturnJNIType Wrap(JNIEnv* env, jobject instance,
                               typename TypeAdapter<Args>::JNIType... args)
     {
+        static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
+                      "Native call proxy only supports void return type");
+
         Impl* const impl = NativePtr<Impl>::Get(env, instance);
         if (!impl) {
             return ReturnJNIType();
         }
         auto self = Owner::LocalRef::Adopt(env, instance);
         const auto res = TypeAdapter<ReturnType>::FromNative(env,
                 (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...));
         self.Forget();
@@ -236,41 +469,58 @@ class NativeStubImpl<Traits, Impl, jni::
     typedef typename Traits::Owner Owner;
 
 public:
     // Instance method
     template<void (Impl::*Method) (Args...)>
     static void Wrap(JNIEnv* env, jobject instance,
                      typename TypeAdapter<Args>::JNIType... args)
     {
+        if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
+            Dispatch(ProxyNativeCall<
+                     Impl, Owner, /* IsStatic */ false, /* HasThisArg */ false,
+                     Args...>(Method, env, instance, args...));
+            return;
+        }
         Impl* const impl = NativePtr<Impl>::Get(env, instance);
         if (!impl) {
             return;
         }
         (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...);
     }
 
     // Instance method with instance reference
     template<void (Impl::*Method) (const typename Owner::LocalRef&, Args...)>
     static void Wrap(JNIEnv* env, jobject instance,
                      typename TypeAdapter<Args>::JNIType... args)
     {
+        if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
+            Dispatch(ProxyNativeCall<
+                     Impl, Owner, /* IsStatic */ false, /* HasThisArg */ true,
+                     Args...>(Method, env, instance, args...));
+            return;
+        }
         Impl* const impl = NativePtr<Impl>::Get(env, instance);
         if (!impl) {
             return;
         }
         auto self = Owner::LocalRef::Adopt(env, instance);
         (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...);
         self.Forget();
     }
 
     // Overload for DisposeNative
     template<void (NativeImpl<Owner, Impl>::*Method) (const typename Owner::LocalRef&)>
     static void Wrap(JNIEnv* env, jobject instance)
     {
+        if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
+            Dispatch(ProxyNativeCall<Impl, Owner, /* IsStatic */ false,
+                    /* HasThisArg */ true>(Method, env, instance));
+            return;
+        }
         Impl* const impl = NativePtr<Impl>::Get(env, instance);
         if (!impl) {
             return;
         }
         auto self = Owner::LocalRef::Adopt(env, instance);
         (impl->*Method)(self);
         self.Forget();
     }
@@ -285,52 +535,72 @@ class NativeStubImpl<Traits, Impl, jni::
     typedef typename TypeAdapter<ReturnType>::JNIType ReturnJNIType;
 
 public:
     // Static method
     template<ReturnType (*Method) (Args...)>
     static ReturnJNIType Wrap(JNIEnv* env, jclass,
                               typename TypeAdapter<Args>::JNIType... args)
     {
+        static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
+                      "Native call proxy only supports void return type");
+
         return TypeAdapter<ReturnType>::FromNative(env,
                 (*Method)(TypeAdapter<Args>::ToNative(env, args)...));
     }
 
     // Static method with class reference
     template<ReturnType (*Method) (const ClassObject::LocalRef&, Args...)>
     static ReturnJNIType Wrap(JNIEnv* env, jclass cls,
                               typename TypeAdapter<Args>::JNIType... args)
     {
+        static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
+                      "Native call proxy only supports void return type");
+
         auto clazz = ClassObject::LocalRef::Adopt(env, cls);
         const auto res = TypeAdapter<ReturnType>::FromNative(env,
                 (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...));
         clazz.Forget();
         return res;
     }
 };
 
 // Specialization for static methods with void return type
 template<class Traits, class Impl, typename... Args>
 class NativeStubImpl<Traits, Impl, jni::Args<Args...>,
                      /* IsStatic = */ true, /* IsVoid = */ true>
 {
+    typedef typename Traits::Owner Owner;
+
 public:
     // Static method
     template<void (*Method) (Args...)>
-    static void Wrap(JNIEnv* env, jclass,
+    static void Wrap(JNIEnv* env, jclass cls,
                      typename TypeAdapter<Args>::JNIType... args)
     {
+        if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
+            Dispatch(ProxyNativeCall<
+                    Impl, Owner, /* IsStatic */ true, /* HasThisArg */ false,
+                    Args...>(Method, env, cls, args...));
+            return;
+        }
         (*Method)(TypeAdapter<Args>::ToNative(env, args)...);
     }
 
     // Static method with class reference
     template<void (*Method) (const ClassObject::LocalRef&, Args...)>
     static void Wrap(JNIEnv* env, jclass cls,
                      typename TypeAdapter<Args>::JNIType... args)
     {
+        if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
+            Dispatch(ProxyNativeCall<
+                    Impl, Owner, /* IsStatic */ true, /* HasThisArg */ true,
+                    Args...>(Method, env, cls, args...));
+            return;
+        }
         auto clazz = ClassObject::LocalRef::Adopt(env, cls);
         (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...);
         clazz.Forget();
     }
 };
 
 } // namespace detail