@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.
- package/CHANGELOG.md +10 -0
- package/cpp/src/crossbind/direct_reads.h +30 -0
- package/cpp/src/emscripten/bind.cpp +129 -175
- package/cpp/src/emscripten/bind.h +45 -45
- package/cpp/src/emscripten/val.h +57 -146
- package/cpp/src/emscripten/wire.h +195 -47
- package/js/embind.js +124 -756
- package/package.json +9 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
390
|
-
out
|
|
391
|
-
|
|
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
|
|
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 <
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
419
|
-
out
|
|
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 <
|
|
524
|
+
while (i < n) {
|
|
422
525
|
unsigned char lead = static_cast<unsigned char>(text[i]);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
547
|
+
*o++ = static_cast<char16_t>(0xD800 + (cp >> 10));
|
|
548
|
+
*o++ = static_cast<char16_t>(0xDC00 + (cp & 0x3FF));
|
|
441
549
|
} else {
|
|
442
|
-
|
|
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.
|
|
586
|
+
return utf8ToUtf16(v.asString(rt).utf8(rt));
|
|
477
587
|
} else {
|
|
478
|
-
return v.
|
|
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
|
-
|
|
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
|
-
|
|
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>(
|
|
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
|