@react-native-quickjs/quickjs 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/NOTICE +31 -0
- package/README.md +217 -0
- package/ReactNativeQuickJS.podspec +197 -0
- package/android/CMakeLists.txt +134 -0
- package/android/build.gradle +214 -0
- package/android/quickjs.gradle +39 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/reactnativequickjs/quickjs/QuickJSInstance.kt +39 -0
- package/android/src/main/java/com/reactnativequickjs/quickjs/QuickJSPackage.kt +32 -0
- package/android/src/main/jni/JJSRuntimeFactory.h +37 -0
- package/android/src/main/jni/JQuickJSInstance.cpp +27 -0
- package/android/src/main/jni/JQuickJSInstance.h +46 -0
- package/android/src/main/jni/OnLoad.cpp +15 -0
- package/app.plugin.js +11 -0
- package/apple/RCTQuickJSInstanceFactory.h +32 -0
- package/apple/RCTQuickJSInstanceFactory.mm +16 -0
- package/bin/qjsc/darwin-arm64/qjsc +0 -0
- package/bin/qjsc/darwin-x64/qjsc +0 -0
- package/bin/qjsc/linux-arm64/qjsc +0 -0
- package/bin/qjsc/linux-x64/qjsc +0 -0
- package/bin/qjsc/manifest.json +11 -0
- package/bin/qjsc/win32-x64/qjsc.exe +0 -0
- package/bin/react-native-quickjs.js +28 -0
- package/cmake/jsi.cmake +24 -0
- package/cmake/quickjs.cmake +125 -0
- package/engine/quickjs-rel/LICENSE +24 -0
- package/engine/quickjs-rel/MANIFEST.json +55 -0
- package/engine/quickjs-rel/builtin-array-fromasync.h +120 -0
- package/engine/quickjs-rel/builtin-iterator-zip-keyed.h +332 -0
- package/engine/quickjs-rel/builtin-iterator-zip.h +337 -0
- package/engine/quickjs-rel/cutils.h +1998 -0
- package/engine/quickjs-rel/dtoa.c +1619 -0
- package/engine/quickjs-rel/dtoa.h +87 -0
- package/engine/quickjs-rel/libregexp-opcode.h +73 -0
- package/engine/quickjs-rel/libregexp.c +3478 -0
- package/engine/quickjs-rel/libregexp.h +101 -0
- package/engine/quickjs-rel/libunicode-table.h +5173 -0
- package/engine/quickjs-rel/libunicode.c +2069 -0
- package/engine/quickjs-rel/libunicode.h +172 -0
- package/engine/quickjs-rel/list.h +107 -0
- package/engine/quickjs-rel/quickjs-atom.h +280 -0
- package/engine/quickjs-rel/quickjs-c-atomics.h +54 -0
- package/engine/quickjs-rel/quickjs-opcode.h +385 -0
- package/engine/quickjs-rel/quickjs.c +64854 -0
- package/engine/quickjs-rel/quickjs.h +1577 -0
- package/modules/cdp/quickjs-cdp.c +1386 -0
- package/modules/cdp/quickjs-cdp.h +122 -0
- package/modules/cdp/react/QuickJSInspector.cpp +322 -0
- package/modules/cdp/react/QuickJSInspector.h +114 -0
- package/modules/hermes-compat/include/hermes/DebuggerAPI.h +20 -0
- package/modules/hermes-compat/include/hermes/Public/CrashManager.h +47 -0
- package/modules/hermes-compat/include/hermes/Public/CtorConfig.h +88 -0
- package/modules/hermes-compat/include/hermes/Public/GCConfig.h +83 -0
- package/modules/hermes-compat/include/hermes/Public/HermesExport.h +17 -0
- package/modules/hermes-compat/include/hermes/Public/RuntimeConfig.h +66 -0
- package/modules/hermes-compat/include/hermes/Public/SamplingProfiler.h +22 -0
- package/modules/hermes-compat/include/hermes/hermes.h +195 -0
- package/modules/hermes-compat/include/hermes/inspector/RuntimeAdapter.h +43 -0
- package/modules/hermes-compat/include/hermes/inspector-modern/chrome/Registration.h +31 -0
- package/modules/hermes-compat/include/hermes-compat/Diagnostics.h +35 -0
- package/modules/hermes-compat/src/HermesCompat.cpp +557 -0
- package/package.json +104 -0
- package/scripts/bytecode/compile.js +99 -0
- package/scripts/expo/plugin.js +231 -0
- package/scripts/postinstall.js +74 -0
- package/scripts/react_native_quickjs_pods.rb +237 -0
- package/scripts/setup/edits.js +311 -0
- package/scripts/setup/run.js +121 -0
- package/src/bytecode/QuickJSBytecode.cpp +50 -0
- package/src/bytecode/QuickJSBytecode.h +50 -0
- package/src/module/QuickJSCompat.cpp +92 -0
- package/src/module/QuickJSCompat.h +19 -0
- package/src/module/QuickJSModule.cpp +70 -0
- package/src/module/QuickJSModule.h +108 -0
- package/src/module/QuickJSModuleNative.h +93 -0
- package/src/runtime/QuickJSInstance.cpp +37 -0
- package/src/runtime/QuickJSInstance.h +74 -0
- package/src/runtime/QuickJSRuntime.cpp +1864 -0
- package/src/runtime/QuickJSRuntime.h +529 -0
- package/src/runtime/QuickJSRuntimeConfig.h +103 -0
- package/src/runtime/QuickJSRuntimeFactory.cpp +34 -0
- package/src/runtime/QuickJSRuntimeFactory.h +24 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <jsi/jsi.h>
|
|
11
|
+
#include <pthread.h>
|
|
12
|
+
#include <quickjs.h>
|
|
13
|
+
|
|
14
|
+
#include <atomic>
|
|
15
|
+
#include <chrono>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <functional>
|
|
18
|
+
#include <mutex>
|
|
19
|
+
#include <new>
|
|
20
|
+
#include <string>
|
|
21
|
+
#include <thread>
|
|
22
|
+
#include <unordered_set>
|
|
23
|
+
#include <vector>
|
|
24
|
+
|
|
25
|
+
#include "QuickJSRuntimeConfig.h"
|
|
26
|
+
|
|
27
|
+
namespace qjs {
|
|
28
|
+
|
|
29
|
+
namespace jsi = facebook::jsi;
|
|
30
|
+
|
|
31
|
+
/// A jsi::Runtime backed by quickjs-ng.
|
|
32
|
+
class QuickJSRuntime : public jsi::Runtime {
|
|
33
|
+
public:
|
|
34
|
+
explicit QuickJSRuntime(QuickJSRuntimeConfig config);
|
|
35
|
+
~QuickJSRuntime() override;
|
|
36
|
+
|
|
37
|
+
QuickJSRuntime(const QuickJSRuntime &) = delete;
|
|
38
|
+
QuickJSRuntime &operator=(const QuickJSRuntime &) = delete;
|
|
39
|
+
|
|
40
|
+
JSRuntime *runtime() const noexcept {
|
|
41
|
+
return runtime_;
|
|
42
|
+
}
|
|
43
|
+
/// Called after each script is evaluated from source, with the URL it was
|
|
44
|
+
/// given and the source itself. The debugger uses it to announce the script
|
|
45
|
+
/// and to bind breakpoints that named its URL. Set after construction,
|
|
46
|
+
/// because whoever installs it needs the context this runtime owns.
|
|
47
|
+
void setScriptEvaluatedHook(
|
|
48
|
+
std::function<void(const std::string &url, const std::string &source)>
|
|
49
|
+
hook) noexcept {
|
|
50
|
+
onScriptEvaluated_ = std::move(hook);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
JSContext *context() const noexcept {
|
|
54
|
+
return context_;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Serialises access to the engine so more than one embedder can run
|
|
59
|
+
* JavaScript on different threads. Lock where native enters JavaScript,
|
|
60
|
+
* unlock when it leaves; the JSI methods in between do no locking.
|
|
61
|
+
*
|
|
62
|
+
* All-or-nothing: quickjs locks nothing itself, so once a second thread runs
|
|
63
|
+
* JavaScript every entry point must hold this. A single-threaded embedder
|
|
64
|
+
* never calls it and pays nothing. Dropping a jsi::Value is the exception --
|
|
65
|
+
* an off-thread release is queued rather than blocked.
|
|
66
|
+
*/
|
|
67
|
+
void lock() noexcept;
|
|
68
|
+
void unlock() noexcept;
|
|
69
|
+
|
|
70
|
+
class Lock {
|
|
71
|
+
public:
|
|
72
|
+
explicit Lock(QuickJSRuntime &runtime) noexcept : runtime_(runtime) {
|
|
73
|
+
runtime_.lock();
|
|
74
|
+
}
|
|
75
|
+
~Lock() {
|
|
76
|
+
runtime_.unlock();
|
|
77
|
+
}
|
|
78
|
+
Lock(const Lock &) = delete;
|
|
79
|
+
Lock &operator=(const Lock &) = delete;
|
|
80
|
+
|
|
81
|
+
private:
|
|
82
|
+
QuickJSRuntime &runtime_;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
bool isOnJSThread() const noexcept {
|
|
86
|
+
return jsThread_.load(std::memory_order_relaxed) ==
|
|
87
|
+
std::this_thread::get_id();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Makes the calling thread the owner and re-bases the recursion bound onto
|
|
91
|
+
/// its stack. A no-op when it already owns the engine. lock() calls it on
|
|
92
|
+
/// every handover; an embedder that never locks calls it at its first
|
|
93
|
+
/// evaluate, since React Native builds the runtime on one thread and runs
|
|
94
|
+
/// JavaScript on another.
|
|
95
|
+
void adoptCurrentThread() noexcept;
|
|
96
|
+
|
|
97
|
+
static size_t remainingNativeStackBytes() noexcept;
|
|
98
|
+
|
|
99
|
+
/// Holds one reference on a JSValue. invalidate() may run on any thread, so
|
|
100
|
+
/// the release is routed through releaseValue(). Nested because
|
|
101
|
+
/// jsi::Runtime::PointerValue is protected.
|
|
102
|
+
class QuickJSPointerValue final : public PointerValue {
|
|
103
|
+
public:
|
|
104
|
+
QuickJSPointerValue(
|
|
105
|
+
QuickJSRuntime &runtime, JSValue value, bool owned = true)
|
|
106
|
+
: runtime_(runtime), owned_(owned), value_(value) {}
|
|
107
|
+
|
|
108
|
+
void invalidate() noexcept override {
|
|
109
|
+
if (owned_) {
|
|
110
|
+
runtime_.releaseValue(value_);
|
|
111
|
+
}
|
|
112
|
+
runtime_.recyclePointerValue(this);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
JSValue value() const noexcept {
|
|
116
|
+
return value_;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private:
|
|
120
|
+
friend class QuickJSRuntime;
|
|
121
|
+
~QuickJSPointerValue() override = default;
|
|
122
|
+
|
|
123
|
+
QuickJSRuntime &runtime_;
|
|
124
|
+
bool owned_;
|
|
125
|
+
union {
|
|
126
|
+
JSValue value_;
|
|
127
|
+
QuickJSPointerValue *nextFree_;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/// Backing store for jsi::PropNameID. Atoms are interned and comparable by
|
|
132
|
+
/// identity, which is what PropNameID needs.
|
|
133
|
+
class QuickJSAtomPointerValue final : public PointerValue {
|
|
134
|
+
public:
|
|
135
|
+
QuickJSAtomPointerValue(QuickJSRuntime &runtime, JSAtom atom)
|
|
136
|
+
: runtime_(runtime), atom_(atom) {}
|
|
137
|
+
|
|
138
|
+
void invalidate() noexcept override {
|
|
139
|
+
runtime_.releaseAtom(atom_);
|
|
140
|
+
runtime_.recycleAtomPointerValue(this);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
JSAtom atom() const noexcept {
|
|
144
|
+
return atom_;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private:
|
|
148
|
+
friend class QuickJSRuntime;
|
|
149
|
+
~QuickJSAtomPointerValue() override = default;
|
|
150
|
+
|
|
151
|
+
QuickJSRuntime &runtime_;
|
|
152
|
+
union {
|
|
153
|
+
JSAtom atom_;
|
|
154
|
+
QuickJSAtomPointerValue *nextFree_;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/// Safe to call from any thread: an off-thread release is queued and freed on
|
|
159
|
+
/// the JS thread.
|
|
160
|
+
void releaseValue(JSValue value) noexcept;
|
|
161
|
+
void releaseAtom(JSAtom atom) noexcept;
|
|
162
|
+
|
|
163
|
+
/// Registration of the native payloads attached to JS objects, so teardown
|
|
164
|
+
/// can release them without waiting for the collector to reach their owners.
|
|
165
|
+
void registerHostObject(void *proxy);
|
|
166
|
+
void unregisterHostObject(void *proxy);
|
|
167
|
+
void registerHostFunction(void *proxy);
|
|
168
|
+
void unregisterHostFunction(void *proxy);
|
|
169
|
+
void registerNativeState(void *proxy);
|
|
170
|
+
void unregisterNativeState(void *proxy);
|
|
171
|
+
|
|
172
|
+
/// Public because the class callbacks are free functions.
|
|
173
|
+
JSClassID hostObjectClassID() const noexcept {
|
|
174
|
+
return hostObjectClassID_;
|
|
175
|
+
}
|
|
176
|
+
JSClassID hostFunctionClassID() const noexcept {
|
|
177
|
+
return hostFunctionClassID_;
|
|
178
|
+
}
|
|
179
|
+
JSClassID nativeStateClassID() const noexcept {
|
|
180
|
+
return nativeStateClassID_;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// jsi::Runtime -- public API
|
|
184
|
+
jsi::Value evaluateJavaScript(
|
|
185
|
+
const std::shared_ptr<const jsi::Buffer> &buffer,
|
|
186
|
+
const std::string &sourceURL) override;
|
|
187
|
+
std::shared_ptr<const jsi::PreparedJavaScript> prepareJavaScript(
|
|
188
|
+
const std::shared_ptr<const jsi::Buffer> &buffer,
|
|
189
|
+
std::string sourceURL) override;
|
|
190
|
+
jsi::Value evaluatePreparedJavaScript(
|
|
191
|
+
const std::shared_ptr<const jsi::PreparedJavaScript> &js) override;
|
|
192
|
+
|
|
193
|
+
void queueMicrotask(const jsi::Function &callback) override;
|
|
194
|
+
bool drainMicrotasks(int maxMicrotasksHint = -1) override;
|
|
195
|
+
|
|
196
|
+
/// Runs a collection deferred by QuickJSRuntimeConfig::deferGC. Call with no
|
|
197
|
+
/// JS on the stack; drainMicrotasks already does when the queue empties.
|
|
198
|
+
void runPendingGC() noexcept;
|
|
199
|
+
|
|
200
|
+
/// Safepoint with an idleness test: collects only if nothing drove JS for
|
|
201
|
+
/// gcIdleGapMs before `taskStart`, if the collection has waited longer than
|
|
202
|
+
/// gcMaxDeferralMs, or if the heap has grown past the pressure limit.
|
|
203
|
+
void runPendingGCIfIdle(
|
|
204
|
+
std::chrono::steady_clock::time_point taskStart) noexcept;
|
|
205
|
+
|
|
206
|
+
/// `live + clamp(live, minSlack, maxSlack)`, re-derived because the engine
|
|
207
|
+
/// computes its own ceiling once, before it has seen the app's heap.
|
|
208
|
+
size_t deferredGCCeiling() const noexcept;
|
|
209
|
+
size_t deferredGCSlack() const noexcept;
|
|
210
|
+
void refreshDeferredGCLimit() noexcept;
|
|
211
|
+
|
|
212
|
+
jsi::Object global() override;
|
|
213
|
+
std::string description() override;
|
|
214
|
+
bool isInspectable() override;
|
|
215
|
+
|
|
216
|
+
protected:
|
|
217
|
+
// jsi::Runtime -- pointer lifetime
|
|
218
|
+
PointerValue *cloneSymbol(const Runtime::PointerValue *pv) override;
|
|
219
|
+
PointerValue *cloneBigInt(const Runtime::PointerValue *pv) override;
|
|
220
|
+
PointerValue *cloneString(const Runtime::PointerValue *pv) override;
|
|
221
|
+
PointerValue *cloneObject(const Runtime::PointerValue *pv) override;
|
|
222
|
+
PointerValue *clonePropNameID(const Runtime::PointerValue *pv) override;
|
|
223
|
+
|
|
224
|
+
// PropNameID
|
|
225
|
+
jsi::PropNameID createPropNameIDFromAscii(
|
|
226
|
+
const char *str, size_t length) override;
|
|
227
|
+
jsi::PropNameID createPropNameIDFromUtf8(
|
|
228
|
+
const uint8_t *utf8, size_t length) override;
|
|
229
|
+
jsi::PropNameID createPropNameIDFromUtf16(
|
|
230
|
+
const char16_t *utf16, size_t length) override;
|
|
231
|
+
jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override;
|
|
232
|
+
jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override;
|
|
233
|
+
std::string utf8(const jsi::PropNameID &) override;
|
|
234
|
+
bool compare(const jsi::PropNameID &, const jsi::PropNameID &) override;
|
|
235
|
+
|
|
236
|
+
// Symbol
|
|
237
|
+
std::string symbolToString(const jsi::Symbol &) override;
|
|
238
|
+
|
|
239
|
+
// BigInt
|
|
240
|
+
jsi::BigInt createBigIntFromInt64(int64_t) override;
|
|
241
|
+
jsi::BigInt createBigIntFromUint64(uint64_t) override;
|
|
242
|
+
bool bigintIsInt64(const jsi::BigInt &) override;
|
|
243
|
+
bool bigintIsUint64(const jsi::BigInt &) override;
|
|
244
|
+
uint64_t truncate(const jsi::BigInt &) override;
|
|
245
|
+
jsi::String bigintToString(const jsi::BigInt &, int) override;
|
|
246
|
+
|
|
247
|
+
// String
|
|
248
|
+
jsi::String createStringFromAscii(const char *str, size_t length) override;
|
|
249
|
+
jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override;
|
|
250
|
+
jsi::String createStringFromUtf16(
|
|
251
|
+
const char16_t *utf16, size_t length) override;
|
|
252
|
+
std::string utf8(const jsi::String &) override;
|
|
253
|
+
|
|
254
|
+
// Object
|
|
255
|
+
jsi::Object createObject() override;
|
|
256
|
+
jsi::Object createObject(std::shared_ptr<jsi::HostObject> ho) override;
|
|
257
|
+
std::shared_ptr<jsi::HostObject> getHostObject(const jsi::Object &) override;
|
|
258
|
+
jsi::HostFunctionType &getHostFunction(const jsi::Function &) override;
|
|
259
|
+
|
|
260
|
+
jsi::Object createObjectWithPrototype(const jsi::Value &prototype) override;
|
|
261
|
+
void setPrototypeOf(
|
|
262
|
+
const jsi::Object &object, const jsi::Value &prototype) override;
|
|
263
|
+
jsi::Value getPrototypeOf(const jsi::Object &object) override;
|
|
264
|
+
|
|
265
|
+
bool hasNativeState(const jsi::Object &) override;
|
|
266
|
+
std::shared_ptr<jsi::NativeState> getNativeState(
|
|
267
|
+
const jsi::Object &) override;
|
|
268
|
+
void setNativeState(
|
|
269
|
+
const jsi::Object &, std::shared_ptr<jsi::NativeState>) override;
|
|
270
|
+
|
|
271
|
+
jsi::Value getProperty(
|
|
272
|
+
const jsi::Object &, const jsi::PropNameID &name) override;
|
|
273
|
+
jsi::Value getProperty(const jsi::Object &, const jsi::String &name) override;
|
|
274
|
+
bool hasProperty(const jsi::Object &, const jsi::PropNameID &name) override;
|
|
275
|
+
bool hasProperty(const jsi::Object &, const jsi::String &name) override;
|
|
276
|
+
void setPropertyValue(
|
|
277
|
+
const jsi::Object &, const jsi::PropNameID &name,
|
|
278
|
+
const jsi::Value &value) override;
|
|
279
|
+
void setPropertyValue(
|
|
280
|
+
const jsi::Object &, const jsi::String &name,
|
|
281
|
+
const jsi::Value &value) override;
|
|
282
|
+
|
|
283
|
+
bool isArray(const jsi::Object &) const override;
|
|
284
|
+
bool isArrayBuffer(const jsi::Object &) const override;
|
|
285
|
+
bool isFunction(const jsi::Object &) const override;
|
|
286
|
+
bool isHostObject(const jsi::Object &) const override;
|
|
287
|
+
bool isHostFunction(const jsi::Function &) const override;
|
|
288
|
+
jsi::Array getPropertyNames(const jsi::Object &) override;
|
|
289
|
+
|
|
290
|
+
// WeakObject
|
|
291
|
+
jsi::WeakObject createWeakObject(const jsi::Object &) override;
|
|
292
|
+
jsi::Value lockWeakObject(const jsi::WeakObject &) override;
|
|
293
|
+
|
|
294
|
+
// Array / ArrayBuffer
|
|
295
|
+
jsi::Array createArray(size_t length) override;
|
|
296
|
+
jsi::ArrayBuffer createArrayBuffer(
|
|
297
|
+
std::shared_ptr<jsi::MutableBuffer> buffer) override;
|
|
298
|
+
size_t size(const jsi::Array &) override;
|
|
299
|
+
size_t size(const jsi::ArrayBuffer &) override;
|
|
300
|
+
uint8_t *data(const jsi::ArrayBuffer &) override;
|
|
301
|
+
jsi::Value getValueAtIndex(const jsi::Array &, size_t i) override;
|
|
302
|
+
void setValueAtIndexImpl(
|
|
303
|
+
const jsi::Array &, size_t i, const jsi::Value &value) override;
|
|
304
|
+
|
|
305
|
+
// Function
|
|
306
|
+
jsi::Function createFunctionFromHostFunction(
|
|
307
|
+
const jsi::PropNameID &name, unsigned int paramCount,
|
|
308
|
+
jsi::HostFunctionType func) override;
|
|
309
|
+
jsi::Value call(
|
|
310
|
+
const jsi::Function &, const jsi::Value &jsThis, const jsi::Value *args,
|
|
311
|
+
size_t count) override;
|
|
312
|
+
jsi::Value callAsConstructor(
|
|
313
|
+
const jsi::Function &, const jsi::Value *args, size_t count) override;
|
|
314
|
+
|
|
315
|
+
// Comparison
|
|
316
|
+
bool strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const override;
|
|
317
|
+
bool strictEquals(const jsi::BigInt &a, const jsi::BigInt &b) const override;
|
|
318
|
+
bool strictEquals(const jsi::String &a, const jsi::String &b) const override;
|
|
319
|
+
bool strictEquals(const jsi::Object &a, const jsi::Object &b) const override;
|
|
320
|
+
bool instanceOf(const jsi::Object &o, const jsi::Function &f) override;
|
|
321
|
+
|
|
322
|
+
void setExternalMemoryPressure(
|
|
323
|
+
const jsi::Object &obj, size_t amount) override;
|
|
324
|
+
|
|
325
|
+
public:
|
|
326
|
+
// Conversions. Public because the class callbacks are free functions; they
|
|
327
|
+
// are not part of the JSI surface.
|
|
328
|
+
|
|
329
|
+
/// Takes ownership of the reference.
|
|
330
|
+
jsi::Value createValue(JSValue value);
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Wraps a JSValue without taking a reference of its own, so the caller's
|
|
334
|
+
* reference must outlive the wrapper.
|
|
335
|
+
*
|
|
336
|
+
* Used for host function arguments: quickjs keeps argv alive for the whole
|
|
337
|
+
* call, so dup'ing each argument on entry and freeing it on return is a pair
|
|
338
|
+
* of refcount operations that provably cancel. Every path that keeps an
|
|
339
|
+
* argument copies it, and every copy path takes a real reference, so a
|
|
340
|
+
* borrowed wrapper cannot outlive the call that made it.
|
|
341
|
+
*/
|
|
342
|
+
jsi::Value borrowValue(JSValue value);
|
|
343
|
+
|
|
344
|
+
/// Takes the reference out of a jsi::Value that is about to be destroyed,
|
|
345
|
+
/// rather than dup'ing a value whose destructor is about to free the
|
|
346
|
+
/// original. Symmetric with borrowValue.
|
|
347
|
+
JSValue takeJSValue(jsi::Value &&value);
|
|
348
|
+
|
|
349
|
+
jsi::Object createObjectFrom(JSValue value);
|
|
350
|
+
jsi::Symbol createSymbolFrom(JSValue value);
|
|
351
|
+
jsi::String createStringFrom(JSValue value);
|
|
352
|
+
jsi::PropNameID createPropNameIDFrom(JSAtom atom);
|
|
353
|
+
|
|
354
|
+
/// Returns a borrowed reference; the caller must not free it.
|
|
355
|
+
static JSValue toJSValue(const jsi::Pointer &pointer);
|
|
356
|
+
JSValue toJSValue(const jsi::Value &value) const;
|
|
357
|
+
static JSAtom toJSAtom(const jsi::PropNameID &name);
|
|
358
|
+
|
|
359
|
+
/// Throws the pending quickjs exception as a jsi::JSError. Never returns.
|
|
360
|
+
[[noreturn]] void throwPendingError();
|
|
361
|
+
|
|
362
|
+
/// Describes `exception` using nothing that can re-enter JavaScript. Always
|
|
363
|
+
/// returns a non-empty string for a non-empty exception and never leaves a
|
|
364
|
+
/// pending exception behind.
|
|
365
|
+
std::string describeExceptionWithoutRunningJS(JSValue exception);
|
|
366
|
+
|
|
367
|
+
/// Throws if `value` is the exception sentinel, otherwise returns it.
|
|
368
|
+
JSValue checkException(JSValue value);
|
|
369
|
+
/// Throws if `result` is negative, the quickjs error convention.
|
|
370
|
+
void checkException(int result);
|
|
371
|
+
|
|
372
|
+
/// Converts an in-flight C++ exception into a pending quickjs exception.
|
|
373
|
+
/// Called from class callbacks, which must not let C++ exceptions escape into
|
|
374
|
+
/// the engine's C frames. `origin` names the kind of callback that threw.
|
|
375
|
+
JSValue throwAsJSException(
|
|
376
|
+
const std::exception *e, const char *origin = "HostFunction") noexcept;
|
|
377
|
+
|
|
378
|
+
private:
|
|
379
|
+
void registerClasses();
|
|
380
|
+
void releaseNativePayloads() noexcept;
|
|
381
|
+
void drainPendingReleases() noexcept;
|
|
382
|
+
void rebaselineHeap() noexcept;
|
|
383
|
+
void noteCollection() noexcept;
|
|
384
|
+
void checkCanHoldNativeState(const jsi::Object &object);
|
|
385
|
+
JSValue evalInternal(const char *source) noexcept;
|
|
386
|
+
|
|
387
|
+
jsi::Value evaluateSource(
|
|
388
|
+
const uint8_t *source, size_t size, const std::string &sourceURL);
|
|
389
|
+
jsi::Value evaluateBytecode(const uint8_t *payload, size_t size);
|
|
390
|
+
|
|
391
|
+
// toJSValue borrows, so the array holds no references and needs no cleanup:
|
|
392
|
+
// the jsi::Values the caller owns outlive the call.
|
|
393
|
+
class ArgumentList {
|
|
394
|
+
public:
|
|
395
|
+
ArgumentList(
|
|
396
|
+
const QuickJSRuntime &runtime, const jsi::Value *args, size_t count) {
|
|
397
|
+
if (count > kInline) {
|
|
398
|
+
heap_.resize(count);
|
|
399
|
+
values_ = heap_.data();
|
|
400
|
+
}
|
|
401
|
+
for (size_t i = 0; i < count; ++i) {
|
|
402
|
+
values_[i] = runtime.toJSValue(args[i]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
JSValue *data() noexcept {
|
|
407
|
+
return values_;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private:
|
|
411
|
+
static constexpr size_t kInline = 8;
|
|
412
|
+
JSValue inline_[kInline];
|
|
413
|
+
std::vector<JSValue> heap_;
|
|
414
|
+
JSValue *values_{inline_};
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// Pooled: one PointerValue per jsi value crossing the boundary, and
|
|
418
|
+
// new/delete measured ~20 ns against a ~4.5 ns property lookup. Inline
|
|
419
|
+
// because the call itself was a measurable part of that cost.
|
|
420
|
+
QuickJSPointerValue *allocPointerValue(JSValue value, bool owned = true) {
|
|
421
|
+
if (freeValues_ == nullptr) {
|
|
422
|
+
refillValueSlab();
|
|
423
|
+
}
|
|
424
|
+
QuickJSPointerValue *slot = freeValues_;
|
|
425
|
+
freeValues_ = slot->nextFree_;
|
|
426
|
+
return new (static_cast<void *>(slot))
|
|
427
|
+
QuickJSPointerValue(*this, value, owned);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
QuickJSAtomPointerValue *allocAtomPointerValue(JSAtom atom) {
|
|
431
|
+
if (freeAtoms_ == nullptr) {
|
|
432
|
+
refillAtomSlab();
|
|
433
|
+
}
|
|
434
|
+
QuickJSAtomPointerValue *slot = freeAtoms_;
|
|
435
|
+
freeAtoms_ = slot->nextFree_;
|
|
436
|
+
return new (static_cast<void *>(slot)) QuickJSAtomPointerValue(*this, atom);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
void recyclePointerValue(QuickJSPointerValue *pv) noexcept {
|
|
440
|
+
if (isOnJSThread()) {
|
|
441
|
+
pv->nextFree_ = freeValues_;
|
|
442
|
+
freeValues_ = pv;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
queuePointerValue(pv);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
void recycleAtomPointerValue(QuickJSAtomPointerValue *pv) noexcept {
|
|
449
|
+
if (isOnJSThread()) {
|
|
450
|
+
pv->nextFree_ = freeAtoms_;
|
|
451
|
+
freeAtoms_ = pv;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
queueAtomPointerValue(pv);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
void refillValueSlab();
|
|
458
|
+
void refillAtomSlab();
|
|
459
|
+
void queuePointerValue(QuickJSPointerValue *pv) noexcept;
|
|
460
|
+
void queueAtomPointerValue(QuickJSAtomPointerValue *pv) noexcept;
|
|
461
|
+
void freePointerValuePool() noexcept;
|
|
462
|
+
|
|
463
|
+
static constexpr size_t kPointerValueSlabSize = 256;
|
|
464
|
+
static constexpr int kMaxTeardownGCPasses = 2;
|
|
465
|
+
|
|
466
|
+
QuickJSRuntimeConfig config_;
|
|
467
|
+
JSRuntime *runtime_{nullptr};
|
|
468
|
+
JSContext *context_{nullptr};
|
|
469
|
+
std::function<void(const std::string &, const std::string &)>
|
|
470
|
+
onScriptEvaluated_;
|
|
471
|
+
|
|
472
|
+
// Atomic because ownership moves while other threads read it to route their
|
|
473
|
+
// releases. Recursive so a host function called from JavaScript can lock
|
|
474
|
+
// without deadlocking against the boundary lock it already runs under.
|
|
475
|
+
std::atomic<std::thread::id> jsThread_;
|
|
476
|
+
std::recursive_mutex engineMutex_;
|
|
477
|
+
|
|
478
|
+
// Building a jsi::JSError reads .message and .stack, which can throw again.
|
|
479
|
+
int errorDepth_{0};
|
|
480
|
+
static constexpr int kMaxErrorDepth = 8;
|
|
481
|
+
|
|
482
|
+
// Described on the way in, because by the time the nesting bound is hit the
|
|
483
|
+
// original exception is gone and the bail-out would name only itself.
|
|
484
|
+
std::string firstErrorDescription_;
|
|
485
|
+
|
|
486
|
+
JSClassID hostObjectClassID_{0};
|
|
487
|
+
JSClassID hostFunctionClassID_{0};
|
|
488
|
+
JSClassID nativeStateClassID_{0};
|
|
489
|
+
|
|
490
|
+
// Intrusive list heads of the live native payloads, held as void* to keep the
|
|
491
|
+
// payload types private to the implementation.
|
|
492
|
+
void *hostObjectProxies_{nullptr};
|
|
493
|
+
void *hostFunctionProxies_{nullptr};
|
|
494
|
+
void *nativeStateProxies_{nullptr};
|
|
495
|
+
|
|
496
|
+
// Engine facilities with no C API, evaluated once at startup.
|
|
497
|
+
JSValue enumeratePropertyNames_{JS_UNDEFINED};
|
|
498
|
+
JSValue symbolToString_{JS_UNDEFINED};
|
|
499
|
+
JSValue weakRefConstructor_{JS_UNDEFINED};
|
|
500
|
+
JSValue weakRefDeref_{JS_UNDEFINED};
|
|
501
|
+
JSValue bigIntToString_{JS_UNDEFINED};
|
|
502
|
+
|
|
503
|
+
// NativeState is an ordinary property under a private symbol, which script
|
|
504
|
+
// has no way to name, list or overwrite. A WeakMap would also be invisible
|
|
505
|
+
// but costs a call into JavaScript on every read and write.
|
|
506
|
+
JSAtom nativeStateKey_{JS_ATOM_NULL};
|
|
507
|
+
|
|
508
|
+
QuickJSPointerValue *freeValues_{nullptr};
|
|
509
|
+
QuickJSAtomPointerValue *freeAtoms_{nullptr};
|
|
510
|
+
std::vector<void *> valueSlabs_;
|
|
511
|
+
std::vector<void *> atomSlabs_;
|
|
512
|
+
|
|
513
|
+
std::atomic<bool> hasPendingReleases_{false};
|
|
514
|
+
// End of the most recent JS task. The gap to the start of the next one is
|
|
515
|
+
// what says whether anything is still driving JS.
|
|
516
|
+
std::chrono::steady_clock::time_point lastTaskEnd_{};
|
|
517
|
+
std::chrono::steady_clock::time_point pendingSince_{};
|
|
518
|
+
// What heap growth is measured from. Reset by a collection and after each
|
|
519
|
+
// top-level script, so `grown` means growth since the app last settled.
|
|
520
|
+
size_t heapBaseline_{0};
|
|
521
|
+
bool startupGCBracketUsed_{false};
|
|
522
|
+
std::mutex pendingMutex_;
|
|
523
|
+
std::vector<JSValue> pendingValues_;
|
|
524
|
+
std::vector<JSAtom> pendingAtoms_;
|
|
525
|
+
std::vector<QuickJSPointerValue *> pendingPointerValues_;
|
|
526
|
+
std::vector<QuickJSAtomPointerValue *> pendingAtomPointerValues_;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
} // namespace qjs
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstddef>
|
|
11
|
+
#include <string>
|
|
12
|
+
|
|
13
|
+
namespace qjs {
|
|
14
|
+
|
|
15
|
+
struct QuickJSRuntimeConfig {
|
|
16
|
+
/// Where compiled bytecode is cached between launches. Empty disables it.
|
|
17
|
+
std::string codeCacheDir;
|
|
18
|
+
|
|
19
|
+
/// Hard cap on the JS heap in bytes. 0 means unlimited.
|
|
20
|
+
size_t memoryLimit{0};
|
|
21
|
+
|
|
22
|
+
/// JS stack size in bytes. 0 uses the quickjs default.
|
|
23
|
+
size_t stackSize{0};
|
|
24
|
+
|
|
25
|
+
/// Installs the `HermesInternal` object React Native probes for. Without it
|
|
26
|
+
/// React Native's `hasPromise()` check fails and every app replaces the
|
|
27
|
+
/// native Promise with a setImmediate polyfill, at 1205 ns per construct and
|
|
28
|
+
/// chain against 775 ns native.
|
|
29
|
+
bool reactNativeCompat{true};
|
|
30
|
+
|
|
31
|
+
/// Must be decided before the bundle is parsed: statement traps are emitted
|
|
32
|
+
/// at parse time, so a runtime created with this off cannot be attached to
|
|
33
|
+
/// later. A frontend would connect, accept breakpoints and never stop.
|
|
34
|
+
bool enableDebugger{false};
|
|
35
|
+
|
|
36
|
+
/// Skips a collection triggered while JS is on the stack and runs it at the
|
|
37
|
+
/// next safepoint instead. Only cycles wait -- acyclic garbage still dies at
|
|
38
|
+
/// its last decref. Measured on a list-rebuilding workload: worst frame
|
|
39
|
+
/// 45.07 ms -> 3.33 ms, frames over budget 68/300 -> 0.
|
|
40
|
+
bool deferGC{true};
|
|
41
|
+
|
|
42
|
+
/// Heap size past which a collection runs wherever execution happens to be.
|
|
43
|
+
/// 0 derives it from the live heap, re-derived after every collection.
|
|
44
|
+
///
|
|
45
|
+
/// The engine's own default is `malloc_gc_threshold * 4` fixed at
|
|
46
|
+
/// construction time, which is 1.00 MiB forever. Deferral is gated on
|
|
47
|
+
/// `live + size < ceiling`, and React Native's entry cascade alone peaks at
|
|
48
|
+
/// 1.557 MB, so that default stops engaging before an app finishes starting.
|
|
49
|
+
size_t deferredGCLimit{0};
|
|
50
|
+
|
|
51
|
+
/// The derived ceiling is `live + clamp(live, minSlack, maxSlack)`. Additive
|
|
52
|
+
/// rather than a multiple, which at a 69 MB heap would give 1.1 GB.
|
|
53
|
+
///
|
|
54
|
+
/// What it bounds is garbage accumulated while deferred, so the rule is
|
|
55
|
+
/// `minSlack > 2 x heap growth between quiet windows` -- measured at 48.4 MB
|
|
56
|
+
/// on the example app with a 50,000-row list. Peak heap can reach
|
|
57
|
+
/// `live + slack`, so lowering these trades memory for frame drops.
|
|
58
|
+
size_t deferredGCMinSlack{48 * 1024 * 1024};
|
|
59
|
+
size_t deferredGCMaxSlack{256 * 1024 * 1024};
|
|
60
|
+
|
|
61
|
+
/// How far the GC threshold may drift above the live heap before it is pulled
|
|
62
|
+
/// back down, as a multiple of it. 0 disables the retune.
|
|
63
|
+
///
|
|
64
|
+
/// The engine only recomputes the threshold at a collection, so refcounting
|
|
65
|
+
/// can shrink the heap far below it and nothing lowers it again. Measured on
|
|
66
|
+
/// device: frozen at 144 MB against a 63 MB heap, two collections in 20
|
|
67
|
+
/// cycles. The engine targets 1.5x, so 3.0 leaves a wide dead band.
|
|
68
|
+
double gcThresholdRetuneRatio{3.0};
|
|
69
|
+
|
|
70
|
+
/// Heap growth allowed to the first top-level script before the collector may
|
|
71
|
+
/// run during it. 0 disables the bracket.
|
|
72
|
+
///
|
|
73
|
+
/// Module instantiation builds a graph and keeps it, so the collector finds
|
|
74
|
+
/// 14 collectable objects across React Native's whole entry cascade and fires
|
|
75
|
+
/// three times before any of them exist -- 772 us freeing nothing. Bounded,
|
|
76
|
+
/// so a larger script resumes normal collection by itself.
|
|
77
|
+
size_t startupGCBracketBytes{8 * 1024 * 1024};
|
|
78
|
+
|
|
79
|
+
/// How long a gap before the current task counts as the app going quiet.
|
|
80
|
+
///
|
|
81
|
+
/// An empty JS stack is not idleness: during a gesture native drives JS every
|
|
82
|
+
/// frame, so collecting at the first safepoint just moves the pause between
|
|
83
|
+
/// two frames of the same gesture. Measured, a scroll took 74 collections at
|
|
84
|
+
/// 0 and 1 at anything from 16 ms up, so this only has to clear the frame
|
|
85
|
+
/// cadence by enough that one slow frame does not read as quiet.
|
|
86
|
+
size_t gcIdleGapMs{120};
|
|
87
|
+
|
|
88
|
+
/// The longest a collection may stay pending before the next safepoint takes
|
|
89
|
+
/// it regardless. 0 disables.
|
|
90
|
+
///
|
|
91
|
+
/// Idleness alone never fires for an app with a ticker. Measured with a 16 ms
|
|
92
|
+
/// setInterval: one collection pending across thousands of safepoints, zero
|
|
93
|
+
/// idle drains, and every collection that ran was forced by the pressure
|
|
94
|
+
/// valve -- mid-gesture, which is what deferral exists to avoid.
|
|
95
|
+
size_t gcMaxDeferralMs{2000};
|
|
96
|
+
|
|
97
|
+
/// Heap size past which the idle requirement is dropped, though the
|
|
98
|
+
/// collection still waits for a safepoint. Sits below deferredGCLimit. 0
|
|
99
|
+
/// derives it from the slack.
|
|
100
|
+
size_t gcPressureBytes{0};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
} // namespace qjs
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "QuickJSRuntimeFactory.h"
|
|
9
|
+
|
|
10
|
+
#include "QuickJSCompat.h"
|
|
11
|
+
#include "QuickJSModule.h"
|
|
12
|
+
#include "QuickJSRuntime.h"
|
|
13
|
+
|
|
14
|
+
namespace qjs {
|
|
15
|
+
|
|
16
|
+
std::unique_ptr<facebook::jsi::Runtime> makeQuickJSRuntime(
|
|
17
|
+
QuickJSRuntimeConfig config) {
|
|
18
|
+
const bool reactNativeCompat = config.reactNativeCompat;
|
|
19
|
+
|
|
20
|
+
auto runtime = std::make_unique<QuickJSRuntime>(std::move(config));
|
|
21
|
+
|
|
22
|
+
// Both install before any application JavaScript runs, because React
|
|
23
|
+
// Native's polyfill checks run very early: anything installed afterwards is
|
|
24
|
+
// shadowed by a polyfill that has already decided the engine lacks it.
|
|
25
|
+
if (reactNativeCompat) {
|
|
26
|
+
installReactNativeCompat(*runtime);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
installModules(*runtime);
|
|
30
|
+
|
|
31
|
+
return runtime;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
} // namespace qjs
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <jsi/jsi.h>
|
|
11
|
+
|
|
12
|
+
#include <memory>
|
|
13
|
+
|
|
14
|
+
#include "QuickJSRuntimeConfig.h"
|
|
15
|
+
|
|
16
|
+
namespace qjs {
|
|
17
|
+
|
|
18
|
+
/// Creates a QuickJS-backed jsi::Runtime with the compatibility shims and every
|
|
19
|
+
/// registered module installed. The only entry point a non-React consumer
|
|
20
|
+
/// needs.
|
|
21
|
+
std::unique_ptr<facebook::jsi::Runtime> makeQuickJSRuntime(
|
|
22
|
+
QuickJSRuntimeConfig config = {});
|
|
23
|
+
|
|
24
|
+
} // namespace qjs
|