@crossbind/core-embind-jsi 2.0.0-beta.58 → 2.0.0-beta.62

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.
@@ -21,11 +21,63 @@
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"
31
+
32
+ // embind registers std::basic_string<unsigned char> as the raw-byte counterpart to UTF-8
33
+ // std::string, but the standard has never defined std::char_traits for unsigned char. libc++ used
34
+ // to instantiate the primary template anyway; from the release NDK r30 ships it static_asserts
35
+ // instead, so merely naming the type stops compiling. Supplying the traits keeps the binding and
36
+ // costs nothing on the toolchains that did not need it.
37
+ namespace std {
38
+ template <>
39
+ struct char_traits<unsigned char> {
40
+ using char_type = unsigned char;
41
+ using int_type = int;
42
+ using off_type = streamoff;
43
+ using pos_type = streampos;
44
+ using state_type = mbstate_t;
45
+
46
+ static constexpr void assign(char_type& a, const char_type& b) noexcept { a = b; }
47
+ static constexpr bool eq(char_type a, char_type b) noexcept { return a == b; }
48
+ static constexpr bool lt(char_type a, char_type b) noexcept { return a < b; }
49
+
50
+ static int compare(const char_type* a, const char_type* b, size_t n) {
51
+ return n == 0 ? 0 : __builtin_memcmp(a, b, n);
52
+ }
53
+ static size_t length(const char_type* s) {
54
+ size_t n = 0;
55
+ while (s[n]) ++n;
56
+ return n;
57
+ }
58
+ static const char_type* find(const char_type* s, size_t n, const char_type& c) {
59
+ for (size_t i = 0; i < n; ++i)
60
+ if (s[i] == c) return s + i;
61
+ return nullptr;
62
+ }
63
+ static char_type* move(char_type* d, const char_type* s, size_t n) {
64
+ return n == 0 ? d : static_cast<char_type*>(__builtin_memmove(d, s, n));
65
+ }
66
+ static char_type* copy(char_type* d, const char_type* s, size_t n) {
67
+ return n == 0 ? d : static_cast<char_type*>(__builtin_memcpy(d, s, n));
68
+ }
69
+ static char_type* assign(char_type* d, size_t n, char_type c) {
70
+ for (size_t i = 0; i < n; ++i) d[i] = c;
71
+ return d;
72
+ }
73
+
74
+ static constexpr int_type not_eof(int_type c) noexcept { return eq_int_type(c, eof()) ? ~eof() : c; }
75
+ static constexpr char_type to_char_type(int_type c) noexcept { return char_type(c); }
76
+ static constexpr int_type to_int_type(char_type c) noexcept { return int_type(c); }
77
+ static constexpr bool eq_int_type(int_type a, int_type b) noexcept { return a == b; }
78
+ static constexpr int_type eof() noexcept { return int_type(EOF); }
79
+ };
80
+ } // namespace std
29
81
 
30
82
  // #include <android/log.h>
31
83
  // #define APPNAME "react-native-crossbind"
@@ -46,25 +98,40 @@ constexpr bool has_unbound_type_names = false;
46
98
 
47
99
  namespace internal {
48
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
+
49
120
  // JS hands native pointers over as BigInt addresses, but a null pointer arrives as the number 0
50
121
  // and enum values as plain numbers: take both spellings.
51
122
  inline uint64_t addressFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
52
123
  if (v.isNumber()) return static_cast<uint64_t>(v.asNumber());
53
124
  if (v.isNull() || v.isUndefined()) return 0;
54
- return v.asBigInt(rt).asUint64(rt);
125
+ return bigIntUint64(rt, v);
55
126
  }
56
127
 
57
128
  inline int64_t integerFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
58
129
  if (v.isNumber()) return static_cast<int64_t>(v.asNumber());
59
- return v.asBigInt(rt).asInt64(rt);
130
+ return bigIntInt64(rt, v);
60
131
  }
61
132
 
62
133
  typedef const void* TYPEID;
63
134
 
64
- // Defined in bind.cpp: registers a JS-side ArrayBuffer window covering `ptr` so
65
- // heap reads on values we hand out by pointer cannot miss every window.
66
- void ensureWindowFor(uint64_t ptr);
67
-
68
135
  // We don't need the full std::type_info implementation. We
69
136
  // just need a unique identifier per type and polymorphic type
70
137
  // identification.
@@ -346,7 +413,7 @@ struct BindingType<int64_t> {
346
413
  return facebook::jsi::BigInt::fromInt64(rt, b);
347
414
  }
348
415
  static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
349
- return wt.asBigInt(rt).asInt64(rt);
416
+ return bigIntInt64(rt, wt);
350
417
  }
351
418
  };
352
419
 
@@ -366,7 +433,33 @@ struct BindingType<uint64_t> {
366
433
  return facebook::jsi::BigInt::fromUint64(rt, b);
367
434
  }
368
435
  static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
369
- 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));
370
463
  }
371
464
  };
372
465
 
@@ -384,64 +477,80 @@ struct BindingType<uint64_t> {
384
477
  };
385
478
 
386
479
  // jsi strings cross as UTF-8, so a std::u16string (embind's byte-preserving string) is transcoded; every code unit of
387
- // well-formed UTF-16 survives the round trip.
480
+ // well-formed UTF-16 survives the round trip. Both directions size their output once and write through a pointer:
481
+ // a push_back per unit was most of the cost of moving bytes (readBytes) across.
388
482
  inline std::string utf16ToUtf8(const std::u16string& text) {
389
- std::string out;
390
- out.reserve(text.size());
391
- for (size_t i = 0; i < text.size(); ++i) {
483
+ // At most three bytes per code unit; a surrogate pair takes four for two.
484
+ std::string out(text.size() * 3, '\0');
485
+ char* o = out.data();
486
+ const size_t n = text.size();
487
+ for (size_t i = 0; i < n; ++i) {
392
488
  char32_t cp = text[i];
393
- if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < text.size() && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
489
+ if (cp < 0x800) {
490
+ // One byte or two, chosen without a branch: in byte data half the units are above 0x7F
491
+ // at random. The spare second byte stays inside the three reserved per unit.
492
+ const bool isTwoBytes = cp >= 0x80;
493
+ o[0] = static_cast<char>(isTwoBytes ? 0xC0 | (cp >> 6) : cp);
494
+ o[1] = static_cast<char>(0x80 | (cp & 0x3F));
495
+ o += 1 + isTwoBytes;
496
+ continue;
497
+ }
498
+ if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < n && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
394
499
  cp = 0x10000 + ((cp - 0xD800) << 10) + (text[++i] - 0xDC00);
395
500
  } else if (cp >= 0xD800 && cp <= 0xDFFF) {
396
501
  cp = 0xFFFD;
397
502
  }
398
- if (cp < 0x80) {
399
- out.push_back(static_cast<char>(cp));
400
- } else if (cp < 0x800) {
401
- out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
402
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
403
- } else if (cp < 0x10000) {
404
- out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
405
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
406
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
503
+ if (cp < 0x10000) {
504
+ *o++ = static_cast<char>(0xE0 | (cp >> 12));
505
+ *o++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
506
+ *o++ = static_cast<char>(0x80 | (cp & 0x3F));
407
507
  } else {
408
- out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
409
- out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
410
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
411
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
508
+ *o++ = static_cast<char>(0xF0 | (cp >> 18));
509
+ *o++ = static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
510
+ *o++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
511
+ *o++ = static_cast<char>(0x80 | (cp & 0x3F));
412
512
  }
413
513
  }
514
+ out.resize(static_cast<size_t>(o - out.data()));
414
515
  return out;
415
516
  }
416
517
 
417
518
  inline std::u16string utf8ToUtf16(const std::string& text) {
418
- std::u16string out;
419
- out.reserve(text.size());
519
+ // Never more code units than bytes.
520
+ std::u16string out(text.size(), u'\0');
521
+ char16_t* o = out.data();
522
+ const size_t n = text.size();
420
523
  size_t i = 0;
421
- while (i < text.size()) {
524
+ while (i < n) {
422
525
  unsigned char lead = static_cast<unsigned char>(text[i]);
423
- size_t extra = lead < 0x80 ? 0 : (lead >> 5) == 0x6 ? 1 : (lead >> 4) == 0xE ? 2 : (lead >> 3) == 0x1E ? 3 : 4;
424
- char32_t cp = extra == 0 ? lead : extra == 1 ? (lead & 0x1F) : extra == 2 ? (lead & 0x0F) : (lead & 0x07);
425
- bool valid = extra < 4 && i + extra < text.size();
526
+ if (lead < 0x80) {
527
+ *o++ = lead;
528
+ ++i;
529
+ continue;
530
+ }
531
+ size_t extra = (lead >> 5) == 0x6 ? 1 : (lead >> 4) == 0xE ? 2 : (lead >> 3) == 0x1E ? 3 : 4;
532
+ char32_t cp = extra == 1 ? (lead & 0x1F) : extra == 2 ? (lead & 0x0F) : (lead & 0x07);
533
+ bool valid = extra < 4 && i + extra < n;
426
534
  for (size_t k = 1; valid && k <= extra; ++k) {
427
535
  unsigned char next = static_cast<unsigned char>(text[i + k]);
428
536
  valid = (next >> 6) == 0x2;
429
537
  cp = (cp << 6) | (next & 0x3F);
430
538
  }
431
539
  if (!valid) {
432
- out.push_back(0xFFFD);
540
+ *o++ = 0xFFFD;
433
541
  ++i;
434
542
  continue;
435
543
  }
436
544
  i += extra + 1;
437
545
  if (cp >= 0x10000) {
438
546
  cp -= 0x10000;
439
- out.push_back(static_cast<char16_t>(0xD800 + (cp >> 10)));
440
- out.push_back(static_cast<char16_t>(0xDC00 + (cp & 0x3FF)));
547
+ *o++ = static_cast<char16_t>(0xD800 + (cp >> 10));
548
+ *o++ = static_cast<char16_t>(0xDC00 + (cp & 0x3FF));
441
549
  } else {
442
- out.push_back(static_cast<char16_t>(cp));
550
+ *o++ = static_cast<char16_t>(cp);
443
551
  }
444
552
  }
553
+ out.resize(static_cast<size_t>(o - out.data()));
445
554
  return out;
446
555
  }
447
556
 
@@ -471,11 +580,12 @@ struct BindingType<std::basic_string<T>> {
471
580
  return facebook::jsi::String::createFromUtf8(rt, v);
472
581
  }
473
582
  }
583
+ // asString, not getString: a non-string argument then throws into JS instead of being read as one.
474
584
  static String fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
475
585
  if constexpr (std::is_same_v<T, char16_t>) {
476
- return utf8ToUtf16(v.getString(rt).utf8(rt));
586
+ return utf8ToUtf16(v.asString(rt).utf8(rt));
477
587
  } else {
478
- return v.getString(rt).utf8(rt);
588
+ return v.asString(rt).utf8(rt);
479
589
  }
480
590
  }
481
591
  };
@@ -566,20 +676,14 @@ struct GenericBindingType {
566
676
  }
567
677
 
568
678
  static WireType2 toWireType2(facebook::jsi::Runtime& rt, const T& v) {
569
- // By-value returns allocate fresh heap blocks; without a covering window JS-side
570
- // memory reads on the new object die with "Heap error ... no covering window".
571
- uint64_t ptr = reinterpret_cast<uint64_t>(new T(v));
572
- ensureWindowFor(ptr);
573
- return facebook::jsi::BigInt::fromUint64(rt, ptr);
679
+ return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(v)));
574
680
  }
575
681
  static WireType2 toWireType2(facebook::jsi::Runtime& rt, T&& v) {
576
- uint64_t ptr = reinterpret_cast<uint64_t>(new T(std::forward<T>(v)));
577
- ensureWindowFor(ptr);
578
- return facebook::jsi::BigInt::fromUint64(rt, ptr);
682
+ return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(std::forward<T>(v))));
579
683
  }
580
684
 
581
685
  static ActualT& fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
582
- return *reinterpret_cast<WireType>(wt.getBigInt(rt).getUint64(rt));
686
+ return *reinterpret_cast<WireType>(bigIntUint64(rt, wt));
583
687
  }
584
688
  };
585
689
 
@@ -647,6 +751,46 @@ constexpr bool typeSupportsMemoryView() {
647
751
  sizeof(T) == 4 || sizeof(T) == 8));
648
752
  }
649
753
 
754
+ template<typename T>
755
+ constexpr const char* typedArrayNameOf() {
756
+ static_assert(typeSupportsMemoryView<T>(), "type does not map to a typed array");
757
+ if constexpr (std::is_floating_point<T>::value) {
758
+ return sizeof(T) == 4 ? "Float32Array" : "Float64Array";
759
+ } else if constexpr (sizeof(T) == 1) {
760
+ return std::is_signed<T>::value ? "Int8Array" : "Uint8Array";
761
+ } else if constexpr (sizeof(T) == 2) {
762
+ return std::is_signed<T>::value ? "Int16Array" : "Uint16Array";
763
+ } else if constexpr (sizeof(T) == 4) {
764
+ return std::is_signed<T>::value ? "Int32Array" : "Uint32Array";
765
+ } else {
766
+ return std::is_signed<T>::value ? "BigInt64Array" : "BigUint64Array";
767
+ }
768
+ }
769
+
770
+ // Native memory reaches JS only as a copy in an ArrayBuffer JS allocated: Electron's V8 memory
771
+ // cage refuses ArrayBuffers over external memory.
772
+ inline facebook::jsi::Value copyToTypedArray(
773
+ facebook::jsi::Runtime& rt, const char* arrayName, const void* data, size_t count, size_t bytes) {
774
+ facebook::jsi::Object array = rt.global().getPropertyAsFunction(rt, arrayName)
775
+ .callAsConstructor(rt, static_cast<double>(count)).asObject(rt);
776
+ if (bytes > 0) {
777
+ facebook::jsi::ArrayBuffer buffer = array.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt);
778
+ memcpy(buffer.data(rt), data, bytes);
779
+ }
780
+ return facebook::jsi::Value(std::move(array));
781
+ }
782
+
783
+ inline void copyFromTypedArray(facebook::jsi::Runtime& rt, const facebook::jsi::Value& typedArray, void* data, size_t bytes) {
784
+ if (bytes == 0) return;
785
+ facebook::jsi::Object view = typedArray.asObject(rt);
786
+ size_t offset = static_cast<size_t>(view.getProperty(rt, "byteOffset").asNumber());
787
+ facebook::jsi::ArrayBuffer buffer = view.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt);
788
+ if (offset > buffer.size(rt) || bytes > buffer.size(rt) - offset) {
789
+ throw facebook::jsi::JSINativeException("crossbind: the typed array is smaller than the native buffer it fills");
790
+ }
791
+ memcpy(data, buffer.data(rt) + offset, bytes);
792
+ }
793
+
650
794
  } // namespace internal
651
795
 
652
796
  template<typename ElementType>
@@ -684,9 +828,13 @@ struct BindingType<memory_view<ElementType>> {
684
828
  // on the C++ side, nor is toWireType implemented in
685
829
  // JavaScript.)
686
830
  typedef memory_view<ElementType> WireType;
831
+ typedef const facebook::jsi::Value WireType2;
687
832
  static WireType toWireType(const memory_view<ElementType>& mv) {
688
833
  return mv;
689
834
  }
835
+ static WireType2 toWireType2(facebook::jsi::Runtime& rt, const memory_view<ElementType>& mv) {
836
+ return copyToTypedArray(rt, typedArrayNameOf<ElementType>(), mv.data, mv.size, mv.size * sizeof(ElementType));
837
+ }
690
838
  };
691
839
 
692
840
  } // namespace internal