├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── build.py ├── crates ├── example │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── wasm_split │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── wasm_split_cli │ ├── Cargo.toml │ └── src │ │ ├── dep_graph.rs │ │ ├── emit.rs │ │ ├── main.rs │ │ ├── module_info.rs │ │ ├── read.rs │ │ ├── split_point.rs │ │ └── todo.txt └── wasm_split_macros │ ├── Cargo.toml │ └── src │ └── lib.rs ├── dprint.json ├── index.html ├── index.js ├── raw ├── rust-toolchain.toml ├── rustfmt.toml ├── test.br └── test.gz /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /pkg 3 | /split_tmp 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "alloc-no-stdlib" 43 | version = "2.0.4" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 46 | 47 | [[package]] 48 | name = "alloc-stdlib" 49 | version = "0.2.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 52 | dependencies = [ 53 | "alloc-no-stdlib", 54 | ] 55 | 56 | [[package]] 57 | name = "anstream" 58 | version = "0.6.13" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 61 | dependencies = [ 62 | "anstyle", 63 | "anstyle-parse", 64 | "anstyle-query", 65 | "anstyle-wincon", 66 | "colorchoice", 67 | "utf8parse", 68 | ] 69 | 70 | [[package]] 71 | name = "anstyle" 72 | version = "1.0.6" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 75 | 76 | [[package]] 77 | name = "anstyle-parse" 78 | version = "0.2.3" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 81 | dependencies = [ 82 | "utf8parse", 83 | ] 84 | 85 | [[package]] 86 | name = "anstyle-query" 87 | version = "1.0.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 90 | dependencies = [ 91 | "windows-sys", 92 | ] 93 | 94 | [[package]] 95 | name = "anstyle-wincon" 96 | version = "3.0.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 99 | dependencies = [ 100 | "anstyle", 101 | "windows-sys", 102 | ] 103 | 104 | [[package]] 105 | name = "anyhow" 106 | version = "1.0.82" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" 109 | dependencies = [ 110 | "backtrace", 111 | ] 112 | 113 | [[package]] 114 | name = "async-compression" 115 | version = "0.4.9" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" 118 | dependencies = [ 119 | "brotli", 120 | "flate2", 121 | "futures-core", 122 | "futures-io", 123 | "memchr", 124 | "pin-project-lite", 125 | ] 126 | 127 | [[package]] 128 | name = "async-once-cell" 129 | version = "0.5.3" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" 132 | 133 | [[package]] 134 | name = "autocfg" 135 | version = "1.2.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 138 | 139 | [[package]] 140 | name = "backtrace" 141 | version = "0.3.71" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 144 | dependencies = [ 145 | "addr2line", 146 | "cc", 147 | "cfg-if", 148 | "libc", 149 | "miniz_oxide", 150 | "object", 151 | "rustc-demangle", 152 | ] 153 | 154 | [[package]] 155 | name = "base16" 156 | version = "0.2.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" 159 | 160 | [[package]] 161 | name = "bitflags" 162 | version = "2.5.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 165 | 166 | [[package]] 167 | name = "block-buffer" 168 | version = "0.10.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 171 | dependencies = [ 172 | "generic-array", 173 | ] 174 | 175 | [[package]] 176 | name = "brotli" 177 | version = "5.0.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" 180 | dependencies = [ 181 | "alloc-no-stdlib", 182 | "alloc-stdlib", 183 | "brotli-decompressor", 184 | ] 185 | 186 | [[package]] 187 | name = "brotli-decompressor" 188 | version = "4.0.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" 191 | dependencies = [ 192 | "alloc-no-stdlib", 193 | "alloc-stdlib", 194 | ] 195 | 196 | [[package]] 197 | name = "bumpalo" 198 | version = "3.16.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 201 | 202 | [[package]] 203 | name = "bytes" 204 | version = "1.6.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 207 | 208 | [[package]] 209 | name = "cc" 210 | version = "1.0.95" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" 213 | 214 | [[package]] 215 | name = "cfg-if" 216 | version = "1.0.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 219 | 220 | [[package]] 221 | name = "clap" 222 | version = "4.5.4" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 225 | dependencies = [ 226 | "clap_builder", 227 | "clap_derive", 228 | ] 229 | 230 | [[package]] 231 | name = "clap_builder" 232 | version = "4.5.2" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 235 | dependencies = [ 236 | "anstream", 237 | "anstyle", 238 | "clap_lex", 239 | "strsim", 240 | ] 241 | 242 | [[package]] 243 | name = "clap_derive" 244 | version = "4.5.4" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 247 | dependencies = [ 248 | "heck", 249 | "proc-macro2", 250 | "quote", 251 | "syn", 252 | ] 253 | 254 | [[package]] 255 | name = "clap_lex" 256 | version = "0.7.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 259 | 260 | [[package]] 261 | name = "colorchoice" 262 | version = "1.0.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 265 | 266 | [[package]] 267 | name = "cpufeatures" 268 | version = "0.2.12" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 271 | dependencies = [ 272 | "libc", 273 | ] 274 | 275 | [[package]] 276 | name = "crc32fast" 277 | version = "1.4.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 280 | dependencies = [ 281 | "cfg-if", 282 | ] 283 | 284 | [[package]] 285 | name = "crypto-common" 286 | version = "0.1.6" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 289 | dependencies = [ 290 | "generic-array", 291 | "typenum", 292 | ] 293 | 294 | [[package]] 295 | name = "digest" 296 | version = "0.10.7" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 299 | dependencies = [ 300 | "block-buffer", 301 | "crypto-common", 302 | ] 303 | 304 | [[package]] 305 | name = "equivalent" 306 | version = "1.0.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 309 | 310 | [[package]] 311 | name = "example" 312 | version = "0.1.0" 313 | dependencies = [ 314 | "anyhow", 315 | "async-compression", 316 | "futures", 317 | "gloo-console", 318 | "gloo-net", 319 | "js-sys", 320 | "wasm-bindgen", 321 | "wasm-bindgen-futures", 322 | "wasm-streams", 323 | "wasm_split", 324 | ] 325 | 326 | [[package]] 327 | name = "flate2" 328 | version = "1.0.30" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" 331 | dependencies = [ 332 | "crc32fast", 333 | "miniz_oxide", 334 | ] 335 | 336 | [[package]] 337 | name = "fnv" 338 | version = "1.0.7" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 341 | 342 | [[package]] 343 | name = "futures" 344 | version = "0.3.30" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 347 | dependencies = [ 348 | "futures-channel", 349 | "futures-core", 350 | "futures-executor", 351 | "futures-io", 352 | "futures-sink", 353 | "futures-task", 354 | "futures-util", 355 | ] 356 | 357 | [[package]] 358 | name = "futures-channel" 359 | version = "0.3.30" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 362 | dependencies = [ 363 | "futures-core", 364 | "futures-sink", 365 | ] 366 | 367 | [[package]] 368 | name = "futures-core" 369 | version = "0.3.30" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 372 | 373 | [[package]] 374 | name = "futures-executor" 375 | version = "0.3.30" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 378 | dependencies = [ 379 | "futures-core", 380 | "futures-task", 381 | "futures-util", 382 | ] 383 | 384 | [[package]] 385 | name = "futures-io" 386 | version = "0.3.30" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 389 | 390 | [[package]] 391 | name = "futures-macro" 392 | version = "0.3.30" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 395 | dependencies = [ 396 | "proc-macro2", 397 | "quote", 398 | "syn", 399 | ] 400 | 401 | [[package]] 402 | name = "futures-sink" 403 | version = "0.3.30" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 406 | 407 | [[package]] 408 | name = "futures-task" 409 | version = "0.3.30" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 412 | 413 | [[package]] 414 | name = "futures-util" 415 | version = "0.3.30" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 418 | dependencies = [ 419 | "futures-channel", 420 | "futures-core", 421 | "futures-io", 422 | "futures-macro", 423 | "futures-sink", 424 | "futures-task", 425 | "memchr", 426 | "pin-project-lite", 427 | "pin-utils", 428 | "slab", 429 | ] 430 | 431 | [[package]] 432 | name = "generic-array" 433 | version = "0.14.7" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 436 | dependencies = [ 437 | "typenum", 438 | "version_check", 439 | ] 440 | 441 | [[package]] 442 | name = "gimli" 443 | version = "0.28.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 446 | 447 | [[package]] 448 | name = "gloo-console" 449 | version = "0.3.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" 452 | dependencies = [ 453 | "gloo-utils 0.2.0", 454 | "js-sys", 455 | "serde", 456 | "wasm-bindgen", 457 | "web-sys", 458 | ] 459 | 460 | [[package]] 461 | name = "gloo-net" 462 | version = "0.3.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" 465 | dependencies = [ 466 | "gloo-utils 0.1.7", 467 | "http", 468 | "js-sys", 469 | "thiserror", 470 | "wasm-bindgen", 471 | "wasm-bindgen-futures", 472 | "web-sys", 473 | ] 474 | 475 | [[package]] 476 | name = "gloo-utils" 477 | version = "0.1.7" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" 480 | dependencies = [ 481 | "js-sys", 482 | "wasm-bindgen", 483 | "web-sys", 484 | ] 485 | 486 | [[package]] 487 | name = "gloo-utils" 488 | version = "0.2.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" 491 | dependencies = [ 492 | "js-sys", 493 | "serde", 494 | "serde_json", 495 | "wasm-bindgen", 496 | "web-sys", 497 | ] 498 | 499 | [[package]] 500 | name = "hashbrown" 501 | version = "0.14.5" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 504 | dependencies = [ 505 | "ahash", 506 | ] 507 | 508 | [[package]] 509 | name = "heck" 510 | version = "0.5.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 513 | 514 | [[package]] 515 | name = "http" 516 | version = "0.2.12" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 519 | dependencies = [ 520 | "bytes", 521 | "fnv", 522 | "itoa", 523 | ] 524 | 525 | [[package]] 526 | name = "indexmap" 527 | version = "2.2.6" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 530 | dependencies = [ 531 | "equivalent", 532 | "hashbrown", 533 | ] 534 | 535 | [[package]] 536 | name = "itoa" 537 | version = "1.0.11" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 540 | 541 | [[package]] 542 | name = "js-sys" 543 | version = "0.3.69" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 546 | dependencies = [ 547 | "wasm-bindgen", 548 | ] 549 | 550 | [[package]] 551 | name = "lazy_static" 552 | version = "1.4.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 555 | 556 | [[package]] 557 | name = "leb128" 558 | version = "0.2.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 561 | 562 | [[package]] 563 | name = "libc" 564 | version = "0.2.154" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 567 | 568 | [[package]] 569 | name = "log" 570 | version = "0.4.21" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 573 | 574 | [[package]] 575 | name = "memchr" 576 | version = "2.7.2" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 579 | 580 | [[package]] 581 | name = "miniz_oxide" 582 | version = "0.7.2" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 585 | dependencies = [ 586 | "adler", 587 | ] 588 | 589 | [[package]] 590 | name = "object" 591 | version = "0.32.2" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 594 | dependencies = [ 595 | "memchr", 596 | ] 597 | 598 | [[package]] 599 | name = "once_cell" 600 | version = "1.19.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 603 | 604 | [[package]] 605 | name = "pin-project-lite" 606 | version = "0.2.14" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 609 | 610 | [[package]] 611 | name = "pin-utils" 612 | version = "0.1.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 615 | 616 | [[package]] 617 | name = "proc-macro2" 618 | version = "1.0.81" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 621 | dependencies = [ 622 | "unicode-ident", 623 | ] 624 | 625 | [[package]] 626 | name = "quote" 627 | version = "1.0.36" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 630 | dependencies = [ 631 | "proc-macro2", 632 | ] 633 | 634 | [[package]] 635 | name = "regex" 636 | version = "1.10.4" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 639 | dependencies = [ 640 | "aho-corasick", 641 | "memchr", 642 | "regex-automata", 643 | "regex-syntax", 644 | ] 645 | 646 | [[package]] 647 | name = "regex-automata" 648 | version = "0.4.6" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 651 | dependencies = [ 652 | "aho-corasick", 653 | "memchr", 654 | "regex-syntax", 655 | ] 656 | 657 | [[package]] 658 | name = "regex-syntax" 659 | version = "0.8.3" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 662 | 663 | [[package]] 664 | name = "rustc-demangle" 665 | version = "0.1.23" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 668 | 669 | [[package]] 670 | name = "ryu" 671 | version = "1.0.17" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 674 | 675 | [[package]] 676 | name = "semver" 677 | version = "1.0.22" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 680 | 681 | [[package]] 682 | name = "serde" 683 | version = "1.0.200" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" 686 | dependencies = [ 687 | "serde_derive", 688 | ] 689 | 690 | [[package]] 691 | name = "serde_derive" 692 | version = "1.0.200" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" 695 | dependencies = [ 696 | "proc-macro2", 697 | "quote", 698 | "syn", 699 | ] 700 | 701 | [[package]] 702 | name = "serde_json" 703 | version = "1.0.116" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" 706 | dependencies = [ 707 | "itoa", 708 | "ryu", 709 | "serde", 710 | ] 711 | 712 | [[package]] 713 | name = "sha2" 714 | version = "0.10.8" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 717 | dependencies = [ 718 | "cfg-if", 719 | "cpufeatures", 720 | "digest", 721 | ] 722 | 723 | [[package]] 724 | name = "slab" 725 | version = "0.4.9" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 728 | dependencies = [ 729 | "autocfg", 730 | ] 731 | 732 | [[package]] 733 | name = "strsim" 734 | version = "0.11.1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 737 | 738 | [[package]] 739 | name = "syn" 740 | version = "2.0.60" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 743 | dependencies = [ 744 | "proc-macro2", 745 | "quote", 746 | "unicode-ident", 747 | ] 748 | 749 | [[package]] 750 | name = "thiserror" 751 | version = "1.0.59" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" 754 | dependencies = [ 755 | "thiserror-impl", 756 | ] 757 | 758 | [[package]] 759 | name = "thiserror-impl" 760 | version = "1.0.59" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" 763 | dependencies = [ 764 | "proc-macro2", 765 | "quote", 766 | "syn", 767 | ] 768 | 769 | [[package]] 770 | name = "typenum" 771 | version = "1.17.0" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 774 | 775 | [[package]] 776 | name = "unicode-ident" 777 | version = "1.0.12" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 780 | 781 | [[package]] 782 | name = "utf8parse" 783 | version = "0.2.1" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 786 | 787 | [[package]] 788 | name = "version_check" 789 | version = "0.9.4" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 792 | 793 | [[package]] 794 | name = "wasm-bindgen" 795 | version = "0.2.92" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 798 | dependencies = [ 799 | "cfg-if", 800 | "wasm-bindgen-macro", 801 | ] 802 | 803 | [[package]] 804 | name = "wasm-bindgen-backend" 805 | version = "0.2.92" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 808 | dependencies = [ 809 | "bumpalo", 810 | "log", 811 | "once_cell", 812 | "proc-macro2", 813 | "quote", 814 | "syn", 815 | "wasm-bindgen-shared", 816 | ] 817 | 818 | [[package]] 819 | name = "wasm-bindgen-futures" 820 | version = "0.4.42" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 823 | dependencies = [ 824 | "cfg-if", 825 | "js-sys", 826 | "wasm-bindgen", 827 | "web-sys", 828 | ] 829 | 830 | [[package]] 831 | name = "wasm-bindgen-macro" 832 | version = "0.2.92" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 835 | dependencies = [ 836 | "quote", 837 | "wasm-bindgen-macro-support", 838 | ] 839 | 840 | [[package]] 841 | name = "wasm-bindgen-macro-support" 842 | version = "0.2.92" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 845 | dependencies = [ 846 | "proc-macro2", 847 | "quote", 848 | "syn", 849 | "wasm-bindgen-backend", 850 | "wasm-bindgen-shared", 851 | ] 852 | 853 | [[package]] 854 | name = "wasm-bindgen-shared" 855 | version = "0.2.92" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 858 | 859 | [[package]] 860 | name = "wasm-encoder" 861 | version = "0.206.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "d759312e1137f199096d80a70be685899cd7d3d09c572836bb2e9b69b4dc3b1e" 864 | dependencies = [ 865 | "leb128", 866 | "wasmparser", 867 | ] 868 | 869 | [[package]] 870 | name = "wasm-streams" 871 | version = "0.4.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" 874 | dependencies = [ 875 | "futures-util", 876 | "js-sys", 877 | "wasm-bindgen", 878 | "wasm-bindgen-futures", 879 | "web-sys", 880 | ] 881 | 882 | [[package]] 883 | name = "wasm_split" 884 | version = "0.1.0" 885 | dependencies = [ 886 | "async-once-cell", 887 | "wasm_split_macros", 888 | ] 889 | 890 | [[package]] 891 | name = "wasm_split_cli" 892 | version = "0.1.0" 893 | dependencies = [ 894 | "anyhow", 895 | "clap", 896 | "lazy_static", 897 | "regex", 898 | "wasm-encoder", 899 | "wasmparser", 900 | ] 901 | 902 | [[package]] 903 | name = "wasm_split_macros" 904 | version = "0.1.0" 905 | dependencies = [ 906 | "base16", 907 | "digest", 908 | "quote", 909 | "sha2", 910 | "syn", 911 | "wasm-bindgen", 912 | ] 913 | 914 | [[package]] 915 | name = "wasmparser" 916 | version = "0.206.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "39192edb55d55b41963db40fd49b0b542156f04447b5b512744a91d38567bdbc" 919 | dependencies = [ 920 | "ahash", 921 | "bitflags", 922 | "hashbrown", 923 | "indexmap", 924 | "semver", 925 | ] 926 | 927 | [[package]] 928 | name = "web-sys" 929 | version = "0.3.69" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 932 | dependencies = [ 933 | "js-sys", 934 | "wasm-bindgen", 935 | ] 936 | 937 | [[package]] 938 | name = "windows-sys" 939 | version = "0.52.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 942 | dependencies = [ 943 | "windows-targets", 944 | ] 945 | 946 | [[package]] 947 | name = "windows-targets" 948 | version = "0.52.5" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 951 | dependencies = [ 952 | "windows_aarch64_gnullvm", 953 | "windows_aarch64_msvc", 954 | "windows_i686_gnu", 955 | "windows_i686_gnullvm", 956 | "windows_i686_msvc", 957 | "windows_x86_64_gnu", 958 | "windows_x86_64_gnullvm", 959 | "windows_x86_64_msvc", 960 | ] 961 | 962 | [[package]] 963 | name = "windows_aarch64_gnullvm" 964 | version = "0.52.5" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 967 | 968 | [[package]] 969 | name = "windows_aarch64_msvc" 970 | version = "0.52.5" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 973 | 974 | [[package]] 975 | name = "windows_i686_gnu" 976 | version = "0.52.5" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 979 | 980 | [[package]] 981 | name = "windows_i686_gnullvm" 982 | version = "0.52.5" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 985 | 986 | [[package]] 987 | name = "windows_i686_msvc" 988 | version = "0.52.5" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 991 | 992 | [[package]] 993 | name = "windows_x86_64_gnu" 994 | version = "0.52.5" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 997 | 998 | [[package]] 999 | name = "windows_x86_64_gnullvm" 1000 | version = "0.52.5" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1003 | 1004 | [[package]] 1005 | name = "windows_x86_64_msvc" 1006 | version = "0.52.5" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1009 | 1010 | [[package]] 1011 | name = "zerocopy" 1012 | version = "0.7.32" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 1015 | dependencies = [ 1016 | "zerocopy-derive", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "zerocopy-derive" 1021 | version = "0.7.32" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 1024 | dependencies = [ 1025 | "proc-macro2", 1026 | "quote", 1027 | "syn", 1028 | ] 1029 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [profile.release] 6 | opt-level = "s" 7 | lto = true 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os 5 | import json 6 | import subprocess 7 | import shutil 8 | 9 | ap = argparse.ArgumentParser() 10 | ap.add_argument("--no-split", action="store_true") 11 | ap.add_argument("--optimize", action="store_true") 12 | args = ap.parse_args() 13 | 14 | root_dir = os.path.dirname(__file__) 15 | 16 | build_options = [ 17 | "--target", 18 | "wasm32-unknown-unknown", 19 | "--release", 20 | ] 21 | 22 | if not args.no_split: 23 | build_options.extend(["--features", "split brotli gzip"]) 24 | 25 | # First build without output redirection in order to allow errors to be 26 | # displayed normally 27 | subprocess.run( 28 | [ 29 | "cargo", 30 | "build", 31 | ] 32 | + build_options, 33 | cwd=os.path.join(root_dir, "crates", "example"), 34 | check=True, 35 | ) 36 | 37 | 38 | json_results = subprocess.run( 39 | ["cargo", "build"] 40 | + build_options 41 | + [ 42 | "--message-format=json", 43 | ], 44 | stdout=subprocess.PIPE, 45 | cwd=os.path.join(root_dir, "crates", "example"), 46 | check=True, 47 | ).stdout.splitlines() 48 | 49 | target_path = None 50 | for json_result in json_results: 51 | msg = json.loads(json_result) 52 | filenames = msg.get("filenames") 53 | if filenames: 54 | target_path = filenames[0] 55 | 56 | print(target_path) 57 | assert target_path is not None 58 | 59 | pkg_dir = os.path.join(root_dir, "pkg") 60 | 61 | shutil.rmtree(pkg_dir, ignore_errors=True) 62 | 63 | if args.no_split: 64 | subprocess.run( 65 | [ 66 | "wasm-bindgen", 67 | target_path, 68 | "--out-dir", 69 | pkg_dir, 70 | "--out-name", 71 | "main", 72 | "--no-demangle", 73 | "--target", 74 | "web", 75 | ], 76 | cwd=root_dir, 77 | check=True, 78 | ) 79 | else: 80 | split_temp_dir = os.path.join(root_dir, "split_tmp") 81 | shutil.rmtree(split_temp_dir, ignore_errors=True) 82 | 83 | subprocess.run( 84 | [ 85 | "cargo", 86 | "run", 87 | "-p", 88 | "wasm_split_cli", 89 | "--", 90 | target_path, 91 | split_temp_dir, 92 | ], 93 | cwd=root_dir, 94 | check=True, 95 | ) 96 | 97 | subprocess.run( 98 | [ 99 | "wasm-bindgen", 100 | os.path.join(split_temp_dir, "main.wasm"), 101 | "--out-dir", 102 | pkg_dir, 103 | "--no-demangle", 104 | "--target", 105 | "web", 106 | "--keep-lld-exports", 107 | ], 108 | cwd=root_dir, 109 | check=True, 110 | ) 111 | 112 | for name in os.listdir(split_temp_dir): 113 | if name == "main.wasm": 114 | continue 115 | shutil.copyfile(os.path.join(split_temp_dir, name), os.path.join(pkg_dir, name)) 116 | 117 | if args.optimize: 118 | for name in os.listdir(pkg_dir): 119 | if not name.endswith(".wasm"): 120 | continue 121 | path = os.path.join(pkg_dir, name) 122 | orig_size = os.stat(path).st_size 123 | subprocess.run(["wasm-opt", "-Os", path, "-o", path], check=True) 124 | new_size = os.stat(path).st_size 125 | print(f"wasm-opt: {path}: {orig_size} -> {new_size}") 126 | -------------------------------------------------------------------------------- /crates/example/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | rustflags = [ 4 | '-Clink-args=--emit-relocs', 5 | ] 6 | -------------------------------------------------------------------------------- /crates/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Jeremy Maitin-Shepard "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | anyhow = "1.0.82" 12 | async-compression = { version = "0.4.8", features = ["futures-io"] } 13 | futures = "0.3.30" 14 | gloo-console = "0.3.0" 15 | gloo-net = { version = "0.3.1", default-features = false, features = ["http"] } 16 | js-sys = "0.3.69" 17 | wasm-bindgen = "0.2.92" 18 | wasm-bindgen-futures = "0.4.42" 19 | wasm-streams = "0.4.0" 20 | wasm_split = { path = "../wasm_split", optional = true } 21 | 22 | [features] 23 | split = ["dep:wasm_split"] 24 | gzip = ["async-compression/gzip"] 25 | brotli = ["async-compression/brotli"] 26 | -------------------------------------------------------------------------------- /crates/example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use futures::io::AsyncReadExt; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[cfg(feature = "split")] 7 | use wasm_split::wasm_split; 8 | 9 | use wasm_streams::ReadableStream; 10 | 11 | #[cfg(feature = "gzip")] 12 | #[cfg_attr(feature = "split", wasm_split::wasm_split(gzip))] 13 | async fn get_gzip_decoder( 14 | encoded_reader: Pin>, 15 | ) -> Pin> { 16 | gloo_console::log!("getting gzip decoder"); 17 | Box::pin(async_compression::futures::bufread::GzipDecoder::new( 18 | encoded_reader, 19 | )) 20 | } 21 | 22 | #[cfg(feature = "brotli")] 23 | #[cfg_attr(feature = "split", wasm_split(brotli))] 24 | async fn get_brotli_decoder( 25 | encoded_reader: Pin>, 26 | ) -> Pin> { 27 | gloo_console::log!("getting brotli decoder"); 28 | Box::pin(async_compression::futures::bufread::BrotliDecoder::new( 29 | encoded_reader, 30 | )) 31 | } 32 | 33 | #[wasm_bindgen] 34 | pub async fn decode(url: &str) -> Result { 35 | let response = gloo_net::http::Request::get(url).send().await?; 36 | if response.status() != 200 { 37 | return Err(JsError::new( 38 | format!( 39 | "Received HTTP error {}: {}", 40 | response.status(), 41 | response.status_text() 42 | ) 43 | .as_str(), 44 | )); 45 | } 46 | let body = ReadableStream::from_raw(response.body().unwrap_throw()).into_async_read(); 47 | let buf_raw = Box::pin(body); 48 | let mut decoded: Pin> = buf_raw; 49 | 50 | #[cfg(feature = "gzip")] 51 | if url.ends_with(".gz") { 52 | decoded = get_gzip_decoder(Box::pin(futures::io::BufReader::new(decoded))).await 53 | } 54 | 55 | #[cfg(feature = "brotli")] 56 | if url.ends_with(".br") { 57 | decoded = get_brotli_decoder(Box::pin(futures::io::BufReader::new(decoded))).await 58 | } 59 | 60 | let mut data = Vec::new(); 61 | decoded.read_to_end(&mut data).await?; 62 | Ok(String::from_utf8(data)?) 63 | } 64 | -------------------------------------------------------------------------------- /crates/wasm_split/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_split" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-once-cell = { version = "0.5.3", features = ["std"] } 8 | wasm_split_macros = { version = "0.1.0", path = "../wasm_split_macros" } 9 | -------------------------------------------------------------------------------- /crates/wasm_split/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::Cell, 3 | ffi::c_void, 4 | future::Future, 5 | pin::Pin, 6 | rc::Rc, 7 | task::{Context, Poll, Waker}, 8 | }; 9 | 10 | pub use wasm_split_macros::wasm_split; 11 | 12 | pub type LoadCallbackFn = unsafe extern "C" fn(*const c_void, bool) -> (); 13 | pub type LoadFn = unsafe extern "C" fn(LoadCallbackFn, *const c_void) -> (); 14 | 15 | type Lazy = async_once_cell::Lazy, SplitLoaderFuture>; 16 | 17 | pub struct LazySplitLoader { 18 | lazy: Pin>, 19 | } 20 | 21 | impl LazySplitLoader { 22 | pub unsafe fn new(load: LoadFn) -> Self { 23 | Self { 24 | lazy: Rc::pin(Lazy::new(SplitLoaderFuture::new(SplitLoader::new(load)))), 25 | } 26 | } 27 | } 28 | 29 | pub async fn ensure_loaded(loader: &'static std::thread::LocalKey) -> Option<()> { 30 | *loader.with(|inner| inner.lazy.clone()).as_ref().await 31 | } 32 | 33 | #[derive(Clone, Copy, Debug)] 34 | enum SplitLoaderState { 35 | Deferred(LoadFn), 36 | Pending, 37 | Completed(Option<()>), 38 | } 39 | 40 | struct SplitLoader { 41 | state: Cell, 42 | waker: Cell>, 43 | } 44 | 45 | impl SplitLoader { 46 | fn new(load: LoadFn) -> Rc { 47 | Rc::new(SplitLoader { 48 | state: Cell::new(SplitLoaderState::Deferred(load)), 49 | waker: Cell::new(None), 50 | }) 51 | } 52 | 53 | fn complete(&self, value: bool) { 54 | self.state.set(SplitLoaderState::Completed(if value { 55 | Some(()) 56 | } else { 57 | None 58 | })); 59 | match self.waker.take() { 60 | Some(waker) => { 61 | waker.wake(); 62 | } 63 | _ => {} 64 | } 65 | } 66 | } 67 | 68 | struct SplitLoaderFuture { 69 | loader: Rc, 70 | } 71 | 72 | impl SplitLoaderFuture { 73 | fn new(loader: Rc) -> Self { 74 | SplitLoaderFuture { loader } 75 | } 76 | } 77 | 78 | impl Future for SplitLoaderFuture { 79 | type Output = Option<()>; 80 | 81 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | match self.loader.state.get() { 83 | SplitLoaderState::Deferred(load) => { 84 | self.loader.state.set(SplitLoaderState::Pending); 85 | self.loader.waker.set(Some(cx.waker().clone())); 86 | unsafe { 87 | load( 88 | load_callback, 89 | Rc::::into_raw(self.loader.clone()) as *const c_void, 90 | ) 91 | }; 92 | Poll::Pending 93 | } 94 | SplitLoaderState::Pending => { 95 | self.loader.waker.set(Some(cx.waker().clone())); 96 | Poll::Pending 97 | } 98 | SplitLoaderState::Completed(value) => Poll::Ready(value), 99 | } 100 | } 101 | } 102 | 103 | unsafe extern "C" fn load_callback(loader: *const c_void, success: bool) { 104 | unsafe { Rc::from_raw(loader as *const SplitLoader) }.complete(success); 105 | } 106 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_split_cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0.82", features = ["backtrace"] } 8 | clap = { version = "4.5.4", features = ["derive"] } 9 | lazy_static = "1.4.0" 10 | regex = "1.10.4" 11 | wasm-encoder = { version = "0.206.0", features = ["wasmparser"] } 12 | wasmparser = "0.206.0" 13 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/dep_graph.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::{HashMap, HashSet}, 4 | fmt::Debug, 5 | ops::Range, 6 | }; 7 | 8 | use anyhow::{anyhow, bail, Context}; 9 | 10 | use crate::read::{InputFuncId, InputModule, SymbolIndex}; 11 | 12 | #[derive(Debug, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Clone)] 13 | pub enum DepNode { 14 | Function(InputFuncId), 15 | DataSymbol(SymbolIndex), 16 | } 17 | 18 | pub type DepGraph = HashMap>; 19 | 20 | pub trait SymbolTable { 21 | fn get_symbol_dep_node(&self, symbol_index: SymbolIndex) -> Option; 22 | } 23 | 24 | impl<'a> SymbolTable for InputModule<'a> { 25 | fn get_symbol_dep_node(&self, symbol_index: SymbolIndex) -> Option { 26 | match self.symbols[symbol_index] { 27 | wasmparser::SymbolInfo::Func { index, .. } => { 28 | Some(DepNode::Function(index as InputFuncId)) 29 | } 30 | wasmparser::SymbolInfo::Data { .. } => Some(DepNode::DataSymbol(symbol_index)), 31 | _ => None, 32 | } 33 | } 34 | } 35 | 36 | pub fn get_dependencies(module: &InputModule) -> anyhow::Result { 37 | let mut deps = DepGraph::new(); 38 | let mut add_dep = |a: DepNode, b: u32| { 39 | if let Some(target) = module.get_symbol_dep_node(b as usize) { 40 | deps.entry(a).or_default().insert(target); 41 | }; 42 | }; 43 | 44 | let shift_range = 45 | |range: Range, offset: usize| (range.start + offset)..(range.end + offset); 46 | 47 | if let Some(relocs) = module.relocs.get(&module.code_section_index) { 48 | for entry in relocs { 49 | let func_index = find_function_containing_range( 50 | module, 51 | shift_range(entry.relocation_range(), module.code_section_offset), 52 | ) 53 | .with_context(|| format!("Invalid relocation entry {entry:?}"))?; 54 | add_dep(DepNode::Function(func_index), entry.index); 55 | } 56 | } 57 | 58 | if let Some(relocs) = module.relocs.get(&module.data_section_index) { 59 | for entry in relocs { 60 | let symbol_index = find_data_symbol_containing_range( 61 | module, 62 | shift_range(entry.relocation_range(), module.data_section_offset), 63 | ) 64 | .with_context(|| format!("Invalid relocation entry {entry:?}"))?; 65 | add_dep(DepNode::DataSymbol(symbol_index), entry.index); 66 | } 67 | } 68 | Ok(deps) 69 | } 70 | 71 | fn find_function_containing_range( 72 | module: &crate::read::InputModule, 73 | range: Range, 74 | ) -> anyhow::Result { 75 | let func_index = find_by_range(&module.defined_funcs, &range, |defined_func| { 76 | defined_func.body.range() 77 | }) 78 | .with_context(|| format!("No match for function relocation range {range:?}"))?; 79 | Ok(module.imported_funcs.len() + func_index) 80 | } 81 | 82 | fn find_data_symbol_containing_range( 83 | module: &crate::read::InputModule, 84 | range: Range, 85 | ) -> anyhow::Result { 86 | let index = find_by_range(&module.data_symbols, &range, |data_symbol| { 87 | data_symbol.range.clone() 88 | }) 89 | .with_context(|| format!("No match for data relocation range {range:?}"))?; 90 | Ok(module.data_symbols[index].symbol_index) 91 | } 92 | 93 | fn find_by_range Range>( 94 | items: &[T], 95 | range: &Range, 96 | get_range: F, 97 | ) -> anyhow::Result { 98 | let index = items 99 | .binary_search_by(|item| { 100 | let item_range = get_range(item); 101 | if item_range.end <= range.start { 102 | Ordering::Less 103 | } else if item_range.start <= range.start { 104 | Ordering::Equal 105 | } else { 106 | Ordering::Greater 107 | } 108 | }) 109 | .or_else(|index| { 110 | bail!( 111 | "Prev range is: {:?}, next range is: {:?}", 112 | items.get(index - 1).map(|item| (item, get_range(item))), 113 | items.get(index).map(|item| (item, get_range(item))) 114 | ) 115 | })?; 116 | if range.end > get_range(&items[index]).end { 117 | bail!( 118 | "Item {:?} has incompatible range {:?}", 119 | items[index], 120 | get_range(&items[index]) 121 | ) 122 | } 123 | Ok(index) 124 | } 125 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/emit.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | convert::identity, 4 | ops::Range, 5 | }; 6 | 7 | use crate::{ 8 | dep_graph::DepNode, 9 | read::{ImportId, InputFuncId, InputModule, SymbolIndex}, 10 | split_point::{OutputModuleInfo, SplitProgramInfo}, 11 | }; 12 | use anyhow::{anyhow, bail, Context, Result}; 13 | use wasmparser::{DataKind, RelocationEntry, RelocationType, SymbolInfo}; 14 | 15 | fn is_indirect_function_reloc(ty: RelocationType) -> bool { 16 | use RelocationType::*; 17 | match ty { 18 | TableIndexSleb | TableIndexI32 | TableIndexRelSleb | TableIndexSleb64 | TableIndexI64 19 | | TableIndexRelSleb64 => true, 20 | _ => false, 21 | } 22 | } 23 | 24 | fn get_indirect_functions(module: &InputModule) -> Result> { 25 | let mut funcs = HashSet::new(); 26 | 27 | for relocs in [module.code_section_index, module.data_section_index] 28 | .iter() 29 | .filter_map(|section_index| module.relocs.get(section_index)) 30 | { 31 | for entry in relocs.iter() { 32 | if is_indirect_function_reloc(entry.ty) { 33 | let symbol = &module.symbols[entry.index as usize]; 34 | let SymbolInfo::Func { index, .. } = symbol else { 35 | bail!("Invalid symbol {symbol:?} referenced by relocation {entry:?}"); 36 | }; 37 | funcs.insert(*index as usize); 38 | } 39 | } 40 | } 41 | 42 | Ok(funcs) 43 | } 44 | 45 | #[derive(Debug)] 46 | struct EmitState { 47 | indirect_functions: IndirectFunctionEmitInfo, 48 | // All relocations, ordered by offset, which are relative to the start of 49 | // the file rather than the start of the section. 50 | all_relocations: Vec, 51 | 52 | // Imports (corresponding to split points) to exclude from all modules. 53 | split_point_imports: HashSet, 54 | } 55 | 56 | impl EmitState { 57 | fn new(module: &InputModule, program_info: &SplitProgramInfo) -> Result { 58 | let indirect_functions = IndirectFunctionEmitInfo::new(module, program_info)?; 59 | let mut all_relocations = Vec::::new(); 60 | for (section_index, section_offset) in [ 61 | (module.code_section_index, module.code_section_offset), 62 | (module.data_section_index, module.data_section_offset), 63 | ] { 64 | let Some(section_relocs) = module.relocs.get(§ion_index) else { 65 | continue; 66 | }; 67 | for reloc in section_relocs { 68 | let mut reloc = reloc.clone(); 69 | reloc.offset = 70 | reloc 71 | .offset 72 | .checked_add(section_offset as u32) 73 | .ok_or_else(|| { 74 | anyhow!( 75 | "Invalid relocation {reloc:?} for section offset {section_offset:?}" 76 | ) 77 | })?; 78 | all_relocations.push(reloc); 79 | } 80 | } 81 | all_relocations.sort_by_key(|reloc| reloc.offset); 82 | let mut split_point_imports = HashSet::::new(); 83 | for (_, output_module) in program_info.output_modules.iter() { 84 | for split_point in output_module.split_points.iter() { 85 | split_point_imports.insert(split_point.import); 86 | } 87 | } 88 | Ok(EmitState { 89 | indirect_functions, 90 | all_relocations, 91 | split_point_imports, 92 | }) 93 | } 94 | 95 | fn get_relocations_for_range(&self, range: &Range) -> &[RelocationEntry] { 96 | let start = self 97 | .all_relocations 98 | .binary_search_by_key(&range.start, |reloc| reloc.offset as usize) 99 | .map_or_else(identity, identity); 100 | let end = self 101 | .all_relocations 102 | .binary_search_by_key(&range.end, |reloc| reloc.offset as usize) 103 | .map_or_else(identity, identity); 104 | &self.all_relocations[start..end] 105 | } 106 | } 107 | 108 | fn emit_main_module( 109 | module_info: &InputModule, 110 | program_info: &SplitProgramInfo, 111 | emit_state: &EmitState, 112 | ) -> anyhow::Result<()> { 113 | // // Currently it is not possible to clone an existing `walrus::Module`. 114 | // // Therefore, we simply reparse the wasm. 115 | // let mut new_module = walrus::Module::from_buffer(module_info.wasm)?; 116 | 117 | // // Modify indirect function table. 118 | // let table = new_module 119 | // .tables 120 | // .get_mut(emit_state.indirect_function_table_id); 121 | // if let Some(mut max) = table.maximum { 122 | // max = max.max(emit_state.max_indirect_function_index); 123 | // }; 124 | 125 | Ok(()) 126 | } 127 | 128 | #[derive(Debug, Default)] 129 | struct IndirectFunctionEmitInfo { 130 | table_entries: Vec, 131 | function_table_index: HashMap, 132 | table_range_for_output_module: Vec>, 133 | } 134 | 135 | impl IndirectFunctionEmitInfo { 136 | fn new(module: &InputModule, program_info: &SplitProgramInfo) -> Result { 137 | let mut indirect_functions = get_indirect_functions(module)?; 138 | 139 | indirect_functions.extend(program_info.shared_funcs.iter()); 140 | 141 | // Remove all split point imports. These are placeholders. Any 142 | // references to these functions will be replaced by a reference to the 143 | // corresponding `SplitPoint::export_func`. 144 | for (_, output_module) in program_info.output_modules.iter() { 145 | for split_point in output_module.split_points.iter() { 146 | indirect_functions.remove(&split_point.import_func); 147 | } 148 | } 149 | 150 | let mut table_entries: Vec<_> = indirect_functions.into_iter().collect(); 151 | table_entries.sort_by_key(|&func_id| { 152 | ( 153 | program_info 154 | .symbol_output_module 155 | .get(&DepNode::Function(func_id)), 156 | func_id, 157 | ) 158 | }); 159 | let function_table_index: HashMap<_, _> = table_entries 160 | .iter() 161 | .enumerate() 162 | .map(|(i, func_id)| (*func_id, i + 1)) 163 | .collect(); 164 | 165 | let mut table_range_for_output_module: Vec> = program_info 166 | .output_modules 167 | .iter() 168 | .map(|_| Range { 169 | start: usize::MAX, 170 | end: 0, 171 | }) 172 | .collect(); 173 | 174 | for (&func, &table_index) in function_table_index.iter() { 175 | if let Some(&output_module_index) = program_info 176 | .symbol_output_module 177 | .get(&DepNode::Function(func)) 178 | { 179 | let range = &mut table_range_for_output_module[output_module_index]; 180 | range.start = range.start.min(table_index); 181 | range.end = range.end.max(table_index + 1); 182 | } 183 | } 184 | 185 | Ok(Self { 186 | table_entries, 187 | function_table_index, 188 | table_range_for_output_module, 189 | }) 190 | } 191 | } 192 | 193 | fn encode_leb128_u32_5byte(mut value: u32, buf: &mut [u8; 5]) { 194 | for i in 0..5 { 195 | buf[i] = (value as u8) & 0x7f; 196 | value >>= 7; 197 | } 198 | for i in 0..4 { 199 | buf[i] |= 0x80; 200 | } 201 | } 202 | 203 | fn encode_leb128_i32_5byte(mut value: i32, buf: &mut [u8; 5]) { 204 | for i in 0..5 { 205 | buf[i] = (value as u8) & 0x7f; 206 | value >>= 7; 207 | } 208 | for i in 0..4 { 209 | buf[i] |= 0x80; 210 | } 211 | } 212 | 213 | fn encode_leb128_u64_10byte(mut value: u64, buf: &mut [u8; 10]) { 214 | for i in 0..10 { 215 | buf[i] = (value as u8) & 0x7f; 216 | value >>= 7; 217 | } 218 | for i in 0..9 { 219 | buf[i] |= 0x80; 220 | } 221 | } 222 | 223 | fn encode_leb128_i64_10byte(mut value: i64, buf: &mut [u8; 10]) { 224 | for i in 0..10 { 225 | buf[i] = (value as u8) & 0x7f; 226 | value >>= 7; 227 | } 228 | for i in 0..9 { 229 | buf[i] |= 0x80; 230 | } 231 | } 232 | 233 | fn encode_u32(value: u32, buf: &mut [u8; 4]) { 234 | *buf = value.to_le_bytes(); 235 | } 236 | 237 | fn encode_u64(value: u64, buf: &mut [u8; 8]) { 238 | *buf = value.to_le_bytes(); 239 | } 240 | 241 | fn encode_i32(value: i32, buf: &mut [u8; 4]) { 242 | *buf = value.to_le_bytes(); 243 | } 244 | 245 | fn encode_i64(value: i64, buf: &mut [u8; 8]) { 246 | *buf = value.to_le_bytes(); 247 | } 248 | 249 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] 250 | enum OutputFunctionKind { 251 | Import, 252 | Defined, 253 | IndirectStub, 254 | } 255 | 256 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 257 | struct OutputFunction { 258 | kind: OutputFunctionKind, 259 | input_func_id: InputFuncId, 260 | } 261 | 262 | struct ModuleEmitState<'a> { 263 | input_module: &'a InputModule<'a>, 264 | output_module_index: usize, 265 | output_module_info: &'a OutputModuleInfo, 266 | emit_state: &'a EmitState, 267 | program_info: &'a SplitProgramInfo, 268 | output_module: wasm_encoder::Module, 269 | output_functions: Vec, 270 | input_function_output_id: HashMap, 271 | indirect_function_table_range: Range, 272 | } 273 | 274 | impl<'a> ModuleEmitState<'a> { 275 | fn new( 276 | module: &'a InputModule<'a>, 277 | emit_state: &'a EmitState, 278 | output_module_index: usize, 279 | program_info: &'a crate::split_point::SplitProgramInfo, 280 | ) -> Self { 281 | let output_module_info = &program_info.output_modules[output_module_index].1; 282 | // We need to include definitions for all of the `included_symbols`. 283 | let mut funcs_to_define = HashSet::::new(); 284 | funcs_to_define.extend(output_module_info.included_symbols.iter().filter_map( 285 | |dep| match dep { 286 | DepNode::Function(func_id) => Some(*func_id), 287 | _ => None, 288 | }, 289 | )); 290 | // In addition, we need to include a stub function for each shared 291 | // import that forwards to an indirect call. These stub functions allow 292 | // us to replace calls to functions defined in other modules using the 293 | // relocation entries alone. These stubs will potentially be optimized 294 | // out by `wasm-opt`. 295 | funcs_to_define.extend(output_module_info.shared_imports.iter()); 296 | 297 | let mut output_functions: Vec<_> = funcs_to_define 298 | .iter() 299 | .map(|&func_id| { 300 | let kind = if output_module_info 301 | .included_symbols 302 | .contains(&DepNode::Function(func_id)) 303 | { 304 | if func_id < module.imported_funcs.len() { 305 | OutputFunctionKind::Import 306 | } else { 307 | OutputFunctionKind::Defined 308 | } 309 | } else { 310 | OutputFunctionKind::IndirectStub 311 | }; 312 | OutputFunction { 313 | kind, 314 | input_func_id: func_id, 315 | } 316 | }) 317 | .collect(); 318 | 319 | output_functions.sort(); 320 | 321 | let mut input_function_output_id: HashMap<_, _> = output_functions 322 | .iter() 323 | .enumerate() 324 | .map(|(output_func_id, &OutputFunction { input_func_id, .. })| { 325 | (input_func_id, output_func_id) 326 | }) 327 | .collect(); 328 | 329 | // Map references to `import_func` to `export_func`. 330 | for (_, output_module) in program_info.output_modules.iter() { 331 | for split_point in output_module.split_points.iter() { 332 | if let Some(&output_func_id) = 333 | input_function_output_id.get(&split_point.export_func) 334 | { 335 | println!("Mapping split point {split_point:?} -> {output_func_id}"); 336 | input_function_output_id.insert(split_point.import_func, output_func_id); 337 | } 338 | } 339 | } 340 | 341 | let indirect_function_table_range = 342 | emit_state.indirect_functions.table_range_for_output_module[output_module_index] 343 | .clone(); 344 | 345 | Self { 346 | input_module: module, 347 | output_module_index, 348 | output_module_info, 349 | emit_state, 350 | program_info, 351 | output_module: wasm_encoder::Module::new(), 352 | output_functions, 353 | input_function_output_id, 354 | indirect_function_table_range, 355 | } 356 | } 357 | 358 | fn is_main(&self) -> bool { 359 | self.output_module_index == 0 360 | } 361 | 362 | fn get_relocation_input_function_index(&self, relocation: &RelocationEntry) -> Result { 363 | let Some(SymbolInfo::Func { 364 | index: input_func_id, 365 | .. 366 | }) = self.input_module.symbols.get(relocation.index as usize) 367 | else { 368 | bail!("Relocation {relocation:?} does not refer to a valid function"); 369 | }; 370 | Ok(*input_func_id as usize) 371 | } 372 | 373 | fn get_relocated_function_index(&self, relocation: &RelocationEntry) -> Result { 374 | let input_func_id = self.get_relocation_input_function_index(relocation)?; 375 | let Some(&output_func_id) = self.input_function_output_id.get(&input_func_id) else { 376 | bail!( 377 | "Dependency analysis error: \ 378 | No output function for input function {input_func_id} \ 379 | referenced by relocation {relocation:?}" 380 | ); 381 | }; 382 | Ok(output_func_id) 383 | } 384 | 385 | fn get_relocated_function_table_index(&self, relocation: &RelocationEntry) -> Result { 386 | let input_func_id = self.get_relocation_input_function_index(relocation)?; 387 | self.emit_state 388 | .indirect_functions 389 | .function_table_index 390 | .get(&input_func_id) 391 | .ok_or_else(|| { 392 | anyhow!( 393 | "Dependency analysis error: \ 394 | No indirect function table index \ 395 | for input function {input_func_id} \ 396 | referenced by relocation {relocation:?}" 397 | ) 398 | }) 399 | .copied() 400 | } 401 | 402 | fn apply_relocation( 403 | &self, 404 | data: &mut [u8], 405 | data_offset: usize, 406 | relocation: &RelocationEntry, 407 | ) -> Result<()> { 408 | let relocation_range = relocation.relocation_range(); 409 | let target = 410 | &mut data[(relocation_range.start - data_offset)..(relocation_range.end - data_offset)]; 411 | use RelocationType::*; 412 | match relocation.ty { 413 | FunctionIndexLeb => { 414 | encode_leb128_u32_5byte( 415 | self.get_relocated_function_index(relocation)? as u32, 416 | target.try_into().unwrap(), 417 | ); 418 | } 419 | TableIndexSleb => { 420 | encode_leb128_i32_5byte( 421 | self.get_relocated_function_table_index(relocation)? as i32, 422 | target.try_into().unwrap(), 423 | ); 424 | } 425 | TableIndexI32 => { 426 | encode_u32( 427 | self.get_relocated_function_table_index(relocation)? as u32, 428 | target.try_into().unwrap(), 429 | ); 430 | } 431 | TableIndexSleb64 => { 432 | encode_leb128_i64_10byte( 433 | self.get_relocated_function_table_index(relocation)? as i64, 434 | target.try_into().unwrap(), 435 | ); 436 | } 437 | TableIndexI64 => { 438 | encode_u64( 439 | self.get_relocated_function_table_index(relocation)? as u64, 440 | target.try_into().unwrap(), 441 | ); 442 | } 443 | FunctionIndexI32 => { 444 | encode_u32( 445 | self.get_relocated_function_index(relocation)? as u32, 446 | target.try_into().unwrap(), 447 | ); 448 | } 449 | FunctionOffsetI32 | SectionOffsetI32 | TableIndexRelSleb | FunctionOffsetI64 450 | | TableIndexRelSleb64 => { 451 | bail!("Unsupported relocation type {relocation:?}"); 452 | } 453 | _ => {} 454 | } 455 | Ok(()) 456 | } 457 | 458 | fn get_relocated_data(&self, range: Range) -> Result> { 459 | let mut data = Vec::from(&self.input_module.raw[range.clone()]); 460 | for relocation in self.emit_state.get_relocations_for_range(&range) { 461 | self.apply_relocation(&mut data, range.start, relocation)?; 462 | } 463 | Ok(data) 464 | } 465 | 466 | fn generate(&mut self) -> Result<()> { 467 | // Encode type section 468 | self.generate_type_section()?; 469 | self.generate_import_section(); 470 | self.generate_function_section(); 471 | self.generate_table_section(); 472 | self.generate_memory_section(); 473 | self.generate_global_section(); 474 | self.generate_export_section(); 475 | self.generate_start_section(); 476 | self.generate_element_section()?; 477 | self.generate_data_count_section(); 478 | self.generate_code_section()?; 479 | self.generate_data_section()?; 480 | self.generate_wasm_bindgen_sections(); 481 | self.generate_name_section()?; 482 | self.generate_target_features_section(); 483 | Ok(()) 484 | } 485 | 486 | fn generate_type_section(&mut self) -> Result<()> { 487 | // Simply copy all types. Unneeded types may be pruned by `wasm-opt`. 488 | let mut section = wasm_encoder::TypeSection::new(); 489 | for input_func_type in self.input_module.types.iter() { 490 | let output_func_type: wasm_encoder::FuncType = 491 | input_func_type.clone().try_into().unwrap(); 492 | section.function( 493 | output_func_type.params().iter().cloned(), 494 | output_func_type.results().iter().cloned(), 495 | ); 496 | } 497 | self.output_module.section(§ion); 498 | Ok(()) 499 | } 500 | 501 | fn get_global_name(&self, index: usize) -> String { 502 | self.input_module 503 | .names 504 | .globals 505 | .get(&index) 506 | .map(|name| name.to_string()) 507 | .or_else(|| { 508 | self.input_module 509 | .export_map 510 | .get(&(wasmparser::ExternalKind::Global as isize, index)) 511 | .map(|(_, name)| name.to_string()) 512 | }) 513 | .unwrap_or_else(|| format!("__global_{index}")) 514 | } 515 | 516 | fn get_memory_name(&self, index: usize) -> String { 517 | self.input_module 518 | .names 519 | .memories 520 | .get(&index) 521 | .map(|name| name.to_string()) 522 | .or_else(|| { 523 | self.input_module 524 | .export_map 525 | .get(&(wasmparser::ExternalKind::Memory as isize, index)) 526 | .map(|(_, name)| name.to_string()) 527 | }) 528 | .unwrap_or_else(|| format!("__memory_{index}")) 529 | } 530 | 531 | fn get_indirect_function_table_type(&self) -> wasm_encoder::TableType { 532 | // + 1 due to empty entry at index 0 533 | let indirect_table_size = self.emit_state.indirect_functions.table_entries.len() + 1; 534 | wasm_encoder::TableType { 535 | element_type: wasm_encoder::RefType::FUNCREF, 536 | minimum: indirect_table_size as u32, 537 | maximum: Some(indirect_table_size as u32), 538 | } 539 | } 540 | 541 | fn generate_import_section(&mut self) { 542 | let mut section = wasm_encoder::ImportSection::new(); 543 | // Function imports 544 | for (func_id, &import_id) in self.input_module.imported_funcs.iter().enumerate() { 545 | if !self 546 | .output_module_info 547 | .included_symbols 548 | .contains(&DepNode::Function(func_id)) 549 | { 550 | continue; 551 | } 552 | let import = &self.input_module.imports[import_id]; 553 | let ty: wasm_encoder::EntityType = import.ty.clone().try_into().unwrap(); 554 | section.import(import.module, import.name, ty); 555 | } 556 | 557 | // Copy all non-function imports from input. 558 | for import in self.input_module.imports.iter() { 559 | if let wasmparser::TypeRef::Func(_) = import.ty { 560 | continue; 561 | } 562 | let ty: wasm_encoder::EntityType = import.ty.clone().try_into().unwrap(); 563 | section.import(import.module, import.name, ty); 564 | } 565 | 566 | if !self.is_main() { 567 | // Import indirect function table. 568 | 569 | section.import( 570 | "__wasm_split", 571 | "__indirect_function_table", 572 | self.get_indirect_function_table_type(), 573 | ); 574 | 575 | // Import all globals defined by the input module. 576 | for (global_index, global) in self.input_module.globals.iter().enumerate() { 577 | let ty: wasm_encoder::GlobalType = global.ty.try_into().unwrap(); 578 | if !ty.mutable { 579 | continue; 580 | } 581 | section.import( 582 | "__wasm_split", 583 | self.get_global_name(global_index).as_str(), 584 | ty, 585 | ); 586 | } 587 | 588 | // Import all memories defined by the input module. 589 | for (memory_index, memory) in self.input_module.memories.iter().enumerate() { 590 | let ty: wasm_encoder::MemoryType = memory.clone().try_into().unwrap(); 591 | section.import( 592 | "__wasm_split", 593 | self.get_memory_name(memory_index).as_str(), 594 | ty, 595 | ); 596 | } 597 | } 598 | self.output_module.section(§ion); 599 | } 600 | 601 | fn generate_function_section(&mut self) { 602 | let mut section = wasm_encoder::FunctionSection::new(); 603 | for OutputFunction { input_func_id, .. } in self 604 | .output_functions 605 | .iter() 606 | .filter(|OutputFunction { kind, .. }| *kind != OutputFunctionKind::Import) 607 | { 608 | section.function(self.input_module.func_type_id(*input_func_id) as u32); 609 | } 610 | self.output_module.section(§ion); 611 | } 612 | 613 | fn generate_table_section(&mut self) { 614 | if !self.is_main() { 615 | return; 616 | } 617 | let mut section = wasm_encoder::TableSection::new(); 618 | section.table(self.get_indirect_function_table_type()); 619 | self.output_module.section(§ion); 620 | } 621 | 622 | fn generate_memory_section(&mut self) { 623 | if !self.is_main() || self.input_module.memories.is_empty() { 624 | return; 625 | } 626 | let mut section = wasm_encoder::MemorySection::new(); 627 | for memory in self.input_module.memories.iter() { 628 | section.memory(memory.clone().try_into().unwrap()); 629 | } 630 | self.output_module.section(§ion); 631 | } 632 | 633 | fn generate_global_section(&mut self) { 634 | if !self.is_main() { 635 | return; 636 | } 637 | let mut section = wasm_encoder::GlobalSection::new(); 638 | for global in self.input_module.globals.iter() { 639 | section.global( 640 | global.ty.clone().try_into().unwrap(), 641 | &global.init_expr.clone().try_into().unwrap(), 642 | ); 643 | } 644 | self.output_module.section(§ion); 645 | } 646 | 647 | fn generate_export_section(&mut self) { 648 | if !self.is_main() { 649 | return; 650 | } 651 | let mut section = wasm_encoder::ExportSection::new(); 652 | let mut existing_exports = HashSet::<&str>::new(); 653 | for export in self.input_module.exports.iter() { 654 | let mut index = export.index; 655 | if export.kind == wasmparser::ExternalKind::Func { 656 | let Some(&func_id) = self.input_function_output_id.get(&(index as InputFuncId)) 657 | else { 658 | continue; 659 | }; 660 | index = func_id as u32; 661 | } 662 | section.export(export.name, export.kind.try_into().unwrap(), index); 663 | existing_exports.insert(export.name); 664 | } 665 | 666 | // Export table. 667 | if !existing_exports.contains("__indirect_function_table") { 668 | section.export( 669 | "__indirect_function_table", 670 | wasm_encoder::ExportKind::Table, 671 | 0, 672 | ); 673 | } 674 | 675 | // Export globals. 676 | for (global_index, global) in self.input_module.globals.iter().enumerate() { 677 | let name = self.get_global_name(global_index); 678 | if existing_exports.contains(name.as_str()) { 679 | continue; 680 | } 681 | if !global.ty.mutable { 682 | break; 683 | } 684 | section.export( 685 | name.as_str(), 686 | wasm_encoder::ExportKind::Global, 687 | global_index as u32, 688 | ); 689 | } 690 | self.output_module.section(§ion); 691 | } 692 | 693 | fn generate_start_section(&mut self) { 694 | if self.is_main() { 695 | if let Some(input_start_func_id) = self.input_module.start { 696 | let output_func = self 697 | .input_function_output_id 698 | .get(&input_start_func_id) 699 | .expect("Failed to map start function to output function index"); 700 | self.output_module.section(&wasm_encoder::StartSection { 701 | function_index: *output_func as u32, 702 | }); 703 | } 704 | } 705 | } 706 | 707 | fn generate_element_section(&mut self) -> Result<()> { 708 | let indirect_range = self.indirect_function_table_range.clone(); 709 | if indirect_range.is_empty() { 710 | panic!("No indirect range"); 711 | return Ok(()); 712 | } 713 | let mut section = wasm_encoder::ElementSection::new(); 714 | let func_ids: Vec = indirect_range 715 | .clone() 716 | .map(|table_index| -> Result { 717 | let input_func_id = 718 | self.emit_state.indirect_functions.table_entries[table_index - 1]; 719 | let output_func_id = *self 720 | .input_function_output_id 721 | .get(&input_func_id) 722 | .ok_or_else(|| { 723 | anyhow!( 724 | "No output function corresponding to input function {input_func_id:?}" 725 | ) 726 | })?; 727 | Ok(output_func_id as u32) 728 | }) 729 | .collect::>>()?; 730 | section.segment(wasm_encoder::ElementSegment { 731 | mode: wasm_encoder::ElementMode::Active { 732 | table: Some(0), 733 | offset: &wasm_encoder::ConstExpr::i32_const(indirect_range.start as i32), 734 | }, 735 | elements: wasm_encoder::Elements::Functions(&func_ids), 736 | }); 737 | self.output_module.section(§ion); 738 | Ok(()) 739 | } 740 | 741 | fn generate_data_count_section(&mut self) { 742 | let section = wasm_encoder::DataCountSection { 743 | count: if self.is_main() { 744 | self.input_module.data_segments.len() as u32 745 | } else { 746 | 0 747 | }, 748 | }; 749 | self.output_module.section(§ion); 750 | } 751 | 752 | fn generate_indirect_stub( 753 | &self, 754 | indirect_index: usize, 755 | type_id: usize, 756 | ) -> wasm_encoder::Function { 757 | let func_type = &self.input_module.types[type_id]; 758 | let mut func = wasm_encoder::Function::new([]); 759 | for (param_i, _param_type) in func_type.params().iter().enumerate() { 760 | func.instruction(&wasm_encoder::Instruction::LocalGet(param_i as u32)); 761 | } 762 | func.instruction(&wasm_encoder::Instruction::I32Const(indirect_index as i32)); 763 | func.instruction(&wasm_encoder::Instruction::CallIndirect { 764 | ty: type_id as u32, 765 | table: 0, 766 | }); 767 | func.instruction(&wasm_encoder::Instruction::End); 768 | func 769 | } 770 | 771 | fn generate_code_section(&mut self) -> Result<()> { 772 | let mut section = wasm_encoder::CodeSection::new(); 773 | for output_func in self.output_functions.iter() { 774 | match output_func.kind { 775 | OutputFunctionKind::Import => {} 776 | OutputFunctionKind::Defined => { 777 | let input_func = &self.input_module.defined_funcs 778 | [output_func.input_func_id - self.input_module.imported_funcs.len()]; 779 | section.raw(&self.get_relocated_data(input_func.body.range())?); 780 | } 781 | OutputFunctionKind::IndirectStub => { 782 | let indirect_index = self 783 | .emit_state 784 | .indirect_functions 785 | .function_table_index 786 | .get(&output_func.input_func_id) 787 | .unwrap(); 788 | let function = self.generate_indirect_stub( 789 | *indirect_index, 790 | self.input_module.func_type_id(output_func.input_func_id), 791 | ); 792 | section.function(&function); 793 | } 794 | } 795 | } 796 | self.output_module.section(§ion); 797 | Ok(()) 798 | } 799 | 800 | fn generate_data_section(&mut self) -> Result<()> { 801 | if !self.is_main() { 802 | return Ok(()); 803 | } 804 | let mut section = wasm_encoder::DataSection::new(); 805 | for input_segment in self.input_module.data_segments.iter() { 806 | // Note: `input_segment.range` includes the segment header. 807 | let range_end = input_segment.range.end; 808 | let data = 809 | self.get_relocated_data((range_end - input_segment.data.len())..range_end)?; 810 | match input_segment.kind { 811 | DataKind::Passive => section.passive(data), 812 | DataKind::Active { 813 | memory_index, 814 | offset_expr, 815 | } => section.active(memory_index, &offset_expr.try_into()?, data), 816 | }; 817 | } 818 | self.output_module.section(§ion); 819 | Ok(()) 820 | } 821 | 822 | fn generate_name_section(&mut self) -> Result<()> { 823 | fn convert_name_map<'a>( 824 | parser_map: &wasmparser::NameMap<'a>, 825 | ) -> Result { 826 | let mut encoder_map = wasm_encoder::NameMap::new(); 827 | for r in parser_map.clone().into_iter() { 828 | let naming = r?; 829 | encoder_map.append(naming.index, naming.name); 830 | } 831 | Ok(encoder_map) 832 | } 833 | 834 | fn convert_name_hash_map(map: &HashMap) -> wasm_encoder::NameMap { 835 | let mut encoder_map = wasm_encoder::NameMap::new(); 836 | let mut names = map.iter().collect::>(); 837 | names.sort(); 838 | for (&i, &name) in names.iter() { 839 | encoder_map.append(i as u32, name); 840 | } 841 | encoder_map 842 | } 843 | let mut section = wasm_encoder::NameSection::new(); 844 | // Function names 845 | { 846 | let mut name_map = wasm_encoder::NameMap::new(); 847 | let mut locals_map = wasm_encoder::IndirectNameMap::new(); 848 | let mut labels_map = wasm_encoder::IndirectNameMap::new(); 849 | for (output_func_id, OutputFunction { input_func_id, .. }) in 850 | self.output_functions.iter().enumerate() 851 | { 852 | if let Some(name) = self.input_module.names.functions.get(input_func_id) { 853 | name_map.append(output_func_id as u32, name); 854 | } 855 | if let Some(name_map) = self.input_module.names.locals.get(input_func_id) { 856 | locals_map.append(output_func_id as u32, &convert_name_map(name_map)?); 857 | } 858 | if let Some(name_map) = self.input_module.names.labels.get(input_func_id) { 859 | labels_map.append(output_func_id as u32, &convert_name_map(name_map)?); 860 | } 861 | } 862 | section.functions(&name_map); 863 | section.locals(&locals_map); 864 | section.labels(&labels_map); 865 | } 866 | section.types(&convert_name_hash_map(&self.input_module.names.types)); 867 | section.tables(&convert_name_hash_map(&self.input_module.names.tables)); 868 | section.memories(&convert_name_hash_map(&self.input_module.names.memories)); 869 | section.globals(&convert_name_hash_map(&self.input_module.names.globals)); 870 | // elements 871 | if self.is_main() { 872 | section.data(&convert_name_hash_map( 873 | &self.input_module.names.data_segments, 874 | )); 875 | } 876 | // tag 877 | // fields 878 | // tags 879 | self.output_module.section(§ion); 880 | Ok(()) 881 | // Type names 882 | } 883 | 884 | fn generate_wasm_bindgen_sections(&mut self) { 885 | for custom in self.input_module.custom_sections.iter() { 886 | if self.is_main() && custom.name == "__wasm_bindgen_unstable" { 887 | self.output_module.section(&wasm_encoder::CustomSection { 888 | name: custom.name.into(), 889 | data: custom.data.into(), 890 | }); 891 | } 892 | } 893 | } 894 | 895 | fn generate_target_features_section(&mut self) { 896 | for custom in self.input_module.custom_sections.iter() { 897 | if custom.name == "target_features" { 898 | self.output_module.section(&wasm_encoder::CustomSection { 899 | name: custom.name.into(), 900 | data: custom.data.into(), 901 | }); 902 | } 903 | } 904 | } 905 | } 906 | 907 | pub fn emit_modules( 908 | module: &InputModule, 909 | program_info: &SplitProgramInfo, 910 | emit_fn: &dyn Fn(usize, &[u8]) -> anyhow::Result<()>, 911 | ) -> anyhow::Result<()> { 912 | // For now we will ignore data symbols because that simplifies things quite a bit. 913 | 914 | let emit_state = EmitState::new(module, program_info)?; 915 | 916 | for output_module_index in 0..program_info.output_modules.len() { 917 | let mut emit_state = 918 | ModuleEmitState::new(module, &emit_state, output_module_index, program_info); 919 | let identifier = &program_info.output_modules[output_module_index].0; 920 | 921 | emit_state 922 | .generate() 923 | .with_context(|| format!("Error generating {:?}", identifier))?; 924 | 925 | emit_fn(output_module_index, emit_state.output_module.as_slice()) 926 | .with_context(|| format!("Error emitting {:?}", identifier))?; 927 | } 928 | 929 | Ok(()) 930 | } 931 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::Path}; 2 | 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use split_point::SplitModuleIdentifier; 6 | 7 | #[derive(Debug, Parser)] 8 | #[command(name = "wasm-split")] 9 | struct Cli { 10 | /// Input .wasm file. 11 | input: Box, 12 | 13 | /// Output directory. 14 | output: Box, 15 | 16 | /// Print verbose split information. 17 | #[arg(short, long)] 18 | verbose: bool, 19 | } 20 | 21 | mod dep_graph; 22 | mod emit; 23 | mod read; 24 | mod split_point; 25 | 26 | fn main() -> Result<()> { 27 | let args = Cli::parse(); 28 | let input_wasm = std::fs::read(&args.input)?; 29 | let module = crate::read::InputModule::parse(&input_wasm)?; 30 | let dep_graph = dep_graph::get_dependencies(&module)?; 31 | let split_points = split_point::get_split_points(&module)?; 32 | let split_program_info = 33 | split_point::compute_split_modules(&module, &dep_graph, &split_points)?; 34 | 35 | if args.verbose { 36 | for (name, split_deps) in split_program_info.output_modules.iter() { 37 | split_deps.print(format!("{:?}", name).as_str(), &module); 38 | } 39 | } 40 | 41 | crate::emit::emit_modules( 42 | &module, 43 | &split_program_info, 44 | &|output_module_index: usize, data: &[u8]| -> Result<()> { 45 | let identifier = &split_program_info.output_modules[output_module_index].0; 46 | let output_filename = identifier.name() + ".wasm"; 47 | let output_path = args.output.join(output_filename); 48 | std::fs::create_dir_all(&args.output)?; 49 | std::fs::write(output_path, data)?; 50 | Ok(()) 51 | }, 52 | )?; 53 | 54 | let mut javascript = String::new(); 55 | javascript.push_str( 56 | r#"import { initSync } from "./main.js"; 57 | function makeLoad(url, deps) { 58 | let alreadyLoaded = false; 59 | return async(callbackIndex, callbackData) => { 60 | if (alreadyLoaded) return; 61 | for (let dep of deps) { 62 | await dep(); 63 | } 64 | let mainExports = undefined; 65 | try { 66 | const response = await fetch(url); 67 | mainExports = initSync(undefined, undefined); 68 | const imports = { 69 | env: { 70 | memory: mainExports.memory, 71 | }, 72 | __wasm_split: { 73 | __indirect_function_table: mainExports.__indirect_function_table, 74 | __stack_pointer: mainExports.__stack_pointer, 75 | __tls_base: mainExports.__tls_base, 76 | memory: mainExports.memory, 77 | }, 78 | }; 79 | const module = await WebAssembly.instantiateStreaming(response, imports); 80 | alreadyLoaded = true; 81 | if (callbackIndex === undefined) return; 82 | mainExports.__indirect_function_table.get(callbackIndex)( 83 | callbackData, 84 | true, 85 | ); 86 | } catch (e) { 87 | if (callbackIndex === undefined) throw e; 88 | console.error("Failed to load " + url.href, e); 89 | if (mainExports === undefined) { 90 | mainExports = initSync(undefined, undefined); 91 | } 92 | mainExports.__indirect_function_table.get(callbackIndex)( 93 | callbackData, 94 | false, 95 | ); 96 | } 97 | }; 98 | } 99 | "#, 100 | ); 101 | let mut split_deps = HashMap::>::new(); 102 | for (name, _) in split_program_info.output_modules.iter() { 103 | let SplitModuleIdentifier::Chunk(splits) = name else { 104 | continue; 105 | }; 106 | for split in splits { 107 | split_deps 108 | .entry(split.clone()) 109 | .or_default() 110 | .push(name.name()); 111 | } 112 | javascript.push_str(format!( 113 | "const __wasm_split_load_{name} = makeLoad(new URL(\"./{name}.wasm\", import.meta.url), []);\n", 114 | name = name.name(), 115 | ).as_str()) 116 | } 117 | for (identifier, _) in split_program_info.output_modules.iter().rev() { 118 | if matches!(identifier, SplitModuleIdentifier::Chunk(_)) { 119 | continue; 120 | } 121 | let name = identifier.name(); 122 | javascript.push_str(format!( 123 | "export const __wasm_split_load_{name} = makeLoad(new URL(\"./{name}.wasm\", import.meta.url), [{deps}]);\n", 124 | name = name, 125 | deps = split_deps 126 | .remove(&name) 127 | .unwrap_or_default() 128 | .iter() 129 | .map(|x| format!("__wasm_split_load_{x}")) 130 | .collect::>() 131 | .join(", "), 132 | ).as_str()) 133 | } 134 | 135 | std::fs::write(args.output.join("__wasm_split.js"), javascript)?; 136 | Ok(()) 137 | } 138 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/module_info.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::{HashMap, HashSet}, 4 | ops::{Deref, Range}, 5 | }; 6 | 7 | use anyhow::{bail, Context}; 8 | use walrus::Module; 9 | 10 | pub type SymbolIndex = usize; 11 | 12 | pub struct SymbolTable<'a>(Vec>); 13 | 14 | impl<'a> SymbolTable<'a> { 15 | fn get_symbol_dep_node(&self, symbol_index: usize) -> Option { 16 | match self.0[symbol_index] { 17 | wasmparser::SymbolInfo::Func { index, .. } => Some(DepNode::Function(index as usize)), 18 | wasmparser::SymbolInfo::Data { .. } => Some(DepNode::DataSymbol(symbol_index)), 19 | _ => None, 20 | } 21 | } 22 | } 23 | 24 | impl<'a> Deref for SymbolTable<'a> { 25 | type Target = Vec>; 26 | fn deref(&self) -> &::Target { 27 | &self.0 28 | } 29 | } 30 | 31 | impl SymbolRangeInfo { 32 | fn new( 33 | wasm: &[u8], 34 | module: &walrus::Module, 35 | symbol_table: &SymbolTable, 36 | ) -> anyhow::Result { 37 | let data_segment_ranges = get_data_segment_ranges(wasm)?; 38 | let mut defined_funcs: Vec<(walrus::FunctionId, Range)> = module 39 | .funcs 40 | .iter_local() 41 | .filter_map(|(id, func)| { 42 | func.original_range 43 | .map(|range| (id, range.start as usize..range.end as usize)) 44 | }) 45 | .collect(); 46 | defined_funcs.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start)); 47 | 48 | let mut data_symbols: Vec<(usize, wasmparser::DefinedDataSymbol)> = symbol_table 49 | .iter() 50 | .enumerate() 51 | .filter_map(|(i, info)| match info { 52 | wasmparser::SymbolInfo::Data { 53 | symbol: Some(symbol), 54 | .. 55 | } => Some((i, *symbol)), 56 | _ => None, 57 | }) 58 | .collect(); 59 | data_symbols.sort_by(|(_, a), (_, b)| (a.index, a.offset).cmp(&(b.index, b.offset))); 60 | Ok(SymbolRangeInfo { 61 | defined_funcs, 62 | data_segment_ranges, 63 | data_symbols, 64 | }) 65 | } 66 | 67 | fn find_function_containing_range(&self, range: Range) -> anyhow::Result { 68 | let Some(func_index) = find_by_range(&self.defined_funcs, &range, |(_, func_range)| { 69 | func_range.clone() 70 | }) else { 71 | bail!("No match for function relocation range {range:?}"); 72 | }; 73 | Ok(self.defined_funcs[func_index].0.index()) 74 | } 75 | 76 | fn find_data_segment_containing_range(&self, range: Range) -> anyhow::Result { 77 | let Some(index) = find_by_range(&self.data_segment_ranges, &range, |data_segment_range| { 78 | data_segment_range.clone() 79 | }) else { 80 | bail!("No match for data relocation range {range:?}"); 81 | }; 82 | Ok(index) 83 | } 84 | 85 | fn find_data_symbol_containing_range(&self, range: Range) -> anyhow::Result { 86 | let get_section_relative_range = |defined_symbol: &wasmparser::DefinedDataSymbol| { 87 | let offset = defined_symbol.offset as usize; 88 | let size = defined_symbol.size as usize; 89 | let base = self.data_segment_ranges[defined_symbol.index as usize].start; 90 | (base + offset)..(base + offset + size) 91 | }; 92 | let Some(index) = find_by_range(&self.data_symbols, &range, |(_, defined_symbol)| { 93 | get_section_relative_range(defined_symbol) 94 | }) else { 95 | bail!( 96 | "No match for data relocation range {range:?} {data_segment_ranges:?}", 97 | data_segment_ranges = &self.data_segment_ranges 98 | ); 99 | }; 100 | Ok(self.data_symbols[index].0) 101 | } 102 | } 103 | 104 | pub struct ModuleInfo<'a> { 105 | pub module: &'a walrus::Module, 106 | pub func_id_map: HashMap, 107 | pub symbol_table: SymbolTable<'a>, 108 | pub func_symbols: HashMap, 109 | pub dep_graph: DepGraph, 110 | } 111 | 112 | pub type DataSegmentRange = Range; 113 | 114 | fn get_symbol_table<'a>(module: &'a walrus::Module) -> anyhow::Result> { 115 | let (Some(section), None) = ({ 116 | let mut iter = module 117 | .customs 118 | .iter() 119 | .map(|(_id, section)| section) 120 | .filter(|section| section.name() == "linking"); 121 | (iter.next(), iter.next()) 122 | }) else { 123 | bail!("No linking section found"); 124 | }; 125 | let raw_section = section 126 | .as_any() 127 | .downcast_ref::() 128 | .unwrap(); 129 | let reader = wasmparser::LinkingSectionReader::new(&raw_section.data[..], 0)?; 130 | let (Some(symbol_table), None) = ({ 131 | let mut iter = reader 132 | .subsections() 133 | .filter_map(|subsection| match subsection { 134 | Ok(wasmparser::Linking::SymbolTable(map)) => Some(map), 135 | _ => None, 136 | }); 137 | (iter.next(), iter.next()) 138 | }) else { 139 | bail!("No symbol table found"); 140 | }; 141 | Ok(SymbolTable( 142 | symbol_table.into_iter().collect::, _>>()?, 143 | )) 144 | } 145 | 146 | const CODE_SECTION_ID: u32 = 10; 147 | 148 | const DATA_SECTION_ID: u32 = 11; 149 | 150 | fn get_data_segment_ranges(wasm: &[u8]) -> anyhow::Result> { 151 | let mut ranges: Vec = Vec::new(); 152 | let parser = wasmparser::Parser::new(0); 153 | for payload in parser.parse_all(wasm) { 154 | match payload? { 155 | wasmparser::Payload::DataSection(data_section) => { 156 | let data_section_offset = data_section.range().start; 157 | for data_segment_result in data_section.into_iter() { 158 | let wasmparser::Data { data, .. } = data_segment_result?; 159 | let offset = data.as_ptr() as usize - wasm.as_ptr() as usize; 160 | // We can't use `wasmparser::Data::range` because it 161 | // includes the segment header, and we need the range of the 162 | // segment data.) 163 | ranges.push( 164 | (offset - data_section_offset)..(offset - data_section_offset + data.len()), 165 | ); 166 | } 167 | } 168 | _ => {} 169 | } 170 | } 171 | Ok(ranges) 172 | } 173 | 174 | #[derive(Debug, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Clone)] 175 | pub enum DepNode { 176 | Function(usize), 177 | DataSymbol(usize), 178 | } 179 | 180 | impl<'a> ModuleInfo<'a> { 181 | pub fn new(wasm: &[u8], module_opt: &'a mut Option) -> anyhow::Result { 182 | *module_opt = Some(Module::from_buffer(wasm)?); 183 | let module = &module_opt.as_ref().unwrap(); 184 | let func_id_map = module 185 | .funcs 186 | .iter() 187 | .map(|f| (f.id().index() as usize, f)) 188 | .collect(); 189 | let symbol_table = get_symbol_table(module)?; 190 | let func_symbols: HashMap = symbol_table 191 | .iter() 192 | .enumerate() 193 | .filter_map(|(i, info)| match info { 194 | wasmparser::SymbolInfo::Func { index, .. } => Some((*index as usize, i)), 195 | _ => None, 196 | }) 197 | .collect(); 198 | let symbol_range_info = SymbolRangeInfo::new(wasm, module, &symbol_table)?; 199 | let dep_graph = get_dependencies(module, &symbol_range_info, &symbol_table)?; 200 | Ok(Self { 201 | module, 202 | func_id_map, 203 | symbol_table, 204 | func_symbols, 205 | dep_graph, 206 | }) 207 | } 208 | } 209 | 210 | pub type DepGraph = HashMap>; 211 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/read.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Result}; 2 | use std::collections::HashMap; 3 | pub use std::ops::Range; 4 | pub use wasmparser::{ 5 | Data, Element, Export, FuncType, FunctionBody, Global, Import, MemoryType, RelocationEntry, 6 | SymbolInfo, Table, TagType, 7 | }; 8 | use wasmparser::{Payload, TypeRef}; 9 | pub type InputRange = Range; 10 | 11 | pub struct CustomSection<'a> { 12 | pub name: &'a str, 13 | pub data_offset: usize, 14 | pub data: &'a [u8], 15 | pub section_index: usize, 16 | pub range: InputRange, 17 | } 18 | 19 | pub type FuncTypeId = usize; 20 | pub type InputFuncId = usize; 21 | pub type TableId = usize; 22 | pub type ImportId = usize; 23 | pub type ExportId = usize; 24 | pub type MemoryId = usize; 25 | pub type GlobalId = usize; 26 | pub type ElementId = usize; 27 | pub type DataSegmentId = usize; 28 | pub type TagId = usize; 29 | pub type SectionIndex = usize; 30 | 31 | #[derive(Debug)] 32 | pub struct DefinedFunc<'a> { 33 | pub type_id: FuncTypeId, 34 | pub body: FunctionBody<'a>, 35 | } 36 | 37 | #[derive(Default, Clone)] 38 | pub struct Names<'a> { 39 | pub module: Option<&'a str>, 40 | pub functions: HashMap, 41 | pub locals: HashMap>, 42 | pub labels: HashMap>, 43 | pub types: HashMap, 44 | pub tables: HashMap, 45 | pub memories: HashMap, 46 | pub globals: HashMap, 47 | pub elements: HashMap, 48 | pub data_segments: HashMap, 49 | pub tags: HashMap, 50 | } 51 | 52 | fn convert_name_map<'a>(name_map: wasmparser::NameMap<'a>) -> Result> { 53 | name_map 54 | .into_iter() 55 | .map(|r| r.map(|naming| (naming.index as usize, naming.name))) 56 | .collect::, _>>() 57 | .map_err(|e| e.into()) 58 | } 59 | 60 | fn convert_indirect_name_map<'a>( 61 | indirect_name_map: wasmparser::IndirectNameMap<'a>, 62 | ) -> Result>> { 63 | Ok(indirect_name_map 64 | .into_iter() 65 | .map(|r| -> Result<(usize, wasmparser::NameMap<'a>)> { 66 | let indirect_naming = r?; 67 | Ok((indirect_naming.index as usize, indirect_naming.names)) 68 | }) 69 | .collect::, _>>()?) 70 | } 71 | 72 | impl<'a> Names<'a> { 73 | fn new(data: &'a [u8], original_offset: usize) -> Result { 74 | let mut names: Self = Default::default(); 75 | for part in wasmparser::NameSectionReader::new(data, original_offset) { 76 | use wasmparser::Name; 77 | match part? { 78 | Name::Module { name, .. } => { 79 | names.module = Some(name); 80 | } 81 | Name::Function(name_map) => { 82 | names.functions = convert_name_map(name_map)?; 83 | } 84 | Name::Local(indirect_name_map) => { 85 | names.locals = convert_indirect_name_map(indirect_name_map)?; 86 | } 87 | Name::Label(indirect_name_map) => { 88 | names.labels = convert_indirect_name_map(indirect_name_map)?; 89 | } 90 | Name::Type(name_map) => { 91 | names.types = convert_name_map(name_map)?; 92 | } 93 | Name::Table(name_map) => { 94 | names.tables = convert_name_map(name_map)?; 95 | } 96 | Name::Memory(name_map) => { 97 | names.memories = convert_name_map(name_map)?; 98 | } 99 | Name::Global(name_map) => { 100 | names.globals = convert_name_map(name_map)?; 101 | } 102 | Name::Data(name_map) => { 103 | names.data_segments = convert_name_map(name_map)?; 104 | } 105 | Name::Element(name_map) => { 106 | names.elements = convert_name_map(name_map)?; 107 | } 108 | Name::Tag(name_map) => { 109 | names.tags = convert_name_map(name_map)?; 110 | } 111 | Name::Field(_name_map) => { 112 | bail!("Field names not supported"); 113 | } 114 | Name::Unknown { ty, .. } => { 115 | bail!("Unknown name subsection: {:?}", ty); 116 | } 117 | } 118 | } 119 | Ok(names) 120 | } 121 | } 122 | 123 | pub type SymbolIndex = usize; 124 | 125 | #[derive(Debug, PartialEq, Eq, Clone)] 126 | pub struct DataSymbol { 127 | pub symbol_index: SymbolIndex, 128 | // Range relative to the start of the WebAssembly file. 129 | pub range: Range, 130 | } 131 | 132 | fn get_data_symbols(data_segments: &[Data], symbols: &[SymbolInfo]) -> Result> { 133 | let mut data_symbols = Vec::new(); 134 | for (symbol_index, info) in symbols.iter().enumerate() { 135 | let SymbolInfo::Data { 136 | symbol: Some(symbol), 137 | .. 138 | } = info 139 | else { 140 | continue; 141 | }; 142 | if symbol.size == 0 { 143 | // Ignore zero-size symbols since they cannot be the target of a relocation. 144 | continue; 145 | } 146 | let data_segment = data_segments 147 | .get(symbol.index as usize) 148 | .ok_or_else(|| anyhow!("Invalid data segment index in symbol: {:?}", symbol))?; 149 | if symbol 150 | .offset 151 | .checked_add(symbol.size) 152 | .ok_or_else(|| anyhow!("Invalid symbol: {symbol:?}"))? as usize 153 | > data_segment.data.len() 154 | { 155 | bail!( 156 | "Invalid symbol {symbol:?} for data segment of size {:?}", 157 | data_segment.data.len() 158 | ); 159 | } 160 | let offset = data_segment.range.end - data_segment.data.len() + (symbol.offset as usize); 161 | let range = offset..(offset + symbol.size as usize); 162 | data_symbols.push(DataSymbol { 163 | symbol_index, 164 | range, 165 | }); 166 | } 167 | data_symbols.sort_by_key(|symbol| symbol.range.start); 168 | Ok(data_symbols) 169 | } 170 | 171 | #[derive(Default)] 172 | pub struct InputModule<'a> { 173 | pub raw: &'a [u8], 174 | pub types: Vec, 175 | pub imports: Vec>, 176 | pub tables: Vec>, 177 | pub tags: Vec, 178 | pub globals: Vec>, 179 | pub exports: Vec>, 180 | pub export_map: HashMap<(isize, usize), (usize, &'a str)>, 181 | pub memories: Vec, 182 | pub elements: Vec>, 183 | pub code_section_offset: usize, 184 | pub code_section_index: usize, 185 | pub data_segments: Vec>, 186 | pub data_section_offset: usize, 187 | pub data_section_index: usize, 188 | pub imported_funcs: Vec, 189 | pub imported_func_map: HashMap, 190 | pub defined_funcs: Vec>, 191 | pub custom_sections: Vec>, 192 | pub start: Option, 193 | pub names: Names<'a>, 194 | pub symbols: Vec>, 195 | pub data_symbols: Vec, 196 | pub relocs: HashMap>, 197 | } 198 | 199 | impl<'a> InputModule<'a> { 200 | pub fn parse(wasm: &'a [u8]) -> anyhow::Result { 201 | let mut module = Self { 202 | raw: wasm, 203 | ..Default::default() 204 | }; 205 | let mut function_types: Vec = Vec::new(); 206 | let mut section_index = 0; 207 | let parser = wasmparser::Parser::new(0); 208 | for payload in parser.parse_all(wasm) { 209 | match payload? { 210 | Payload::Version { .. } => {} 211 | Payload::TypeSection(reader) => { 212 | module.types = reader 213 | .into_iter_err_on_gc_types() 214 | .collect::, _>>()?; 215 | section_index += 1; 216 | } 217 | Payload::ImportSection(reader) => { 218 | module.imports = reader.into_iter().collect::, _>>()?; 219 | section_index += 1; 220 | } 221 | Payload::FunctionSection(reader) => { 222 | function_types = reader 223 | .into_iter() 224 | .map(|t| t.map(|id| id as FuncTypeId)) 225 | .collect::, _>>()?; 226 | section_index += 1; 227 | } 228 | Payload::TableSection(reader) => { 229 | module.tables = reader.into_iter().collect::, _>>()?; 230 | section_index += 1; 231 | } 232 | Payload::MemorySection(reader) => { 233 | module.memories = reader.into_iter().collect::, _>>()?; 234 | section_index += 1; 235 | } 236 | Payload::TagSection(reader) => { 237 | module.tags = reader.into_iter().collect::, _>>()?; 238 | section_index += 1; 239 | } 240 | Payload::GlobalSection(reader) => { 241 | module.globals = reader.into_iter().collect::, _>>()?; 242 | section_index += 1; 243 | } 244 | Payload::ExportSection(reader) => { 245 | module.exports = reader.into_iter().collect::, _>>()?; 246 | module.export_map = module 247 | .exports 248 | .iter() 249 | .enumerate() 250 | .map(|(i, export)| { 251 | ( 252 | (export.kind as isize, export.index as usize), 253 | (i, export.name), 254 | ) 255 | }) 256 | .collect(); 257 | section_index += 1; 258 | } 259 | Payload::StartSection { func, .. } => { 260 | module.start = Some(func as usize); 261 | section_index += 1; 262 | } 263 | Payload::ElementSection(reader) => { 264 | module.elements = reader.into_iter().collect::, _>>()?; 265 | section_index += 1; 266 | } 267 | Payload::DataCountSection { .. } => { 268 | section_index += 1; 269 | } 270 | Payload::DataSection(reader) => { 271 | module.data_section_offset = reader.range().start; 272 | module.data_segments = reader.into_iter().collect::, _>>()?; 273 | module.data_section_index = section_index; 274 | section_index += 1; 275 | } 276 | Payload::CodeSectionStart { range, .. } => { 277 | module.code_section_offset = range.start; 278 | module.code_section_index = section_index; 279 | section_index += 1; 280 | } 281 | Payload::CodeSectionEntry(body) => { 282 | let index = module.defined_funcs.len(); 283 | module.defined_funcs.push(DefinedFunc { 284 | type_id: function_types[index].clone(), 285 | body, 286 | }); 287 | } 288 | Payload::CustomSection(reader) => { 289 | module.custom_sections.push(CustomSection { 290 | name: reader.name(), 291 | section_index, 292 | data: reader.data(), 293 | range: reader.range(), 294 | data_offset: reader.data_offset(), 295 | }); 296 | section_index += 1; 297 | } 298 | Payload::End(_) => {} 299 | section => { 300 | bail!("Unknown section: {:?}", section); 301 | } 302 | } 303 | } 304 | 305 | for section in module.custom_sections.iter() { 306 | if section.name == "name" { 307 | module.names = Names::new(section.data, section.data_offset)?; 308 | } else if section.name == "linking" { 309 | let reader = 310 | wasmparser::LinkingSectionReader::new(section.data, section.data_offset)?; 311 | for subsection in reader.subsections() { 312 | match subsection? { 313 | wasmparser::Linking::SymbolTable(map) => { 314 | module.symbols = map.into_iter().collect::, _>>()?; 315 | } 316 | _ => {} 317 | } 318 | } 319 | } else if section.name.starts_with("reloc.") { 320 | let reader = 321 | wasmparser::RelocSectionReader::new(section.data, section.data_offset)?; 322 | module.relocs.insert( 323 | reader.section_index() as SectionIndex, 324 | reader 325 | .entries() 326 | .into_iter() 327 | .collect::, _>>()?, 328 | ); 329 | } 330 | } 331 | module.data_symbols = get_data_symbols(&module.data_segments, &module.symbols)?; 332 | module.imported_funcs = module 333 | .imports 334 | .iter() 335 | .enumerate() 336 | .filter_map(|(import_id, import)| match import.ty { 337 | TypeRef::Func(_) => Some(import_id as ImportId), 338 | _ => None, 339 | }) 340 | .collect(); 341 | module.imported_func_map = module 342 | .imported_funcs 343 | .iter() 344 | .enumerate() 345 | .map(|(func_id, &import_id)| (import_id, func_id)) 346 | .collect(); 347 | Ok(module) 348 | } 349 | 350 | pub fn func_type_id(&self, func_id: InputFuncId) -> FuncTypeId { 351 | if func_id < self.imported_funcs.len() { 352 | let import_id = self.imported_funcs[func_id]; 353 | let wasmparser::TypeRef::Func(type_id) = self.imports[import_id].ty else { 354 | panic!("Expected import to be a function"); 355 | }; 356 | type_id as usize 357 | } else { 358 | self.defined_funcs[func_id - self.imported_funcs.len()].type_id 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/split_point.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet, VecDeque}; 2 | 3 | use crate::dep_graph::{DepGraph, DepNode}; 4 | use crate::read::{ExportId, ImportId, InputFuncId, InputModule, SymbolIndex}; 5 | use anyhow::{anyhow, bail}; 6 | use lazy_static::lazy_static; 7 | use regex::Regex; 8 | 9 | #[derive(Debug, PartialEq, Eq, Clone)] 10 | pub struct SplitModule { 11 | pub module_name: String, 12 | pub load_func: SymbolIndex, 13 | } 14 | 15 | #[derive(Debug, PartialEq, Eq, Clone)] 16 | pub struct SplitPoint { 17 | pub module_name: String, 18 | pub import: ImportId, 19 | pub import_func: InputFuncId, 20 | pub export: ExportId, 21 | pub export_func: InputFuncId, 22 | } 23 | 24 | pub fn get_split_modules(module: &InputModule) -> HashMap { 25 | const PREFIX: &str = "__wasm_split_load_"; 26 | let mut split_modules: HashMap = HashMap::new(); 27 | for (i, info) in module.symbols.iter().enumerate() { 28 | let wasmparser::SymbolInfo::Func { 29 | name: Some(symbol_name), 30 | .. 31 | } = info 32 | else { 33 | continue; 34 | }; 35 | if !symbol_name.starts_with(PREFIX) { 36 | continue; 37 | } 38 | let name = &symbol_name[PREFIX.len()..]; 39 | split_modules.insert( 40 | name.into(), 41 | SplitModule { 42 | module_name: String::from(name), 43 | load_func: i, 44 | }, 45 | ); 46 | } 47 | split_modules 48 | } 49 | 50 | pub fn get_split_points(module: &InputModule) -> anyhow::Result> { 51 | macro_rules! process_imports_or_exports { 52 | ($pattern:expr, $map:ident, $member:ident, $id_ty:ty) => { 53 | let mut $map = HashMap::<(String, String), $id_ty>::new(); 54 | { 55 | lazy_static! { 56 | static ref PATTERN: Regex = Regex::new($pattern).unwrap(); 57 | } 58 | 59 | for (id, item) in module.$member.iter().enumerate() { 60 | let Some(captures) = PATTERN.captures(&item.name) else { 61 | continue; 62 | }; 63 | let (_, [module_name, unique_id]) = captures.extract(); 64 | $map.insert((module_name.into(), unique_id.into()), id); 65 | } 66 | } 67 | }; 68 | } 69 | 70 | process_imports_or_exports!( 71 | "__wasm_split_00(.*)00_import_([0-9a-f]{32})", 72 | import_map, 73 | imports, 74 | ImportId 75 | ); 76 | process_imports_or_exports!( 77 | "__wasm_split_00(.*)00_export_([0-9a-f]{32})", 78 | export_map, 79 | exports, 80 | ExportId 81 | ); 82 | 83 | let split_points = import_map 84 | .drain() 85 | .map(|(key, import_id)| -> anyhow::Result { 86 | let export_id = export_map.remove(&key).ok_or_else(|| { 87 | anyhow::anyhow!("No corresponding export for split import {key:?}") 88 | })?; 89 | let export = module.exports[export_id]; 90 | let wasmparser::Export { 91 | kind: wasmparser::ExternalKind::Func, 92 | index, 93 | .. 94 | } = export 95 | else { 96 | bail!("Expected exported function but received: {export:?}"); 97 | }; 98 | let &import_func = module.imported_func_map.get(&import_id).ok_or_else(|| { 99 | anyhow!( 100 | "Expected imported function but received: {:?}", 101 | &module.imports[import_id] 102 | ) 103 | })?; 104 | Ok(SplitPoint { 105 | module_name: key.0, 106 | import: import_id, 107 | import_func, 108 | export: export_id, 109 | export_func: index as InputFuncId, 110 | }) 111 | }) 112 | .collect::>>()?; 113 | 114 | for (key, _) in export_map.iter() { 115 | anyhow::bail!("No corresponding import for split export {key:?}"); 116 | } 117 | 118 | Ok(split_points) 119 | } 120 | 121 | #[derive(Debug, Default)] 122 | pub struct ReachabilityGraph { 123 | pub reachable: HashSet, 124 | pub parents: HashMap, 125 | } 126 | 127 | #[derive(Debug, Default)] 128 | pub struct OutputModuleInfo { 129 | pub included_symbols: HashSet, 130 | pub parents: HashMap, 131 | pub shared_imports: HashSet, 132 | pub split_points: Vec, 133 | } 134 | 135 | impl OutputModuleInfo { 136 | pub fn print(&self, module_name: &str, module: &InputModule) { 137 | print_deps(module_name, module, &self.included_symbols, &self.parents); 138 | } 139 | } 140 | 141 | impl From for OutputModuleInfo { 142 | fn from(reachability: ReachabilityGraph) -> Self { 143 | Self { 144 | included_symbols: reachability.reachable, 145 | parents: reachability.parents, 146 | ..Default::default() 147 | } 148 | } 149 | } 150 | 151 | fn print_deps( 152 | module_name: &str, 153 | module: &InputModule, 154 | reachable: &HashSet, 155 | parents: &HashMap, 156 | ) { 157 | let format_dep = |dep: &DepNode| match dep { 158 | DepNode::Function(index) => { 159 | let name = module.names.functions.get(index); 160 | format!("func[{index}] <{name:?}>") 161 | } 162 | DepNode::DataSymbol(index) => { 163 | let symbol = module.symbols[*index]; 164 | format!("{symbol:?}") 165 | } 166 | }; 167 | 168 | println!("SPLIT: ============== {module_name}"); 169 | let mut total_size: usize = 0; 170 | for dep in reachable.iter() { 171 | let DepNode::Function(index) = dep else { 172 | continue; 173 | }; 174 | let size = index 175 | .checked_sub(module.imported_funcs.len()) 176 | .map(|defined_index| { 177 | module.defined_funcs[index - module.imported_funcs.len()] 178 | .body 179 | .range() 180 | .len() 181 | }) 182 | .unwrap_or_default(); 183 | total_size += size; 184 | println!(" {} size={size:?}", format_dep(dep)); 185 | let mut node = dep; 186 | while let Some(parent) = parents.get(node) { 187 | println!(" <== {}", format_dep(parent)); 188 | node = parent; 189 | } 190 | } 191 | println!("SPLIT: ============== {module_name} : total size: {total_size}"); 192 | } 193 | 194 | impl ReachabilityGraph { 195 | pub fn print(&self, module_name: &str, module: &InputModule) { 196 | print_deps(module_name, module, &self.reachable, &self.parents); 197 | } 198 | } 199 | 200 | pub fn find_reachable_deps( 201 | deps: &DepGraph, 202 | roots: &HashSet, 203 | exclude: &HashSet, 204 | ) -> ReachabilityGraph { 205 | let mut queue: VecDeque = roots.iter().copied().collect(); 206 | let mut seen = HashSet::::new(); 207 | let mut parents = HashMap::::new(); 208 | while let Some(node) = queue.pop_front() { 209 | seen.insert(node); 210 | let Some(children) = deps.get(&node) else { 211 | continue; 212 | }; 213 | for child in children { 214 | if seen.contains(&child) || exclude.contains(&child) { 215 | continue; 216 | } 217 | parents.entry(*child).or_insert(node); 218 | queue.push_back(*child); 219 | } 220 | } 221 | ReachabilityGraph { 222 | reachable: seen, 223 | parents, 224 | } 225 | } 226 | 227 | pub fn get_main_module_roots( 228 | module: &InputModule, 229 | split_points: &[SplitPoint], 230 | ) -> HashSet { 231 | let mut roots: HashSet = HashSet::new(); 232 | if let Some(id) = module.start { 233 | roots.insert(DepNode::Function(id)); 234 | } 235 | for export in module.exports.iter() { 236 | let wasmparser::Export { 237 | index, 238 | kind: wasmparser::ExternalKind::Func, 239 | .. 240 | } = export 241 | else { 242 | continue; 243 | }; 244 | roots.insert(DepNode::Function(*index as usize)); 245 | } 246 | for func_id in 0..module.imported_funcs.len() { 247 | roots.insert(DepNode::Function(func_id)); 248 | } 249 | for split_point in split_points.iter() { 250 | roots.remove(&DepNode::Function(split_point.export_func)); 251 | roots.remove(&DepNode::Function(split_point.import_func)); 252 | } 253 | roots 254 | } 255 | 256 | pub fn get_split_points_by_module( 257 | split_points: &[SplitPoint], 258 | ) -> HashMap> { 259 | split_points 260 | .iter() 261 | .fold(HashMap::new(), |mut map, split_point| { 262 | map.entry(split_point.module_name.clone()) 263 | .or_insert_with(|| Vec::new()) 264 | .push(&split_point); 265 | map 266 | }) 267 | } 268 | 269 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] 270 | pub enum SplitModuleIdentifier { 271 | Main, 272 | Split(String), 273 | Chunk(Vec), 274 | } 275 | 276 | impl SplitModuleIdentifier { 277 | pub fn name(&self) -> String { 278 | match self { 279 | Self::Main => "main".to_string(), 280 | Self::Split(name) => name.clone(), 281 | Self::Chunk(names) => names.join("_"), 282 | } 283 | } 284 | } 285 | 286 | #[derive(Debug, Default)] 287 | pub struct SplitProgramInfo { 288 | pub output_modules: Vec<(SplitModuleIdentifier, OutputModuleInfo)>, 289 | pub output_module_identifiers: HashMap, 290 | pub shared_funcs: HashSet, 291 | pub symbol_output_module: HashMap, 292 | } 293 | 294 | pub fn compute_split_modules( 295 | module: &InputModule, 296 | dep_graph: &DepGraph, 297 | split_points: &[SplitPoint], 298 | ) -> anyhow::Result { 299 | let split_points_by_module = get_split_points_by_module(&split_points[..]); 300 | 301 | println!("split_points={split_points:?}"); 302 | 303 | let split_func_map: HashMap = split_points 304 | .iter() 305 | .map(|split_point| (split_point.import_func, split_point.export_func)) 306 | .collect(); 307 | 308 | let remove_ignored_deps = |deps: &mut HashSet| { 309 | for split_point in split_points.iter() { 310 | deps.remove(&DepNode::Function(split_point.import_func)); 311 | } 312 | }; 313 | let remove_ignored_funcs = |deps: &mut HashSet| { 314 | for split_point in split_points.iter() { 315 | deps.remove(&split_point.import_func); 316 | } 317 | }; 318 | 319 | let main_roots = get_main_module_roots(module, &split_points); 320 | 321 | let mut main_deps = find_reachable_deps(dep_graph, &main_roots, &HashSet::new()); 322 | 323 | remove_ignored_deps(&mut main_deps.reachable); 324 | 325 | // Determine reachable symbols (excluding main module symbols) for each 326 | // split module. Symbols may be reachable from more than one split module; 327 | // these symbols will be moved to a separate module. 328 | let mut split_module_candidates: HashMap = split_points_by_module 329 | .iter() 330 | .map(|(module_name, entry_points)| { 331 | let mut roots = HashSet::::new(); 332 | for entry_point in entry_points.iter() { 333 | roots.insert(DepNode::Function(entry_point.export_func)); 334 | } 335 | let mut split_functions = find_reachable_deps(dep_graph, &roots, &main_deps.reachable); 336 | remove_ignored_deps(&mut split_functions.reachable); 337 | (module_name.clone(), split_functions) 338 | }) 339 | .collect(); 340 | 341 | // Set of split modules from which each symbol is reachable. 342 | let mut dep_candidate_modules = HashMap::>::new(); 343 | for (module_name, deps) in split_module_candidates.iter() { 344 | for dep in deps.reachable.iter() { 345 | dep_candidate_modules 346 | .entry(*dep) 347 | .or_default() 348 | .push(module_name.clone()); 349 | } 350 | } 351 | 352 | let mut program_info = SplitProgramInfo::default(); 353 | 354 | let mut split_module_contents = HashMap::::new(); 355 | 356 | split_module_contents.insert(SplitModuleIdentifier::Main, main_deps.into()); 357 | 358 | for (dep, mut modules) in dep_candidate_modules { 359 | if modules.len() > 1 { 360 | modules.sort(); 361 | for module in modules.iter() { 362 | let module_contents = split_module_candidates.get_mut(module).unwrap(); 363 | module_contents.reachable.remove(&dep); 364 | } 365 | split_module_contents 366 | .entry(SplitModuleIdentifier::Chunk(modules)) 367 | .or_default() 368 | .included_symbols 369 | .insert(dep); 370 | } 371 | } 372 | 373 | split_module_contents.extend( 374 | split_module_candidates 375 | .drain() 376 | .map(|(module_name, deps)| (SplitModuleIdentifier::Split(module_name), deps.into())), 377 | ); 378 | 379 | for contents in split_module_contents.values_mut() { 380 | for symbol in contents.included_symbols.iter() { 381 | let Some(neighbors) = dep_graph.get(symbol) else { 382 | continue; 383 | }; 384 | for mut called_func_id in neighbors.iter().filter_map(|symbol| match symbol { 385 | DepNode::Function(func_id) => Some(*func_id), 386 | _ => None, 387 | }) { 388 | called_func_id = *split_func_map 389 | .get(&called_func_id) 390 | .unwrap_or(&called_func_id); 391 | if !contents 392 | .included_symbols 393 | .contains(&DepNode::Function(called_func_id)) 394 | { 395 | contents.shared_imports.insert(called_func_id); 396 | program_info.shared_funcs.insert(called_func_id); 397 | } 398 | } 399 | } 400 | remove_ignored_funcs(&mut contents.shared_imports); 401 | } 402 | remove_ignored_funcs(&mut program_info.shared_funcs); 403 | 404 | for split_point in split_points { 405 | program_info.shared_funcs.insert(split_point.export_func); 406 | let output_module = split_module_contents 407 | .get_mut(&SplitModuleIdentifier::Split( 408 | split_point.module_name.to_string(), 409 | )) 410 | .unwrap(); 411 | output_module.split_points.push(split_point.clone()); 412 | } 413 | 414 | program_info.output_modules = split_module_contents.drain().collect(); 415 | program_info 416 | .output_modules 417 | .sort_by_key(|(identifier, _)| (*identifier).clone()); 418 | program_info.output_module_identifiers = program_info 419 | .output_modules 420 | .iter() 421 | .enumerate() 422 | .map(|(index, (identifier, _))| (identifier.clone(), index)) 423 | .collect(); 424 | 425 | for (output_index, (_, info)) in program_info.output_modules.iter().enumerate() { 426 | for &symbol in info.included_symbols.iter() { 427 | program_info 428 | .symbol_output_module 429 | .insert(symbol, output_index); 430 | } 431 | } 432 | 433 | Ok(program_info) 434 | } 435 | -------------------------------------------------------------------------------- /crates/wasm_split_cli/src/todo.txt: -------------------------------------------------------------------------------- 1 | Make all names unique by appending random string 2 | 3 | 4 | Map dependencies from original (pre-bindgen) wasm 5 | 6 | Apply dependencies to post-bindgen wasm (validate?) 7 | 8 | 9 | 10 | For now we will ignore non-function dependencies 11 | -------------------------------------------------------------------------------- /crates/wasm_split_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_split_macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | base16 = "0.2.1" 8 | digest = "0.10.7" 9 | quote = "1.0.36" 10 | sha2 = "0.10.8" 11 | syn = "2.0.59" 12 | wasm-bindgen = "0.2.92" 13 | 14 | [lib] 15 | proc-macro = true 16 | -------------------------------------------------------------------------------- /crates/wasm_split_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use digest::Digest; 4 | use quote::{format_ident, quote}; 5 | use syn::{parse_macro_input, Ident, ItemFn, Signature}; 6 | 7 | #[proc_macro_attribute] 8 | pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream { 9 | let module_ident = parse_macro_input!(args as Ident); 10 | let item_fn = parse_macro_input!(input as ItemFn); 11 | 12 | let name = &item_fn.sig.ident; 13 | 14 | let unique_identifier = base16::encode_lower( 15 | &sha2::Sha256::digest(format!("{name} {span:?}", span = name.span()))[..16], 16 | ); 17 | 18 | let load_module_ident = format_ident!("__wasm_split_load_{module_ident}"); 19 | let split_loader_ident = format_ident!("__wasm_split_loader"); 20 | let impl_import_ident = 21 | format_ident!("__wasm_split_00{module_ident}00_import_{unique_identifier}_{name}"); 22 | let impl_export_ident = 23 | format_ident!("__wasm_split_00{module_ident}00_export_{unique_identifier}_{name}"); 24 | 25 | let import_sig = Signature { 26 | ident: impl_import_ident.clone(), 27 | asyncness: None, 28 | ..item_fn.sig.clone() 29 | }; 30 | let export_sig = Signature { 31 | ident: impl_export_ident.clone(), 32 | asyncness: None, 33 | ..item_fn.sig.clone() 34 | }; 35 | 36 | let mut wrapper_sig = item_fn.sig; 37 | wrapper_sig.asyncness = Some(Default::default()); 38 | let mut args = Vec::new(); 39 | for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() { 40 | match param { 41 | syn::FnArg::Typed(pat_type) => { 42 | let param_ident = format_ident!("__wasm_split_arg_{i}"); 43 | args.push(param_ident.clone()); 44 | pat_type.pat = Box::new(syn::Pat::Ident(syn::PatIdent { 45 | attrs: vec![], 46 | by_ref: None, 47 | mutability: None, 48 | ident: param_ident, 49 | subpat: None, 50 | })); 51 | } 52 | syn::FnArg::Receiver(_) => { 53 | args.push(format_ident!("self")); 54 | } 55 | } 56 | } 57 | 58 | let attrs = item_fn.attrs; 59 | 60 | let stmts = &item_fn.block.stmts; 61 | 62 | quote! { 63 | #wrapper_sig { 64 | thread_local! { 65 | static #split_loader_ident: ::wasm_split::LazySplitLoader = unsafe { ::wasm_split::LazySplitLoader::new(#load_module_ident) }; 66 | } 67 | 68 | #[link(wasm_import_module = "./__wasm_split.js")] 69 | extern "C" { 70 | #[no_mangle] 71 | fn #load_module_ident (callback: unsafe extern "C" fn(*const ::std::ffi::c_void, bool), data: *const ::std::ffi::c_void) -> (); 72 | 73 | #[allow(improper_ctypes)] 74 | #[no_mangle] 75 | #import_sig; 76 | } 77 | 78 | #(#attrs)* 79 | #[allow(improper_ctypes_definitions)] 80 | #[no_mangle] 81 | pub extern "C" #export_sig { 82 | #(#stmts)* 83 | } 84 | 85 | ::wasm_split::ensure_loaded(&#split_loader_ident).await.unwrap(); 86 | unsafe { #impl_import_ident( #(#args),* ) } 87 | } 88 | } 89 | .into() 90 | } 91 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "toml": { 3 | }, 4 | "excludes": [], 5 | "plugins": [ 6 | "https://plugins.dprint.dev/toml-0.6.1.wasm" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import initializeWasm, * as main from "./pkg/main.js"; 2 | 3 | const url = document.getElementById("url"); 4 | const form = document.getElementById("form"); 5 | const result = document.getElementById("result"); 6 | form.addEventListener("submit", async (event) => { 7 | event.preventDefault(); 8 | try { 9 | await initializeWasm(); 10 | const urlValue = url.value; 11 | const decoded = await main.decode(urlValue); 12 | result.textContent = decoded; 13 | } catch (e) { 14 | result.textContent = "Error: " + e.toString(); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /raw: -------------------------------------------------------------------------------- 1 | Hello from raw 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rust-src"] 4 | targets = ["wasm32-unknown-unknown"] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | reorder_imports = true 3 | -------------------------------------------------------------------------------- /test.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbms/wasm-split-prototype/e90d2870871782a96237e7b5660d25721389d4f5/test.br -------------------------------------------------------------------------------- /test.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbms/wasm-split-prototype/e90d2870871782a96237e7b5660d25721389d4f5/test.gz --------------------------------------------------------------------------------