peachsim 0.1.2224 → 0.1.2225

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.
Files changed (146) hide show
  1. package/cli/internal-child.ts +4 -2
  2. package/dist-lib/agent-daemon-client.cjs +1 -1
  3. package/dist-lib/agent-events.cjs +1 -1
  4. package/dist-lib/attached-projects.cjs +1 -1
  5. package/dist-lib/auth/shared-session.cjs +1 -1
  6. package/dist-lib/backend-origin.cjs +1 -1
  7. package/dist-lib/beta.cjs +1 -1
  8. package/dist-lib/beta.mjs +1 -1
  9. package/dist-lib/bridge-constants.cjs +1 -1
  10. package/dist-lib/bridge-contract-input.cjs +1 -1
  11. package/dist-lib/bridge-contract-input.mjs +1 -1
  12. package/dist-lib/bridge-contract.cjs +1 -1
  13. package/dist-lib/bridge-contract.mjs +1 -1
  14. package/dist-lib/bundle-channel.cjs +858 -451
  15. package/dist-lib/capture-contract.cjs +1 -1
  16. package/dist-lib/capture-contract.mjs +1 -1
  17. package/dist-lib/cloud-contract.cjs +1 -1
  18. package/dist-lib/cloud-contract.mjs +1 -1
  19. package/dist-lib/cloud-test-args.cjs +1 -1
  20. package/dist-lib/cloud-test-args.mjs +1 -1
  21. package/dist-lib/cloud.cjs +1 -1
  22. package/dist-lib/cloud.mjs +1 -1
  23. package/dist-lib/config.cjs +1 -1
  24. package/dist-lib/config.mjs +1 -1
  25. package/dist-lib/detox/index.cjs +1 -1
  26. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  27. package/dist-lib/expo-swift-bundle.cjs +795 -388
  28. package/dist-lib/expo-swift-metadata.cjs +1 -1
  29. package/dist-lib/expo-swift-metadata.mjs +1 -1
  30. package/dist-lib/home-paths.cjs +1 -1
  31. package/dist-lib/host/bridge-host.cjs +853 -446
  32. package/dist-lib/host/fetch-proxy-handler.cjs +54 -11
  33. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  35. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  36. package/dist-lib/host/websocket-proxy.cjs +1 -1
  37. package/dist-lib/index.cjs +59 -15
  38. package/dist-lib/jump-to-source-babel.cjs +1 -1
  39. package/dist-lib/jump-to-source-native.cjs +1 -1
  40. package/dist-lib/kotlin-host.cjs +1 -1
  41. package/dist-lib/menu.cjs +1 -1
  42. package/dist-lib/menu.mjs +1 -1
  43. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  44. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  45. package/dist-lib/metro-production-bundle.cjs +1 -1
  46. package/dist-lib/metro-production-bundle.mjs +1 -1
  47. package/dist-lib/metro.cjs +59 -15
  48. package/dist-lib/native-app-identity-project.cjs +53 -9
  49. package/dist-lib/native-app-identity-project.mjs +84 -37
  50. package/dist-lib/native-app-identity.cjs +1 -1
  51. package/dist-lib/native-app-identity.mjs +1 -1
  52. package/dist-lib/native-firebase-metadata.cjs +1 -1
  53. package/dist-lib/native-firebase-metadata.mjs +1 -1
  54. package/dist-lib/pdfium-raster.cjs +1 -1
  55. package/dist-lib/pdfium-raster.mjs +1 -1
  56. package/dist-lib/plist.cjs +1 -1
  57. package/dist-lib/plist.mjs +1 -1
  58. package/dist-lib/profiles.cjs +1 -1
  59. package/dist-lib/public-brand.cjs +1 -1
  60. package/dist-lib/public-origins-lib-esm.json +26 -24
  61. package/dist-lib/public-origins-lib.json +65 -54
  62. package/dist-lib/react-native-host-modules.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.mjs +1 -1
  64. package/dist-lib/reap-browser.cjs +1 -1
  65. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  66. package/dist-lib/sdk.cjs +1 -1
  67. package/dist-lib/sdk.mjs +1 -1
  68. package/dist-lib/skills.cjs +901 -494
  69. package/dist-lib/swift-compile.cjs +491 -119
  70. package/dist-lib/swift-compile.mjs +629 -257
  71. package/dist-lib/swift-package.cjs +1 -1
  72. package/dist-lib/swift-package.mjs +1 -1
  73. package/dist-lib/swift-submit.cjs +1 -1
  74. package/dist-lib/swift-submit.mjs +1 -1
  75. package/dist-lib/swift-toolchain.cjs +38 -2
  76. package/dist-lib/swift.cjs +1 -1
  77. package/dist-lib/vite-swift.cjs +1654 -1206
  78. package/dist-lib/vite.cjs +54 -11
  79. package/dist-lib/worklet-executable.cjs +1 -1
  80. package/dist-lib/worklet-executable.mjs +1 -1
  81. package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +1 -1
  82. package/dist-types/packages/peach-engine/src/preview/preview-room-contract.d.ts +6 -0
  83. package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +12 -0
  84. package/kotlin/sdk.tar.gz +0 -0
  85. package/package.json +1 -1
  86. package/sdk/ios/System/Library/Frameworks/CoreFoundation.framework/Headers/CFCGTypes.h +0 -6
  87. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCharacterSet.h +1 -1
  88. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSURL.h +1 -1
  89. package/sdk/ios/usr/include/AvailabilityInternal.h +2 -2
  90. package/sdk/ios/usr/include/peach/pthread.h +1 -7
  91. package/sdk/ios/wasm/include/TargetConditionals.h +2 -3
  92. package/sdk/ios/wasm/include/__struct_timeval.h +15 -0
  93. package/sdk/ios/wasm/include/__typedef_suseconds_t.h +9 -0
  94. package/sdk/ios/wasm/include/limits.h +1 -1
  95. package/sdk/ios/wasm/include/machine/_types.h +1 -1
  96. package/sdk/ios/wasm/include/machine/limits.h +1 -1
  97. package/sdk/ios/wasm/include/machine/param.h +2 -2
  98. package/sdk/ios/wasm/include/math.h +13 -0
  99. package/sdk/ios/wasm/include/pthread.h +1 -13
  100. package/sdk/ios/wasm/include/sys/resource.h +3 -0
  101. package/sdk/ios/wasm/include/sys/socket.h +0 -10
  102. package/sdk/ios/wasm/include/sys/stat.h +5 -2
  103. package/sdk/ios/wasm/include/sys/time.h +16 -3
  104. package/sdk/ios/wasm/include/sys/types.h +4 -1
  105. package/sdk/ios/wasm/include/time.h +2 -0
  106. package/sdk/ios/wasm/storage-include/TargetConditionals.h +2 -3
  107. package/sdk/native-hashes.json +14 -14
  108. package/src/native-slices.ts +3 -2
  109. package/src/native-static-libraries.ts +23 -23
  110. package/src/native-swift-slices.ts +2 -2
  111. package/src/nitro-adapter.lock.json +288 -288
  112. package/src/nitro-compiler-digest.ts +0 -1
  113. package/src/nitro-dependencies.ts +8 -9
  114. package/src/nitro-superset.lock.json +4 -4
  115. package/src/react-native-core-build.ts +2 -2
  116. package/src/swift-compile.ts +132 -45
  117. package/src/swift-compiler-digest.ts +2 -0
  118. package/src/swift-cxx-standin.ts +1 -1
  119. package/src/swift-darwin-records.ts +4 -4
  120. package/src/swift-distribution-contract.ts +2 -2
  121. package/src/swift-distribution.lock.json +13 -12
  122. package/src/swift-guest-compile.ts +6 -5
  123. package/src/swift-guest-image-ledger.ts +5 -5
  124. package/src/swift-guest-link.ts +1 -1
  125. package/src/swift-guest-linked-spans.ts +7 -4
  126. package/src/swift-guest-manifest.ts +2 -2
  127. package/src/swift-guest-source-inputs.ts +16 -5
  128. package/src/swift-host.ts +10 -5
  129. package/src/swift-icu.ts +1 -1
  130. package/src/swift-objc-compile.ts +9 -4
  131. package/src/swift-objc-standin.ts +1 -4
  132. package/src/swift-package-manifest.ts +5 -1
  133. package/src/swift-packages.ts +2 -2
  134. package/src/swift-shared-sdk.ts +1 -1
  135. package/src/swift-standin-emit.ts +2 -16
  136. package/src/swift-standins.ts +111 -87
  137. package/src/swift-toolchain.ts +37 -1
  138. package/src/swift-uniform-type-constants.ts +1 -1
  139. package/src/swift-wasm-prune.ts +17 -12
  140. package/src/swiftGuestImageContract.ts +4 -4
  141. package/src/swiftNativePinContract.ts +1 -1
  142. package/src/swiftRuntimeProductContract.ts +1 -1
  143. package/src/turbo-runtime.lock.json +28 -28
  144. package/src/wasm-narrow-boundary.ts +296 -0
  145. package/src/wasm-optimize.ts +59 -14
  146. package/src/xcode-project.ts +6 -4
@@ -67,7 +67,10 @@ function encodeULEB(val: number): number[] {
67
67
  return out
68
68
  }
69
69
 
70
- const HAS_ADDEND = new Set([3, 4, 5, 8, 9, 11, 13, 14, 15, 16, 21, 22, 23, 24, 25])
70
+ // llvm's relocTypeHasAddend: memory addresses, function and section offsets.
71
+ const HAS_ADDEND = new Set([3, 4, 5, 8, 9, 11, 14, 15, 16, 17, 21, 22, 23, 25])
72
+ // the 64-bit address, table index and function offset relocations patch eight bytes.
73
+ const WIDE_DATA_RELOCS = new Set([16, 19, 22])
71
74
 
72
75
  const ROOT_SEG_PREFIXES = [
73
76
  '.init_array',
@@ -1254,7 +1257,7 @@ export function pruneSwiftWasmLink(
1254
1257
  } else if (
1255
1258
  !inStartLib &&
1256
1259
  t.endsWith('.a') &&
1257
- !t.endsWith('libclang_rt.builtins-wasm32.a') &&
1260
+ !t.endsWith('libclang_rt.builtins-wasm64.a') &&
1258
1261
  fs.existsSync(t)
1259
1262
  ) {
1260
1263
  const members = readArMembers(new Uint8Array(fs.readFileSync(t)))
@@ -1841,7 +1844,12 @@ export function pruneSwiftWasmLink(
1841
1844
  }
1842
1845
 
1843
1846
  const keptDataRelocBytes: Uint8Array[] = []
1844
- const zeroDataRelocOffsets: number[] = []
1847
+ const zeroDataRelocs: { ro: number; width: number }[] = []
1848
+ const zeroDataReloc = (r: { ro: number; rawBytes: Uint8Array }) =>
1849
+ zeroDataRelocs.push({
1850
+ ro: r.ro,
1851
+ width: WIDE_DATA_RELOCS.has(r.rawBytes[0]!) ? 8 : 4,
1852
+ })
1845
1853
  const usedSyms = new Set<number>()
1846
1854
  // a type index relocation carries a type index, not a symbol index
1847
1855
  for (const r of keptCodeRelocs) if (r.rawBytes[0] !== 6) usedSyms.add(r.rs)
@@ -1854,7 +1862,7 @@ export function pruneSwiftWasmLink(
1854
1862
  keptDataRelocBytes.push(r.rawBytes)
1855
1863
  usedSyms.add(r.rs)
1856
1864
  } else if (sName === '__objc_classes') {
1857
- zeroDataRelocOffsets.push(r.ro)
1865
+ zeroDataReloc(r)
1858
1866
  } else {
1859
1867
  const repl = firstLiveInSpecialSeg.get(r.segIdx)!
1860
1868
  const roBytes = encodeULEB(r.ro)
@@ -1867,13 +1875,13 @@ export function pruneSwiftWasmLink(
1867
1875
  } else if (r.ownerSymIdx !== -1) {
1868
1876
  if (dLive.has(r.ownerSymIdx)) {
1869
1877
  if (obj.isDist && dropsReflection(obj, obj.symNames[r.rs])) {
1870
- zeroDataRelocOffsets.push(r.ro)
1878
+ zeroDataReloc(r)
1871
1879
  } else {
1872
1880
  keptDataRelocBytes.push(r.rawBytes)
1873
1881
  usedSyms.add(r.rs)
1874
1882
  }
1875
1883
  } else if (sName === '__objc_class_refs') {
1876
- zeroDataRelocOffsets.push(r.ro)
1884
+ zeroDataReloc(r)
1877
1885
  }
1878
1886
  } else if (sLive[r.segIdx] === 1) {
1879
1887
  if (
@@ -1881,7 +1889,7 @@ export function pruneSwiftWasmLink(
1881
1889
  obj.symNames[r.rs]?.startsWith('l__swift5_reflection_descriptor') &&
1882
1890
  dropsReflection(obj, obj.symNames[r.rs])
1883
1891
  ) {
1884
- zeroDataRelocOffsets.push(r.ro)
1892
+ zeroDataReloc(r)
1885
1893
  } else {
1886
1894
  keptDataRelocBytes.push(r.rawBytes)
1887
1895
  usedSyms.add(r.rs)
@@ -2096,11 +2104,8 @@ export function pruneSwiftWasmLink(
2096
2104
  outChunks.push(buf.subarray(secStart, end))
2097
2105
  }
2098
2106
  } else {
2099
- if (id === 11 && zeroDataRelocOffsets.length > 0) {
2100
- for (const ro of zeroDataRelocOffsets) {
2101
- buf.fill(0, p + ro, p + ro + 4)
2102
- }
2103
- }
2107
+ if (id === 11)
2108
+ for (const { ro, width } of zeroDataRelocs) buf.fill(0, p + ro, p + ro + width)
2104
2109
  outChunks.push(buf.subarray(secStart, end))
2105
2110
  }
2106
2111
  pos = end
@@ -24,8 +24,8 @@ export interface SwiftGuestImageManifest {
24
24
  installName: string
25
25
  fileType: 'executable' | 'dylib'
26
26
  target: {
27
- triple: 'wasm32-unknown-wasip1'
28
- pointerWidth: 32
27
+ triple: 'wasm64-unknown-wasip1'
28
+ pointerWidth: 64
29
29
  byteOrder: 'little'
30
30
  cpuType: -1
31
31
  cpuSubtype: 0
@@ -66,8 +66,8 @@ export function isSwiftGuestImageManifest(
66
66
  !text(value.installName) ||
67
67
  (value.fileType !== 'executable' && value.fileType !== 'dylib') ||
68
68
  !isRecord(value.target) ||
69
- value.target.triple !== 'wasm32-unknown-wasip1' ||
70
- value.target.pointerWidth !== 32 ||
69
+ value.target.triple !== 'wasm64-unknown-wasip1' ||
70
+ value.target.pointerWidth !== 64 ||
71
71
  value.target.byteOrder !== 'little' ||
72
72
  value.target.cpuType !== -1 ||
73
73
  value.target.cpuSubtype !== 0 ||
@@ -16,7 +16,7 @@ import {
16
16
  import type { SwiftHostCompilerPin } from './swift-toolchain'
17
17
 
18
18
  export const SWIFT_NATIVE_PIN_PATH = 'node/swift-native.json'
19
- export const SWIFT_NATIVE_PIN_SCHEMA = 2
19
+ export const SWIFT_NATIVE_PIN_SCHEMA = 3
20
20
 
21
21
  export interface SwiftNativePin {
22
22
  schema: typeof SWIFT_NATIVE_PIN_SCHEMA
@@ -46,7 +46,7 @@ export const swiftSdkProductSchema = v.pipe(
46
46
  v.object({
47
47
  id: v.string(),
48
48
  installName: v.string(),
49
- target: v.literal('wasm32-unknown-wasip1'),
49
+ target: v.literal('wasm64-unknown-wasip1'),
50
50
  linkNames: v.array(v.string()),
51
51
  // absent records declare no framework namespace, even when a library name matches.
52
52
  frameworkAliases: v.optional(
@@ -1,90 +1,90 @@
1
1
  {
2
2
  "schema": 2,
3
3
  "platform": "ios",
4
- "target": "wasm32-unknown-wasip1",
5
- "recipe": "704fe0a2e79278bce00a7af1b1dc1846943873c3f54942a174833a515d222b79",
4
+ "target": "wasm64-unknown-wasip1",
5
+ "recipe": "34baf639742120cf206d06dd95e4da3444d9f9f377b8db6eafe78417840d79d7",
6
6
  "adapter": {
7
- "sha256": "0d31912293226deb3354b3b32f2944e9c61c68590e3020d8b5af0c12a6677893"
7
+ "sha256": "2ba7674c823a6adf902af9ccdd489cfe47d68e9788a7214fe8b83c784fc3fa98"
8
8
  },
9
- "compiler": "swift-6.4.0-RELEASE-custom-11345d60dca47496",
9
+ "compiler": "swift-6.4.0-RELEASE-custom-fb42d2535739dfd2",
10
10
  "sdk": {
11
- "id": "swift-6.4.0-RELEASE_wasm-peach-19471d4b",
12
- "checksum": "764c16e7e34156bf90d6c3372a2b77b98b9d8c3470235520d0f9619f90f33456"
11
+ "id": "swift-6.4.0-RELEASE_wasm-peach-b577231e",
12
+ "checksum": "9a8f0c694ada318ede49413cd72395e34a7fbb1b9a227b31eff8b05371ffb9aa"
13
13
  },
14
14
  "framework": {
15
- "distribution": "e7ae8fdb51086f73",
16
- "wireAbi": "246b8412514b644113e6a26252c8ebd6de49ab4156ac5f5c98bb9e4c2ff2568b"
15
+ "distribution": "c7825880e72c77d4",
16
+ "wireAbi": "38eed69f6b7391584112981b7892914e7033d7e0c17c2e8d4a4448030618bd2b"
17
17
  },
18
18
  "archives": [
19
19
  {
20
20
  "headers": {
21
21
  "reactNativeJsi": "0f8c2defbca69a27a848a81ae72363073bf9602fd316e57a75811be1a0528358"
22
22
  },
23
- "sha256": "e48499ac95604409b5da4b0f113e8ec3d3e7de9bd170f2f401513c4a45c234dc",
24
- "size": 25392
23
+ "sha256": "675b1c6d919198a3329fa48d9ad74c05c093201fc4abe246436f7f8e1d47887f",
24
+ "size": 26175
25
25
  },
26
26
  {
27
27
  "headers": {
28
28
  "reactNativeJsi": "21a624ea24fd17d56bf26832d496fc1177067f89640414c20e6486c7984dfb0a"
29
29
  },
30
- "sha256": "bf331e9b3d141240d9fda77dc67e1d4b584513bdc455b0c37ae6aeb1a89038ed",
31
- "size": 26137
30
+ "sha256": "fb2a2b16ef4963022676c04a8acc23a10a3c7de9b1fb2760743a299eb5fb6cda",
31
+ "size": 26914
32
32
  },
33
33
  {
34
34
  "headers": {
35
35
  "reactNativeJsi": "324aefd8591d9ce688bce7589c4c2b516cd33ecb95d35c47239a87a8fcf6a8da"
36
36
  },
37
- "sha256": "181c4fef940e164d52f0e8a6284990cfa23a19c42afb445f6945cd959f708bf9",
38
- "size": 25392
37
+ "sha256": "3b5f5db71ed80b6c5563332e81edd9d9a7d626740471892e3a3aecf34946df8d",
38
+ "size": 26167
39
39
  },
40
40
  {
41
41
  "headers": {
42
42
  "reactNativeJsi": "33596664ee7d7a6623f0796a6feb429543316c3a41b211150acda6e1d835286c"
43
43
  },
44
- "sha256": "44ac75e98b6badd32e32556bfd04beab8728bbce58cf32f31528a7e17b6b3665",
45
- "size": 25166
44
+ "sha256": "7577e8dc552780eed3ee136fb7a77e19345199c4d87420b21a444adf647bf2ec",
45
+ "size": 25957
46
46
  },
47
47
  {
48
48
  "headers": {
49
49
  "reactNativeJsi": "66b2cd5a38511195207f6c8a76aba735f9b9cc66a52a0d00780ffe5d331623f7"
50
50
  },
51
- "sha256": "dab069c53f92c911f1f84c7c037077c6e73df2fbd1673cb9dbbff0c6014fb9ac",
52
- "size": 25398
51
+ "sha256": "1ccfb1335ba66d6f53d27a5a1ee9c223b7d05a9c0abb6eb7dccdc6124245e470",
52
+ "size": 26172
53
53
  },
54
54
  {
55
55
  "headers": {
56
56
  "reactNativeJsi": "96db4e758dd53e9320cef7cad485e00aa06e7a982cefd920f17007de8a5bd9f9"
57
57
  },
58
- "sha256": "6612544a0287c8cc231436ad31e9a45f4d824fbbc5e7da7460ac11728568c353",
59
- "size": 26139
58
+ "sha256": "15e27e276e9add8769400c323ea880867ebbefd3efb823b203b2eaae0be23fa4",
59
+ "size": 26913
60
60
  },
61
61
  {
62
62
  "headers": {
63
63
  "reactNativeJsi": "aeab590ddd1218df963462242d3954ce467b0fa88f70e91429e4ffc74d230893"
64
64
  },
65
- "sha256": "c73fb45ff0e2037bf21c6a0e019e80c645ff141e80052b750c7d14570a4f7e72",
66
- "size": 26138
65
+ "sha256": "f0243c32cdbe887ce7672e2abf3338dccbd76c43dfeb8686b7269f0d3272fb88",
66
+ "size": 26914
67
67
  },
68
68
  {
69
69
  "headers": {
70
70
  "reactNativeJsi": "b5fb260522384dd7ab07d740cdc534b529922446fcec798341c7817d9eaa49f4"
71
71
  },
72
- "sha256": "997372e6c03141640cfc3aab115b8afe9b04f6f6e806721f14f4fc0dc871d271",
73
- "size": 25267
72
+ "sha256": "b107936623043c5cbf4353acbbfadb6426750138304befd9e0fa768d6e256e49",
73
+ "size": 26046
74
74
  },
75
75
  {
76
76
  "headers": {
77
77
  "reactNativeJsi": "c6b391a1e870ccf7cdd534be63582862840f8245b99ebd253317deece1e6ffd5"
78
78
  },
79
- "sha256": "60f8e1e7da90a20ee5b112b950e264f93329014bfabdcbcd5aa1281945f2725b",
80
- "size": 26143
79
+ "sha256": "3c56322e46544e260cbbc6f90720cb2bdfd5373e744801e55b778113d99e8799",
80
+ "size": 26912
81
81
  },
82
82
  {
83
83
  "headers": {
84
84
  "reactNativeJsi": "c8270f1315745ecc204c4209a4f50f58ff6f14b4a385ad6820a69800270b53c6"
85
85
  },
86
- "sha256": "47269b8662bf147cbf9ccc011de79f0faffc30aa6b09344ad4f95dbdfae656c9",
87
- "size": 25105
86
+ "sha256": "e96bfa3c28e45bd3d4d64a059449d1960e7136a966d746662834ff3aa2d1ce6b",
87
+ "size": 25921
88
88
  }
89
89
  ]
90
90
  }
@@ -0,0 +1,296 @@
1
+ // narrows the host boundary of a linked wasm64 module to 32 bits, so host
2
+ // javascript keeps Number pointers once the memory is lowered. scalar i64 host
3
+ // imports retain their existing BigInt ABI; wasi pointers are already 32-bit
4
+ // in libc. the module's wide imports and exports get private
5
+ // names here, and wasm-merge fuses them with the thunk module that converts.
6
+
7
+ const I64 = 0x7e
8
+ const VALUE_TYPES = new Map([
9
+ [0x7f, 'i32'],
10
+ [I64, 'i64'],
11
+ [0x7d, 'f32'],
12
+ [0x7c, 'f64'],
13
+ [0x7b, 'v128'],
14
+ [0x70, 'funcref'],
15
+ [0x6f, 'externref'],
16
+ ])
17
+ const GUEST = 'guest'
18
+ const BOUNDARY = 'boundary'
19
+ const WIDE = '$wide.'
20
+ const NARROW = '$narrow.'
21
+
22
+ type FunctionType = { parameters: number[]; results: number[] }
23
+
24
+ function encodeNumber(value: number): number[] {
25
+ const bytes: number[] = []
26
+ for (let rest = value; ; ) {
27
+ const byte = rest % 128
28
+ rest = Math.floor(rest / 128)
29
+ if (rest === 0) return [...bytes, byte]
30
+ bytes.push(byte | 128)
31
+ }
32
+ }
33
+
34
+ function encodeName(name: string): number[] {
35
+ const bytes = [...Buffer.from(name)]
36
+ return [...encodeNumber(bytes.length), ...bytes]
37
+ }
38
+
39
+ function reader(bytes: Uint8Array, start: number, end: number) {
40
+ let at = start
41
+ const number = () => {
42
+ let value = 0
43
+ for (let shift = 0; ; shift += 7) {
44
+ if (at >= end || shift > 63) throw new Error('invalid wasm integer')
45
+ const byte = bytes[at++]!
46
+ value += (byte & 127) * 2 ** shift
47
+ if (!(byte & 128)) return value
48
+ }
49
+ }
50
+ return {
51
+ number,
52
+ byte: () => {
53
+ if (at >= end) throw new Error('truncated wasm section')
54
+ return bytes[at++]!
55
+ },
56
+ name: () => {
57
+ const length = number()
58
+ at += length
59
+ if (at > end) throw new Error('truncated wasm name')
60
+ return Buffer.from(bytes.subarray(at - length, at)).toString()
61
+ },
62
+ position: () => at,
63
+ done: () => at === end,
64
+ }
65
+ }
66
+
67
+ function sections(bytes: Uint8Array): { id: number; start: number; end: number }[] {
68
+ if (Buffer.from(bytes.subarray(0, 8)).toString('hex') !== '0061736d01000000')
69
+ throw new Error('not a wasm module')
70
+ const found: { id: number; start: number; end: number }[] = []
71
+ for (let at = 8; at < bytes.length; ) {
72
+ const header = reader(bytes, at, bytes.length)
73
+ const id = header.byte()
74
+ const size = header.number()
75
+ const start = header.position()
76
+ found.push({ id, start, end: start + size })
77
+ at = start + size
78
+ }
79
+ return found
80
+ }
81
+
82
+ // replaces whole sections and keeps every other byte.
83
+ function withSections(
84
+ bytes: Uint8Array,
85
+ layout: readonly { id: number; start: number; end: number }[],
86
+ replacements: ReadonlyMap<number, number[]>,
87
+ ): Buffer {
88
+ const parts: Uint8Array[] = [bytes.subarray(0, 8)]
89
+ let at = 8
90
+ for (const section of layout) {
91
+ const body = replacements.get(section.id)
92
+ parts.push(
93
+ body
94
+ ? Uint8Array.from([section.id, ...encodeNumber(body.length), ...body])
95
+ : bytes.subarray(at, section.end),
96
+ )
97
+ at = section.end
98
+ }
99
+ return Buffer.concat(parts)
100
+ }
101
+
102
+ const quoted = (name: string) =>
103
+ `"${[...Buffer.from(name)].map((byte) => `\\${byte.toString(16).padStart(2, '0')}`).join('')}"`
104
+
105
+ const signature = (types: readonly number[], keyword: string, narrow: boolean) =>
106
+ types.length === 0
107
+ ? ''
108
+ : ` (${keyword} ${types
109
+ .map((type) => {
110
+ const name = VALUE_TYPES.get(narrow && type === I64 ? 0x7f : type)
111
+ if (!name) throw new Error(`unsupported wasm boundary type ${type}`)
112
+ return name
113
+ })
114
+ .join(' ')})`
115
+
116
+ const wide = (type: FunctionType) =>
117
+ type.parameters.includes(I64) || type.results.includes(I64)
118
+
119
+ /**
120
+ * the module with its wide imports and exports renamed for the merge, and the
121
+ * thunk module's text; undefined when the boundary is already narrow.
122
+ */
123
+ export function narrowWasmBoundary(
124
+ bytes: Uint8Array,
125
+ ): { module: Buffer; thunks: string } | undefined {
126
+ const layout = sections(bytes)
127
+ const section = (id: number) => {
128
+ const found = layout.find((entry) => entry.id === id)
129
+ return found && reader(bytes, found.start, found.end)
130
+ }
131
+
132
+ const types: FunctionType[] = []
133
+ const typeSection = section(1)
134
+ if (typeSection) {
135
+ for (let count = typeSection.number(); count > 0; count--) {
136
+ if (typeSection.byte() !== 0x60) throw new Error('unsupported wasm type')
137
+ const values = () =>
138
+ Array.from({ length: typeSection.number() }, () => typeSection.byte())
139
+ types.push({ parameters: values(), results: values() })
140
+ }
141
+ }
142
+ const typeAt = (index: number) => {
143
+ const type = types[index]
144
+ if (!type) throw new Error('invalid wasm type index')
145
+ return type
146
+ }
147
+
148
+ const thunks: string[] = []
149
+ const functionTypes: FunctionType[] = []
150
+ const importBody: number[] = []
151
+ const importSection = section(2)
152
+ if (importSection) {
153
+ const count = importSection.number()
154
+ importBody.push(...encodeNumber(count))
155
+ for (let index = 0; index < count; index++) {
156
+ const module = importSection.name()
157
+ const name = importSection.name()
158
+ const start = importSection.position()
159
+ const kind = importSection.byte()
160
+ const limits = () => {
161
+ const flags = importSection.number()
162
+ importSection.number()
163
+ if (flags & 1) importSection.number()
164
+ }
165
+ let type: FunctionType | undefined
166
+ if (kind === 0) {
167
+ type = typeAt(importSection.number())
168
+ functionTypes.push(type)
169
+ } else if (kind === 1) {
170
+ importSection.byte()
171
+ limits()
172
+ } else if (kind === 2) limits()
173
+ else if (kind === 3) {
174
+ importSection.byte()
175
+ importSection.byte()
176
+ } else if (kind === 4) {
177
+ importSection.byte()
178
+ importSection.number()
179
+ } else throw new Error('unsupported wasm import')
180
+ const scalar64 = (parameter: number | null) =>
181
+ (module === 'wasi_snapshot_preview1' &&
182
+ (!name.startsWith('peach_') ||
183
+ (name === 'peach_fs_path_birthtime_set' && parameter === 2))) ||
184
+ (module === 'peach' && name === 'hw_memsize' && parameter === null) ||
185
+ (module === 'peach_notify' && name === 'set' && parameter === 1)
186
+ const parameters = type?.parameters.map((value, index) =>
187
+ value === I64 && !scalar64(index) ? 0x7f : value,
188
+ )
189
+ const results = type?.results.map((value) =>
190
+ value === I64 && !scalar64(null) ? 0x7f : value,
191
+ )
192
+ if (
193
+ type &&
194
+ parameters &&
195
+ results &&
196
+ (parameters.some((value, index) => value !== type.parameters[index]) ||
197
+ results.some((value, index) => value !== type.results[index]))
198
+ ) {
199
+ if (type.results.length > 1)
200
+ throw new Error(`import ${module}.${name} returns several values`)
201
+ const id = thunks.length
202
+ const call = `(call $host${id}${type.parameters
203
+ .map((value, at) =>
204
+ value !== parameters[at]
205
+ ? ` (i32.wrap_i64 (local.get ${at}))`
206
+ : ` (local.get ${at})`,
207
+ )
208
+ .join('')})`
209
+ thunks.push(
210
+ `(import ${quoted(module)} ${quoted(name)} (func $host${id}${signature(parameters, 'param', false)}${signature(results, 'result', false)}))`,
211
+ `(func (export "${NARROW}${id}")${signature(type.parameters, 'param', false)}${signature(type.results, 'result', false)} ${type.results[0] !== results[0] ? `(i64.extend_i32_u ${call})` : call})`,
212
+ )
213
+ importBody.push(...encodeName(BOUNDARY), ...encodeName(`${NARROW}${id}`))
214
+ } else importBody.push(...encodeName(module), ...encodeName(name))
215
+ importBody.push(...bytes.subarray(start, importSection.position()))
216
+ }
217
+ if (!importSection.done()) throw new Error('invalid wasm imports')
218
+ }
219
+
220
+ const functionSection = section(3)
221
+ if (functionSection)
222
+ for (let count = functionSection.number(); count > 0; count--)
223
+ functionTypes.push(typeAt(functionSection.number()))
224
+
225
+ const exportBody: number[] = []
226
+ const exportSection = section(7)
227
+ if (exportSection) {
228
+ const count = exportSection.number()
229
+ exportBody.push(...encodeNumber(count))
230
+ for (let index = 0; index < count; index++) {
231
+ const name = exportSection.name()
232
+ const kind = exportSection.byte()
233
+ const target = exportSection.number()
234
+ const type = kind === 0 ? functionTypes[target] : undefined
235
+ if (kind === 0 && !type) throw new Error('invalid wasm export')
236
+ if (type && wide(type)) {
237
+ if (type.results.length > 1)
238
+ throw new Error(`export ${name} returns several values`)
239
+ const id = thunks.length
240
+ const call = `(call $guest${id}${type.parameters
241
+ .map((value, at) =>
242
+ value === I64
243
+ ? ` (i64.extend_i32_u (local.get ${at}))`
244
+ : ` (local.get ${at})`,
245
+ )
246
+ .join('')})`
247
+ thunks.push(
248
+ `(import "${GUEST}" "${WIDE}${id}" (func $guest${id}${signature(type.parameters, 'param', false)}${signature(type.results, 'result', false)}))`,
249
+ `(func (export ${quoted(name)})${signature(type.parameters, 'param', true)}${signature(type.results, 'result', true)} ${type.results[0] === I64 ? `(i32.wrap_i64 ${call})` : call})`,
250
+ )
251
+ exportBody.push(...encodeName(`${WIDE}${id}`))
252
+ } else exportBody.push(...encodeName(name))
253
+ exportBody.push(kind, ...encodeNumber(target))
254
+ }
255
+ if (!exportSection.done()) throw new Error('invalid wasm exports')
256
+ }
257
+
258
+ if (thunks.length === 0) return undefined
259
+ // imports precede definitions in the text format.
260
+ const imports = thunks.filter((line) => line.startsWith('(import'))
261
+ const functions = thunks.filter((line) => !line.startsWith('(import'))
262
+ return {
263
+ module: withSections(
264
+ bytes,
265
+ layout,
266
+ new Map([
267
+ [2, importBody],
268
+ [7, exportBody],
269
+ ]),
270
+ ),
271
+ thunks: `(module\n${[...imports, ...functions].join('\n')}\n)\n`,
272
+ }
273
+ }
274
+
275
+ export const NARROW_MERGE_NAMES = { guest: GUEST, boundary: BOUNDARY }
276
+
277
+ /** drops the private names the merge left exported. */
278
+ export function dropBoundaryExports(bytes: Uint8Array): Buffer {
279
+ const layout = sections(bytes)
280
+ const found = layout.find((entry) => entry.id === 7)
281
+ if (!found) throw new Error('merged module has no exports')
282
+ const exportSection = reader(bytes, found.start, found.end)
283
+ const kept: number[][] = []
284
+ for (let count = exportSection.number(); count > 0; count--) {
285
+ const name = exportSection.name()
286
+ const kind = exportSection.byte()
287
+ const target = exportSection.number()
288
+ if (!name.startsWith(WIDE) && !name.startsWith(NARROW))
289
+ kept.push([...encodeName(name), kind, ...encodeNumber(target)])
290
+ }
291
+ return withSections(
292
+ bytes,
293
+ layout,
294
+ new Map([[7, [...encodeNumber(kept.length), ...kept.flat()]]]),
295
+ )
296
+ }
@@ -1,15 +1,25 @@
1
- import { existsSync } from 'node:fs'
2
- import { join } from 'node:path'
1
+ import { execFile } from 'node:child_process'
2
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { promisify } from 'node:util'
3
5
  import { installVerifiedTarball } from '@contrast/runtime-delivery'
4
6
  import { BINARYEN_VERSION, wasmOpt } from 'wasm-opt'
5
7
  import { windowAdmission } from './admitted-mac-build'
6
8
  import { rnxHomeDir } from './home-paths'
9
+ import {
10
+ dropBoundaryExports,
11
+ NARROW_MERGE_NAMES,
12
+ narrowWasmBoundary,
13
+ } from './wasm-narrow-boundary'
7
14
 
8
- // packaged native executables, size-optimized. clang's own
9
- // optimizer pass is off (-O0 on the link) so an edit never pays for it and the
10
- // artifact never depends on the machine; this runs the pinned Binaryen once per
11
- // cached artifact instead. the features are the ones the link declares.
15
+ // the linked wasm64 executable, made loadable: its host boundary narrowed to
16
+ // 32 bits and its memory and table lowered to 32-bit addresses, so pointers stay
17
+ // 8 bytes in memory while every browser runs the module. clang's own optimizer
18
+ // pass is off (-O0 on the link) so the artifact never depends on the machine;
19
+ // the pinned Binaryen size-optimizes once per cached artifact instead. the
20
+ // features are the ones the link declares.
12
21
  const FEATURES = [
22
+ 'memory64',
13
23
  'threads',
14
24
  'bulk-memory',
15
25
  'bulk-memory-opt',
@@ -25,8 +35,10 @@ const FEATURES = [
25
35
  // PEACH_SWIFT_NAMES=1 keeps the name section so a trap stack names its functions.
26
36
  export const KEEP_WASM_NAMES = process.env.PEACH_SWIFT_NAMES === '1'
27
37
 
38
+ const LOWERING = ['--memory64-lowering', '--table64-lowering']
39
+
28
40
  /** feeds the artifact hash: a new optimizer or flag set is a new artifact. */
29
- export const WASM_OPTIMIZE_IDENTITY = `wasm-opt ${BINARYEN_VERSION} -Oz ${FEATURES.join(',')}${KEEP_WASM_NAMES ? ' -g' : ''}`
41
+ export const WASM_LOWER_IDENTITY = `binaryen ${BINARYEN_VERSION} narrow ${LOWERING.join(' ')} ${FEATURES.join(',')} unoptimized-names`
30
42
 
31
43
  // the sha256 of each Binaryen release archive wasm-opt resolves for its own platform
32
44
  // keys; update both when the wasm-opt package moves BINARYEN_VERSION.
@@ -61,6 +73,7 @@ const BINARYEN_RELEASES = new Map([
61
73
  async function runWasmOpt(
62
74
  file: string,
63
75
  output: string,
76
+ optimize: boolean,
64
77
  skipPasses: readonly string[],
65
78
  binary: string,
66
79
  ): Promise<void> {
@@ -73,18 +86,44 @@ async function runWasmOpt(
73
86
  ? Math.min(admittedCores, requestedCores)
74
87
  : admittedCores,
75
88
  )
89
+ const features = FEATURES.map((feature) => `--enable-${feature}`)
90
+ // an unoptimized link keeps its name section, as before lowering, so a trap names its function
91
+ const names = optimize && !KEEP_WASM_NAMES ? [] : ['-g']
92
+ const narrowed = narrowWasmBoundary(readFileSync(file))
93
+ const merged = `${output}.narrowed`
94
+ if (narrowed) {
95
+ const guest = `${output}.guest`
96
+ const thunks = `${output}.thunks.wat`
97
+ writeFileSync(guest, narrowed.module)
98
+ writeFileSync(thunks, narrowed.thunks)
99
+ await promisify(execFile)(join(dirname(binary), 'wasm-merge'), [
100
+ ...features,
101
+ ...names,
102
+ guest,
103
+ NARROW_MERGE_NAMES.guest,
104
+ thunks,
105
+ NARROW_MERGE_NAMES.boundary,
106
+ '-o',
107
+ merged,
108
+ ])
109
+ writeFileSync(merged, dropBoundaryExports(readFileSync(merged)))
110
+ rmSync(guest)
111
+ rmSync(thunks)
112
+ }
76
113
  const result = await wasmOpt(
77
114
  [
78
- '-Oz',
79
- ...(KEEP_WASM_NAMES ? ['-g'] : []),
115
+ ...LOWERING,
116
+ ...(optimize ? ['-Oz'] : []),
117
+ ...names,
80
118
  ...skipPasses.flatMap((pass) => ['--skip-pass', pass]),
81
- ...FEATURES.map((feature) => `--enable-${feature}`),
82
- file,
119
+ ...features,
120
+ narrowed ? merged : file,
83
121
  '-o',
84
122
  output,
85
123
  ],
86
124
  { env: environment },
87
125
  )
126
+ if (narrowed) rmSync(merged)
88
127
  if (result.status !== 0)
89
128
  throw new Error(`wasm-opt exited ${result.status}: ${result.stderr}`)
90
129
  }
@@ -110,10 +149,16 @@ async function installWasmOpt(): Promise<string> {
110
149
  return binary
111
150
  }
112
151
 
113
- export async function optimizeWasm(
152
+ export async function lowerWasm(
114
153
  file: string,
115
154
  output: string,
116
- skipPasses: readonly string[] = [],
155
+ options: { optimize: boolean; skipPasses?: readonly string[] },
117
156
  ): Promise<void> {
118
- await runWasmOpt(file, output, skipPasses, await installWasmOpt())
157
+ await runWasmOpt(
158
+ file,
159
+ output,
160
+ options.optimize,
161
+ options.skipPasses ?? [],
162
+ await installWasmOpt(),
163
+ )
119
164
  }