| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -760,6 +760,74 @@ const dynamicLibrary = await import.source('./library.wasm'); | |||
| 760 | 760 | const instance = await WebAssembly.instantiate(dynamicLibrary, importObject); | |
| 761 | 761 | ``` | |
| 762 | 762 | ||
| 763 | + ### JavaScript String Builtins | ||
| 764 | + | ||
| 765 | + <!-- YAML | ||
| 766 | + added: REPLACEME | ||
| 767 | + --> | ||
| 768 | + | ||
| 769 | + When importing WebAssembly modules, the | ||
| 770 | + [WebAssembly JS String Builtins Proposal][] is automatically enabled through the | ||
| 771 | + ESM Integration. This allows WebAssembly modules to directly use efficient | ||
| 772 | + compile-time string builtins from the `wasm:js-string` namespace. | ||
| 773 | + | ||
| 774 | + For example, the following Wasm module exports a string `getLength` function using | ||
| 775 | + the `wasm:js-string` `length` builtin: | ||
| 776 | + | ||
| 777 | + ```text | ||
| 778 | + (module | ||
| 779 | + ;; Compile-time import of the string length builtin. | ||
| 780 | + (import "wasm:js-string" "length" (func $string_length (param externref) (result i32))) | ||
| 781 | + | ||
| 782 | + ;; Define getLength, taking a JS value parameter assumed to be a string, | ||
| 783 | + ;; calling string length on it and returning the result. | ||
| 784 | + (func $getLength (param $str externref) (result i32) | ||
| 785 | + local.get $str | ||
| 786 | + call $string_length | ||
| 787 | + ) | ||
| 788 | + | ||
| 789 | + ;; Export the getLength function. | ||
| 790 | + (export "getLength" (func $get_length)) | ||
| 791 | + ) | ||
| 792 | + ``` | ||
| 793 | + | ||
| 794 | + ```js | ||
| 795 | + import { getLength } from './string-len.wasm'; | ||
| 796 | + getLength('foo'); // Returns 3. | ||
| 797 | + ``` | ||
| 798 | + | ||
| 799 | + Wasm builtins are compile-time imports that are linked during module compilation | ||
| 800 | + rather than during instantiation. They do not behave like normal module graph | ||
| 801 | + imports and they cannot be inspected via `WebAssembly.Module.imports(mod)` | ||
| 802 | + or virtualized unless recompiling the module using the direct | ||
| 803 | + `WebAssembly.compile` API with string builtins disabled. | ||
| 804 | + | ||
| 805 | + Importing a module in the source phase before it has been instantiated will also | ||
| 806 | + use the compile-time builtins automatically: | ||
| 807 | + | ||
| 808 | + ```js | ||
| 809 | + import source mod from './string-len.wasm'; | ||
| 810 | + const { exports: { getLength } } = await WebAssembly.instantiate(mod, {}); | ||
| 811 | + getLength('foo'); // Also returns 3. | ||
| 812 | + ``` | ||
| 813 | + | ||
| 814 | + ### Reserved Wasm Namespaces | ||
| 815 | + | ||
| 816 | + <!-- YAML | ||
| 817 | + added: REPLACEME | ||
| 818 | + --> | ||
| 819 | + | ||
| 820 | + When importing WebAssembly modules through the ESM Integration, they cannot use | ||
| 821 | + import module names or import/export names that start with reserved prefixes: | ||
| 822 | + | ||
| 823 | + * `wasm-js:` - reserved in all module import names, module names and export | ||
| 824 | + names. | ||
| 825 | + * `wasm:` - reserved in module import names and export names (imported module | ||
| 826 | + names are allowed in order to support future builtin polyfills). | ||
| 827 | + | ||
| 828 | + Importing a module using the above reserved names will throw a | ||
| 829 | + `WebAssembly.LinkError`. | ||
| 830 | + | ||
| 763 | 831 | <i id="esm_experimental_top_level_await"></i> | |
| 764 | 832 | ||
| 765 | 833 | ## Top-level `await` | |
@@ -1202,6 +1270,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. | |||
| 1202 | 1270 | [Source Phase Imports]: https://github.com/tc39/proposal-source-phase-imports | |
| 1203 | 1271 | [Terminology]: #terminology | |
| 1204 | 1272 | [URL]: https://url.spec.whatwg.org/ | |
| 1273 | + [WebAssembly JS String Builtins Proposal]: https://github.com/WebAssembly/js-string-builtins | ||
| 1205 | 1274 | [`"exports"`]: packages.md#exports | |
| 1206 | 1275 | [`"type"`]: packages.md#type | |
| 1207 | 1276 | [`--input-type`]: cli.md#--input-typetype | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -506,7 +506,10 @@ translators.set('wasm', async function(url, source) { | |||
| 506 | 506 | // TODO(joyeecheung): implement a translator that just uses | |
| 507 | 507 | // compiled = new WebAssembly.Module(source) to compile it | |
| 508 | 508 | // synchronously. | |
| 509 | - compiled = await WebAssembly.compile(source); | ||
| 509 | + compiled = await WebAssembly.compile(source, { | ||
| 510 | + // The ESM Integration auto-enables Wasm JS builtins by default when available. | ||
| 511 | + builtins: ['js-string'], | ||
| 512 | + }); | ||
| 510 | 513 | } catch (err) { | |
| 511 | 514 | err.message = errPath(url) + ': ' + err.message; | |
| 512 | 515 | throw err; | |
@@ -518,6 +521,13 @@ translators.set('wasm', async function(url, source) { | |||
| 518 | 521 | if (impt.kind === 'global') { | |
| 519 | 522 | ArrayPrototypePush(wasmGlobalImports, impt); | |
| 520 | 523 | } | |
| 524 | + // Prefix reservations per https://webassembly.github.io/esm-integration/js-api/index.html#parse-a-webassembly-module. | ||
| 525 | + if (impt.module.startsWith('wasm-js:')) { | ||
| 526 | + throw new WebAssembly.LinkError(`Invalid Wasm import "${impt.module}" in ${url}`); | ||
| 527 | + } | ||
| 528 | + if (impt.name.startsWith('wasm:') || impt.name.startsWith('wasm-js:')) { | ||
| 529 | + throw new WebAssembly.LinkError(`Invalid Wasm import name "${impt.module}" in ${url}`); | ||
| 530 | + } | ||
| 521 | 531 | importsList.add(impt.module); | |
| 522 | 532 | } | |
| 523 | 533 | ||
@@ -527,6 +537,9 @@ translators.set('wasm', async function(url, source) { | |||
| 527 | 537 | if (expt.kind === 'global') { | |
| 528 | 538 | wasmGlobalExports.add(expt.name); | |
| 529 | 539 | } | |
| 540 | + if (expt.name.startsWith('wasm:') || expt.name.startsWith('wasm-js:')) { | ||
| 541 | + throw new WebAssembly.LinkError(`Invalid Wasm export name "${expt.name}" in ${url}`); | ||
| 542 | + } | ||
| 530 | 543 | exportsList.add(expt.name); | |
| 531 | 544 | } | |
| 532 | 545 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -403,4 +403,95 @@ describe('ESM: WASM modules', { concurrency: !process.env.TEST_PARALLEL }, () => | |||
| 403 | 403 | strictEqual(stdout, ''); | |
| 404 | 404 | notStrictEqual(code, 0); | |
| 405 | 405 | }); | |
| 406 | + | ||
| 407 | + it('should reject wasm: import names', async () => { | ||
| 408 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 409 | + '--no-warnings', | ||
| 410 | + '--experimental-wasm-modules', | ||
| 411 | + '--input-type=module', | ||
| 412 | + '--eval', | ||
| 413 | + `import(${JSON.stringify(fixtures.fileURL('es-modules/invalid-import-name.wasm'))})`, | ||
| 414 | + ]); | ||
| 415 | + | ||
| 416 | + match(stderr, /Invalid Wasm import name/); | ||
| 417 | + strictEqual(stdout, ''); | ||
| 418 | + notStrictEqual(code, 0); | ||
| 419 | + }); | ||
| 420 | + | ||
| 421 | + it('should reject wasm-js: import names', async () => { | ||
| 422 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 423 | + '--no-warnings', | ||
| 424 | + '--experimental-wasm-modules', | ||
| 425 | + '--input-type=module', | ||
| 426 | + '--eval', | ||
| 427 | + `import(${JSON.stringify(fixtures.fileURL('es-modules/invalid-import-name-wasm-js.wasm'))})`, | ||
| 428 | + ]); | ||
| 429 | + | ||
| 430 | + match(stderr, /Invalid Wasm import name/); | ||
| 431 | + strictEqual(stdout, ''); | ||
| 432 | + notStrictEqual(code, 0); | ||
| 433 | + }); | ||
| 434 | + | ||
| 435 | + it('should reject wasm-js: import module names', async () => { | ||
| 436 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 437 | + '--no-warnings', | ||
| 438 | + '--experimental-wasm-modules', | ||
| 439 | + '--input-type=module', | ||
| 440 | + '--eval', | ||
| 441 | + `import(${JSON.stringify(fixtures.fileURL('es-modules/invalid-import-module.wasm'))})`, | ||
| 442 | + ]); | ||
| 443 | + | ||
| 444 | + match(stderr, /Invalid Wasm import/); | ||
| 445 | + strictEqual(stdout, ''); | ||
| 446 | + notStrictEqual(code, 0); | ||
| 447 | + }); | ||
| 448 | + | ||
| 449 | + it('should reject wasm: export names', async () => { | ||
| 450 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 451 | + '--no-warnings', | ||
| 452 | + '--experimental-wasm-modules', | ||
| 453 | + '--input-type=module', | ||
| 454 | + '--eval', | ||
| 455 | + `import(${JSON.stringify(fixtures.fileURL('es-modules/invalid-export-name.wasm'))})`, | ||
| 456 | + ]); | ||
| 457 | + | ||
| 458 | + match(stderr, /Invalid Wasm export/); | ||
| 459 | + strictEqual(stdout, ''); | ||
| 460 | + notStrictEqual(code, 0); | ||
| 461 | + }); | ||
| 462 | + | ||
| 463 | + it('should reject wasm-js: export names', async () => { | ||
| 464 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 465 | + '--no-warnings', | ||
| 466 | + '--experimental-wasm-modules', | ||
| 467 | + '--input-type=module', | ||
| 468 | + '--eval', | ||
| 469 | + `import(${JSON.stringify(fixtures.fileURL('es-modules/invalid-export-name-wasm-js.wasm'))})`, | ||
| 470 | + ]); | ||
| 471 | + | ||
| 472 | + match(stderr, /Invalid Wasm export/); | ||
| 473 | + strictEqual(stdout, ''); | ||
| 474 | + notStrictEqual(code, 0); | ||
| 475 | + }); | ||
| 476 | + | ||
| 477 | + it('should support js-string builtins', async () => { | ||
| 478 | + const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||
| 479 | + '--no-warnings', | ||
| 480 | + '--experimental-wasm-modules', | ||
| 481 | + '--input-type=module', | ||
| 482 | + '--eval', | ||
| 483 | + [ | ||
| 484 | + 'import { strictEqual } from "node:assert";', | ||
| 485 | + `import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/js-string-builtins.wasm'))};`, | ||
| 486 | + 'strictEqual(wasmExports.getLength("hello"), 5);', | ||
| 487 | + 'strictEqual(wasmExports.concatStrings("hello", " world"), "hello world");', | ||
| 488 | + 'strictEqual(wasmExports.compareStrings("test", "test"), 1);', | ||
| 489 | + 'strictEqual(wasmExports.compareStrings("test", "different"), 0);', | ||
| 490 | + ].join('\n'), | ||
| 491 | + ]); | ||
| 492 | + | ||
| 493 | + strictEqual(stderr, ''); | ||
| 494 | + strictEqual(stdout, ''); | ||
| 495 | + strictEqual(code, 0); | ||
| 496 | + }); | ||
| 406 | 497 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + ;; Test WASM module with invalid export name starting with 'wasm-js:' | ||
| 2 | + (module | ||
| 3 | + (func $test (result i32) | ||
| 4 | + i32.const 42 | ||
| 5 | + ) | ||
| 6 | + (export "wasm-js:invalid" (func $test)) | ||
| 7 | + ) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + ;; Test WASM module with invalid export name starting with 'wasm:' | ||
| 2 | + (module | ||
| 3 | + (func $test (result i32) | ||
| 4 | + i32.const 42 | ||
| 5 | + ) | ||
| 6 | + (export "wasm:invalid" (func $test)) | ||
| 7 | + ) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,8 @@ | |||
| 1 | + ;; Test WASM module with invalid import module name starting with 'wasm-js:' | ||
| 2 | + (module | ||
| 3 | + (import "wasm-js:invalid" "test" (func $invalidImport (result i32))) | ||
| 4 | + (export "test" (func $test)) | ||
| 5 | + (func $test (result i32) | ||
| 6 | + call $invalidImport | ||
| 7 | + ) | ||
| 8 | + ) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments