@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,1864 @@
|
|
|
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 "QuickJSRuntime.h"
|
|
9
|
+
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <string_view>
|
|
13
|
+
#include <type_traits>
|
|
14
|
+
#include <utility>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
#include "QuickJSBytecode.h"
|
|
18
|
+
|
|
19
|
+
namespace qjs {
|
|
20
|
+
|
|
21
|
+
static_assert(
|
|
22
|
+
std::atomic<std::thread::id>::is_always_lock_free,
|
|
23
|
+
"std::thread::id must be lock-free to be read on the release path");
|
|
24
|
+
|
|
25
|
+
static_assert(
|
|
26
|
+
!std::is_abstract<QuickJSRuntime>::value,
|
|
27
|
+
"QuickJSRuntime does not implement every jsi::Runtime method");
|
|
28
|
+
|
|
29
|
+
namespace {
|
|
30
|
+
|
|
31
|
+
struct NativePayloadLink {
|
|
32
|
+
NativePayloadLink *prev{nullptr};
|
|
33
|
+
NativePayloadLink *next{nullptr};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/// Opaque payload of a host object. Owned by the JS object and released by the
|
|
37
|
+
/// class finalizer.
|
|
38
|
+
struct HostObjectProxy {
|
|
39
|
+
NativePayloadLink link; // must stay first
|
|
40
|
+
QuickJSRuntime *runtime;
|
|
41
|
+
std::shared_ptr<jsi::HostObject> hostObject;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/// Opaque payload of a host function. `function` is returned by reference from
|
|
45
|
+
/// getHostFunction, so it must keep a stable address for the lifetime of the JS
|
|
46
|
+
/// function.
|
|
47
|
+
struct HostFunctionProxy {
|
|
48
|
+
NativePayloadLink link; // must stay first: the registry casts through it
|
|
49
|
+
QuickJSRuntime *runtime;
|
|
50
|
+
jsi::HostFunctionType function;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
QuickJSRuntime *runtimeOf(JSContext *ctx) {
|
|
54
|
+
return static_cast<QuickJSRuntime *>(JS_GetContextOpaque(ctx));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void payloadLink(void *&head, void *proxy) {
|
|
58
|
+
auto *link = static_cast<NativePayloadLink *>(proxy);
|
|
59
|
+
link->prev = nullptr;
|
|
60
|
+
link->next = static_cast<NativePayloadLink *>(head);
|
|
61
|
+
if (link->next != nullptr) {
|
|
62
|
+
link->next->prev = link;
|
|
63
|
+
}
|
|
64
|
+
head = link;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void payloadUnlink(void *&head, void *proxy) {
|
|
68
|
+
auto *link = static_cast<NativePayloadLink *>(proxy);
|
|
69
|
+
if (link->prev != nullptr) {
|
|
70
|
+
link->prev->next = link->next;
|
|
71
|
+
} else if (head == link) {
|
|
72
|
+
head = link->next;
|
|
73
|
+
} else {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (link->next != nullptr) {
|
|
77
|
+
link->next->prev = link->prev;
|
|
78
|
+
}
|
|
79
|
+
link->prev = link->next = nullptr;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
struct HostObjectHandlers {
|
|
83
|
+
static HostObjectProxy *proxyOf(JSContext *ctx, JSValueConst obj) {
|
|
84
|
+
return static_cast<HostObjectProxy *>(
|
|
85
|
+
JS_GetOpaque(obj, runtimeOf(ctx)->hostObjectClassID()));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static int getOwnProperty(
|
|
89
|
+
JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj,
|
|
90
|
+
JSAtom prop) {
|
|
91
|
+
auto *proxy = proxyOf(ctx, obj);
|
|
92
|
+
if (proxy == nullptr) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
// A host object answers for every key, so an existence check needs no call
|
|
96
|
+
// into C++.
|
|
97
|
+
if (desc == nullptr) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
auto *runtime = proxy->runtime;
|
|
102
|
+
try {
|
|
103
|
+
jsi::PropNameID name =
|
|
104
|
+
runtime->createPropNameIDFrom(JS_DupAtom(ctx, prop));
|
|
105
|
+
jsi::Value value = proxy->hostObject->get(*runtime, name);
|
|
106
|
+
desc->flags = JS_PROP_C_W_E;
|
|
107
|
+
desc->value = JS_DupValue(ctx, runtime->toJSValue(value));
|
|
108
|
+
desc->getter = JS_UNDEFINED;
|
|
109
|
+
desc->setter = JS_UNDEFINED;
|
|
110
|
+
return true;
|
|
111
|
+
} catch (const std::exception &e) {
|
|
112
|
+
runtime->throwAsJSException(&e, "HostObject");
|
|
113
|
+
return -1;
|
|
114
|
+
} catch (...) {
|
|
115
|
+
runtime->throwAsJSException(nullptr, "HostObject");
|
|
116
|
+
return -1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static int getOwnPropertyNames(
|
|
121
|
+
JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) {
|
|
122
|
+
auto *proxy = proxyOf(ctx, obj);
|
|
123
|
+
if (proxy == nullptr) {
|
|
124
|
+
*ptab = nullptr;
|
|
125
|
+
*plen = 0;
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
auto *runtime = proxy->runtime;
|
|
130
|
+
std::vector<jsi::PropNameID> names;
|
|
131
|
+
try {
|
|
132
|
+
names = proxy->hostObject->getPropertyNames(*runtime);
|
|
133
|
+
} catch (const std::exception &e) {
|
|
134
|
+
runtime->throwAsJSException(&e, "HostObject");
|
|
135
|
+
return -1;
|
|
136
|
+
} catch (...) {
|
|
137
|
+
runtime->throwAsJSException(nullptr, "HostObject");
|
|
138
|
+
return -1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// JSI allows duplicates here; the property enumeration protocol does not.
|
|
142
|
+
std::vector<JSAtom> unique;
|
|
143
|
+
std::unordered_set<JSAtom> seen;
|
|
144
|
+
unique.reserve(names.size());
|
|
145
|
+
for (const jsi::PropNameID &name : names) {
|
|
146
|
+
JSAtom atom = QuickJSRuntime::toJSAtom(name);
|
|
147
|
+
if (seen.insert(atom).second) {
|
|
148
|
+
unique.push_back(atom);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
auto *table = static_cast<JSPropertyEnum *>(js_malloc(
|
|
153
|
+
ctx, sizeof(JSPropertyEnum) * std::max<size_t>(unique.size(), 1)));
|
|
154
|
+
if (table == nullptr) {
|
|
155
|
+
return -1;
|
|
156
|
+
}
|
|
157
|
+
for (size_t i = 0; i < unique.size(); ++i) {
|
|
158
|
+
table[i].is_enumerable = true;
|
|
159
|
+
table[i].atom = JS_DupAtom(ctx, unique[i]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
*ptab = table;
|
|
163
|
+
*plen = static_cast<uint32_t>(unique.size());
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static int setProperty(
|
|
168
|
+
JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value,
|
|
169
|
+
JSValueConst receiver, int flags) {
|
|
170
|
+
auto *proxy = proxyOf(ctx, obj);
|
|
171
|
+
if (proxy == nullptr) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// OrdinarySetWithOwnDescriptor: an assignment that reached this host object
|
|
176
|
+
// by walking a receiver's prototype chain creates an own property on the
|
|
177
|
+
// receiver instead of calling HostObject::set. React Native's TurboModules
|
|
178
|
+
// depend on it -- TurboModule::get memoises each method by writing it back
|
|
179
|
+
// onto a plain object whose prototype is the module's HostObject.
|
|
180
|
+
const bool receiverIsThisObject =
|
|
181
|
+
JS_VALUE_GET_TAG(receiver) == JS_VALUE_GET_TAG(obj) &&
|
|
182
|
+
JS_VALUE_GET_PTR(receiver) == JS_VALUE_GET_PTR(obj);
|
|
183
|
+
|
|
184
|
+
if (!JS_IsUndefined(receiver) && !receiverIsThisObject) {
|
|
185
|
+
// Step 3.a returns false for a non-object receiver, reachable through
|
|
186
|
+
// Reflect.set(hostObj, k, v, 5). Defining would throw instead.
|
|
187
|
+
if (JS_VALUE_GET_TAG(receiver) != JS_TAG_OBJECT) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
// Defining is safe: quickjs only consults a prototype's set_property
|
|
191
|
+
// after failing to find an own property. Both throw flags are forwarded
|
|
192
|
+
// so the caller's own strictness policy is reproduced -- without them a
|
|
193
|
+
// strict assignment onto a frozen receiver returns false where the spec
|
|
194
|
+
// throws.
|
|
195
|
+
int defined = JS_DefinePropertyValue(
|
|
196
|
+
ctx, receiver, JS_DupAtom(ctx, atom), JS_DupValue(ctx, value),
|
|
197
|
+
JS_PROP_C_W_E | (flags & (JS_PROP_THROW | JS_PROP_THROW_STRICT)));
|
|
198
|
+
return defined < 0 ? -1 : defined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
auto *runtime = proxy->runtime;
|
|
202
|
+
try {
|
|
203
|
+
jsi::PropNameID name =
|
|
204
|
+
runtime->createPropNameIDFrom(JS_DupAtom(ctx, atom));
|
|
205
|
+
proxy->hostObject->set(
|
|
206
|
+
*runtime, name, runtime->createValue(JS_DupValue(ctx, value)));
|
|
207
|
+
return true;
|
|
208
|
+
} catch (const std::exception &e) {
|
|
209
|
+
runtime->throwAsJSException(&e, "HostObject");
|
|
210
|
+
return -1;
|
|
211
|
+
} catch (...) {
|
|
212
|
+
runtime->throwAsJSException(nullptr, "HostObject");
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static void finalizer(JSRuntime *rt, JSValueConst value) {
|
|
218
|
+
auto *runtime = static_cast<QuickJSRuntime *>(JS_GetRuntimeOpaque(rt));
|
|
219
|
+
auto *proxy = static_cast<HostObjectProxy *>(
|
|
220
|
+
JS_GetOpaque(value, runtime->hostObjectClassID()));
|
|
221
|
+
runtime->unregisterHostObject(proxy);
|
|
222
|
+
delete proxy;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
JSClassExoticMethods gHostObjectExotic = {
|
|
227
|
+
/* get_own_property */ HostObjectHandlers::getOwnProperty,
|
|
228
|
+
/* get_own_property_names */ HostObjectHandlers::getOwnPropertyNames,
|
|
229
|
+
/* delete_property */ nullptr,
|
|
230
|
+
/* define_own_property */ nullptr,
|
|
231
|
+
/* has_property */ nullptr,
|
|
232
|
+
/* get_property */ nullptr,
|
|
233
|
+
/* set_property */ HostObjectHandlers::setProperty,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
struct HostFunctionHandlers {
|
|
237
|
+
static JSValue call(
|
|
238
|
+
JSContext *ctx, JSValueConst funcObj, JSValueConst thisVal, int argc,
|
|
239
|
+
JSValueConst *argv, int /*flags*/) {
|
|
240
|
+
auto *runtime = runtimeOf(ctx);
|
|
241
|
+
auto *proxy = static_cast<HostFunctionProxy *>(
|
|
242
|
+
JS_GetOpaque(funcObj, runtime->hostFunctionClassID()));
|
|
243
|
+
if (proxy == nullptr) {
|
|
244
|
+
return JS_ThrowTypeError(ctx, "not a host function");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
// Borrowed, not dup'd: quickjs keeps argv and thisVal alive for the whole
|
|
249
|
+
// call, so a reference taken here and dropped on return would cancel out.
|
|
250
|
+
constexpr int kInlineArgs = 8;
|
|
251
|
+
jsi::Value inlineArgs[kInlineArgs];
|
|
252
|
+
std::vector<jsi::Value> heapArgs;
|
|
253
|
+
jsi::Value *args = inlineArgs;
|
|
254
|
+
if (argc > kInlineArgs) {
|
|
255
|
+
heapArgs.resize(static_cast<size_t>(argc));
|
|
256
|
+
args = heapArgs.data();
|
|
257
|
+
}
|
|
258
|
+
for (int i = 0; i < argc; ++i) {
|
|
259
|
+
args[i] = runtime->borrowValue(argv[i]);
|
|
260
|
+
}
|
|
261
|
+
jsi::Value thisValue = runtime->borrowValue(thisVal);
|
|
262
|
+
jsi::Value result =
|
|
263
|
+
proxy->function(*runtime, thisValue, args, static_cast<size_t>(argc));
|
|
264
|
+
return runtime->takeJSValue(std::move(result));
|
|
265
|
+
} catch (const std::exception &e) {
|
|
266
|
+
return runtime->throwAsJSException(&e);
|
|
267
|
+
} catch (...) {
|
|
268
|
+
return runtime->throwAsJSException(nullptr);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static void finalizer(JSRuntime *rt, JSValueConst value) {
|
|
273
|
+
auto *runtime = static_cast<QuickJSRuntime *>(JS_GetRuntimeOpaque(rt));
|
|
274
|
+
auto *proxy = static_cast<HostFunctionProxy *>(
|
|
275
|
+
JS_GetOpaque(value, runtime->hostFunctionClassID()));
|
|
276
|
+
runtime->unregisterHostFunction(proxy);
|
|
277
|
+
delete proxy;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
struct NativeStateProxy {
|
|
282
|
+
NativePayloadLink link; // must stay first
|
|
283
|
+
std::shared_ptr<jsi::NativeState> state;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
void nativeStateFinalizer(JSRuntime *rt, JSValueConst value) {
|
|
287
|
+
auto *runtime = static_cast<QuickJSRuntime *>(JS_GetRuntimeOpaque(rt));
|
|
288
|
+
auto *proxy = static_cast<NativeStateProxy *>(
|
|
289
|
+
JS_GetOpaque(value, runtime->nativeStateClassID()));
|
|
290
|
+
runtime->unregisterNativeState(proxy);
|
|
291
|
+
delete proxy;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Keeps a jsi::MutableBuffer alive for as long as the ArrayBuffer pointing
|
|
295
|
+
/// into it.
|
|
296
|
+
struct ArrayBufferProxy {
|
|
297
|
+
std::shared_ptr<jsi::MutableBuffer> buffer;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
void freeArrayBufferProxy(JSRuntime * /*rt*/, void *opaque, void * /*ptr*/) {
|
|
301
|
+
delete static_cast<ArrayBufferProxy *>(opaque);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const char *kEnumeratePropertyNamesSource =
|
|
305
|
+
"(function (o) { const r = []; for (const k in o) r.push(k); return r; })";
|
|
306
|
+
JSValue microtaskJob(JSContext *ctx, int argc, JSValueConst *argv) {
|
|
307
|
+
if (argc < 1) {
|
|
308
|
+
return JS_UNDEFINED;
|
|
309
|
+
}
|
|
310
|
+
return JS_Call(ctx, argv[0], JS_UNDEFINED, 0, nullptr);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const char *kSymbolToStringSource = "(function (s) { return s.toString(); })";
|
|
314
|
+
const char *kBigIntToStringSource =
|
|
315
|
+
"(function (b, radix) { return b.toString(radix); })";
|
|
316
|
+
|
|
317
|
+
} // namespace
|
|
318
|
+
|
|
319
|
+
QuickJSRuntime::QuickJSRuntime(QuickJSRuntimeConfig config)
|
|
320
|
+
: config_(std::move(config)), jsThread_(std::this_thread::get_id()) {
|
|
321
|
+
runtime_ = JS_NewRuntime();
|
|
322
|
+
if (runtime_ == nullptr) {
|
|
323
|
+
throw jsi::JSINativeException("QuickJSRuntime: JS_NewRuntime failed");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (config_.memoryLimit > 0) {
|
|
327
|
+
JS_SetMemoryLimit(runtime_, config_.memoryLimit);
|
|
328
|
+
}
|
|
329
|
+
if (config_.stackSize > 0) {
|
|
330
|
+
JS_SetMaxStackSize(runtime_, config_.stackSize);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (config_.deferGC) {
|
|
334
|
+
JS_SetGCDeferred(runtime_, true, config_.deferredGCLimit);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
context_ = JS_NewContext(runtime_);
|
|
338
|
+
if (context_ == nullptr) {
|
|
339
|
+
JS_FreeRuntime(runtime_);
|
|
340
|
+
runtime_ = nullptr;
|
|
341
|
+
throw jsi::JSINativeException("QuickJSRuntime: JS_NewContext failed");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
JS_SetContextOpaque(context_, this);
|
|
345
|
+
JS_SetRuntimeOpaque(runtime_, this);
|
|
346
|
+
|
|
347
|
+
// Baseline for the pressure valve. Left at zero, the first `grown` reading is
|
|
348
|
+
// the whole live heap and the valve fires whatever the app is doing.
|
|
349
|
+
heapBaseline_ = JS_GetMallocSize(runtime_);
|
|
350
|
+
|
|
351
|
+
registerClasses();
|
|
352
|
+
enumeratePropertyNames_ = evalInternal(kEnumeratePropertyNamesSource);
|
|
353
|
+
symbolToString_ = evalInternal(kSymbolToStringSource);
|
|
354
|
+
bigIntToString_ = evalInternal(kBigIntToStringSource);
|
|
355
|
+
|
|
356
|
+
JSValue nativeStateKey = JS_NewPrivateSymbol(context_, "nativeState");
|
|
357
|
+
nativeStateKey_ = JS_ValueToAtom(context_, nativeStateKey);
|
|
358
|
+
JS_FreeValue(context_, nativeStateKey);
|
|
359
|
+
|
|
360
|
+
// WeakRef has no C API, so reach for the JS-visible constructor once.
|
|
361
|
+
JSValue global = JS_GetGlobalObject(context_);
|
|
362
|
+
weakRefConstructor_ = JS_GetPropertyStr(context_, global, "WeakRef");
|
|
363
|
+
if (JS_IsObject(weakRefConstructor_)) {
|
|
364
|
+
JSValue proto =
|
|
365
|
+
JS_GetPropertyStr(context_, weakRefConstructor_, "prototype");
|
|
366
|
+
weakRefDeref_ = JS_GetPropertyStr(context_, proto, "deref");
|
|
367
|
+
JS_FreeValue(context_, proto);
|
|
368
|
+
}
|
|
369
|
+
JS_FreeValue(context_, global);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
void QuickJSRuntime::registerClasses() {
|
|
373
|
+
JS_NewClassID(runtime_, &hostObjectClassID_);
|
|
374
|
+
JSClassDef hostObjectDef = {};
|
|
375
|
+
hostObjectDef.class_name = "HostObject";
|
|
376
|
+
hostObjectDef.finalizer = HostObjectHandlers::finalizer;
|
|
377
|
+
hostObjectDef.exotic = &gHostObjectExotic;
|
|
378
|
+
JS_NewClass(runtime_, hostObjectClassID_, &hostObjectDef);
|
|
379
|
+
|
|
380
|
+
JS_NewClassID(runtime_, &hostFunctionClassID_);
|
|
381
|
+
JSClassDef hostFunctionDef = {};
|
|
382
|
+
hostFunctionDef.class_name = "HostFunction";
|
|
383
|
+
hostFunctionDef.finalizer = HostFunctionHandlers::finalizer;
|
|
384
|
+
hostFunctionDef.call = HostFunctionHandlers::call;
|
|
385
|
+
JS_NewClass(runtime_, hostFunctionClassID_, &hostFunctionDef);
|
|
386
|
+
|
|
387
|
+
JS_NewClassID(runtime_, &nativeStateClassID_);
|
|
388
|
+
JSClassDef nativeStateDef = {};
|
|
389
|
+
nativeStateDef.class_name = "NativeState";
|
|
390
|
+
nativeStateDef.finalizer = nativeStateFinalizer;
|
|
391
|
+
JS_NewClass(runtime_, nativeStateClassID_, &nativeStateDef);
|
|
392
|
+
|
|
393
|
+
// Host functions must be indistinguishable from JS functions: without
|
|
394
|
+
// Function.prototype, `instanceof Function` is false and bind/call/apply are
|
|
395
|
+
// missing.
|
|
396
|
+
JSValue global = JS_GetGlobalObject(context_);
|
|
397
|
+
JSValue functionConstructor = JS_GetPropertyStr(context_, global, "Function");
|
|
398
|
+
JS_SetClassProto(
|
|
399
|
+
context_, hostFunctionClassID_,
|
|
400
|
+
JS_GetPropertyStr(context_, functionConstructor, "prototype"));
|
|
401
|
+
JS_FreeValue(context_, functionConstructor);
|
|
402
|
+
JS_FreeValue(context_, global);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
void QuickJSRuntime::registerHostObject(void *proxy) {
|
|
406
|
+
payloadLink(hostObjectProxies_, proxy);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
void QuickJSRuntime::unregisterHostObject(void *proxy) {
|
|
410
|
+
payloadUnlink(hostObjectProxies_, proxy);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
void QuickJSRuntime::registerNativeState(void *proxy) {
|
|
414
|
+
payloadLink(nativeStateProxies_, proxy);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
void QuickJSRuntime::unregisterNativeState(void *proxy) {
|
|
418
|
+
payloadUnlink(nativeStateProxies_, proxy);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
void QuickJSRuntime::registerHostFunction(void *proxy) {
|
|
422
|
+
payloadLink(hostFunctionProxies_, proxy);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
void QuickJSRuntime::unregisterHostFunction(void *proxy) {
|
|
426
|
+
payloadUnlink(hostFunctionProxies_, proxy);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Leaves the lists intact: the proxies stay allocated and their finalizers
|
|
430
|
+
/// still run during JS_FreeRuntime, which is what unlinks them. Releasing a
|
|
431
|
+
/// payload can free jsi values and so re-enter, hence remembering `next` first.
|
|
432
|
+
void QuickJSRuntime::releaseNativePayloads() noexcept {
|
|
433
|
+
for (auto *l = static_cast<NativePayloadLink *>(hostObjectProxies_);
|
|
434
|
+
l != nullptr;) {
|
|
435
|
+
auto *next = l->next;
|
|
436
|
+
reinterpret_cast<HostObjectProxy *>(l)->hostObject.reset();
|
|
437
|
+
l = next;
|
|
438
|
+
}
|
|
439
|
+
for (auto *l = static_cast<NativePayloadLink *>(hostFunctionProxies_);
|
|
440
|
+
l != nullptr;) {
|
|
441
|
+
auto *next = l->next;
|
|
442
|
+
reinterpret_cast<HostFunctionProxy *>(l)->function = nullptr;
|
|
443
|
+
l = next;
|
|
444
|
+
}
|
|
445
|
+
for (auto *l = static_cast<NativePayloadLink *>(nativeStateProxies_);
|
|
446
|
+
l != nullptr;) {
|
|
447
|
+
auto *next = l->next;
|
|
448
|
+
reinterpret_cast<NativeStateProxy *>(l)->state.reset();
|
|
449
|
+
l = next;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// Undefined rather than throwing: a runtime that cannot build a helper is
|
|
454
|
+
/// still usable for everything that does not need it.
|
|
455
|
+
JSValue QuickJSRuntime::evalInternal(const char *source) noexcept {
|
|
456
|
+
JSValue fn = JS_Eval(
|
|
457
|
+
context_, source, std::strlen(source), "<jsi-internal>",
|
|
458
|
+
JS_EVAL_TYPE_GLOBAL);
|
|
459
|
+
if (JS_IsException(fn)) {
|
|
460
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
461
|
+
return JS_UNDEFINED;
|
|
462
|
+
}
|
|
463
|
+
return fn;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
QuickJSRuntime::~QuickJSRuntime() {
|
|
467
|
+
drainPendingReleases();
|
|
468
|
+
releaseNativePayloads();
|
|
469
|
+
JS_FreeValue(context_, enumeratePropertyNames_);
|
|
470
|
+
JS_FreeValue(context_, symbolToString_);
|
|
471
|
+
JS_FreeValue(context_, weakRefConstructor_);
|
|
472
|
+
JS_FreeValue(context_, weakRefDeref_);
|
|
473
|
+
JS_FreeValue(context_, bigIntToString_);
|
|
474
|
+
JS_FreeAtom(context_, nativeStateKey_);
|
|
475
|
+
|
|
476
|
+
if (context_ != nullptr) {
|
|
477
|
+
JS_FreeContext(context_);
|
|
478
|
+
context_ = nullptr;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (runtime_ != nullptr) {
|
|
482
|
+
// JS_FreeRuntime runs one GC pass then asserts the heap is empty, but a
|
|
483
|
+
// finalizer releasing a jsi value dirties it again, so settle first.
|
|
484
|
+
for (int pass = 0; pass < kMaxTeardownGCPasses; ++pass) {
|
|
485
|
+
JS_RunGC(runtime_);
|
|
486
|
+
drainPendingReleases();
|
|
487
|
+
}
|
|
488
|
+
JS_FreeRuntime(runtime_);
|
|
489
|
+
runtime_ = nullptr;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// After the engine: teardown still recycles into the free list.
|
|
493
|
+
freePointerValuePool();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
void QuickJSRuntime::lock() noexcept {
|
|
497
|
+
engineMutex_.lock();
|
|
498
|
+
adoptCurrentThread();
|
|
499
|
+
drainPendingReleases();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
void QuickJSRuntime::unlock() noexcept {
|
|
503
|
+
engineMutex_.unlock();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* quickjs captures rt->stack_top inside JS_NewRuntime, on whichever thread
|
|
508
|
+
* called it. On React Native's JS thread that mark was 1,115,672 bytes above
|
|
509
|
+
* the real stack pointer, so every call threw "Maximum call stack size
|
|
510
|
+
* exceeded" and the app aborted while loading its bundle.
|
|
511
|
+
*
|
|
512
|
+
* The budget is recomputed rather than only re-based, because a 1 MiB default
|
|
513
|
+
* on a smaller thread puts the limit past the end of the mapping and trades a
|
|
514
|
+
* catchable RangeError for a stack smash. The quarter held back covers the
|
|
515
|
+
* native frames reached after the overflow check fires.
|
|
516
|
+
*/
|
|
517
|
+
void QuickJSRuntime::adoptCurrentThread() noexcept {
|
|
518
|
+
if (isOnJSThread() || runtime_ == nullptr) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
jsThread_.store(std::this_thread::get_id(), std::memory_order_relaxed);
|
|
522
|
+
JS_UpdateStackTop(runtime_);
|
|
523
|
+
|
|
524
|
+
if (config_.stackSize > 0) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const size_t remaining = remainingNativeStackBytes();
|
|
528
|
+
if (remaining > 0) {
|
|
529
|
+
JS_SetMaxStackSize(runtime_, remaining / 4 * 3);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/// 0 when the platform cannot say, so the caller leaves the engine default
|
|
534
|
+
/// alone rather than guessing.
|
|
535
|
+
size_t QuickJSRuntime::remainingNativeStackBytes() noexcept {
|
|
536
|
+
char here = 0;
|
|
537
|
+
const auto sp = reinterpret_cast<uintptr_t>(&here);
|
|
538
|
+
#if defined(__ANDROID__) || defined(__linux__)
|
|
539
|
+
pthread_attr_t attr;
|
|
540
|
+
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
void *base = nullptr;
|
|
544
|
+
size_t size = 0;
|
|
545
|
+
const int rc = pthread_attr_getstack(&attr, &base, &size);
|
|
546
|
+
pthread_attr_destroy(&attr);
|
|
547
|
+
if (rc != 0 || base == nullptr || size == 0) {
|
|
548
|
+
return 0;
|
|
549
|
+
}
|
|
550
|
+
// pthread_attr_getstack reports the lowest address; the stack grows down.
|
|
551
|
+
const auto lowest = reinterpret_cast<uintptr_t>(base);
|
|
552
|
+
return sp > lowest ? static_cast<size_t>(sp - lowest) : 0;
|
|
553
|
+
#elif defined(__APPLE__)
|
|
554
|
+
void *high = pthread_get_stackaddr_np(pthread_self());
|
|
555
|
+
const size_t size = pthread_get_stacksize_np(pthread_self());
|
|
556
|
+
if (high == nullptr || size == 0) {
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
const auto lowest = reinterpret_cast<uintptr_t>(high) - size;
|
|
560
|
+
return sp > lowest ? static_cast<size_t>(sp - lowest) : 0;
|
|
561
|
+
#else
|
|
562
|
+
(void)sp;
|
|
563
|
+
return 0;
|
|
564
|
+
#endif
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
void QuickJSRuntime::releaseValue(JSValue value) noexcept {
|
|
568
|
+
if (isOnJSThread()) {
|
|
569
|
+
JS_FreeValueRT(runtime_, value);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
std::lock_guard<std::mutex> lock(pendingMutex_);
|
|
573
|
+
pendingValues_.push_back(value);
|
|
574
|
+
// Published after the push, so a drainer that sees the flag sees the item.
|
|
575
|
+
hasPendingReleases_.store(true, std::memory_order_release);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
void QuickJSRuntime::releaseAtom(JSAtom atom) noexcept {
|
|
579
|
+
if (isOnJSThread()) {
|
|
580
|
+
JS_FreeAtomRT(runtime_, atom);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
std::lock_guard<std::mutex> lock(pendingMutex_);
|
|
584
|
+
pendingAtoms_.push_back(atom);
|
|
585
|
+
hasPendingReleases_.store(true, std::memory_order_release);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
void QuickJSRuntime::drainPendingReleases() noexcept {
|
|
589
|
+
// Racing a producer is harmless: the flag is set after the push, so a release
|
|
590
|
+
// landing just after this load is drained by the next call.
|
|
591
|
+
if (!hasPendingReleases_.load(std::memory_order_acquire)) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
std::vector<JSValue> values;
|
|
596
|
+
std::vector<JSAtom> atoms;
|
|
597
|
+
std::vector<QuickJSPointerValue *> pointerValues;
|
|
598
|
+
std::vector<QuickJSAtomPointerValue *> atomPointerValues;
|
|
599
|
+
{
|
|
600
|
+
std::lock_guard<std::mutex> lock(pendingMutex_);
|
|
601
|
+
hasPendingReleases_.store(false, std::memory_order_relaxed);
|
|
602
|
+
values.swap(pendingValues_);
|
|
603
|
+
atoms.swap(pendingAtoms_);
|
|
604
|
+
pointerValues.swap(pendingPointerValues_);
|
|
605
|
+
atomPointerValues.swap(pendingAtomPointerValues_);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (JSValue value : values) {
|
|
609
|
+
JS_FreeValueRT(runtime_, value);
|
|
610
|
+
}
|
|
611
|
+
for (JSAtom atom : atoms) {
|
|
612
|
+
JS_FreeAtomRT(runtime_, atom);
|
|
613
|
+
}
|
|
614
|
+
for (QuickJSPointerValue *pv : pointerValues) {
|
|
615
|
+
pv->nextFree_ = freeValues_;
|
|
616
|
+
freeValues_ = pv;
|
|
617
|
+
}
|
|
618
|
+
for (QuickJSAtomPointerValue *pv : atomPointerValues) {
|
|
619
|
+
pv->nextFree_ = freeAtoms_;
|
|
620
|
+
freeAtoms_ = pv;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
void QuickJSRuntime::refillValueSlab() {
|
|
625
|
+
auto *slab = static_cast<QuickJSPointerValue *>(
|
|
626
|
+
::operator new(sizeof(QuickJSPointerValue) * kPointerValueSlabSize));
|
|
627
|
+
valueSlabs_.push_back(slab);
|
|
628
|
+
for (size_t i = 0; i < kPointerValueSlabSize; ++i) {
|
|
629
|
+
auto *slot = slab + i;
|
|
630
|
+
slot->nextFree_ = freeValues_;
|
|
631
|
+
freeValues_ = slot;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
void QuickJSRuntime::refillAtomSlab() {
|
|
636
|
+
auto *slab = static_cast<QuickJSAtomPointerValue *>(
|
|
637
|
+
::operator new(sizeof(QuickJSAtomPointerValue) * kPointerValueSlabSize));
|
|
638
|
+
atomSlabs_.push_back(slab);
|
|
639
|
+
for (size_t i = 0; i < kPointerValueSlabSize; ++i) {
|
|
640
|
+
auto *slot = slab + i;
|
|
641
|
+
slot->nextFree_ = freeAtoms_;
|
|
642
|
+
freeAtoms_ = slot;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
void QuickJSRuntime::queuePointerValue(QuickJSPointerValue *pv) noexcept {
|
|
647
|
+
std::lock_guard<std::mutex> lock(pendingMutex_);
|
|
648
|
+
pendingPointerValues_.push_back(pv);
|
|
649
|
+
hasPendingReleases_.store(true, std::memory_order_release);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
void QuickJSRuntime::queueAtomPointerValue(
|
|
653
|
+
QuickJSAtomPointerValue *pv) noexcept {
|
|
654
|
+
std::lock_guard<std::mutex> lock(pendingMutex_);
|
|
655
|
+
pendingAtomPointerValues_.push_back(pv);
|
|
656
|
+
hasPendingReleases_.store(true, std::memory_order_release);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
void QuickJSRuntime::freePointerValuePool() noexcept {
|
|
660
|
+
for (void *slab : valueSlabs_) {
|
|
661
|
+
::operator delete(slab);
|
|
662
|
+
}
|
|
663
|
+
for (void *slab : atomSlabs_) {
|
|
664
|
+
::operator delete(slab);
|
|
665
|
+
}
|
|
666
|
+
valueSlabs_.clear();
|
|
667
|
+
atomSlabs_.clear();
|
|
668
|
+
freeValues_ = nullptr;
|
|
669
|
+
freeAtoms_ = nullptr;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
JSValue QuickJSRuntime::toJSValue(const jsi::Pointer &pointer) {
|
|
673
|
+
return static_cast<const QuickJSPointerValue *>(getPointerValue(pointer))
|
|
674
|
+
->value();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
JSAtom QuickJSRuntime::toJSAtom(const jsi::PropNameID &name) {
|
|
678
|
+
return static_cast<const QuickJSAtomPointerValue *>(getPointerValue(name))
|
|
679
|
+
->atom();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
JSValue QuickJSRuntime::toJSValue(const jsi::Value &value) const {
|
|
683
|
+
if (value.isUndefined()) {
|
|
684
|
+
return JS_UNDEFINED;
|
|
685
|
+
}
|
|
686
|
+
if (value.isNull()) {
|
|
687
|
+
return JS_NULL;
|
|
688
|
+
}
|
|
689
|
+
if (value.isBool()) {
|
|
690
|
+
return JS_NewBool(context_, value.getBool());
|
|
691
|
+
}
|
|
692
|
+
if (value.isNumber()) {
|
|
693
|
+
return JS_NewFloat64(context_, value.getNumber());
|
|
694
|
+
}
|
|
695
|
+
return static_cast<const QuickJSPointerValue *>(getPointerValue(value))
|
|
696
|
+
->value();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
jsi::Value QuickJSRuntime::createValue(JSValue value) {
|
|
700
|
+
if (JS_IsException(value)) {
|
|
701
|
+
throwPendingError();
|
|
702
|
+
}
|
|
703
|
+
if (JS_IsUndefined(value)) {
|
|
704
|
+
return jsi::Value::undefined();
|
|
705
|
+
}
|
|
706
|
+
if (JS_IsNull(value)) {
|
|
707
|
+
return jsi::Value::null();
|
|
708
|
+
}
|
|
709
|
+
if (JS_IsBool(value)) {
|
|
710
|
+
return jsi::Value(static_cast<bool>(JS_ToBool(context_, value)));
|
|
711
|
+
}
|
|
712
|
+
if (JS_IsNumber(value)) {
|
|
713
|
+
double number = 0;
|
|
714
|
+
JS_ToFloat64(context_, &number, value);
|
|
715
|
+
return jsi::Value(number);
|
|
716
|
+
}
|
|
717
|
+
if (JS_IsString(value)) {
|
|
718
|
+
return jsi::Value(createStringFrom(value));
|
|
719
|
+
}
|
|
720
|
+
if (JS_IsSymbol(value)) {
|
|
721
|
+
return jsi::Value(createSymbolFrom(value));
|
|
722
|
+
}
|
|
723
|
+
if (JS_IsBigInt(value)) {
|
|
724
|
+
return jsi::Value(make<jsi::BigInt>(allocPointerValue(value)));
|
|
725
|
+
}
|
|
726
|
+
return jsi::Value(createObjectFrom(value));
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
jsi::Value QuickJSRuntime::borrowValue(JSValue value) {
|
|
730
|
+
// Primitives carry no reference, so they are already borrow-shaped.
|
|
731
|
+
if (JS_IsUndefined(value)) {
|
|
732
|
+
return jsi::Value::undefined();
|
|
733
|
+
}
|
|
734
|
+
if (JS_IsNull(value)) {
|
|
735
|
+
return jsi::Value::null();
|
|
736
|
+
}
|
|
737
|
+
if (JS_IsBool(value)) {
|
|
738
|
+
return jsi::Value(static_cast<bool>(JS_ToBool(context_, value)));
|
|
739
|
+
}
|
|
740
|
+
if (JS_IsNumber(value)) {
|
|
741
|
+
double number = 0;
|
|
742
|
+
JS_ToFloat64(context_, &number, value);
|
|
743
|
+
return jsi::Value(number);
|
|
744
|
+
}
|
|
745
|
+
if (JS_IsString(value)) {
|
|
746
|
+
return jsi::Value(make<jsi::String>(allocPointerValue(value, false)));
|
|
747
|
+
}
|
|
748
|
+
if (JS_IsSymbol(value)) {
|
|
749
|
+
return jsi::Value(make<jsi::Symbol>(allocPointerValue(value, false)));
|
|
750
|
+
}
|
|
751
|
+
if (JS_IsBigInt(value)) {
|
|
752
|
+
return jsi::Value(make<jsi::BigInt>(allocPointerValue(value, false)));
|
|
753
|
+
}
|
|
754
|
+
return jsi::Value(make<jsi::Object>(allocPointerValue(value, false)));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
JSValue QuickJSRuntime::takeJSValue(jsi::Value &&value) {
|
|
758
|
+
if (value.isUndefined()) {
|
|
759
|
+
return JS_UNDEFINED;
|
|
760
|
+
}
|
|
761
|
+
if (value.isNull()) {
|
|
762
|
+
return JS_NULL;
|
|
763
|
+
}
|
|
764
|
+
if (value.isBool()) {
|
|
765
|
+
return JS_NewBool(context_, value.getBool());
|
|
766
|
+
}
|
|
767
|
+
if (value.isNumber()) {
|
|
768
|
+
return JS_NewFloat64(context_, value.getNumber());
|
|
769
|
+
}
|
|
770
|
+
auto *pv = const_cast<QuickJSPointerValue *>(
|
|
771
|
+
static_cast<const QuickJSPointerValue *>(getPointerValue(value)));
|
|
772
|
+
if (!pv->owned_) {
|
|
773
|
+
return JS_DupValue(context_, pv->value());
|
|
774
|
+
}
|
|
775
|
+
// Hand the reference over and disarm the destructor, which is about to run
|
|
776
|
+
// as `value` goes out of scope.
|
|
777
|
+
pv->owned_ = false;
|
|
778
|
+
return pv->value();
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
jsi::Object QuickJSRuntime::createObjectFrom(JSValue value) {
|
|
782
|
+
return make<jsi::Object>(allocPointerValue(value));
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
jsi::Symbol QuickJSRuntime::createSymbolFrom(JSValue value) {
|
|
786
|
+
return make<jsi::Symbol>(allocPointerValue(value));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
jsi::String QuickJSRuntime::createStringFrom(JSValue value) {
|
|
790
|
+
return make<jsi::String>(allocPointerValue(value));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFrom(JSAtom atom) {
|
|
794
|
+
return make<jsi::PropNameID>(allocAtomPointerValue(atom));
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Runs where describing an exception normally has already failed: JS_ToCString
|
|
799
|
+
* calls toString, and reading .stack is what jsi::JSError does and what was
|
|
800
|
+
* throwing. So own properties only, and anything they raise is swallowed --
|
|
801
|
+
* a pending exception left behind would surface later against unrelated code.
|
|
802
|
+
*/
|
|
803
|
+
std::string QuickJSRuntime::describeExceptionWithoutRunningJS(
|
|
804
|
+
JSValue exception) {
|
|
805
|
+
if (JS_IsString(exception)) {
|
|
806
|
+
const char *s = JS_ToCString(context_, exception);
|
|
807
|
+
std::string out = s != nullptr
|
|
808
|
+
? std::string("<string exception: ") + s + ">"
|
|
809
|
+
: std::string("<indescribable exception>");
|
|
810
|
+
if (s != nullptr) {
|
|
811
|
+
JS_FreeCString(context_, s);
|
|
812
|
+
}
|
|
813
|
+
return out;
|
|
814
|
+
}
|
|
815
|
+
if (!JS_IsObject(exception)) {
|
|
816
|
+
return "<non-object exception>";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
auto readString = [&](const char *key) -> std::string {
|
|
820
|
+
JSValue v = JS_GetPropertyStr(context_, exception, key);
|
|
821
|
+
if (JS_IsException(v)) {
|
|
822
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
823
|
+
return {};
|
|
824
|
+
}
|
|
825
|
+
std::string out;
|
|
826
|
+
if (JS_IsString(v)) {
|
|
827
|
+
const char *s = JS_ToCString(context_, v);
|
|
828
|
+
if (s != nullptr) {
|
|
829
|
+
out = s;
|
|
830
|
+
JS_FreeCString(context_, s);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
JS_FreeValue(context_, v);
|
|
834
|
+
return out;
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
std::string name = readString("name");
|
|
838
|
+
std::string message = readString("message");
|
|
839
|
+
if (name.empty() && message.empty()) {
|
|
840
|
+
return "<indescribable exception>";
|
|
841
|
+
}
|
|
842
|
+
if (message.empty()) {
|
|
843
|
+
return name;
|
|
844
|
+
}
|
|
845
|
+
if (name.empty()) {
|
|
846
|
+
return message;
|
|
847
|
+
}
|
|
848
|
+
return name + ": " + message;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
void QuickJSRuntime::throwPendingError() {
|
|
852
|
+
JSValue exception = JS_GetException(context_);
|
|
853
|
+
if (JS_IsNull(exception) || JS_IsUndefined(exception)) {
|
|
854
|
+
JS_FreeValue(context_, exception);
|
|
855
|
+
throw jsi::JSINativeException(
|
|
856
|
+
"QuickJSRuntime: operation failed with no pending exception");
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (errorDepth_ >= kMaxErrorDepth) {
|
|
860
|
+
JS_FreeValue(context_, exception);
|
|
861
|
+
std::string what =
|
|
862
|
+
"QuickJSRuntime: exception thrown while handling an exception";
|
|
863
|
+
if (!firstErrorDescription_.empty()) {
|
|
864
|
+
what += "; original exception: " + firstErrorDescription_;
|
|
865
|
+
}
|
|
866
|
+
// A recursive bail-out is usually a stack-limit problem, and the limit is
|
|
867
|
+
// the one thing invisible from JavaScript.
|
|
868
|
+
what += "; jsThreadOwnsEngine=";
|
|
869
|
+
what += isOnJSThread() ? "true" : "false";
|
|
870
|
+
what +=
|
|
871
|
+
", remainingNativeStack=" + std::to_string(remainingNativeStackBytes());
|
|
872
|
+
what += ", configuredStackSize=" + std::to_string(config_.stackSize);
|
|
873
|
+
throw jsi::JSINativeException(what);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (errorDepth_ == 0) {
|
|
877
|
+
firstErrorDescription_ = describeExceptionWithoutRunningJS(exception);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
struct DepthGuard {
|
|
881
|
+
int &depth;
|
|
882
|
+
explicit DepthGuard(int &d) : depth(d) {
|
|
883
|
+
++depth;
|
|
884
|
+
}
|
|
885
|
+
~DepthGuard() {
|
|
886
|
+
--depth;
|
|
887
|
+
}
|
|
888
|
+
} guard(errorDepth_);
|
|
889
|
+
|
|
890
|
+
throw jsi::JSError(*this, createValue(exception));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
JSValue QuickJSRuntime::checkException(JSValue value) {
|
|
894
|
+
if (JS_IsException(value)) {
|
|
895
|
+
throwPendingError();
|
|
896
|
+
}
|
|
897
|
+
return value;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
void QuickJSRuntime::checkException(int result) {
|
|
901
|
+
if (result < 0) {
|
|
902
|
+
throwPendingError();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
JSValue QuickJSRuntime::throwAsJSException(
|
|
907
|
+
const std::exception *e, const char *origin) noexcept {
|
|
908
|
+
// A JSError already carries a thrown JS value, so rethrow it unchanged.
|
|
909
|
+
if (const auto *jsError = dynamic_cast<const jsi::JSError *>(e)) {
|
|
910
|
+
return JS_Throw(
|
|
911
|
+
context_, JS_DupValue(context_, toJSValue(jsError->value())));
|
|
912
|
+
}
|
|
913
|
+
return JS_ThrowInternalError(
|
|
914
|
+
context_, "Exception in %s: %s", origin,
|
|
915
|
+
e != nullptr ? e->what() : "unknown C++ exception");
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
std::string QuickJSRuntime::description() {
|
|
919
|
+
return "QuickJSRuntime";
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/// Tied to enableDebugger rather than an unconditional true: a runtime built
|
|
923
|
+
/// without instrumentation cannot bind a breakpoint, because the statement
|
|
924
|
+
/// traps are emitted at parse time.
|
|
925
|
+
bool QuickJSRuntime::isInspectable() {
|
|
926
|
+
return config_.enableDebugger;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
namespace {
|
|
930
|
+
|
|
931
|
+
void throwIfHermesBytecode(
|
|
932
|
+
const uint8_t *data, size_t size, const std::string &sourceURL) {
|
|
933
|
+
if (!isHermesBytecode(data, size)) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
throw jsi::JSINativeException(
|
|
937
|
+
"QuickJSRuntime: " +
|
|
938
|
+
(sourceURL.empty() ? std::string("this bundle") : sourceURL) +
|
|
939
|
+
" is Hermes bytecode, which this engine cannot execute. In a React "
|
|
940
|
+
"Native app that usually means hermesEnabled is still true in "
|
|
941
|
+
"android/gradle.properties, or :hermes_enabled in the Podfile. Turn it "
|
|
942
|
+
"off so Metro ships plain JavaScript, or precompile with qjsc.");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* The name a script should be known by: its `//# sourceURL=` comment if it has
|
|
947
|
+
* one, otherwise the URL the embedder passed.
|
|
948
|
+
*
|
|
949
|
+
* Every other engine renames a script by that comment -- it is what
|
|
950
|
+
* `error.stack` shows and what `Debugger.setBreakpointByUrl` matches against.
|
|
951
|
+
* quickjs ignores it and records whatever filename JS_Eval was handed, so a
|
|
952
|
+
* script carrying the comment would be known by two names and a breakpoint set
|
|
953
|
+
* on it would never fire.
|
|
954
|
+
*
|
|
955
|
+
* Only the last 8 KiB are scanned, which makes this O(1) in bundle size rather
|
|
956
|
+
* than a full pass over a multi-megabyte bundle on the startup path. Tools
|
|
957
|
+
* append the comment at the end; one placed earlier is not honoured.
|
|
958
|
+
*/
|
|
959
|
+
constexpr size_t kSourceURLScanBytes = 8192;
|
|
960
|
+
|
|
961
|
+
std::string effectiveSourceURL(
|
|
962
|
+
const uint8_t *data, size_t size, const std::string &sourceURL) {
|
|
963
|
+
const size_t start =
|
|
964
|
+
size > kSourceURLScanBytes ? size - kSourceURLScanBytes : 0;
|
|
965
|
+
const std::string_view tail(
|
|
966
|
+
reinterpret_cast<const char *>(data) + start, size - start);
|
|
967
|
+
|
|
968
|
+
size_t best = std::string_view::npos;
|
|
969
|
+
for (std::string_view form : {"//# sourceURL=", "//@ sourceURL="}) {
|
|
970
|
+
const size_t pos = tail.rfind(form);
|
|
971
|
+
if (pos != std::string_view::npos &&
|
|
972
|
+
(best == std::string_view::npos || pos > best)) {
|
|
973
|
+
best = pos + form.size();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (best == std::string_view::npos) {
|
|
977
|
+
return sourceURL;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const size_t end = tail.find_first_of("\r\n", best);
|
|
981
|
+
const std::string_view value = tail.substr(
|
|
982
|
+
best,
|
|
983
|
+
end == std::string_view::npos ? std::string_view::npos : end - best);
|
|
984
|
+
const size_t first = value.find_first_not_of(" \t");
|
|
985
|
+
if (first == std::string_view::npos) {
|
|
986
|
+
return sourceURL;
|
|
987
|
+
}
|
|
988
|
+
const size_t last = value.find_last_not_of(" \t");
|
|
989
|
+
return std::string(value.substr(first, last - first + 1));
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/// Raises the collection threshold for the first top-level script and hands
|
|
993
|
+
/// pacing back at the rung the engine would otherwise be on. Bounded rather
|
|
994
|
+
/// than a disable, so a larger bundle degrades to normal pacing by itself. See
|
|
995
|
+
/// QuickJSRuntimeConfig::startupGCBracketBytes.
|
|
996
|
+
class StartupGCBracket final {
|
|
997
|
+
public:
|
|
998
|
+
StartupGCBracket(JSRuntime *rt, size_t bytes, bool &used) {
|
|
999
|
+
if (rt == nullptr || bytes == 0 || used) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
used = true;
|
|
1003
|
+
rt_ = rt;
|
|
1004
|
+
JS_SetGCThreshold(rt_, JS_GetMallocSize(rt_) + bytes);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
~StartupGCBracket() {
|
|
1008
|
+
if (rt_ == nullptr) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const size_t live = JS_GetMallocSize(rt_);
|
|
1012
|
+
JS_SetGCThreshold(rt_, live + (live >> 1));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
StartupGCBracket(const StartupGCBracket &) = delete;
|
|
1016
|
+
StartupGCBracket &operator=(const StartupGCBracket &) = delete;
|
|
1017
|
+
|
|
1018
|
+
private:
|
|
1019
|
+
JSRuntime *rt_{nullptr};
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
/// Compiled form of a script: a JS_WriteObject payload, whether it arrived as
|
|
1023
|
+
/// source or as a container.
|
|
1024
|
+
class QuickJSPreparedJavaScript final : public jsi::PreparedJavaScript {
|
|
1025
|
+
public:
|
|
1026
|
+
explicit QuickJSPreparedJavaScript(std::vector<uint8_t> bytecode)
|
|
1027
|
+
: bytecode_(std::move(bytecode)) {}
|
|
1028
|
+
|
|
1029
|
+
const std::vector<uint8_t> &bytecode() const noexcept {
|
|
1030
|
+
return bytecode_;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
private:
|
|
1034
|
+
std::vector<uint8_t> bytecode_;
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
} // namespace
|
|
1038
|
+
|
|
1039
|
+
jsi::Value QuickJSRuntime::evaluateSource(
|
|
1040
|
+
const uint8_t *source, size_t size, const std::string &sourceURL) {
|
|
1041
|
+
// JS_Eval wants a NUL-terminated buffer, which jsi::Buffer does not promise.
|
|
1042
|
+
const std::string owned(reinterpret_cast<const char *>(source), size);
|
|
1043
|
+
return createValue(JS_Eval(
|
|
1044
|
+
context_, owned.c_str(), owned.size(), sourceURL.c_str(),
|
|
1045
|
+
JS_EVAL_TYPE_GLOBAL));
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
jsi::Value QuickJSRuntime::evaluateBytecode(
|
|
1049
|
+
const uint8_t *payload, size_t size) {
|
|
1050
|
+
JSValue function =
|
|
1051
|
+
JS_ReadObject(context_, payload, size, JS_READ_OBJ_BYTECODE);
|
|
1052
|
+
if (JS_IsException(function)) {
|
|
1053
|
+
// Almost always an engine mismatch. quickjs bytecode loads only in the
|
|
1054
|
+
// build that wrote it, and "bytecode function expected" does not say so.
|
|
1055
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
1056
|
+
throw jsi::JSINativeException(
|
|
1057
|
+
"QuickJSRuntime: could not load bytecode. It was most likely compiled "
|
|
1058
|
+
"by a different build of the engine -- recompile it with the qjsc "
|
|
1059
|
+
"from this checkout.");
|
|
1060
|
+
}
|
|
1061
|
+
return createValue(JS_EvalFunction(context_, function));
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
jsi::Value QuickJSRuntime::evaluateJavaScript(
|
|
1065
|
+
const std::shared_ptr<const jsi::Buffer> &buffer,
|
|
1066
|
+
const std::string &sourceURL) {
|
|
1067
|
+
adoptCurrentThread();
|
|
1068
|
+
drainPendingReleases();
|
|
1069
|
+
|
|
1070
|
+
const uint8_t *data = buffer->data();
|
|
1071
|
+
const size_t size = buffer->size();
|
|
1072
|
+
|
|
1073
|
+
StartupGCBracket bracket(
|
|
1074
|
+
runtime_, config_.startupGCBracketBytes, startupGCBracketUsed_);
|
|
1075
|
+
|
|
1076
|
+
jsi::Value result;
|
|
1077
|
+
if (isBytecodeContainer(data, size)) {
|
|
1078
|
+
result = evaluateBytecode(
|
|
1079
|
+
data + kBytecodeHeaderSize, size - kBytecodeHeaderSize);
|
|
1080
|
+
} else {
|
|
1081
|
+
throwIfHermesBytecode(data, size, sourceURL);
|
|
1082
|
+
const std::string url = effectiveSourceURL(data, size, sourceURL);
|
|
1083
|
+
// Before evaluating, not after: a script is parsed before it runs, and a
|
|
1084
|
+
// stack captured while it is running has to be able to name it.
|
|
1085
|
+
if (onScriptEvaluated_) {
|
|
1086
|
+
onScriptEvaluated_(
|
|
1087
|
+
url, std::string(reinterpret_cast<const char *>(data), size));
|
|
1088
|
+
}
|
|
1089
|
+
result = evaluateSource(data, size, url);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
rebaselineHeap();
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
std::shared_ptr<const jsi::PreparedJavaScript>
|
|
1097
|
+
QuickJSRuntime::prepareJavaScript(
|
|
1098
|
+
const std::shared_ptr<const jsi::Buffer> &buffer, std::string sourceURL) {
|
|
1099
|
+
// Compiling is an engine entry like any other, and it recurses: the parser
|
|
1100
|
+
// descends once per nesting level. Without this the recursion bound is still
|
|
1101
|
+
// keyed to whichever thread built the runtime, and a script prepared on the
|
|
1102
|
+
// JS thread before anything has evaluated runs off the end of its stack.
|
|
1103
|
+
adoptCurrentThread();
|
|
1104
|
+
drainPendingReleases();
|
|
1105
|
+
|
|
1106
|
+
const uint8_t *data = buffer->data();
|
|
1107
|
+
const size_t size = buffer->size();
|
|
1108
|
+
|
|
1109
|
+
if (isBytecodeContainer(data, size)) {
|
|
1110
|
+
return std::make_shared<const QuickJSPreparedJavaScript>(
|
|
1111
|
+
std::vector<uint8_t>(data + kBytecodeHeaderSize, data + size));
|
|
1112
|
+
}
|
|
1113
|
+
throwIfHermesBytecode(data, size, sourceURL);
|
|
1114
|
+
|
|
1115
|
+
// The name compiled in here is the only one that survives into the function
|
|
1116
|
+
// bytecode, so it has to be the name the debugger will look the script up by.
|
|
1117
|
+
sourceURL = effectiveSourceURL(data, size, sourceURL);
|
|
1118
|
+
|
|
1119
|
+
const std::string owned(reinterpret_cast<const char *>(data), size);
|
|
1120
|
+
JSValue compiled = checkException(JS_Eval(
|
|
1121
|
+
context_, owned.c_str(), owned.size(), sourceURL.c_str(),
|
|
1122
|
+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY));
|
|
1123
|
+
|
|
1124
|
+
size_t length = 0;
|
|
1125
|
+
uint8_t *bytes =
|
|
1126
|
+
JS_WriteObject(context_, &length, compiled, JS_WRITE_OBJ_BYTECODE);
|
|
1127
|
+
JS_FreeValue(context_, compiled);
|
|
1128
|
+
if (bytes == nullptr) {
|
|
1129
|
+
throwPendingError();
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
std::vector<uint8_t> bytecode(bytes, bytes + length);
|
|
1133
|
+
js_free(context_, bytes);
|
|
1134
|
+
return std::make_shared<const QuickJSPreparedJavaScript>(std::move(bytecode));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
jsi::Value QuickJSRuntime::evaluatePreparedJavaScript(
|
|
1138
|
+
const std::shared_ptr<const jsi::PreparedJavaScript> &js) {
|
|
1139
|
+
adoptCurrentThread();
|
|
1140
|
+
drainPendingReleases();
|
|
1141
|
+
|
|
1142
|
+
auto prepared =
|
|
1143
|
+
std::dynamic_pointer_cast<const QuickJSPreparedJavaScript>(js);
|
|
1144
|
+
if (!prepared) {
|
|
1145
|
+
throw jsi::JSINativeException(
|
|
1146
|
+
"QuickJSRuntime: PreparedJavaScript was created by a different runtime "
|
|
1147
|
+
"implementation");
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
StartupGCBracket bracket(
|
|
1151
|
+
runtime_, config_.startupGCBracketBytes, startupGCBracketUsed_);
|
|
1152
|
+
|
|
1153
|
+
jsi::Value result = evaluateBytecode(
|
|
1154
|
+
prepared->bytecode().data(), prepared->bytecode().size());
|
|
1155
|
+
rebaselineHeap();
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
void QuickJSRuntime::queueMicrotask(const jsi::Function &callback) {
|
|
1160
|
+
JSValue function = toJSValue(callback);
|
|
1161
|
+
checkException(JS_EnqueueJob(context_, microtaskJob, 1, &function));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
bool QuickJSRuntime::drainMicrotasks(int maxMicrotasksHint) {
|
|
1165
|
+
const auto taskStart = std::chrono::steady_clock::now();
|
|
1166
|
+
adoptCurrentThread();
|
|
1167
|
+
drainPendingReleases();
|
|
1168
|
+
|
|
1169
|
+
int executed = 0;
|
|
1170
|
+
while (maxMicrotasksHint < 0 || executed < maxMicrotasksHint) {
|
|
1171
|
+
JSContext *jobContext = nullptr;
|
|
1172
|
+
int result = JS_ExecutePendingJob(runtime_, &jobContext);
|
|
1173
|
+
if (result == 0) {
|
|
1174
|
+
// Queue drained, so the JS stack is empty: a safepoint. Every safepoint
|
|
1175
|
+
// ends a task, whether or not it collected.
|
|
1176
|
+
runPendingGCIfIdle(taskStart);
|
|
1177
|
+
lastTaskEnd_ = std::chrono::steady_clock::now();
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
1180
|
+
if (result < 0) {
|
|
1181
|
+
// JSI discards the exceptional job and keeps going; the queue is only
|
|
1182
|
+
// reported as not drained if work remains.
|
|
1183
|
+
JSContext *ctx = jobContext != nullptr ? jobContext : context_;
|
|
1184
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
1185
|
+
}
|
|
1186
|
+
++executed;
|
|
1187
|
+
}
|
|
1188
|
+
return !JS_IsJobPending(runtime_);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
void QuickJSRuntime::runPendingGC() noexcept {
|
|
1192
|
+
// Unconditional: the embedder is asserting the app is idle. Safe only with an
|
|
1193
|
+
// empty JS stack, never from inside a host function.
|
|
1194
|
+
if (runtime_ == nullptr) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
JS_RunPendingGC(runtime_);
|
|
1198
|
+
noteCollection();
|
|
1199
|
+
lastTaskEnd_ = std::chrono::steady_clock::now();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/// The live set just changed, so the growth baseline and the ceiling derived
|
|
1203
|
+
/// from it are stale.
|
|
1204
|
+
void QuickJSRuntime::rebaselineHeap() noexcept {
|
|
1205
|
+
heapBaseline_ = JS_GetMallocSize(runtime_);
|
|
1206
|
+
refreshDeferredGCLimit();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/// A collection ran, so the clock the max-deferral valve measures from restarts
|
|
1210
|
+
/// too. Only a collection may reset it: a script that merely rebaselines the
|
|
1211
|
+
/// heap would restart the wait without ending it, and an app that keeps
|
|
1212
|
+
/// evaluating would defer a pending collection forever.
|
|
1213
|
+
void QuickJSRuntime::noteCollection() noexcept {
|
|
1214
|
+
rebaselineHeap();
|
|
1215
|
+
pendingSince_ = {};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
size_t QuickJSRuntime::deferredGCSlack() const noexcept {
|
|
1219
|
+
size_t slack = JS_GetMallocSize(runtime_);
|
|
1220
|
+
if (slack < config_.deferredGCMinSlack) {
|
|
1221
|
+
slack = config_.deferredGCMinSlack;
|
|
1222
|
+
}
|
|
1223
|
+
if (slack > config_.deferredGCMaxSlack) {
|
|
1224
|
+
slack = config_.deferredGCMaxSlack;
|
|
1225
|
+
}
|
|
1226
|
+
return slack;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
size_t QuickJSRuntime::deferredGCCeiling() const noexcept {
|
|
1230
|
+
if (config_.deferredGCLimit != 0) {
|
|
1231
|
+
return config_.deferredGCLimit;
|
|
1232
|
+
}
|
|
1233
|
+
return JS_GetMallocSize(runtime_) + deferredGCSlack();
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
void QuickJSRuntime::refreshDeferredGCLimit() noexcept {
|
|
1237
|
+
if (runtime_ == nullptr || !config_.deferGC) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (config_.deferredGCLimit != 0) {
|
|
1241
|
+
return; // an explicit ceiling belongs to the embedder
|
|
1242
|
+
}
|
|
1243
|
+
JS_SetGCDeferred(runtime_, true, deferredGCCeiling());
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
void QuickJSRuntime::runPendingGCIfIdle(
|
|
1247
|
+
std::chrono::steady_clock::time_point taskStart) noexcept {
|
|
1248
|
+
if (runtime_ == nullptr) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Before the pending check on purpose: the case this fixes is where nothing
|
|
1253
|
+
// is pending because the trigger has drifted out of reach of the live heap.
|
|
1254
|
+
if (config_.gcThresholdRetuneRatio > 0.0) {
|
|
1255
|
+
const size_t liveNow = JS_GetMallocSize(runtime_);
|
|
1256
|
+
const size_t threshold = JS_GetGCThreshold(runtime_);
|
|
1257
|
+
const double drift = liveNow > 0 ? static_cast<double>(threshold) /
|
|
1258
|
+
static_cast<double>(liveNow)
|
|
1259
|
+
: 0.0;
|
|
1260
|
+
if (liveNow > 0 && drift > config_.gcThresholdRetuneRatio) {
|
|
1261
|
+
JS_SetGCThreshold(runtime_, liveNow + (liveNow >> 1));
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (!JS_HasPendingGC(runtime_)) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Measured against the end of the previous task, not the last collection:
|
|
1270
|
+
// timing from the collection makes any long stretch of work look idle.
|
|
1271
|
+
const auto gap = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1272
|
+
taskStart - lastTaskEnd_)
|
|
1273
|
+
.count();
|
|
1274
|
+
const bool idle = lastTaskEnd_.time_since_epoch().count() == 0 ||
|
|
1275
|
+
gap >= static_cast<long long>(config_.gcIdleGapMs);
|
|
1276
|
+
|
|
1277
|
+
if (pendingSince_.time_since_epoch().count() == 0) {
|
|
1278
|
+
pendingSince_ = taskStart;
|
|
1279
|
+
}
|
|
1280
|
+
bool stale = false;
|
|
1281
|
+
if (!idle && config_.gcMaxDeferralMs > 0) {
|
|
1282
|
+
const auto waited = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1283
|
+
taskStart - pendingSince_)
|
|
1284
|
+
.count();
|
|
1285
|
+
stale = waited >= static_cast<long long>(config_.gcMaxDeferralMs);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Pressure is growth since the last collection, not size. Comparing live size
|
|
1289
|
+
// against half the ceiling reduces to `live > slack`, which is true on every
|
|
1290
|
+
// safepoint for any real app, turning the valve into "collect constantly".
|
|
1291
|
+
const size_t live = JS_GetMallocSize(runtime_);
|
|
1292
|
+
const size_t grown = live > heapBaseline_ ? live - heapBaseline_ : 0;
|
|
1293
|
+
size_t pressureLimit = config_.gcPressureBytes;
|
|
1294
|
+
if (pressureLimit == 0) {
|
|
1295
|
+
// Also capped by the live heap, or the valve is unreachable on an app whose
|
|
1296
|
+
// whole heap is smaller than the slack -- measured as a collection pending
|
|
1297
|
+
// forever on an app with a ticker, which never reaches an idle gap either.
|
|
1298
|
+
const size_t bySlack = deferredGCSlack() / 2;
|
|
1299
|
+
const size_t byHeap = live / 2;
|
|
1300
|
+
pressureLimit = bySlack < byHeap ? bySlack : byHeap;
|
|
1301
|
+
}
|
|
1302
|
+
if (!idle && !stale && grown < pressureLimit) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
JS_RunPendingGC(runtime_);
|
|
1307
|
+
noteCollection();
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
jsi::Object QuickJSRuntime::global() {
|
|
1311
|
+
return createObjectFrom(JS_GetGlobalObject(context_));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
jsi::Runtime::PointerValue *QuickJSRuntime::cloneSymbol(
|
|
1315
|
+
const Runtime::PointerValue *pv) {
|
|
1316
|
+
const auto *value = static_cast<const QuickJSPointerValue *>(pv);
|
|
1317
|
+
return allocPointerValue(JS_DupValue(context_, value->value()));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
jsi::Runtime::PointerValue *QuickJSRuntime::cloneBigInt(
|
|
1321
|
+
const Runtime::PointerValue *pv) {
|
|
1322
|
+
const auto *value = static_cast<const QuickJSPointerValue *>(pv);
|
|
1323
|
+
return allocPointerValue(JS_DupValue(context_, value->value()));
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
jsi::Runtime::PointerValue *QuickJSRuntime::cloneString(
|
|
1327
|
+
const Runtime::PointerValue *pv) {
|
|
1328
|
+
const auto *value = static_cast<const QuickJSPointerValue *>(pv);
|
|
1329
|
+
return allocPointerValue(JS_DupValue(context_, value->value()));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
jsi::Runtime::PointerValue *QuickJSRuntime::cloneObject(
|
|
1333
|
+
const Runtime::PointerValue *pv) {
|
|
1334
|
+
const auto *value = static_cast<const QuickJSPointerValue *>(pv);
|
|
1335
|
+
return allocPointerValue(JS_DupValue(context_, value->value()));
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
jsi::Runtime::PointerValue *QuickJSRuntime::clonePropNameID(
|
|
1339
|
+
const Runtime::PointerValue *pv) {
|
|
1340
|
+
const auto *value = static_cast<const QuickJSAtomPointerValue *>(pv);
|
|
1341
|
+
return allocAtomPointerValue(JS_DupAtom(context_, value->atom()));
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFromAscii(
|
|
1345
|
+
const char *str, size_t length) {
|
|
1346
|
+
return createPropNameIDFrom(JS_NewAtomLen(context_, str, length));
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFromUtf8(
|
|
1350
|
+
const uint8_t *utf8, size_t length) {
|
|
1351
|
+
return createPropNameIDFrom(
|
|
1352
|
+
JS_NewAtomLen(context_, reinterpret_cast<const char *>(utf8), length));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFromUtf16(
|
|
1356
|
+
const char16_t *utf16, size_t length) {
|
|
1357
|
+
JSValue string = JS_NewStringUTF16(
|
|
1358
|
+
context_, reinterpret_cast<const uint16_t *>(utf16), length);
|
|
1359
|
+
checkException(string);
|
|
1360
|
+
JSAtom atom = JS_ValueToAtom(context_, string);
|
|
1361
|
+
JS_FreeValue(context_, string);
|
|
1362
|
+
return createPropNameIDFrom(atom);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFromString(
|
|
1366
|
+
const jsi::String &str) {
|
|
1367
|
+
return createPropNameIDFrom(JS_ValueToAtom(context_, toJSValue(str)));
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
jsi::PropNameID QuickJSRuntime::createPropNameIDFromSymbol(
|
|
1371
|
+
const jsi::Symbol &sym) {
|
|
1372
|
+
return createPropNameIDFrom(JS_ValueToAtom(context_, toJSValue(sym)));
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
std::string QuickJSRuntime::utf8(const jsi::PropNameID &name) {
|
|
1376
|
+
size_t length = 0;
|
|
1377
|
+
const char *chars = JS_AtomToCStringLen(context_, &length, toJSAtom(name));
|
|
1378
|
+
if (chars == nullptr) {
|
|
1379
|
+
throwPendingError();
|
|
1380
|
+
}
|
|
1381
|
+
std::string result(chars, length);
|
|
1382
|
+
JS_FreeCString(context_, chars);
|
|
1383
|
+
return result;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
bool QuickJSRuntime::compare(
|
|
1387
|
+
const jsi::PropNameID &a, const jsi::PropNameID &b) {
|
|
1388
|
+
return toJSAtom(a) == toJSAtom(b);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
std::string QuickJSRuntime::symbolToString(const jsi::Symbol &symbol) {
|
|
1392
|
+
JSValue argument = toJSValue(symbol);
|
|
1393
|
+
JSValue result =
|
|
1394
|
+
JS_Call(context_, symbolToString_, JS_UNDEFINED, 1, &argument);
|
|
1395
|
+
checkException(result);
|
|
1396
|
+
|
|
1397
|
+
size_t length = 0;
|
|
1398
|
+
const char *chars = JS_ToCStringLen(context_, &length, result);
|
|
1399
|
+
JS_FreeValue(context_, result);
|
|
1400
|
+
if (chars == nullptr) {
|
|
1401
|
+
throwPendingError();
|
|
1402
|
+
}
|
|
1403
|
+
std::string string(chars, length);
|
|
1404
|
+
JS_FreeCString(context_, chars);
|
|
1405
|
+
return string;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
jsi::BigInt QuickJSRuntime::createBigIntFromInt64(int64_t value) {
|
|
1409
|
+
JSValue bigint = JS_NewBigInt64(context_, value);
|
|
1410
|
+
checkException(bigint);
|
|
1411
|
+
return make<jsi::BigInt>(allocPointerValue(bigint));
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
jsi::BigInt QuickJSRuntime::createBigIntFromUint64(uint64_t value) {
|
|
1415
|
+
JSValue bigint = JS_NewBigUint64(context_, value);
|
|
1416
|
+
checkException(bigint);
|
|
1417
|
+
return make<jsi::BigInt>(allocPointerValue(bigint));
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
bool QuickJSRuntime::bigintIsInt64(const jsi::BigInt &bigint) {
|
|
1421
|
+
int64_t truncated = 0;
|
|
1422
|
+
if (JS_ToBigInt64(context_, &truncated, toJSValue(bigint)) != 0) {
|
|
1423
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
// JS_ToBigInt64 wraps rather than failing, so round-trip to detect loss.
|
|
1427
|
+
JSValue roundTrip = JS_NewBigInt64(context_, truncated);
|
|
1428
|
+
bool fits = JS_IsStrictEqual(context_, roundTrip, toJSValue(bigint));
|
|
1429
|
+
JS_FreeValue(context_, roundTrip);
|
|
1430
|
+
return fits;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
bool QuickJSRuntime::bigintIsUint64(const jsi::BigInt &bigint) {
|
|
1434
|
+
uint64_t truncated = 0;
|
|
1435
|
+
if (JS_ToBigUint64(context_, &truncated, toJSValue(bigint)) != 0) {
|
|
1436
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
1437
|
+
return false;
|
|
1438
|
+
}
|
|
1439
|
+
JSValue roundTrip = JS_NewBigUint64(context_, truncated);
|
|
1440
|
+
bool fits = JS_IsStrictEqual(context_, roundTrip, toJSValue(bigint));
|
|
1441
|
+
JS_FreeValue(context_, roundTrip);
|
|
1442
|
+
return fits;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
uint64_t QuickJSRuntime::truncate(const jsi::BigInt &bigint) {
|
|
1446
|
+
uint64_t truncated = 0;
|
|
1447
|
+
if (JS_ToBigUint64(context_, &truncated, toJSValue(bigint)) != 0) {
|
|
1448
|
+
throwPendingError();
|
|
1449
|
+
}
|
|
1450
|
+
return truncated;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
jsi::String QuickJSRuntime::bigintToString(
|
|
1454
|
+
const jsi::BigInt &bigint, int radix) {
|
|
1455
|
+
if (radix < 2 || radix > 36) {
|
|
1456
|
+
throw jsi::JSINativeException(
|
|
1457
|
+
"QuickJSRuntime: radix must be between 2 and 36");
|
|
1458
|
+
}
|
|
1459
|
+
JSValue arguments[2] = {toJSValue(bigint), JS_NewInt32(context_, radix)};
|
|
1460
|
+
JSValue result =
|
|
1461
|
+
JS_Call(context_, bigIntToString_, JS_UNDEFINED, 2, arguments);
|
|
1462
|
+
checkException(result);
|
|
1463
|
+
return createStringFrom(result);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
jsi::String QuickJSRuntime::createStringFromAscii(
|
|
1467
|
+
const char *str, size_t length) {
|
|
1468
|
+
return createStringFromUtf8(reinterpret_cast<const uint8_t *>(str), length);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
jsi::String QuickJSRuntime::createStringFromUtf8(
|
|
1472
|
+
const uint8_t *utf8, size_t length) {
|
|
1473
|
+
JSValue string =
|
|
1474
|
+
JS_NewStringLen(context_, reinterpret_cast<const char *>(utf8), length);
|
|
1475
|
+
checkException(string);
|
|
1476
|
+
return createStringFrom(string);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
jsi::String QuickJSRuntime::createStringFromUtf16(
|
|
1480
|
+
const char16_t *utf16, size_t length) {
|
|
1481
|
+
JSValue string = JS_NewStringUTF16(
|
|
1482
|
+
context_, reinterpret_cast<const uint16_t *>(utf16), length);
|
|
1483
|
+
checkException(string);
|
|
1484
|
+
return createStringFrom(string);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
std::string QuickJSRuntime::utf8(const jsi::String &string) {
|
|
1488
|
+
size_t length = 0;
|
|
1489
|
+
const char *chars = JS_ToCStringLen(context_, &length, toJSValue(string));
|
|
1490
|
+
if (chars == nullptr) {
|
|
1491
|
+
throwPendingError();
|
|
1492
|
+
}
|
|
1493
|
+
std::string result(chars, length);
|
|
1494
|
+
JS_FreeCString(context_, chars);
|
|
1495
|
+
return result;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
jsi::Object QuickJSRuntime::createObject() {
|
|
1499
|
+
return createObjectFrom(checkException(JS_NewObject(context_)));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
jsi::Object QuickJSRuntime::createObject(std::shared_ptr<jsi::HostObject> ho) {
|
|
1503
|
+
JSValue object = JS_NewObjectClass(context_, hostObjectClassID_);
|
|
1504
|
+
checkException(object);
|
|
1505
|
+
auto *proxy = new HostObjectProxy{{}, this, std::move(ho)};
|
|
1506
|
+
registerHostObject(proxy);
|
|
1507
|
+
JS_SetOpaque(object, proxy);
|
|
1508
|
+
return createObjectFrom(object);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
std::shared_ptr<jsi::HostObject> QuickJSRuntime::getHostObject(
|
|
1512
|
+
const jsi::Object &object) {
|
|
1513
|
+
auto *proxy = static_cast<HostObjectProxy *>(
|
|
1514
|
+
JS_GetOpaque(toJSValue(object), hostObjectClassID_));
|
|
1515
|
+
if (proxy == nullptr) {
|
|
1516
|
+
throw jsi::JSINativeException("QuickJSRuntime: not a host object");
|
|
1517
|
+
}
|
|
1518
|
+
return proxy->hostObject;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
jsi::HostFunctionType &QuickJSRuntime::getHostFunction(
|
|
1522
|
+
const jsi::Function &function) {
|
|
1523
|
+
auto *proxy = static_cast<HostFunctionProxy *>(
|
|
1524
|
+
JS_GetOpaque(toJSValue(function), hostFunctionClassID_));
|
|
1525
|
+
if (proxy == nullptr) {
|
|
1526
|
+
throw jsi::JSINativeException("QuickJSRuntime: not a host function");
|
|
1527
|
+
}
|
|
1528
|
+
return proxy->function;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
jsi::Object QuickJSRuntime::createObjectWithPrototype(
|
|
1532
|
+
const jsi::Value &prototype) {
|
|
1533
|
+
if (!prototype.isObject() && !prototype.isNull()) {
|
|
1534
|
+
throw jsi::JSINativeException(
|
|
1535
|
+
"QuickJSRuntime: prototype must be an object or null");
|
|
1536
|
+
}
|
|
1537
|
+
return createObjectFrom(
|
|
1538
|
+
checkException(JS_NewObjectProto(context_, toJSValue(prototype))));
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
void QuickJSRuntime::setPrototypeOf(
|
|
1542
|
+
const jsi::Object &object, const jsi::Value &prototype) {
|
|
1543
|
+
if (!prototype.isObject() && !prototype.isNull()) {
|
|
1544
|
+
throw jsi::JSINativeException(
|
|
1545
|
+
"QuickJSRuntime: prototype must be an object or null");
|
|
1546
|
+
}
|
|
1547
|
+
checkException(
|
|
1548
|
+
JS_SetPrototype(context_, toJSValue(object), toJSValue(prototype)));
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
jsi::Value QuickJSRuntime::getPrototypeOf(const jsi::Object &object) {
|
|
1552
|
+
return createValue(
|
|
1553
|
+
checkException(JS_GetPrototype(context_, toJSValue(object))));
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/// JSI reserves native state for ordinary objects: a proxy would route the
|
|
1557
|
+
/// property through its traps, and a host object answers for every key.
|
|
1558
|
+
void QuickJSRuntime::checkCanHoldNativeState(const jsi::Object &object) {
|
|
1559
|
+
JSValue value = toJSValue(object);
|
|
1560
|
+
if (JS_IsProxy(value)) {
|
|
1561
|
+
throw jsi::JSINativeException(
|
|
1562
|
+
"QuickJSRuntime: cannot set native state on a proxy");
|
|
1563
|
+
}
|
|
1564
|
+
if (JS_GetOpaque(value, hostObjectClassID_) != nullptr) {
|
|
1565
|
+
throw jsi::JSINativeException(
|
|
1566
|
+
"QuickJSRuntime: cannot set native state on a host object");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
bool QuickJSRuntime::hasNativeState(const jsi::Object &object) {
|
|
1571
|
+
JSValue holder = JS_GetProperty(context_, toJSValue(object), nativeStateKey_);
|
|
1572
|
+
if (JS_IsException(holder)) {
|
|
1573
|
+
JS_FreeValue(context_, JS_GetException(context_));
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
const bool present = JS_GetOpaque(holder, nativeStateClassID_) != nullptr;
|
|
1577
|
+
JS_FreeValue(context_, holder);
|
|
1578
|
+
return present;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
std::shared_ptr<jsi::NativeState> QuickJSRuntime::getNativeState(
|
|
1582
|
+
const jsi::Object &object) {
|
|
1583
|
+
JSValue holder = checkException(
|
|
1584
|
+
JS_GetProperty(context_, toJSValue(object), nativeStateKey_));
|
|
1585
|
+
auto *proxy = static_cast<NativeStateProxy *>(
|
|
1586
|
+
JS_GetOpaque(holder, nativeStateClassID_));
|
|
1587
|
+
JS_FreeValue(context_, holder);
|
|
1588
|
+
if (proxy == nullptr) {
|
|
1589
|
+
throw jsi::JSINativeException("QuickJSRuntime: object has no native state");
|
|
1590
|
+
}
|
|
1591
|
+
// May be null: JSI allows clearing the state, after which hasNativeState
|
|
1592
|
+
// still reports true.
|
|
1593
|
+
return proxy->state;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
void QuickJSRuntime::setNativeState(
|
|
1597
|
+
const jsi::Object &object, std::shared_ptr<jsi::NativeState> state) {
|
|
1598
|
+
checkCanHoldNativeState(object);
|
|
1599
|
+
|
|
1600
|
+
// Overwriting is a pointer swap: the holder is reachable only from this
|
|
1601
|
+
// property, so nothing else can be looking at it.
|
|
1602
|
+
JSValue existing = checkException(
|
|
1603
|
+
JS_GetProperty(context_, toJSValue(object), nativeStateKey_));
|
|
1604
|
+
auto *held = static_cast<NativeStateProxy *>(
|
|
1605
|
+
JS_GetOpaque(existing, nativeStateClassID_));
|
|
1606
|
+
JS_FreeValue(context_, existing);
|
|
1607
|
+
if (held != nullptr) {
|
|
1608
|
+
held->state = std::move(state);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
JSValue holder = JS_NewObjectClass(context_, nativeStateClassID_);
|
|
1613
|
+
checkException(holder);
|
|
1614
|
+
auto *proxy = new NativeStateProxy{{}, std::move(state)};
|
|
1615
|
+
registerNativeState(proxy);
|
|
1616
|
+
JS_SetOpaque(holder, proxy);
|
|
1617
|
+
|
|
1618
|
+
// Writable and configurable so a later call can replace it, which JSI
|
|
1619
|
+
// requires. Neither weakens anything: script has no way to name the key.
|
|
1620
|
+
checkException(JS_DefinePropertyValue(
|
|
1621
|
+
context_, toJSValue(object), JS_DupAtom(context_, nativeStateKey_),
|
|
1622
|
+
holder, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE));
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
jsi::Value QuickJSRuntime::getProperty(
|
|
1626
|
+
const jsi::Object &object, const jsi::PropNameID &name) {
|
|
1627
|
+
return createValue(
|
|
1628
|
+
JS_GetProperty(context_, toJSValue(object), toJSAtom(name)));
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
jsi::Value QuickJSRuntime::getProperty(
|
|
1632
|
+
const jsi::Object &object, const jsi::String &name) {
|
|
1633
|
+
JSAtom atom = JS_ValueToAtom(context_, toJSValue(name));
|
|
1634
|
+
JSValue value = JS_GetProperty(context_, toJSValue(object), atom);
|
|
1635
|
+
JS_FreeAtom(context_, atom);
|
|
1636
|
+
return createValue(value);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
bool QuickJSRuntime::hasProperty(
|
|
1640
|
+
const jsi::Object &object, const jsi::PropNameID &name) {
|
|
1641
|
+
int result = JS_HasProperty(context_, toJSValue(object), toJSAtom(name));
|
|
1642
|
+
checkException(result);
|
|
1643
|
+
return result != 0;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
bool QuickJSRuntime::hasProperty(
|
|
1647
|
+
const jsi::Object &object, const jsi::String &name) {
|
|
1648
|
+
JSAtom atom = JS_ValueToAtom(context_, toJSValue(name));
|
|
1649
|
+
int result = JS_HasProperty(context_, toJSValue(object), atom);
|
|
1650
|
+
JS_FreeAtom(context_, atom);
|
|
1651
|
+
checkException(result);
|
|
1652
|
+
return result != 0;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
void QuickJSRuntime::setPropertyValue(
|
|
1656
|
+
const jsi::Object &object, const jsi::PropNameID &name,
|
|
1657
|
+
const jsi::Value &value) {
|
|
1658
|
+
checkException(JS_SetProperty(
|
|
1659
|
+
context_, toJSValue(object), toJSAtom(name),
|
|
1660
|
+
JS_DupValue(context_, toJSValue(value))));
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
void QuickJSRuntime::setPropertyValue(
|
|
1664
|
+
const jsi::Object &object, const jsi::String &name,
|
|
1665
|
+
const jsi::Value &value) {
|
|
1666
|
+
JSAtom atom = JS_ValueToAtom(context_, toJSValue(name));
|
|
1667
|
+
int result = JS_SetProperty(
|
|
1668
|
+
context_, toJSValue(object), atom,
|
|
1669
|
+
JS_DupValue(context_, toJSValue(value)));
|
|
1670
|
+
JS_FreeAtom(context_, atom);
|
|
1671
|
+
checkException(result);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
bool QuickJSRuntime::isArray(const jsi::Object &object) const {
|
|
1675
|
+
return JS_IsArray(toJSValue(object));
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
bool QuickJSRuntime::isArrayBuffer(const jsi::Object &object) const {
|
|
1679
|
+
return JS_IsArrayBuffer(toJSValue(object));
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
bool QuickJSRuntime::isFunction(const jsi::Object &object) const {
|
|
1683
|
+
return JS_IsFunction(context_, toJSValue(object));
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
bool QuickJSRuntime::isHostObject(const jsi::Object &object) const {
|
|
1687
|
+
return JS_GetOpaque(toJSValue(object), hostObjectClassID_) != nullptr;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
bool QuickJSRuntime::isHostFunction(const jsi::Function &function) const {
|
|
1691
|
+
return JS_GetOpaque(toJSValue(function), hostFunctionClassID_) != nullptr;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
jsi::Array QuickJSRuntime::getPropertyNames(const jsi::Object &object) {
|
|
1695
|
+
// Own and inherited enumerable string keys is exactly for-in, which has no
|
|
1696
|
+
// C API.
|
|
1697
|
+
JSValue argument = toJSValue(object);
|
|
1698
|
+
JSValue names =
|
|
1699
|
+
JS_Call(context_, enumeratePropertyNames_, JS_UNDEFINED, 1, &argument);
|
|
1700
|
+
checkException(names);
|
|
1701
|
+
return make<jsi::Object>(allocPointerValue(names)).getArray(*this);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
jsi::WeakObject QuickJSRuntime::createWeakObject(const jsi::Object &object) {
|
|
1705
|
+
if (!JS_IsObject(weakRefConstructor_)) {
|
|
1706
|
+
throw jsi::JSINativeException(
|
|
1707
|
+
"QuickJSRuntime: WeakRef is unavailable in this context");
|
|
1708
|
+
}
|
|
1709
|
+
JSValue argument = toJSValue(object);
|
|
1710
|
+
JSValue weakRef =
|
|
1711
|
+
JS_CallConstructor(context_, weakRefConstructor_, 1, &argument);
|
|
1712
|
+
checkException(weakRef);
|
|
1713
|
+
return make<jsi::WeakObject>(allocPointerValue(weakRef));
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
jsi::Value QuickJSRuntime::lockWeakObject(const jsi::WeakObject &weakObject) {
|
|
1717
|
+
JSValue result =
|
|
1718
|
+
JS_Call(context_, weakRefDeref_, toJSValue(weakObject), 0, nullptr);
|
|
1719
|
+
checkException(result);
|
|
1720
|
+
return createValue(result);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
jsi::Array QuickJSRuntime::createArray(size_t length) {
|
|
1724
|
+
JSValue array = JS_NewArray(context_);
|
|
1725
|
+
checkException(array);
|
|
1726
|
+
if (JS_SetLength(context_, array, static_cast<int64_t>(length)) < 0) {
|
|
1727
|
+
JS_FreeValue(context_, array);
|
|
1728
|
+
throwPendingError();
|
|
1729
|
+
}
|
|
1730
|
+
return make<jsi::Object>(allocPointerValue(array)).getArray(*this);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
jsi::ArrayBuffer QuickJSRuntime::createArrayBuffer(
|
|
1734
|
+
std::shared_ptr<jsi::MutableBuffer> buffer) {
|
|
1735
|
+
uint8_t *data = buffer->data();
|
|
1736
|
+
size_t size = buffer->size();
|
|
1737
|
+
auto *proxy = new ArrayBufferProxy{std::move(buffer)};
|
|
1738
|
+
|
|
1739
|
+
JSValue arrayBuffer = JS_NewArrayBuffer(
|
|
1740
|
+
context_, data, size, freeArrayBufferProxy, proxy, false);
|
|
1741
|
+
if (JS_IsException(arrayBuffer)) {
|
|
1742
|
+
delete proxy;
|
|
1743
|
+
throwPendingError();
|
|
1744
|
+
}
|
|
1745
|
+
return make<jsi::Object>(allocPointerValue(arrayBuffer))
|
|
1746
|
+
.getArrayBuffer(*this);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
size_t QuickJSRuntime::size(const jsi::Array &array) {
|
|
1750
|
+
int64_t length = 0;
|
|
1751
|
+
checkException(JS_GetLength(context_, toJSValue(array), &length));
|
|
1752
|
+
return static_cast<size_t>(length);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
size_t QuickJSRuntime::size(const jsi::ArrayBuffer &arrayBuffer) {
|
|
1756
|
+
size_t size = 0;
|
|
1757
|
+
if (JS_GetArrayBuffer(context_, &size, toJSValue(arrayBuffer)) == nullptr) {
|
|
1758
|
+
throwPendingError();
|
|
1759
|
+
}
|
|
1760
|
+
return size;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
uint8_t *QuickJSRuntime::data(const jsi::ArrayBuffer &arrayBuffer) {
|
|
1764
|
+
size_t size = 0;
|
|
1765
|
+
uint8_t *data = JS_GetArrayBuffer(context_, &size, toJSValue(arrayBuffer));
|
|
1766
|
+
if (data == nullptr) {
|
|
1767
|
+
throwPendingError();
|
|
1768
|
+
}
|
|
1769
|
+
return data;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
jsi::Value QuickJSRuntime::getValueAtIndex(
|
|
1773
|
+
const jsi::Array &array, size_t index) {
|
|
1774
|
+
return createValue(JS_GetPropertyUint32(
|
|
1775
|
+
context_, toJSValue(array), static_cast<uint32_t>(index)));
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
void QuickJSRuntime::setValueAtIndexImpl(
|
|
1779
|
+
const jsi::Array &array, size_t index, const jsi::Value &value) {
|
|
1780
|
+
checkException(JS_SetPropertyUint32(
|
|
1781
|
+
context_, toJSValue(array), static_cast<uint32_t>(index),
|
|
1782
|
+
JS_DupValue(context_, toJSValue(value))));
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
jsi::Function QuickJSRuntime::createFunctionFromHostFunction(
|
|
1786
|
+
const jsi::PropNameID &name, unsigned int paramCount,
|
|
1787
|
+
jsi::HostFunctionType func) {
|
|
1788
|
+
JSValue function = JS_NewObjectClass(context_, hostFunctionClassID_);
|
|
1789
|
+
checkException(function);
|
|
1790
|
+
auto *proxy = new HostFunctionProxy{{}, this, std::move(func)};
|
|
1791
|
+
registerHostFunction(proxy);
|
|
1792
|
+
JS_SetOpaque(function, proxy);
|
|
1793
|
+
JS_SetConstructorBit(context_, function, true);
|
|
1794
|
+
|
|
1795
|
+
// `name` and `length` are ordinary own properties on JS functions, and code
|
|
1796
|
+
// in the wild reads them.
|
|
1797
|
+
JS_DefinePropertyValueStr(
|
|
1798
|
+
context_, function, "length",
|
|
1799
|
+
JS_NewInt32(context_, static_cast<int32_t>(paramCount)),
|
|
1800
|
+
JS_PROP_CONFIGURABLE);
|
|
1801
|
+
JS_DefinePropertyValueStr(
|
|
1802
|
+
context_, function, "name", JS_AtomToString(context_, toJSAtom(name)),
|
|
1803
|
+
JS_PROP_CONFIGURABLE);
|
|
1804
|
+
|
|
1805
|
+
return make<jsi::Object>(allocPointerValue(function)).getFunction(*this);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
jsi::Value QuickJSRuntime::call(
|
|
1809
|
+
const jsi::Function &function, const jsi::Value &jsThis,
|
|
1810
|
+
const jsi::Value *args, size_t count) {
|
|
1811
|
+
adoptCurrentThread();
|
|
1812
|
+
drainPendingReleases();
|
|
1813
|
+
|
|
1814
|
+
ArgumentList arguments(*this, args, count);
|
|
1815
|
+
return createValue(JS_Call(
|
|
1816
|
+
context_, toJSValue(function), toJSValue(jsThis), static_cast<int>(count),
|
|
1817
|
+
arguments.data()));
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
jsi::Value QuickJSRuntime::callAsConstructor(
|
|
1821
|
+
const jsi::Function &function, const jsi::Value *args, size_t count) {
|
|
1822
|
+
adoptCurrentThread();
|
|
1823
|
+
drainPendingReleases();
|
|
1824
|
+
|
|
1825
|
+
ArgumentList arguments(*this, args, count);
|
|
1826
|
+
return createValue(JS_CallConstructor(
|
|
1827
|
+
context_, toJSValue(function), static_cast<int>(count),
|
|
1828
|
+
arguments.data()));
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
bool QuickJSRuntime::strictEquals(
|
|
1832
|
+
const jsi::Symbol &a, const jsi::Symbol &b) const {
|
|
1833
|
+
return JS_IsStrictEqual(context_, toJSValue(a), toJSValue(b));
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
bool QuickJSRuntime::strictEquals(
|
|
1837
|
+
const jsi::BigInt &a, const jsi::BigInt &b) const {
|
|
1838
|
+
return JS_IsStrictEqual(context_, toJSValue(a), toJSValue(b));
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
bool QuickJSRuntime::strictEquals(
|
|
1842
|
+
const jsi::String &a, const jsi::String &b) const {
|
|
1843
|
+
return JS_IsStrictEqual(context_, toJSValue(a), toJSValue(b));
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
bool QuickJSRuntime::strictEquals(
|
|
1847
|
+
const jsi::Object &a, const jsi::Object &b) const {
|
|
1848
|
+
return JS_IsStrictEqual(context_, toJSValue(a), toJSValue(b));
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
bool QuickJSRuntime::instanceOf(
|
|
1852
|
+
const jsi::Object &object, const jsi::Function &function) {
|
|
1853
|
+
int result =
|
|
1854
|
+
JS_IsInstanceOf(context_, toJSValue(object), toJSValue(function));
|
|
1855
|
+
checkException(result);
|
|
1856
|
+
return result != 0;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
void QuickJSRuntime::setExternalMemoryPressure(
|
|
1860
|
+
const jsi::Object & /*object*/, size_t /*amount*/) {
|
|
1861
|
+
// quickjs has no external memory pressure hook.
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
} // namespace qjs
|