1 #pragma once
2 
3 // JS_RUNTIME_HERMES is only set on Android so we have to check __has_include
4 // on iOS.
5 #if __APPLE__ &&    \
6     (__has_include( \
7         <reacthermes/HermesExecutorFactory.h>) || __has_include(<hermes/hermes.h>))
8 #define JS_RUNTIME_HERMES 1
9 #endif
10 
11 // Only include this file in Hermes-enabled builds as some platforms (like tvOS)
12 // don't support hermes and it causes the compilation to fail.
13 #if JS_RUNTIME_HERMES
14 
15 #include <cxxreact/MessageQueueThread.h>
16 #include <jsi/decorator.h>
17 #include <jsi/jsi.h>
18 
19 #include <memory>
20 #include <thread>
21 
22 #if __has_include(<reacthermes/HermesExecutorFactory.h>)
23 #include <reacthermes/HermesExecutorFactory.h>
24 #else // __has_include(<hermes/hermes.h>) || ANDROID
25 #include <hermes/hermes.h>
26 #endif
27 
28 #if REACT_NATIVE_MINOR_VERSION >= 71
29 #include <hermes/inspector/chrome/Registration.h>
30 #endif
31 
32 namespace reanimated {
33 
34 using namespace facebook;
35 using namespace react;
36 
37 // ReentrancyCheck is copied from React Native
38 // from ReactCommon/hermes/executor/HermesExecutorFactory.cpp
39 // https://github.com/facebook/react-native/blob/main/ReactCommon/hermes/executor/HermesExecutorFactory.cpp
40 struct ReanimatedReentrancyCheck {
41 // This is effectively a very subtle and complex assert, so only
42 // include it in builds which would include asserts.
43 #ifndef NDEBUG
44   ReanimatedReentrancyCheck() : tid(std::thread::id()), depth(0) {}
45 
46   void before() {
47     std::thread::id this_id = std::this_thread::get_id();
48     std::thread::id expected = std::thread::id();
49 
50     // A note on memory ordering: the main purpose of these checks is
51     // to observe a before/before race, without an intervening after.
52     // This will be detected by the compare_exchange_strong atomicity
53     // properties, regardless of memory order.
54     //
55     // For everything else, it is easiest to think of 'depth' as a
56     // proxy for any access made inside the VM.  If access to depth
57     // are reordered incorrectly, the same could be true of any other
58     // operation made by the VM.  In fact, using acquire/release
59     // memory ordering could create barriers which mask a programmer
60     // error.  So, we use relaxed memory order, to avoid masking
61     // actual ordering errors.  Although, in practice, ordering errors
62     // of this sort would be surprising, because the decorator would
63     // need to call after() without before().
64 
65     if (tid.compare_exchange_strong(
66             expected, this_id, std::memory_order_relaxed)) {
67       // Returns true if tid and expected were the same.  If they
68       // were, then the stored tid referred to no thread, and we
69       // atomically saved this thread's tid.  Now increment depth.
70       assert(depth == 0 && "No thread id, but depth != 0");
71       ++depth;
72     } else if (expected == this_id) {
73       // If the stored tid referred to a thread, expected was set to
74       // that value.  If that value is this thread's tid, that's ok,
75       // just increment depth again.
76       assert(depth != 0 && "Thread id was set, but depth == 0");
77       ++depth;
78     } else {
79       // The stored tid was some other thread.  This indicates a bad
80       // programmer error, where VM methods were called on two
81       // different threads unsafely.  Fail fast (and hard) so the
82       // crash can be analyzed.
83       __builtin_trap();
84     }
85   }
86 
87   void after() {
88     assert(
89         tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
90         "No thread id in after()");
91     if (--depth == 0) {
92       // If we decremented depth to zero, store no-thread into tid.
93       std::thread::id expected = std::this_thread::get_id();
94       bool didWrite = tid.compare_exchange_strong(
95           expected, std::thread::id(), std::memory_order_relaxed);
96       assert(didWrite && "Decremented to zero, but no tid write");
97     }
98   }
99 
100   std::atomic<std::thread::id> tid;
101   // This is not atomic, as it is only written or read from the owning
102   // thread.
103   unsigned int depth;
104 #endif // NDEBUG
105 };
106 
107 // This is in fact a subclass of jsi::Runtime! WithRuntimeDecorator is a
108 // template class that is a subclass of DecoratedRuntime which is also a
109 // template class that then inherits its template, which in this case is
110 // jsi::Runtime. So the inheritance is: ReanimatedHermesRuntime ->
111 // WithRuntimeDecorator -> DecoratedRuntime -> jsi::Runtime You can find out
112 // more about this in ReactCommon/jsi/jsi/Decorator.h or by following this link:
113 // https://github.com/facebook/react-native/blob/main/ReactCommon/jsi/jsi/decorator.h
114 class ReanimatedHermesRuntime
115     : public jsi::WithRuntimeDecorator<ReanimatedReentrancyCheck> {
116  public:
117   ReanimatedHermesRuntime(
118       std::unique_ptr<facebook::hermes::HermesRuntime> runtime,
119       std::shared_ptr<MessageQueueThread> jsQueue);
120   ~ReanimatedHermesRuntime();
121 
122  private:
123   std::unique_ptr<facebook::hermes::HermesRuntime> runtime_;
124   ReanimatedReentrancyCheck reentrancyCheck_;
125 #if HERMES_ENABLE_DEBUGGER
126 #if REACT_NATIVE_MINOR_VERSION >= 71
127   facebook::hermes::inspector::chrome::DebugSessionToken debugToken_;
128 #endif // REACT_NATIVE_MINOR_VERSION >= 71
129 #endif // HERMES_ENABLE_DEBUGGER
130 };
131 
132 } // namespace reanimated
133 
134 #endif // JS_RUNTIME_HERMES
135