@crossbind/core-embind-jsi 2.0.0-beta.60 → 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/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 +145 -47
- package/js/embind.js +124 -756
- package/package.json +9 -1
|
@@ -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
|
|
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
|
|
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
|
|
416
|
+
return bigIntInt64(rt, wt);
|
|
400
417
|
}
|
|
401
418
|
};
|
|
402
419
|
|
|
@@ -416,7 +433,33 @@ 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
|
|
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));
|
|
420
463
|
}
|
|
421
464
|
};
|
|
422
465
|
|
|
@@ -434,64 +477,80 @@ struct BindingType<uint64_t> {
|
|
|
434
477
|
};
|
|
435
478
|
|
|
436
479
|
// 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.
|
|
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.
|
|
438
482
|
inline std::string utf16ToUtf8(const std::u16string& text) {
|
|
439
|
-
|
|
440
|
-
out
|
|
441
|
-
|
|
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) {
|
|
442
488
|
char32_t cp = text[i];
|
|
443
|
-
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) {
|
|
444
499
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (text[++i] - 0xDC00);
|
|
445
500
|
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
|
|
446
501
|
cp = 0xFFFD;
|
|
447
502
|
}
|
|
448
|
-
if (cp <
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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)));
|
|
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));
|
|
457
507
|
} else {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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));
|
|
462
512
|
}
|
|
463
513
|
}
|
|
514
|
+
out.resize(static_cast<size_t>(o - out.data()));
|
|
464
515
|
return out;
|
|
465
516
|
}
|
|
466
517
|
|
|
467
518
|
inline std::u16string utf8ToUtf16(const std::string& text) {
|
|
468
|
-
|
|
469
|
-
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();
|
|
470
523
|
size_t i = 0;
|
|
471
|
-
while (i <
|
|
524
|
+
while (i < n) {
|
|
472
525
|
unsigned char lead = static_cast<unsigned char>(text[i]);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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;
|
|
476
534
|
for (size_t k = 1; valid && k <= extra; ++k) {
|
|
477
535
|
unsigned char next = static_cast<unsigned char>(text[i + k]);
|
|
478
536
|
valid = (next >> 6) == 0x2;
|
|
479
537
|
cp = (cp << 6) | (next & 0x3F);
|
|
480
538
|
}
|
|
481
539
|
if (!valid) {
|
|
482
|
-
|
|
540
|
+
*o++ = 0xFFFD;
|
|
483
541
|
++i;
|
|
484
542
|
continue;
|
|
485
543
|
}
|
|
486
544
|
i += extra + 1;
|
|
487
545
|
if (cp >= 0x10000) {
|
|
488
546
|
cp -= 0x10000;
|
|
489
|
-
|
|
490
|
-
|
|
547
|
+
*o++ = static_cast<char16_t>(0xD800 + (cp >> 10));
|
|
548
|
+
*o++ = static_cast<char16_t>(0xDC00 + (cp & 0x3FF));
|
|
491
549
|
} else {
|
|
492
|
-
|
|
550
|
+
*o++ = static_cast<char16_t>(cp);
|
|
493
551
|
}
|
|
494
552
|
}
|
|
553
|
+
out.resize(static_cast<size_t>(o - out.data()));
|
|
495
554
|
return out;
|
|
496
555
|
}
|
|
497
556
|
|
|
@@ -521,11 +580,12 @@ struct BindingType<std::basic_string<T>> {
|
|
|
521
580
|
return facebook::jsi::String::createFromUtf8(rt, v);
|
|
522
581
|
}
|
|
523
582
|
}
|
|
583
|
+
// asString, not getString: a non-string argument then throws into JS instead of being read as one.
|
|
524
584
|
static String fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
|
|
525
585
|
if constexpr (std::is_same_v<T, char16_t>) {
|
|
526
|
-
return utf8ToUtf16(v.
|
|
586
|
+
return utf8ToUtf16(v.asString(rt).utf8(rt));
|
|
527
587
|
} else {
|
|
528
|
-
return v.
|
|
588
|
+
return v.asString(rt).utf8(rt);
|
|
529
589
|
}
|
|
530
590
|
}
|
|
531
591
|
};
|
|
@@ -616,20 +676,14 @@ struct GenericBindingType {
|
|
|
616
676
|
}
|
|
617
677
|
|
|
618
678
|
static WireType2 toWireType2(facebook::jsi::Runtime& rt, const T& v) {
|
|
619
|
-
|
|
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);
|
|
679
|
+
return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(v)));
|
|
624
680
|
}
|
|
625
681
|
static WireType2 toWireType2(facebook::jsi::Runtime& rt, T&& v) {
|
|
626
|
-
|
|
627
|
-
ensureWindowFor(ptr);
|
|
628
|
-
return facebook::jsi::BigInt::fromUint64(rt, ptr);
|
|
682
|
+
return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(new T(std::forward<T>(v))));
|
|
629
683
|
}
|
|
630
684
|
|
|
631
685
|
static ActualT& fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
|
|
632
|
-
return *reinterpret_cast<WireType>(
|
|
686
|
+
return *reinterpret_cast<WireType>(bigIntUint64(rt, wt));
|
|
633
687
|
}
|
|
634
688
|
};
|
|
635
689
|
|
|
@@ -697,6 +751,46 @@ constexpr bool typeSupportsMemoryView() {
|
|
|
697
751
|
sizeof(T) == 4 || sizeof(T) == 8));
|
|
698
752
|
}
|
|
699
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
|
+
|
|
700
794
|
} // namespace internal
|
|
701
795
|
|
|
702
796
|
template<typename ElementType>
|
|
@@ -734,9 +828,13 @@ struct BindingType<memory_view<ElementType>> {
|
|
|
734
828
|
// on the C++ side, nor is toWireType implemented in
|
|
735
829
|
// JavaScript.)
|
|
736
830
|
typedef memory_view<ElementType> WireType;
|
|
831
|
+
typedef const facebook::jsi::Value WireType2;
|
|
737
832
|
static WireType toWireType(const memory_view<ElementType>& mv) {
|
|
738
833
|
return mv;
|
|
739
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
|
+
}
|
|
740
838
|
};
|
|
741
839
|
|
|
742
840
|
} // namespace internal
|