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

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.
@@ -172,7 +172,7 @@ struct GenericWireTypeConverter {
172
172
  template<typename Pointee>
173
173
  struct GenericWireTypeConverter<Pointee*> {
174
174
  static Pointee* from(facebook::jsi::Value& wt) {
175
- return reinterpret_cast<Pointee*>(wt.asBigInt(*jsRuntime).asUint64(*jsRuntime));
175
+ return reinterpret_cast<Pointee*>(addressFromValue(*jsRuntime, wt));
176
176
  }
177
177
  };
178
178
 
@@ -20,6 +20,7 @@
20
20
 
21
21
  #include <cstdio>
22
22
  #include <cstdlib>
23
+ #include <cstdint>
23
24
  #include <memory>
24
25
  #include <optional>
25
26
  #include <string>
@@ -45,6 +46,19 @@ constexpr bool has_unbound_type_names = false;
45
46
 
46
47
  namespace internal {
47
48
 
49
+ // JS hands native pointers over as BigInt addresses, but a null pointer arrives as the number 0
50
+ // and enum values as plain numbers: take both spellings.
51
+ inline uint64_t addressFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
52
+ if (v.isNumber()) return static_cast<uint64_t>(v.asNumber());
53
+ if (v.isNull() || v.isUndefined()) return 0;
54
+ return v.asBigInt(rt).asUint64(rt);
55
+ }
56
+
57
+ inline int64_t integerFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
58
+ if (v.isNumber()) return static_cast<int64_t>(v.asNumber());
59
+ return v.asBigInt(rt).asInt64(rt);
60
+ }
61
+
48
62
  typedef const void* TYPEID;
49
63
 
50
64
  // Defined in bind.cpp: registers a JS-side ArrayBuffer window covering `ptr` so
@@ -369,6 +383,68 @@ struct BindingType<uint64_t> {
369
383
  }
370
384
  };
371
385
 
386
+ // 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.
388
+ 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) {
392
+ char32_t cp = text[i];
393
+ if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < text.size() && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
394
+ cp = 0x10000 + ((cp - 0xD800) << 10) + (text[++i] - 0xDC00);
395
+ } else if (cp >= 0xD800 && cp <= 0xDFFF) {
396
+ cp = 0xFFFD;
397
+ }
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)));
407
+ } 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)));
412
+ }
413
+ }
414
+ return out;
415
+ }
416
+
417
+ inline std::u16string utf8ToUtf16(const std::string& text) {
418
+ std::u16string out;
419
+ out.reserve(text.size());
420
+ size_t i = 0;
421
+ while (i < text.size()) {
422
+ 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();
426
+ for (size_t k = 1; valid && k <= extra; ++k) {
427
+ unsigned char next = static_cast<unsigned char>(text[i + k]);
428
+ valid = (next >> 6) == 0x2;
429
+ cp = (cp << 6) | (next & 0x3F);
430
+ }
431
+ if (!valid) {
432
+ out.push_back(0xFFFD);
433
+ ++i;
434
+ continue;
435
+ }
436
+ i += extra + 1;
437
+ if (cp >= 0x10000) {
438
+ cp -= 0x10000;
439
+ out.push_back(static_cast<char16_t>(0xD800 + (cp >> 10)));
440
+ out.push_back(static_cast<char16_t>(0xDC00 + (cp & 0x3FF)));
441
+ } else {
442
+ out.push_back(static_cast<char16_t>(cp));
443
+ }
444
+ }
445
+ return out;
446
+ }
447
+
372
448
  template<typename T>
373
449
  struct BindingType<std::basic_string<T>> {
374
450
  using String = std::basic_string<T>;
@@ -389,10 +465,18 @@ struct BindingType<std::basic_string<T>> {
389
465
  }
390
466
 
391
467
  static WireType2 toWireType2(facebook::jsi::Runtime& rt, String v) {
392
- return facebook::jsi::String::createFromUtf8(rt, v);
468
+ if constexpr (std::is_same_v<T, char16_t>) {
469
+ return facebook::jsi::String::createFromUtf8(rt, utf16ToUtf8(v));
470
+ } else {
471
+ return facebook::jsi::String::createFromUtf8(rt, v);
472
+ }
393
473
  }
394
474
  static String fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
395
- return v.getString(rt).utf8(rt);
475
+ if constexpr (std::is_same_v<T, char16_t>) {
476
+ return utf8ToUtf16(v.getString(rt).utf8(rt));
477
+ } else {
478
+ return v.getString(rt).utf8(rt);
479
+ }
396
480
  }
397
481
  };
398
482
 
@@ -459,7 +543,7 @@ struct BindingType<T*> {
459
543
  return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(b));
460
544
  }
461
545
  static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
462
- return (WireType) wt.asBigInt(rt).asUint64(rt);
546
+ return (WireType) addressFromValue(rt, wt);
463
547
  }
464
548
  };
465
549
 
@@ -537,7 +621,7 @@ struct EnumBindingType {
537
621
  return WireType2((int) v);
538
622
  }
539
623
  static Enum fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
540
- return (WireType) v.asBigInt(rt).asUint64(rt);
624
+ return (WireType) integerFromValue(rt, v);
541
625
  }
542
626
  };
543
627
 
package/js/embind.js CHANGED
@@ -1411,10 +1411,10 @@ function __embind_register_optional(rawOptionalType, rawInnerType) {
1411
1411
  var inner = innerTypes[0];
1412
1412
  return [{
1413
1413
  name: `std::optional<${inner.name}>`,
1414
- // No value crosses as undefined (JS may also hand back null); a present value
1414
+ // No value crosses as null (a wire of undefined reads the same); a present value
1415
1415
  // rides the inner type's own converters unchanged.
1416
1416
  'fromWireType': function (wt) {
1417
- if (wt === undefined || wt === null) return undefined;
1417
+ if (wt === undefined || wt === null) return null;
1418
1418
  return inner['fromWireType'](wt);
1419
1419
  },
1420
1420
  'toWireType': function (destructors, o) {
@@ -1547,6 +1547,11 @@ function __embind_register_bigint(primitiveType, name, size, minRange, maxRange)
1547
1547
  if (typeof value != "bigint" && typeof value != "number") {
1548
1548
  throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${this.name}`);
1549
1549
  }
1550
+ // A Number crosses only while it is an exact integer: BigInt(2**53 + 1) would round
1551
+ // silently, and a 64-bit slot deserves the exact value (the wasm glue gets the same rule).
1552
+ if (typeof value == "number" && !Number.isSafeInteger(value)) {
1553
+ throw new TypeError(`a 64-bit integer parameter takes a BigInt or a safe integer Number, got ${embindRepr(value)}`);
1554
+ }
1550
1555
  if (value < minRange || value > maxRange) {
1551
1556
  throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`);
1552
1557
  }
@@ -2006,66 +2011,17 @@ function lengthBytesUTF32(str) {
2006
2011
  }
2007
2012
  function __embind_register_std_wstring(rawType, charSize, name) {
2008
2013
  name = readLatin1String(name);
2009
- var decodeString, encodeString, getHeap, lengthBytesUTF, shift;
2010
- if (charSize === 2) {
2011
- decodeString = UTF16ToString;
2012
- encodeString = stringToUTF16;
2013
- lengthBytesUTF = lengthBytesUTF16;
2014
- getHeap = () => HEAPU16;
2015
- shift = 1;
2016
- } else if (charSize === 4) {
2017
- decodeString = UTF32ToString;
2018
- encodeString = stringToUTF32;
2019
- lengthBytesUTF = lengthBytesUTF32;
2020
- getHeap = () => HEAPU32;
2021
- shift = 2;
2022
- }
2023
2014
  registerType(rawType, {
2024
2015
  name: name,
2016
+ // The C++ BindingType turns the jsi string into the C++ string and back (wire.h), as it does for std::string.
2025
2017
  'fromWireType': function (value) {
2026
- // Code mostly taken from _embind_register_std_string fromWireType
2027
- var length = HEAPU32[value >> 2];
2028
- var HEAP = getHeap();
2029
- var str;
2030
-
2031
- var decodeStartPtr = value + 4;
2032
- // Looping here to support possible embedded '0' bytes
2033
- for (var i = 0; i <= length; ++i) {
2034
- var currentBytePtr = value + 4 + i * charSize;
2035
- if (i == length || HEAP[currentBytePtr >> shift] == 0) {
2036
- var maxReadBytes = currentBytePtr - decodeStartPtr;
2037
- var stringSegment = decodeString(decodeStartPtr, maxReadBytes);
2038
- if (str === undefined) {
2039
- str = stringSegment;
2040
- } else {
2041
- str += String.fromCharCode(0);
2042
- str += stringSegment;
2043
- }
2044
- decodeStartPtr = currentBytePtr + charSize;
2045
- }
2046
- }
2047
-
2048
- _free(value);
2049
-
2050
- return str;
2018
+ return value;
2051
2019
  },
2052
2020
  'toWireType': function (destructors, value) {
2053
2021
  if (!(typeof value == 'string')) {
2054
2022
  throwBindingError('Cannot pass non-string to C++ string type ' + name);
2055
2023
  }
2056
- // console.log('registerType toWireType');
2057
-
2058
- // assumes 4-byte alignment
2059
- var length = lengthBytesUTF(value);
2060
- var ptr = _malloc(4 + length + charSize);
2061
- HEAPU32[ptr >> 2] = length >> shift;
2062
-
2063
- encodeString(value, ptr + 4, length + charSize);
2064
-
2065
- if (destructors !== null) {
2066
- destructors.push(_free, ptr);
2067
- }
2068
- return ptr;
2024
+ return value;
2069
2025
  },
2070
2026
  'argPackAdvance': 8n,
2071
2027
  'readValueFromPointer': simpleReadValueFromPointer,
@@ -2192,7 +2148,13 @@ function __crossbind_json_to_handle(text) {
2192
2148
  globalThis.__crossbind_json_to_handle = __crossbind_json_to_handle;
2193
2149
 
2194
2150
  function __crossbind_handle_to_json(handle) {
2195
- const s = JSON.stringify(Emval.toValue(handle));
2151
+ // A Set or Map has no JSON form of its own: write it as the array or object the Rust side
2152
+ // expects, the same rule the web adapter's replacer follows.
2153
+ const s = JSON.stringify(Emval.toValue(handle), (key, value) => {
2154
+ if (value instanceof Set) return Array.from(value);
2155
+ if (value instanceof Map) return Object.fromEntries(value);
2156
+ return value;
2157
+ });
2196
2158
  __emval_decref(handle);
2197
2159
  return s === undefined ? 'null' : s;
2198
2160
  }
@@ -2215,6 +2177,18 @@ function __crossbind_v_from_num(v) {
2215
2177
  }
2216
2178
  globalThis.__crossbind_v_from_num = __crossbind_v_from_num;
2217
2179
 
2180
+ // A handle's value as a plain array, so the adapter can copy a typed array element by element.
2181
+ function __crossbind_v_to_array(h) {
2182
+ return Array.from(Emval.toValue(h));
2183
+ }
2184
+ globalThis.__crossbind_v_to_array = __crossbind_v_to_array;
2185
+
2186
+ // Any JS value the adapter already built (a typed array, say) gets a handle of its own.
2187
+ function __crossbind_v_from_value(v) {
2188
+ return Emval.toHandle(v);
2189
+ }
2190
+ globalThis.__crossbind_v_from_value = __crossbind_v_from_value;
2191
+
2218
2192
  function __crossbind_v_from_bool(v) {
2219
2193
  return Emval.toHandle(!!v);
2220
2194
  }
@@ -2507,13 +2481,30 @@ function replacePublicSymbol(name, value, numArguments) {
2507
2481
 
2508
2482
 
2509
2483
 
2484
+ // A Rust binding cannot throw through Hermes: an exception escaping a host function comes back
2485
+ // as "Exception in HostFunction: ...", with the message rewritten and any property (the error
2486
+ // `code`) gone. The adapter parks the error here instead, and every invoker rethrows it on the
2487
+ // way out, so JS sees an Error this runtime built.
2488
+ globalThis.__crossbind_park_error = function (message, code) {
2489
+ var error = new Error(message);
2490
+ if (code !== undefined) error.code = code;
2491
+ globalThis.__crossbindParkedError = error;
2492
+ };
2493
+
2510
2494
  function getDynCaller(signature, rawFunction, slice) {
2511
2495
  return (...args) => {
2512
2496
  //console.log('getDynCallerReturn', signature, rawFunction, args.length, ...args);
2513
2497
  if (slice) {
2514
2498
  args = args.slice(1);
2515
2499
  }
2516
- return rawFunction(...args);
2500
+ globalThis.__crossbindParkedError = null;
2501
+ const result = rawFunction(...args);
2502
+ const parked = globalThis.__crossbindParkedError;
2503
+ if (parked) {
2504
+ globalThis.__crossbindParkedError = null;
2505
+ throw parked;
2506
+ }
2507
+ return result;
2517
2508
  };
2518
2509
  }
2519
2510
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossbind/core-embind-jsi",
3
- "version": "2.0.0-beta.56",
3
+ "version": "2.0.0-beta.58",
4
4
  "description": "The Embind JSI integration tool enables seamless C++ integration with React Native and Expo.",
5
5
  "homepage": "https://github.com/crossbind/crossbind/tree/main/core/embind-jsi#readme",
6
6
  "repository": {