@crossbind/core-embind-jsi 2.0.0-beta.60 → 2.0.0-beta.63

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.
@@ -17,6 +17,7 @@
17
17
  #include <climits>
18
18
  #include "./wire.h"
19
19
  #include <cstdint> // uintptr_t
20
+ #include <stdexcept>
20
21
  #include <vector>
21
22
 
22
23
  // #include <android/log.h>
@@ -48,15 +49,14 @@ enum {
48
49
  _EMVAL_FALSE = 4
49
50
  };
50
51
 
51
- typedef struct _EM_DESTRUCTORS* EM_DESTRUCTORS;
52
52
  typedef struct _EM_METHOD_CALLER* EM_METHOD_CALLER;
53
53
  typedef facebook::jsi::Value EM_GENERIC_WIRE_TYPE;
54
- typedef const void* EM_VAR_ARGS;
55
54
 
56
55
  void _emval_incref(EM_VAL value);
57
56
  void _emval_decref(EM_VAL value);
58
57
 
59
- void _emval_run_destructors(EM_DESTRUCTORS handle);
58
+ // Runs the destructors the latest _emval_as or _emval_call_method conversion queued.
59
+ void _emval_run_destructors();
60
60
 
61
61
  EM_VAL _emval_new_array();
62
62
  EM_VAL _emval_new_array_from_memory_view(EM_VAL mv);
@@ -65,19 +65,19 @@ EM_VAL _emval_new_cstring(const char*);
65
65
  EM_VAL _emval_new_u8string(const char*);
66
66
  EM_VAL _emval_new_u16string(const char16_t*);
67
67
 
68
- EM_VAL _emval_take_value(TYPEID type, EM_VAR_ARGS argv);
68
+ EM_VAL _emval_take_value(TYPEID type, const facebook::jsi::Value& value);
69
69
 
70
70
  EM_VAL _emval_new(
71
71
  EM_VAL value,
72
72
  unsigned argCount,
73
73
  const TYPEID argTypes[],
74
- EM_VAR_ARGS argv);
74
+ facebook::jsi::Value* argv);
75
75
 
76
76
  EM_VAL _emval_get_global(const char* name);
77
77
  EM_VAL _emval_get_module_property(const char* name);
78
78
  EM_VAL _emval_get_property(EM_VAL object, EM_VAL key);
79
79
  void _emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value);
80
- EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* destructors);
80
+ EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType);
81
81
  int64_t _emval_as_int64(EM_VAL value, TYPEID returnType);
82
82
  uint64_t _emval_as_uint64(EM_VAL value, TYPEID returnType);
83
83
 
@@ -91,7 +91,7 @@ EM_VAL _emval_call(
91
91
  EM_VAL value,
92
92
  unsigned argCount,
93
93
  const TYPEID argTypes[],
94
- EM_VAR_ARGS argv);
94
+ facebook::jsi::Value* argv);
95
95
 
96
96
  // DO NOT call this more than once per signature. It will
97
97
  // leak generated function objects!
@@ -102,13 +102,14 @@ EM_GENERIC_WIRE_TYPE _emval_call_method(
102
102
  EM_METHOD_CALLER caller,
103
103
  EM_VAL handle,
104
104
  const char* methodName,
105
- EM_DESTRUCTORS* destructors,
106
- EM_VAR_ARGS argv);
105
+ unsigned argCount,
106
+ facebook::jsi::Value* argv);
107
107
  void _emval_call_void_method(
108
108
  EM_METHOD_CALLER caller,
109
109
  EM_VAL handle,
110
110
  const char* methodName,
111
- EM_VAR_ARGS argv);
111
+ unsigned argCount,
112
+ facebook::jsi::Value* argv);
112
113
  EM_VAL _emval_typeof(EM_VAL value);
113
114
  bool _emval_instanceof(EM_VAL object, EM_VAL constructor);
114
115
  bool _emval_is_number(EM_VAL object);
@@ -146,20 +147,17 @@ struct Signature {
146
147
  }
147
148
  };
148
149
 
150
+ // Scoped to one conversion: every _emval_as / _emval_call_method that returns queues exactly one
151
+ // destructor list in JS, and this runs it once the C++ side has read the value.
149
152
  struct DestructorsRunner {
150
153
  public:
151
- explicit DestructorsRunner(EM_DESTRUCTORS d)
152
- : destructors(d)
153
- {}
154
+ DestructorsRunner() = default;
154
155
  ~DestructorsRunner() {
155
- _emval_run_destructors(destructors);
156
+ _emval_run_destructors();
156
157
  }
157
158
 
158
159
  DestructorsRunner(const DestructorsRunner&) = delete;
159
160
  void operator=(const DestructorsRunner&) = delete;
160
-
161
- private:
162
- EM_DESTRUCTORS destructors;
163
161
  };
164
162
 
165
163
  template<typename WireType>
@@ -178,115 +176,27 @@ struct GenericWireTypeConverter<Pointee*> {
178
176
 
179
177
  template<typename T>
180
178
  T fromGenericWireType(facebook::jsi::Value& g) {
181
- if constexpr (std::is_same<T, std::string>::value) {
182
- return g.getString(*jsRuntime).utf8(*jsRuntime);
183
- }
184
- typedef typename BindingType<T>::WireType WireType;
185
- WireType wt = GenericWireTypeConverter<WireType>::from(g);
186
- return BindingType<T>::fromWireType(wt);
187
- }
188
-
189
- template<typename... Args>
190
- struct PackSize;
191
-
192
- template<>
193
- struct PackSize<> {
194
- static constexpr size_t value = 0;
195
- };
196
-
197
- template<typename Arg, typename... Args>
198
- struct PackSize<Arg, Args...> {
199
- static constexpr size_t value = (sizeof(typename BindingType<Arg>::WireType) + 7) / 8 + PackSize<Args...>::value;
200
- };
201
-
202
- union GenericWireType {
203
- union {
204
- unsigned u;
205
- float f;
206
- // Use uint32_t for pointer values. This limits us, for now, to 32-bit
207
- // address ranges even on wasm64. This is enforced by assertions below.
208
- // TODO(sbc): Allow full 64-bit address range here under wasm64, most
209
- // likely by increasing the size of GenericWireType on wasm64.
210
- uint32_t p;
211
- } w[2];
212
- double d;
213
- uint64_t u;
214
- };
215
- static_assert(sizeof(GenericWireType) == 8, "GenericWireType must be 8 bytes");
216
- static_assert(alignof(GenericWireType) == 8, "GenericWireType must be 8-byte-aligned");
217
-
218
- inline void writeGenericWireType(GenericWireType*& cursor, float wt) {
219
- cursor->w[0].f = wt;
220
- ++cursor;
221
- }
222
-
223
- inline void writeGenericWireType(GenericWireType*& cursor, double wt) {
224
- cursor->d = wt;
225
- ++cursor;
226
- }
227
-
228
- inline void writeGenericWireType(GenericWireType*& cursor, int64_t wt) {
229
- cursor->u = wt;
230
- ++cursor;
231
- }
232
-
233
- inline void writeGenericWireType(GenericWireType*& cursor, uint64_t wt) {
234
- cursor->u = wt;
235
- ++cursor;
236
- }
237
-
238
- template<typename T>
239
- void writeGenericWireType(GenericWireType*& cursor, T* wt) {
240
- uint64_t short_ptr = reinterpret_cast<uint64_t>(wt);
241
- // assert(short_ptr <= UINT32_MAX);
242
- cursor->u = short_ptr;
243
- ++cursor;
244
- }
245
-
246
- template<typename ElementType>
247
- inline void writeGenericWireType(GenericWireType*& cursor, const memory_view<ElementType>& wt) {
248
- uintptr_t short_ptr = reinterpret_cast<uintptr_t>(wt.data);
249
- // This wire slot stores the data pointer in 32 bits (upstream wasm32 layout). On 64-bit
250
- // native a pointer above 4 GiB cannot fit; abort loudly rather than silently truncating to
251
- // a bogus address (release builds compile out the assert). Full 64-bit memory_view support
252
- // needs a wider wire encoding on both the writer and the reader.
253
- assert(short_ptr <= UINT32_MAX);
254
- if (short_ptr > UINT32_MAX) {
255
- abort();
256
- }
257
- cursor->w[0].u = wt.size;
258
- cursor->w[1].p = static_cast<uint32_t>(short_ptr);
259
- ++cursor;
260
- }
261
-
262
- template<typename T>
263
- void writeGenericWireType(GenericWireType*& cursor, T wt) {
264
- cursor->w[0].u = static_cast<unsigned>(wt);
265
- ++cursor;
266
- }
267
-
268
- inline void writeGenericWireTypes(GenericWireType*&) {
269
- }
270
-
271
- template<typename First, typename... Rest>
272
- EMSCRIPTEN_ALWAYS_INLINE void writeGenericWireTypes(GenericWireType*& cursor, First&& first, Rest&&... rest) {
273
- writeGenericWireType(cursor, BindingType<First>::toWireType(std::forward<First>(first)));
274
- writeGenericWireTypes(cursor, std::forward<Rest>(rest)...);
179
+ if constexpr (std::is_same<T, std::string>::value) {
180
+ return g.asString(*jsRuntime).utf8(*jsRuntime);
181
+ } else if constexpr (std::is_same<T, facebook::jsi::Value>::value) {
182
+ return std::move(g);
183
+ } else {
184
+ typedef typename BindingType<T>::WireType WireType;
185
+ WireType wt = GenericWireTypeConverter<WireType>::from(g);
186
+ return BindingType<T>::fromWireType(wt);
187
+ }
275
188
  }
276
189
 
190
+ // Arguments cross as the values embind's JS types accept (toWireType2), the same way bound
191
+ // functions return them, never as bytes JS would have to read out of native memory.
192
+ // jsi::Value has no copy constructor: each toWireType2 prvalue initializes its element directly,
193
+ // so routing one through a named const Value first would stop this from compiling.
277
194
  template<typename... Args>
278
- struct WireTypePack {
279
- WireTypePack(Args&&... args) {
280
- GenericWireType* cursor = elements.data();
281
- writeGenericWireTypes(cursor, std::forward<Args>(args)...);
282
- }
195
+ struct WireValues {
196
+ explicit WireValues(Args&&... args)
197
+ : values{{BindingType<Args>::toWireType2(*jsRuntime, std::forward<Args>(args))...}} {}
283
198
 
284
- operator EM_VAR_ARGS() const {
285
- return elements.data();
286
- }
287
-
288
- private:
289
- std::array<GenericWireType, PackSize<Args...>::value> elements;
199
+ std::array<facebook::jsi::Value, sizeof...(Args)> values;
290
200
  };
291
201
 
292
202
  template<typename ReturnType, typename... Args>
@@ -294,15 +204,14 @@ struct MethodCaller {
294
204
  static ReturnType call(EM_VAL handle, const char* methodName, Args&&... args) {
295
205
  auto caller = Signature<ReturnType, Args...>::get_method_caller();
296
206
 
297
- WireTypePack<Args...> argv(std::forward<Args>(args)...);
298
- EM_DESTRUCTORS destructors;
207
+ WireValues<Args...> argv(std::forward<Args>(args)...);
299
208
  EM_GENERIC_WIRE_TYPE result = _emval_call_method(
300
209
  caller,
301
210
  handle,
302
211
  methodName,
303
- &destructors,
304
- argv);
305
- DestructorsRunner rd(destructors);
212
+ sizeof...(Args),
213
+ argv.values.data());
214
+ DestructorsRunner rd;
306
215
  return fromGenericWireType<ReturnType>(result);
307
216
  }
308
217
  };
@@ -312,12 +221,13 @@ struct MethodCaller<void, Args...> {
312
221
  static void call(EM_VAL handle, const char* methodName, Args&&... args) {
313
222
  auto caller = Signature<void, Args...>::get_method_caller();
314
223
 
315
- WireTypePack<Args...> argv(std::forward<Args>(args)...);
224
+ WireValues<Args...> argv(std::forward<Args>(args)...);
316
225
  _emval_call_void_method(
317
226
  caller,
318
227
  handle,
319
228
  methodName,
320
- argv);
229
+ sizeof...(Args),
230
+ argv.values.data());
321
231
  }
322
232
  };
323
233
 
@@ -410,9 +320,9 @@ public:
410
320
  explicit val(T&& value) {
411
321
  using namespace internal;
412
322
 
413
- typedef internal::BindingType<T> BT;
414
- WireTypePack<T> argv(std::forward<T>(value));
415
- handle = _emval_take_value(internal::TypeID<T>::get(), argv);
323
+ handle = _emval_take_value(
324
+ internal::TypeID<T>::get(),
325
+ BindingType<T>::toWireType2(*jsRuntime, std::forward<T>(value)));
416
326
  }
417
327
 
418
328
  val() : handle(EM_VAL(internal::_EMVAL_UNDEFINED)) {}
@@ -555,15 +465,10 @@ public:
555
465
  T as(Policies...) const {
556
466
  using namespace internal;
557
467
 
558
- typedef BindingType<T> BT;
559
468
  typename WithPolicies<Policies...>::template ArgTypeList<T> targetType;
560
469
 
561
- EM_DESTRUCTORS destructors;
562
- EM_GENERIC_WIRE_TYPE result = _emval_as(
563
- handle,
564
- targetType.getTypes()[0],
565
- &destructors);
566
- DestructorsRunner dr(destructors);
470
+ EM_GENERIC_WIRE_TYPE result = _emval_as(handle, targetType.getTypes()[0]);
471
+ DestructorsRunner dr;
567
472
  return fromGenericWireType<T>(result);
568
473
  }
569
474
 
@@ -629,8 +534,8 @@ private:
629
534
  using namespace internal;
630
535
 
631
536
  WithPolicies<>::ArgTypeList<Args...> argList;
632
- WireTypePack<Args...> argv(std::forward<Args>(args)...);
633
- return val(impl(handle, argList.getCount(), argList.getTypes(), argv));
537
+ WireValues<Args...> argv(std::forward<Args>(args)...);
538
+ return val(impl(handle, argList.getCount(), argList.getTypes(), argv.values.data()));
634
539
  }
635
540
 
636
541
  template<typename T>
@@ -669,7 +574,7 @@ struct BindingType<val> {
669
574
  static val fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
670
575
  // return val::undefined();
671
576
  // return val::take_ownership(reinterpret_cast<EM_VAL>((int) v.getNumber()));
672
- return val::take_ownership(reinterpret_cast<EM_VAL>(v.asBigInt(rt).asUint64(rt)));
577
+ return val::take_ownership(reinterpret_cast<EM_VAL>(bigIntUint64(rt, v)));
673
578
  }
674
579
  };
675
580
 
@@ -690,17 +595,23 @@ std::vector<T> vecFromJSArray(const val& v, Policies... policies) {
690
595
 
691
596
  template <typename T>
692
597
  std::vector<T> convertJSArrayToNumberVector(const val& v) {
693
- const size_t l = v["length"].as<size_t>();
598
+ // size_t is not a registered embind type on every 64-bit ABI; the length is a Number anyway.
599
+ const double l = v["length"].as<double>();
600
+ if (!(l >= 0 && l <= static_cast<double>(std::vector<T>().max_size()))) {
601
+ throw std::length_error("crossbind: the array length is not a valid size");
602
+ }
694
603
 
695
604
  std::vector<T> rv;
696
- rv.resize(l);
605
+ rv.resize(static_cast<size_t>(l));
697
606
 
698
607
  // Copy the array into our vector through the use of typed arrays.
699
608
  // It will try to convert each element through Number().
700
609
  // See https://www.ecma-international.org/ecma-262/6.0/#sec-%typedarray%.prototype.set-array-offset
701
610
  // and https://www.ecma-international.org/ecma-262/6.0/#sec-tonumber
702
- val memoryView{ typed_memory_view(l, rv.data()) };
703
- memoryView.call<void>("set", v);
611
+ // The typed array is JS-owned and copied out: one aliasing rv would need external memory.
612
+ val typedArray = val::global(internal::typedArrayNameOf<T>()).new_(l);
613
+ typedArray.call<void>("set", v);
614
+ internal::copyFromTypedArray(*jsRuntime, typedArray.as<facebook::jsi::Value>(), rv.data(), rv.size() * sizeof(T));
704
615
 
705
616
  return rv;
706
617
  }
@@ -21,11 +21,13 @@
21
21
  #include <cstdio>
22
22
  #include <cstdlib>
23
23
  #include <cstdint>
24
+ #include <cstring>
24
25
  #include <memory>
25
26
  #include <optional>
26
27
  #include <string>
27
28
 
28
29
  #include <jsi/jsi.h>
30
+ #include "../crossbind/direct_reads.h"
29
31
 
30
32
  // embind registers std::basic_string<unsigned char> as the raw-byte counterpart to UTF-8
31
33
  // std::string, but the standard has never defined std::char_traits for unsigned char. libc++ used
@@ -96,25 +98,40 @@ constexpr bool has_unbound_type_names = false;
96
98
 
97
99
  namespace internal {
98
100
 
101
+ // A BigInt read straight from the engine where the runtime offers crossbind::IDirectReads, instead
102
+ // of cloning it into a jsi::BigInt first; `this` arrives this way on every method call. Anything
103
+ // that is not a BigInt, or does not fit, takes the jsi path and throws as it did.
104
+ inline uint64_t bigIntUint64(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
105
+ auto* reads = static_cast<crossbind::IDirectReads*>(rt.castInterface(crossbind::IDirectReads::uuid));
106
+ uint64_t bits = 0;
107
+ bool lossless = false;
108
+ if (reads != nullptr && reads->bigIntToUint64(v, bits, lossless) && lossless) return bits;
109
+ return v.asBigInt(rt).asUint64(rt);
110
+ }
111
+
112
+ inline int64_t bigIntInt64(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
113
+ auto* reads = static_cast<crossbind::IDirectReads*>(rt.castInterface(crossbind::IDirectReads::uuid));
114
+ int64_t bits = 0;
115
+ bool lossless = false;
116
+ if (reads != nullptr && reads->bigIntToInt64(v, bits, lossless) && lossless) return bits;
117
+ return v.asBigInt(rt).asInt64(rt);
118
+ }
119
+
99
120
  // JS hands native pointers over as BigInt addresses, but a null pointer arrives as the number 0
100
121
  // and enum values as plain numbers: take both spellings.
101
122
  inline uint64_t addressFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
102
123
  if (v.isNumber()) return static_cast<uint64_t>(v.asNumber());
103
124
  if (v.isNull() || v.isUndefined()) return 0;
104
- return v.asBigInt(rt).asUint64(rt);
125
+ return bigIntUint64(rt, v);
105
126
  }
106
127
 
107
128
  inline int64_t integerFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
108
129
  if (v.isNumber()) return static_cast<int64_t>(v.asNumber());
109
- return v.asBigInt(rt).asInt64(rt);
130
+ return bigIntInt64(rt, v);
110
131
  }
111
132
 
112
133
  typedef const void* TYPEID;
113
134
 
114
- // Defined in bind.cpp: registers a JS-side ArrayBuffer window covering `ptr` so
115
- // heap reads on values we hand out by pointer cannot miss every window.
116
- void ensureWindowFor(uint64_t ptr);
117
-
118
135
  // We don't need the full std::type_info implementation. We
119
136
  // just need a unique identifier per type and polymorphic type
120
137
  // identification.
@@ -396,7 +413,7 @@ struct BindingType<int64_t> {
396
413
  return facebook::jsi::BigInt::fromInt64(rt, b);
397
414
  }
398
415
  static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
399
- return wt.asBigInt(rt).asInt64(rt);
416
+ return bigIntInt64(rt, wt);
400
417
  }
401
418
  };
402
419
 
@@ -416,7 +433,57 @@ struct BindingType<uint64_t> {
416
433
  return facebook::jsi::BigInt::fromUint64(rt, b);
417
434
  }
418
435
  static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
419
- return wt.asBigInt(rt).asUint64(rt);
436
+ return bigIntUint64(rt, wt);
437
+ }
438
+ };
439
+
440
+ // int64_t is long long on Apple platforms and long on Android: the other 64-bit pair crosses as a BigInt too.
441
+ template<typename T>
442
+ constexpr bool isOther64BitInteger = std::is_integral_v<T> && sizeof(T) == 8
443
+ && !std::is_same_v<T, int64_t> && !std::is_same_v<T, uint64_t>;
444
+
445
+ template<typename T>
446
+ struct BindingType<T, std::enable_if_t<isOther64BitInteger<T>>> {
447
+ using Exact = std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>;
448
+ typedef T WireType;
449
+ typedef const facebook::jsi::Value WireType2;
450
+
451
+ static WireType toWireType(WireType b) {
452
+ return b;
453
+ }
454
+ static WireType fromWireType(WireType wt) {
455
+ return wt;
456
+ }
457
+
458
+ static WireType2 toWireType2(facebook::jsi::Runtime& rt, WireType b) {
459
+ return BindingType<Exact>::toWireType2(rt, static_cast<Exact>(b));
460
+ }
461
+ static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
462
+ return static_cast<WireType>(BindingType<Exact>::fromWireType2(rt, wt));
463
+ }
464
+ };
465
+
466
+ // Windows keeps long at 32 bits, still a type apart from int: it crosses as a Number like int does.
467
+ template<typename T>
468
+ constexpr bool isNarrowLong = (std::is_same_v<T, long> || std::is_same_v<T, unsigned long>) && sizeof(T) == 4;
469
+
470
+ template<typename T>
471
+ struct BindingType<T, std::enable_if_t<isNarrowLong<T>>> {
472
+ typedef T WireType;
473
+ typedef const facebook::jsi::Value WireType2;
474
+
475
+ static WireType toWireType(WireType b) {
476
+ return b;
477
+ }
478
+ static WireType fromWireType(WireType wt) {
479
+ return wt;
480
+ }
481
+
482
+ static WireType2 toWireType2(facebook::jsi::Runtime& rt, WireType b) {
483
+ return WireType2((double) b);
484
+ }
485
+ static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
486
+ return (WireType) wt.getNumber();
420
487
  }
421
488
  };
422
489
 
@@ -434,64 +501,80 @@ struct BindingType<uint64_t> {
434
501
  };
435
502
 
436
503
  // jsi strings cross as UTF-8, so a std::u16string (embind's byte-preserving string) is transcoded; every code unit of
437
- // well-formed UTF-16 survives the round trip.
504
+ // well-formed UTF-16 survives the round trip. Both directions size their output once and write through a pointer:
505
+ // a push_back per unit was most of the cost of moving bytes (readBytes) across.
438
506
  inline std::string utf16ToUtf8(const std::u16string& text) {
439
- std::string out;
440
- out.reserve(text.size());
441
- for (size_t i = 0; i < text.size(); ++i) {
507
+ // At most three bytes per code unit; a surrogate pair takes four for two.
508
+ std::string out(text.size() * 3, '\0');
509
+ char* o = out.data();
510
+ const size_t n = text.size();
511
+ for (size_t i = 0; i < n; ++i) {
442
512
  char32_t cp = text[i];
443
- if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < text.size() && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
513
+ if (cp < 0x800) {
514
+ // One byte or two, chosen without a branch: in byte data half the units are above 0x7F
515
+ // at random. The spare second byte stays inside the three reserved per unit.
516
+ const bool isTwoBytes = cp >= 0x80;
517
+ o[0] = static_cast<char>(isTwoBytes ? 0xC0 | (cp >> 6) : cp);
518
+ o[1] = static_cast<char>(0x80 | (cp & 0x3F));
519
+ o += 1 + isTwoBytes;
520
+ continue;
521
+ }
522
+ if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < n && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
444
523
  cp = 0x10000 + ((cp - 0xD800) << 10) + (text[++i] - 0xDC00);
445
524
  } else if (cp >= 0xD800 && cp <= 0xDFFF) {
446
525
  cp = 0xFFFD;
447
526
  }
448
- if (cp < 0x80) {
449
- out.push_back(static_cast<char>(cp));
450
- } else if (cp < 0x800) {
451
- out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
452
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
453
- } else if (cp < 0x10000) {
454
- out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
455
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
456
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
527
+ if (cp < 0x10000) {
528
+ *o++ = static_cast<char>(0xE0 | (cp >> 12));
529
+ *o++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
530
+ *o++ = static_cast<char>(0x80 | (cp & 0x3F));
457
531
  } else {
458
- out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
459
- out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
460
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
461
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
532
+ *o++ = static_cast<char>(0xF0 | (cp >> 18));
533
+ *o++ = static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
534
+ *o++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
535
+ *o++ = static_cast<char>(0x80 | (cp & 0x3F));
462
536
  }
463
537
  }
538
+ out.resize(static_cast<size_t>(o - out.data()));
464
539
  return out;
465
540
  }
466
541
 
467
542
  inline std::u16string utf8ToUtf16(const std::string& text) {
468
- std::u16string out;
469
- out.reserve(text.size());
543
+ // Never more code units than bytes.
544
+ std::u16string out(text.size(), u'\0');
545
+ char16_t* o = out.data();
546
+ const size_t n = text.size();
470
547
  size_t i = 0;
471
- while (i < text.size()) {
548
+ while (i < n) {
472
549
  unsigned char lead = static_cast<unsigned char>(text[i]);
473
- size_t extra = lead < 0x80 ? 0 : (lead >> 5) == 0x6 ? 1 : (lead >> 4) == 0xE ? 2 : (lead >> 3) == 0x1E ? 3 : 4;
474
- char32_t cp = extra == 0 ? lead : extra == 1 ? (lead & 0x1F) : extra == 2 ? (lead & 0x0F) : (lead & 0x07);
475
- bool valid = extra < 4 && i + extra < text.size();
550
+ if (lead < 0x80) {
551
+ *o++ = lead;
552
+ ++i;
553
+ continue;
554
+ }
555
+ size_t extra = (lead >> 5) == 0x6 ? 1 : (lead >> 4) == 0xE ? 2 : (lead >> 3) == 0x1E ? 3 : 4;
556
+ char32_t cp = extra == 1 ? (lead & 0x1F) : extra == 2 ? (lead & 0x0F) : (lead & 0x07);
557
+ bool valid = extra < 4 && i + extra < n;
476
558
  for (size_t k = 1; valid && k <= extra; ++k) {
477
559
  unsigned char next = static_cast<unsigned char>(text[i + k]);
478
560
  valid = (next >> 6) == 0x2;
479
561
  cp = (cp << 6) | (next & 0x3F);
480
562
  }
481
563
  if (!valid) {
482
- out.push_back(0xFFFD);
564
+ *o++ = 0xFFFD;
483
565
  ++i;
484
566
  continue;
485
567
  }
486
568
  i += extra + 1;
487
569
  if (cp >= 0x10000) {
488
570
  cp -= 0x10000;
489
- out.push_back(static_cast<char16_t>(0xD800 + (cp >> 10)));
490
- out.push_back(static_cast<char16_t>(0xDC00 + (cp & 0x3FF)));
571
+ *o++ = static_cast<char16_t>(0xD800 + (cp >> 10));
572
+ *o++ = static_cast<char16_t>(0xDC00 + (cp & 0x3FF));
491
573
  } else {
492
- out.push_back(static_cast<char16_t>(cp));
574
+ *o++ = static_cast<char16_t>(cp);
493
575
  }
494
576
  }
577
+ out.resize(static_cast<size_t>(o - out.data()));
495
578
  return out;
496
579
  }
497
580
 
@@ -521,11 +604,12 @@ struct BindingType<std::basic_string<T>> {
521
604
  return facebook::jsi::String::createFromUtf8(rt, v);
522
605
  }
523
606
  }
607
+ // asString, not getString: a non-string argument then throws into JS instead of being read as one.
524
608
  static String fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
525
609
  if constexpr (std::is_same_v<T, char16_t>) {
526
- return utf8ToUtf16(v.getString(rt).utf8(rt));
610
+ return utf8ToUtf16(v.asString(rt).utf8(rt));
527
611
  } else {
528
- return v.getString(rt).utf8(rt);
612
+ return v.asString(rt).utf8(rt);
529
613
  }
530
614
  }
531
615
  };
@@ -616,20 +700,14 @@ struct GenericBindingType {
616
700
  }
617
701
 
618
702
  static WireType2 toWireType2(facebook::jsi::Runtime& rt, const T& v) {
619
- // By-value returns allocate fresh heap blocks; without a covering window JS-side
620
- // memory reads on the new object die with "Heap error ... no covering window".
621
- uint64_t ptr = reinterpret_cast<uint64_t>(new T(v));
622
- ensureWindowFor(ptr);
623
- return facebook::jsi::BigInt::fromUint64(rt, ptr);
703
+ return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(v)));
624
704
  }
625
705
  static WireType2 toWireType2(facebook::jsi::Runtime& rt, T&& v) {
626
- uint64_t ptr = reinterpret_cast<uint64_t>(new T(std::forward<T>(v)));
627
- ensureWindowFor(ptr);
628
- return facebook::jsi::BigInt::fromUint64(rt, ptr);
706
+ return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(std::forward<T>(v))));
629
707
  }
630
708
 
631
709
  static ActualT& fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
632
- return *reinterpret_cast<WireType>(wt.getBigInt(rt).getUint64(rt));
710
+ return *reinterpret_cast<WireType>(bigIntUint64(rt, wt));
633
711
  }
634
712
  };
635
713
 
@@ -697,6 +775,46 @@ constexpr bool typeSupportsMemoryView() {
697
775
  sizeof(T) == 4 || sizeof(T) == 8));
698
776
  }
699
777
 
778
+ template<typename T>
779
+ constexpr const char* typedArrayNameOf() {
780
+ static_assert(typeSupportsMemoryView<T>(), "type does not map to a typed array");
781
+ if constexpr (std::is_floating_point<T>::value) {
782
+ return sizeof(T) == 4 ? "Float32Array" : "Float64Array";
783
+ } else if constexpr (sizeof(T) == 1) {
784
+ return std::is_signed<T>::value ? "Int8Array" : "Uint8Array";
785
+ } else if constexpr (sizeof(T) == 2) {
786
+ return std::is_signed<T>::value ? "Int16Array" : "Uint16Array";
787
+ } else if constexpr (sizeof(T) == 4) {
788
+ return std::is_signed<T>::value ? "Int32Array" : "Uint32Array";
789
+ } else {
790
+ return std::is_signed<T>::value ? "BigInt64Array" : "BigUint64Array";
791
+ }
792
+ }
793
+
794
+ // Native memory reaches JS only as a copy in an ArrayBuffer JS allocated: Electron's V8 memory
795
+ // cage refuses ArrayBuffers over external memory.
796
+ inline facebook::jsi::Value copyToTypedArray(
797
+ facebook::jsi::Runtime& rt, const char* arrayName, const void* data, size_t count, size_t bytes) {
798
+ facebook::jsi::Object array = rt.global().getPropertyAsFunction(rt, arrayName)
799
+ .callAsConstructor(rt, static_cast<double>(count)).asObject(rt);
800
+ if (bytes > 0) {
801
+ facebook::jsi::ArrayBuffer buffer = array.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt);
802
+ memcpy(buffer.data(rt), data, bytes);
803
+ }
804
+ return facebook::jsi::Value(std::move(array));
805
+ }
806
+
807
+ inline void copyFromTypedArray(facebook::jsi::Runtime& rt, const facebook::jsi::Value& typedArray, void* data, size_t bytes) {
808
+ if (bytes == 0) return;
809
+ facebook::jsi::Object view = typedArray.asObject(rt);
810
+ size_t offset = static_cast<size_t>(view.getProperty(rt, "byteOffset").asNumber());
811
+ facebook::jsi::ArrayBuffer buffer = view.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt);
812
+ if (offset > buffer.size(rt) || bytes > buffer.size(rt) - offset) {
813
+ throw facebook::jsi::JSINativeException("crossbind: the typed array is smaller than the native buffer it fills");
814
+ }
815
+ memcpy(data, buffer.data(rt) + offset, bytes);
816
+ }
817
+
700
818
  } // namespace internal
701
819
 
702
820
  template<typename ElementType>
@@ -734,9 +852,13 @@ struct BindingType<memory_view<ElementType>> {
734
852
  // on the C++ side, nor is toWireType implemented in
735
853
  // JavaScript.)
736
854
  typedef memory_view<ElementType> WireType;
855
+ typedef const facebook::jsi::Value WireType2;
737
856
  static WireType toWireType(const memory_view<ElementType>& mv) {
738
857
  return mv;
739
858
  }
859
+ static WireType2 toWireType2(facebook::jsi::Runtime& rt, const memory_view<ElementType>& mv) {
860
+ return copyToTypedArray(rt, typedArrayNameOf<ElementType>(), mv.data, mv.size, mv.size * sizeof(ElementType));
861
+ }
740
862
  };
741
863
 
742
864
  } // namespace internal