├── .eslintrc.cjs ├── .github └── workflows │ ├── build.yml │ ├── jest.yml │ ├── lint.yml │ └── rust.yml ├── .gitignore ├── .swcrc ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── TODO.md ├── hello ├── .cargo │ └── config ├── .gitignore ├── Cargo-component.lock ├── Cargo.lock ├── Cargo.toml ├── README.md ├── hello-test.mjs ├── rust-toolchain.toml ├── src │ └── lib.rs ├── wasm │ └── hello.wasm ├── wat │ ├── cut-hello.wat │ └── hello.wat └── wit │ └── hello.wit ├── inspiration.md ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── .types.d.ts ├── index.ts ├── model │ ├── README.md │ ├── aliases.ts │ ├── canonicals.ts │ ├── core.ts │ ├── exports.ts │ ├── imports.ts │ ├── instances.ts │ ├── names.ts │ ├── start.ts │ ├── tags.ts │ └── types.ts ├── parser │ ├── README.md │ ├── alias.test.ts │ ├── alias.ts │ ├── canon.ts │ ├── coreInstance.ts │ ├── export.test.ts │ ├── export.ts │ ├── hello.test.ts │ ├── import.ts │ ├── index.test.ts │ ├── index.ts │ ├── instance.ts │ ├── jest-utils.ts │ ├── module.ts │ ├── otherSection.ts │ ├── type.ts │ ├── types.ts │ ├── values.ts │ └── zoo.test.ts ├── resolver │ ├── README.md │ ├── _temp.ts │ ├── api-types.ts │ ├── binding │ │ ├── cache.ts │ │ ├── index.ts │ │ ├── to-abi.ts │ │ ├── to-js.ts │ │ └── types.ts │ ├── component-exports.ts │ ├── component-functions.ts │ ├── component-imports.ts │ ├── component-instances.ts │ ├── component-types.ts │ ├── context.ts │ ├── core-exports.ts │ ├── core-functions.ts │ ├── core-instance.ts │ ├── core-module.ts │ ├── hello.test.ts │ ├── index.ts │ ├── types.ts │ └── zoo.would-be-test.ts └── utils │ ├── assert.ts │ ├── fetch-like.ts │ └── streaming.ts ├── tests ├── hello-component.d.ts ├── hello.ts ├── resolve-hello.ts ├── utils.ts ├── zoo-food-eater.d.ts ├── zoo-food-food.d.ts └── zoo.ts ├── tsconfig.json ├── usage.mjs ├── usage2.mjs └── zoo ├── .cargo └── config ├── .gitignore ├── Cargo-component.lock ├── Cargo.lock ├── Cargo.toml ├── README.md ├── rust-toolchain.toml ├── src └── lib.rs ├── wasm └── zoo.wasm ├── wat └── zoo.wat ├── wit └── zoo.wit └── zoo-test.mjs /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 12, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint" 18 | ], 19 | "ignorePatterns": [ 20 | "node_modules/**/*.*", 21 | "hello/**/*.*", 22 | ], 23 | "rules": { 24 | "@typescript-eslint/no-unused-vars": "warn", // TODO: remove this later 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/no-non-null-assertion": "off", 27 | "@typescript-eslint/ban-types": "off", 28 | "@typescript-eslint/no-loss-of-precision": "off", 29 | "indent": [ 30 | "error", 31 | 4, 32 | { 33 | SwitchCase: 1, 34 | "ignoredNodes": ["VariableDeclaration[declarations.length=0]"] // fixes https://github.com/microsoft/vscode-eslint/issues/1149 35 | } 36 | ], 37 | "no-multi-spaces": ["error"], 38 | "no-console": ["warn"], 39 | "arrow-spacing": ["error"], 40 | "block-spacing": ["error"], 41 | "comma-spacing": ["error"], 42 | "quotes": [ 43 | "error", 44 | "single" 45 | ], 46 | "semi": [ 47 | "error", 48 | "always" 49 | ] 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | env: 17 | ContinuousIntegrationBuild: true 18 | Configuration: Release 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install npm dependencies 23 | run: npm install -ci 24 | 25 | - name: Rollup project 26 | working-directory: ./hello 27 | run: npm run build:npm 28 | 29 | - name: Run hello sample as sanity check 30 | working-directory: ./hello 31 | run: npm run test:usage 32 | 33 | - name: Rollup artifact 34 | uses: actions/upload-artifact@v3 35 | with: 36 | name: jsco.${{github.sha}} 37 | path: | 38 | dist/*.* 39 | dist/LICENSE 40 | -------------------------------------------------------------------------------- /.github/workflows/jest.yml: -------------------------------------------------------------------------------- 1 | name: jest 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | env: 17 | NODE_OPTIONS: --experimental-vm-modules 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Install npm dependencies 22 | run: npm install -ci 23 | 24 | - name: Run Jest unit tests 25 | working-directory: ./hello 26 | run: npm run test:ci 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install npm dependencies 17 | run: npm install -ci 18 | 19 | - name: run linter 20 | run: npm run lint 21 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | paths: [ "hello/**", "zoo/**" ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Install latest stable 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | override: true 26 | components: rustc, rustfmt, rust-std, cargo 27 | 28 | - uses: Swatinem/rust-cache@v2.6.2 29 | with: 30 | cache-all-crates: true 31 | cache-targets: true 32 | workspaces: "hello" 33 | 34 | - name: Install JCO 35 | run: npm install 36 | 37 | - name: Install cargo components 38 | run: npm run setup:rust:ci 39 | 40 | - name: Build rust demo 41 | run: npm run build:hello 42 | 43 | - name: Export demo as .wat 44 | run: npm run build:hello-wat 45 | 46 | - name: Export demo as .wat JCO javaScript 47 | run: npm run build:hello-js 48 | 49 | - name: Build rust demo 50 | run: npm run build:zoo 51 | 52 | - name: Export demo as .wat 53 | run: npm run build:zoo-wat 54 | 55 | - name: Export demo as .wat JCO javaScript 56 | run: npm run build:zoo-js 57 | 58 | - name: Sanity test that JCO can run hello component 59 | run: npm run test:jco 60 | 61 | - name: WASM hello component artifact 62 | uses: actions/upload-artifact@v3 63 | with: 64 | name: hello.${{github.sha}} 65 | path: hello/target/wasm32-unknown-unknown/release/hello.wa* 66 | 67 | - name: WASM zoo component artifact 68 | uses: actions/upload-artifact@v3 69 | with: 70 | name: zoo.${{github.sha}} 71 | path: zoo/target/wasm32-unknown-unknown/release/zoo.wa* 72 | 73 | - name: JCO component artifact 74 | uses: actions/upload-artifact@v3 75 | with: 76 | name: hello-jco.${{github.sha}} 77 | path: hello/target/js-jco/**/*.* 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | /tests/actual-hello.json 132 | /tests/expected-hello.json 133 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "parser": { 4 | "syntax": "typescript", 5 | "tsx": false, 6 | "decorators": false, 7 | "dynamicImport": false 8 | }, 9 | "target": "es2022" 10 | }, 11 | "module": { 12 | "type": "es6", 13 | "strict": false, 14 | "strictMode": true, 15 | "lazy": false, 16 | "noInterop": false 17 | }, 18 | "sourceMaps": "inline" 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "configurations": [ 4 | 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Jest: current file", 9 | "env": { "NODE_OPTIONS": "--experimental-vm-modules" }, 10 | "program": "${workspaceFolder}/node_modules/.bin/jest", 11 | "args": ["${fileBasenameNoExtension}", "--config", "jest.config.js"], 12 | "console": "integratedTerminal", 13 | "disableOptimisticBPs": true, 14 | "windows": { 15 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 16 | } 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Jest: src/resolver/hello.test.ts", 22 | "env": { "NODE_OPTIONS": "--experimental-vm-modules" }, 23 | "program": "${workspaceFolder}/node_modules/.bin/jest", 24 | "args": ["hello.test", "--config", "jest.config.js"], 25 | "console": "integratedTerminal", 26 | "disableOptimisticBPs": true, 27 | "windows": { 28 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 29 | } 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "bargs", 4 | "bctx", 5 | "hackathon", 6 | "JSCO", 7 | "rargs", 8 | "rctx" 9 | ] 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Pavel Savara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsco - Browser polyfill for running WASM components 2 | 3 | ### Demo 4 | 5 | See [live demo](https://pavelsavara.github.io/jsco/) and [browser demo sources](https://github.com/pavelsavara/jsco/tree/demo-page) 6 | 7 | ## Goals 8 | - browser polyfill for running WASM components. 9 | - streaming parser of binary WIT 10 | - streaming compilation of WASM module during .wasm file download 11 | - in-the-browser creation of instances and necessary JavaScript interop 12 | - small download size, fast enough (current prototype is 35 KB) 13 | 14 | ## How 15 | - parser: read binary WIT to produce model of the component, it's sub components, modules and types 16 | - compile modules via Browser API [`WebAssembly.compileStreaming`](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/compileStreaming) 17 | - resolver: resolve dependencies, create instances, bind it together. 18 | - JS binding: for component's imports and exports 19 | - just JS (no rust dependency), TypeScript, RollupJS 20 | 21 | ## Status 22 | 🚧 This is demo-ware quality right now: There are still few things **hardcoded and fake** 🚧 23 | 24 | 🚧 Parser: 90%, Resolver: 50%, Lifting/Lowering: 5% 🚧 25 | 26 | See [./TODO.md](./TODO.md), contributors are welcome! 27 | 28 | [![test](https://github.com/pavelsavara/jsco/actions/workflows/jest.yml/badge.svg)](https://github.com/pavelsavara/jsco/actions/workflows/jest.yml) 29 | 30 | ### Demo scope 31 | - hello world demo [hello.wit](./hello/wit/hello.wit) [hello.wat](./hello/wat/hello.wat) [lib.rs](./hello/src/lib.rs) 32 | - this is just small attempt in limited time. It may grow into something larger ... 33 | - binding for `string` and `i32`, `record` just one direction. Only necessary resolver. 34 | - as a hackathon week project in 2023 35 | - to learn more about WASM component model 36 | 37 | ## Why 38 | - to provide host which could do the binding in the browser 39 | - browsers currently don't implement built-in WASM component model host 40 | - because independent implementation will help the WASM/WIT/WASI to make progress 41 | - [JCO](https://github.com/bytecodealliance/jco) is great alternative, really. 42 | - But it is too large to use as dynamic host, because download size matters to browser folks. 43 | - When you have all your components available at dev machine, JCO transpiler could be better choice. 44 | 45 | ## Usage 46 | ```js 47 | import { instantiateComponent } from '@pavelsavara/jsco'; 48 | const instance = await instantiateComponent('./hello/wasm/hello.wasm', { 49 | 'hello:city/city': { sendMessage: console.log } 50 | }); 51 | const run = instance.exports['hello:city/greeter'].run; 52 | run({ name: 'Kladno', headCount: 100000, budget: 0n}); 53 | ``` 54 | Prints `Welcome to Kladno!` to the console. 55 | 56 | See [./usage.mjs](./usage.mjs) for full commented sample. 57 | 58 | ## Contribute 59 | - install [rust](https://www.rust-lang.org/tools/install) 60 | - install [nodejs + npm](https://nodejs.org/en/download) 61 | - use eslint plugin to VS code, with format on save 62 | - see "scripts" in package.json 63 | 64 | ```bash 65 | npm install 66 | npm run setup:rust 67 | npm run build 68 | npm run build:hello && npm run build:hello-wat && npm run build:hello-js 69 | npm run test:jco 70 | npm run test:unix 71 | ``` 72 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Resolver todo 2 | - `resolveCanonicalFunctionLower` has **hardcoded lookup of the data type**, this would not work outside of this demo! 3 | - handling of the import/export names/namespaces is probably wrong 4 | - resolve data types, `TODO types` 5 | - remove or simplify debugging helpers 6 | - simplify Resolver types 7 | - be able to run `zoo` sample 8 | - invoke start section ? 9 | - nested modules 10 | - export and import ABI interfaces for direct binding without JS. Something needs to copy the bytes ... "fused adapters" ? 11 | - bind WASI preview 2 `@bytecodealliance/preview2-shim` npm package when necessary 12 | - make sure we don't keep references to model after component was created. To not leak memory. How to test this ? 13 | - consider "inlining" https://github.com/bytecodealliance/wasmtime/blob/main/crates/environ/src/component/translate/inline.rs 14 | - all `export const enum` could be converted to numeric, which will be faster and save few KB of the download. But debugging and JSON would have numbers. Maybe regexp in rollup. 15 | 16 | # Binder todo 17 | - implement all data types 18 | - implement argument spilling 19 | - implement record flattening 20 | - https://github.com/bytecodealliance/wasmtime/blob/2ad057d735edc43f8ba89428d483f2b2430c1068/crates/environ/src/component.rs#L29-L38 21 | - https://github.com/WebAssembly/component-model/blob/673d5c43c3cc0f4aeb8996a5c0931af623f16808/design/mvp/canonical-abi/definitions.py#L788 22 | - implement size and alignment 23 | - implement `CallContext` and own/borrow 24 | - respect model options (UTF8/UTF16) 25 | - trap exceptions and kill the component or marshall the error 26 | - option to bind lazily only when methods all called 27 | - fused adapters https://github.com/bytecodealliance/wasmtime/blob/main/crates/environ/src/component/translate/adapt.rs 28 | 29 | # Parser todo 30 | - load start section 31 | - add options to delay parsing core modules 32 | - add options to skip parsing/storing custom sections 33 | 34 | # Testing 35 | - add more WIT text based test scenarios into parser tests like[](src/parser/alias.test.ts) 36 | - change `zoo` to be program with main, not lib 37 | - create sample app with nested modules 38 | - create sample app in go 39 | - create sample app in JS 40 | - use JSCO to bind multiple components at runtime 41 | - improve test coverage 42 | - add test with Chrome, FF 43 | 44 | # Build 45 | - add coverage to CI, fail if lower than some % 46 | - produce NPM package and release it to www.npmjs.com 47 | - get rid of rollup `Circular dependencies` warning 48 | - rollup magic to eliminate debug helpers and asserts 49 | 50 | # Demo 51 | - create demo web site 52 | - command line in the browser for WASI cli programs 53 | 54 | # Other 55 | - convert this TODO into github issues (this is more convenient for now) 56 | - attract more contributors 57 | - review license & add CoC 58 | - donate this project to @bytecodealliance 59 | - write article on how it works 60 | - multi-memory https://github.com/bytecodealliance/jco/blob/main/crates/js-component-bindgen/src/core.rs -------------------------------------------------------------------------------- /hello/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | rustflags = ["-Clink-arg=-zstack-size=16384","-Clink-arg=--max-memory=655360"] 3 | # TODO "-Clink-arg=--initial-memory=655360" 4 | -------------------------------------------------------------------------------- /hello/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /hello/Cargo-component.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by cargo-component. 2 | # It is not intended for manual editing. 3 | version = 1 4 | -------------------------------------------------------------------------------- /hello/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 = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "2.4.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 22 | 23 | [[package]] 24 | name = "cargo-component-bindings" 25 | version = "0.1.0" 26 | source = "git+https://github.com/bytecodealliance/cargo-component?rev=36c221e41db3e87dec4c82eadcb9bc8f37626533#36c221e41db3e87dec4c82eadcb9bc8f37626533" 27 | dependencies = [ 28 | "cargo-component-macro", 29 | "wit-bindgen", 30 | ] 31 | 32 | [[package]] 33 | name = "cargo-component-macro" 34 | version = "0.1.0" 35 | source = "git+https://github.com/bytecodealliance/cargo-component?rev=36c221e41db3e87dec4c82eadcb9bc8f37626533#36c221e41db3e87dec4c82eadcb9bc8f37626533" 36 | dependencies = [ 37 | "heck", 38 | "proc-macro2", 39 | "quote", 40 | "syn", 41 | "wit-bindgen-core", 42 | "wit-bindgen-rust", 43 | "wit-bindgen-rust-lib", 44 | "wit-component", 45 | ] 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "0.1.10" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 52 | 53 | [[package]] 54 | name = "equivalent" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 58 | 59 | [[package]] 60 | name = "form_urlencoded" 61 | version = "1.2.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 64 | dependencies = [ 65 | "percent-encoding", 66 | ] 67 | 68 | [[package]] 69 | name = "hashbrown" 70 | version = "0.14.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 73 | 74 | [[package]] 75 | name = "heck" 76 | version = "0.4.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 79 | dependencies = [ 80 | "unicode-segmentation", 81 | ] 82 | 83 | [[package]] 84 | name = "hello" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "cargo-component-bindings", 88 | "wee_alloc", 89 | ] 90 | 91 | [[package]] 92 | name = "id-arena" 93 | version = "2.2.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 96 | 97 | [[package]] 98 | name = "idna" 99 | version = "0.4.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 102 | dependencies = [ 103 | "unicode-bidi", 104 | "unicode-normalization", 105 | ] 106 | 107 | [[package]] 108 | name = "indexmap" 109 | version = "2.0.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 112 | dependencies = [ 113 | "equivalent", 114 | "hashbrown", 115 | "serde", 116 | ] 117 | 118 | [[package]] 119 | name = "itoa" 120 | version = "1.0.9" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 123 | 124 | [[package]] 125 | name = "leb128" 126 | version = "0.2.5" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 129 | 130 | [[package]] 131 | name = "libc" 132 | version = "0.2.147" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 135 | 136 | [[package]] 137 | name = "log" 138 | version = "0.4.20" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 141 | 142 | [[package]] 143 | name = "memchr" 144 | version = "2.6.3" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 147 | 148 | [[package]] 149 | name = "memory_units" 150 | version = "0.4.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 153 | 154 | [[package]] 155 | name = "percent-encoding" 156 | version = "2.3.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 159 | 160 | [[package]] 161 | name = "proc-macro2" 162 | version = "1.0.66" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 165 | dependencies = [ 166 | "unicode-ident", 167 | ] 168 | 169 | [[package]] 170 | name = "pulldown-cmark" 171 | version = "0.9.3" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" 174 | dependencies = [ 175 | "bitflags 1.3.2", 176 | "memchr", 177 | "unicase", 178 | ] 179 | 180 | [[package]] 181 | name = "quote" 182 | version = "1.0.33" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 185 | dependencies = [ 186 | "proc-macro2", 187 | ] 188 | 189 | [[package]] 190 | name = "ryu" 191 | version = "1.0.15" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 194 | 195 | [[package]] 196 | name = "semver" 197 | version = "1.0.18" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 200 | 201 | [[package]] 202 | name = "serde" 203 | version = "1.0.188" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 206 | dependencies = [ 207 | "serde_derive", 208 | ] 209 | 210 | [[package]] 211 | name = "serde_derive" 212 | version = "1.0.188" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 215 | dependencies = [ 216 | "proc-macro2", 217 | "quote", 218 | "syn", 219 | ] 220 | 221 | [[package]] 222 | name = "serde_json" 223 | version = "1.0.105" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 226 | dependencies = [ 227 | "itoa", 228 | "ryu", 229 | "serde", 230 | ] 231 | 232 | [[package]] 233 | name = "smallvec" 234 | version = "1.11.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 237 | 238 | [[package]] 239 | name = "spdx" 240 | version = "0.10.2" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" 243 | dependencies = [ 244 | "smallvec", 245 | ] 246 | 247 | [[package]] 248 | name = "syn" 249 | version = "2.0.31" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" 252 | dependencies = [ 253 | "proc-macro2", 254 | "quote", 255 | "unicode-ident", 256 | ] 257 | 258 | [[package]] 259 | name = "tinyvec" 260 | version = "1.6.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 263 | dependencies = [ 264 | "tinyvec_macros", 265 | ] 266 | 267 | [[package]] 268 | name = "tinyvec_macros" 269 | version = "0.1.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 272 | 273 | [[package]] 274 | name = "unicase" 275 | version = "2.7.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 278 | dependencies = [ 279 | "version_check", 280 | ] 281 | 282 | [[package]] 283 | name = "unicode-bidi" 284 | version = "0.3.13" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 287 | 288 | [[package]] 289 | name = "unicode-ident" 290 | version = "1.0.11" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 293 | 294 | [[package]] 295 | name = "unicode-normalization" 296 | version = "0.1.22" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 299 | dependencies = [ 300 | "tinyvec", 301 | ] 302 | 303 | [[package]] 304 | name = "unicode-segmentation" 305 | version = "1.10.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 308 | 309 | [[package]] 310 | name = "unicode-xid" 311 | version = "0.2.4" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 314 | 315 | [[package]] 316 | name = "url" 317 | version = "2.4.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 320 | dependencies = [ 321 | "form_urlencoded", 322 | "idna", 323 | "percent-encoding", 324 | ] 325 | 326 | [[package]] 327 | name = "version_check" 328 | version = "0.9.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 331 | 332 | [[package]] 333 | name = "wasm-encoder" 334 | version = "0.32.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" 337 | dependencies = [ 338 | "leb128", 339 | ] 340 | 341 | [[package]] 342 | name = "wasm-metadata" 343 | version = "0.10.3" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471" 346 | dependencies = [ 347 | "anyhow", 348 | "indexmap", 349 | "serde", 350 | "serde_json", 351 | "spdx", 352 | "wasm-encoder", 353 | "wasmparser", 354 | ] 355 | 356 | [[package]] 357 | name = "wasmparser" 358 | version = "0.112.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf" 361 | dependencies = [ 362 | "indexmap", 363 | "semver", 364 | ] 365 | 366 | [[package]] 367 | name = "wee_alloc" 368 | version = "0.4.5" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 371 | dependencies = [ 372 | "cfg-if", 373 | "libc", 374 | "memory_units", 375 | "winapi", 376 | ] 377 | 378 | [[package]] 379 | name = "winapi" 380 | version = "0.3.9" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 383 | dependencies = [ 384 | "winapi-i686-pc-windows-gnu", 385 | "winapi-x86_64-pc-windows-gnu", 386 | ] 387 | 388 | [[package]] 389 | name = "winapi-i686-pc-windows-gnu" 390 | version = "0.4.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 393 | 394 | [[package]] 395 | name = "winapi-x86_64-pc-windows-gnu" 396 | version = "0.4.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 399 | 400 | [[package]] 401 | name = "wit-bindgen" 402 | version = "0.11.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279" 405 | dependencies = [ 406 | "bitflags 2.4.0", 407 | "wit-bindgen-rust-macro", 408 | ] 409 | 410 | [[package]] 411 | name = "wit-bindgen-core" 412 | version = "0.11.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e" 415 | dependencies = [ 416 | "anyhow", 417 | "wit-component", 418 | "wit-parser", 419 | ] 420 | 421 | [[package]] 422 | name = "wit-bindgen-rust" 423 | version = "0.11.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae" 426 | dependencies = [ 427 | "anyhow", 428 | "heck", 429 | "wasm-metadata", 430 | "wit-bindgen-core", 431 | "wit-bindgen-rust-lib", 432 | "wit-component", 433 | ] 434 | 435 | [[package]] 436 | name = "wit-bindgen-rust-lib" 437 | version = "0.11.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb" 440 | dependencies = [ 441 | "heck", 442 | "wit-bindgen-core", 443 | ] 444 | 445 | [[package]] 446 | name = "wit-bindgen-rust-macro" 447 | version = "0.11.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22" 450 | dependencies = [ 451 | "anyhow", 452 | "proc-macro2", 453 | "syn", 454 | "wit-bindgen-core", 455 | "wit-bindgen-rust", 456 | "wit-bindgen-rust-lib", 457 | "wit-component", 458 | ] 459 | 460 | [[package]] 461 | name = "wit-component" 462 | version = "0.14.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876" 465 | dependencies = [ 466 | "anyhow", 467 | "bitflags 2.4.0", 468 | "indexmap", 469 | "log", 470 | "serde", 471 | "serde_json", 472 | "wasm-encoder", 473 | "wasm-metadata", 474 | "wasmparser", 475 | "wit-parser", 476 | ] 477 | 478 | [[package]] 479 | name = "wit-parser" 480 | version = "0.11.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc" 483 | dependencies = [ 484 | "anyhow", 485 | "id-arena", 486 | "indexmap", 487 | "log", 488 | "pulldown-cmark", 489 | "semver", 490 | "unicode-xid", 491 | "url", 492 | ] 493 | -------------------------------------------------------------------------------- /hello/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["pavel.savara@gmail.com"] 6 | 7 | [dependencies] 8 | cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component", rev = "36c221e41db3e87dec4c82eadcb9bc8f37626533" } 9 | wee_alloc = { version = "0.4.5" } 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [package.metadata.component] 15 | package = "hello:city" 16 | 17 | [package.metadata.component.target] 18 | path = "wit" 19 | 20 | [package.metadata.component.dependencies] 21 | 22 | [profile.release] 23 | lto = true 24 | opt-level = 's' 25 | 26 | [build] 27 | incremental = true 28 | -------------------------------------------------------------------------------- /hello/README.md: -------------------------------------------------------------------------------- 1 | # Build with cargo_component_bindings 2 | 3 | ```shell 4 | cargo component build --release --target wasm32-unknown-unknown 5 | jco transpile --instantiation --no-wasi-shim -b 0 --out-dir target/js-jco target/wasm32-unknown-unknown/release/hello.wasm 6 | ``` -------------------------------------------------------------------------------- /hello/hello-test.mjs: -------------------------------------------------------------------------------- 1 | import { instantiate } from "./target/js-jco/hello.js" 2 | 3 | const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; 4 | let _fs; 5 | async function fetchCompile(url) { 6 | let self = import.meta.url.substring("file://".length); 7 | if (self.indexOf(":") === 2) { 8 | self = self.substring(1); 9 | } 10 | const u2 = self.substring(0, self.lastIndexOf("/"))+"/target/js-jco/"+url; 11 | if (isNode) { 12 | _fs = _fs || await import('fs/promises'); 13 | return WebAssembly.compile(await _fs.readFile(u2)); 14 | } 15 | return fetch(u2).then(WebAssembly.compileStreaming); 16 | } 17 | 18 | const expectdMessage="Welcome to Prague, we invite you for a drink!"; 19 | let actualMessage; 20 | const imports = { 21 | 'hello:city/city': { 22 | sendMessage: (message) => { 23 | actualMessage = message; 24 | console.log(message); 25 | } 26 | } 27 | } 28 | const component = await instantiate(fetchCompile, imports, WebAssembly.instantiate); 29 | const exports = component['greeter']; 30 | exports.run({ 31 | name: "Prague", 32 | headCount: 1000000, 33 | budget: BigInt(200000000) 34 | }); 35 | 36 | if (actualMessage !== expectdMessage) { 37 | throw new Error(`sendMessage: expected "${expectdMessage}" actual "${actualMessage}"`); 38 | } -------------------------------------------------------------------------------- /hello/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustc", "rustfmt", "rust-std", "cargo" ] 4 | targets = [ "wasm32-unknown-unknown", "wasm32-wasi" ] 5 | profile = "default" -------------------------------------------------------------------------------- /hello/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[global_allocator] 2 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 3 | 4 | cargo_component_bindings::generate!({ 5 | implementor: Greeter, 6 | }); 7 | 8 | use bindings::exports::hello::city::greeter::Guest; 9 | use bindings::hello::city::city::{send_message, CityInfo}; 10 | 11 | struct Greeter; 12 | 13 | impl Guest for Greeter { 14 | fn run(info: CityInfo) { 15 | if ((info.budget as f64) / (info.head_count as f64)) > 100.0 { 16 | send_message(&format!( 17 | "Welcome to {}, we invite you for a drink!", 18 | info.name 19 | )); 20 | } else if info.head_count > 1_000_000 { 21 | send_message(&format!("Welcome to {} mega polis!", info.name)); 22 | } else { 23 | send_message(&format!("Welcome to {}!", info.name)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hello/wasm/hello.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavelsavara/jsco/14742a63af10b86c93b949f56e9e6f42d9d3122b/hello/wasm/hello.wasm -------------------------------------------------------------------------------- /hello/wat/cut-hello.wat: -------------------------------------------------------------------------------- 1 | (component 2 | (type (;0;) 3 | (instance 4 | (type (;0;) (record (field "name" string) (field "head-count" u32) (field "budget" s64))) 5 | (export (;1;) "city-info" (type (eq 0))) 6 | (type (;2;) (func (param "message" string))) 7 | (export (;0;) "send-message" (func (type 2))) 8 | ) 9 | ) 10 | (import (interface "hello:city/city") (instance (;0;) (type 0))) 11 | (core instance (;0;) (instantiate 1)) 12 | (alias core export 0 "0" (core func (;0;))) 13 | (core instance (;1;) 14 | (export "send-message" (func 0)) 15 | ) 16 | (core instance (;2;) (instantiate 0 17 | (with "hello:city/city" (instance 1)) 18 | ) 19 | ) 20 | (alias core export 2 "memory" (core memory (;0;))) 21 | (alias core export 2 "cabi_realloc" (core func (;1;))) 22 | (alias core export 0 "$imports" (core table (;0;))) 23 | (alias export 0 "send-message" (func (;0;))) 24 | (core func (;2;) (canon lower (func 0) (memory 0) string-encoding=utf8)) 25 | (core instance (;3;) 26 | (export "$imports" (table 0)) 27 | (export "0" (func 2)) 28 | ) 29 | (core instance (;4;) (instantiate 2 30 | (with "" (instance 3)) 31 | ) 32 | ) 33 | (alias export 0 "city-info" (type (;1;))) 34 | (type (;2;) (func (param "info" 1))) 35 | (alias core export 2 "hello:city/greeter#run" (core func (;3;))) 36 | (func (;1;) (type 2) (canon lift (core func 3) (memory 0) (realloc 1) string-encoding=utf8)) 37 | (alias export 0 "city-info" (type (;3;))) 38 | (component (;0;) 39 | (type (;0;) (record (field "name" string) (field "head-count" u32) (field "budget" s64))) 40 | (import "import-type-city-info" (type (;1;) (eq 0))) 41 | (import "import-type-city-info0" (type (;2;) (eq 1))) 42 | (type (;3;) (func (param "info" 2))) 43 | (import "import-func-run" (func (;0;) (type 3))) 44 | (export (;4;) "city-info" (type 1)) 45 | (type (;5;) (func (param "info" 4))) 46 | (export (;1;) "run" (func 0) (func (type 5))) 47 | ) 48 | (instance (;1;) (instantiate 0 49 | (with "import-func-run" (func 1)) 50 | (with "import-type-city-info" (type 3)) 51 | (with "import-type-city-info0" (type 1)) 52 | ) 53 | ) 54 | (export (;2;) (interface "hello:city/greeter") (instance 1)) 55 | (@producers 56 | (processed-by "wit-component" "0.14.0") 57 | (processed-by "cargo-component" "0.1.0 (36c221e 2023-09-07 wasi:134dddc)") 58 | ) 59 | ) -------------------------------------------------------------------------------- /hello/wit/hello.wit: -------------------------------------------------------------------------------- 1 | package hello:city 2 | 3 | interface city { 4 | record city-info { 5 | name: string, 6 | head-count: u32, 7 | budget: s64, 8 | } 9 | 10 | send-message: func(message: string) 11 | } 12 | 13 | interface greeter { 14 | use city.{city-info} 15 | run: func(info: city-info) 16 | } 17 | 18 | world hello { 19 | import city 20 | export greeter 21 | } 22 | -------------------------------------------------------------------------------- /inspiration.md: -------------------------------------------------------------------------------- 1 | # Inspiration 2 | 3 | ## Documentation 4 | - https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md 5 | - https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md 6 | - https://github.com/WebAssembly/module-linking/blob/main/proposals/module-linking/Explainer.md 7 | - https://www.fermyon.com/blog/webassembly-component-model 8 | - https://github.com/WebAssembly/WASI/blob/main/Proposals.md 9 | 10 | ## Inspiration 11 | - https://github.com/bytecodealliance/jco 12 | - https://github.com/bytecodealliance/jco/blob/main/crates/js-component-bindgen/src/transpile_bindgen.rs 13 | - https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-parser 14 | - https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/decoding.rs 15 | - https://github.com/bytecodealliance/wasmtime/blob/main/crates/environ/src/component/types.rs 16 | - https://github.com/bytecodealliance/jco/blob/main/src/cmd/transpile.js 17 | - https://github.com/WebAssembly/component-model/blob/main/design/mvp/canonical-abi/definitions.py 18 | 19 | ### resolver 20 | - https://github.com/bytecodealliance/wasm-interface-types/blob/main/SEMANTICS.md 21 | - https://github.com/bytecodealliance/jco/blob/177f295422db38ddd7853bd371fd096c6bb70965/crates/js-component-bindgen/src/lib.rs#L101-L114 22 | - https://github.com/bytecodealliance/wasmtime/blob/2ad057d735edc43f8ba89428d483f2b2430c1068/crates/environ/src/component/translate.rs#L277 23 | 24 | ### binding 25 | - https://github.com/bytecodealliance/jco/blob/main/crates/js-component-bindgen/src/intrinsics.rs 26 | - https://github.com/WebAssembly/component-model/blob/673d5c43c3cc0f4aeb8996a5c0931af623f16808/design/mvp/canonical-abi/definitions.py 27 | 28 | ## WASI 29 | - https://github.com/WebAssembly/wasi-cli/tree/main/wit 30 | - https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi/wit/main.wit 31 | - https://github.com/bytecodealliance/jco/blob/main/packages/preview2-shim/README.md 32 | 33 | ## WASM Core binary parser in TS 34 | https://github.com/yskszk63/stream-wasm-parser 35 | 36 | ## WIT text AST parser + TypeScript generator 37 | https://github.com/microsoft/vscode-wasm/tree/dbaeumer/monetary-ox-cyan 38 | 39 | ## Other stuff 40 | https://github.com/rylev/wepl 41 | https://wasmbuilder.app/ -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'roots': [ 3 | '/src' 4 | ], 5 | 'testMatch': [ 6 | '**/?(*.)+(spec|test).+(ts|js)' 7 | ], 8 | 'transform': { 9 | //"^.+\\.(ts|tsx)$": "esbuild-jest" 10 | '^.+\\.(ts|tsx)$': ['@swc/jest'], 11 | //"^.+\\.(ts|tsx)$": "ts-jest" 12 | }, 13 | extensionsToTreatAsEsm: ['.ts'], 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pavelsavara/jsco", 3 | "version": "0.1.0", 4 | "description": "browser polyfill for running WASM components", 5 | "main": "index.js", 6 | "type": "module", 7 | "types": "index.d.ts", 8 | "scripts": { 9 | "build": "rollup -c", 10 | "build:npm": "npm run build && cp package.json dist/ && cp README.md dist/ && cp LICENSE dist/", 11 | "build:npmw": "set Configuration=Release&& set ContinuousIntegrationBuild=true&& npm run build && copy package.json dist && copy README.md dist && copy LICENSE dist", 12 | "build:hello": "cd hello && cargo component build --release --target wasm32-unknown-unknown", 13 | "build:hello-js": "cd hello && jco transpile --instantiation --no-wasi-shim -b 0 --out-dir target/js-jco target/wasm32-unknown-unknown/release/hello.wasm", 14 | "build:hello-js2": "cd hello && jco transpile -b 0 --out-dir target/js-jco2 target/wasm32-unknown-unknown/release/hello.wasm", 15 | "build:hello-wat": "cd hello && jco print target/wasm32-unknown-unknown/release/hello.wasm -o wat/hello.wat", 16 | "build:zoo": "cd zoo && cargo component build --release --target wasm32-unknown-unknown", 17 | "build:zoo-js": "cd zoo && jco transpile --instantiation --no-wasi-shim -b 0 --out-dir target/js-jco target/wasm32-unknown-unknown/release/zoo.wasm", 18 | "build:zoo-js2": "cd zoo && jco transpile -b 0 --out-dir target/js-jco2 target/wasm32-unknown-unknown/release/zoo.wasm", 19 | "build:zoo-wat": "cd zoo && jco print target/wasm32-unknown-unknown/release/zoo.wasm -o wat/zoo.wat", 20 | "lint": "eslint --no-color \"./**/*.ts\"", 21 | "lint:fix": "eslint --fix \"./**/*.ts\"", 22 | "TODO: strict lint": "eslint --no-color --max-warnings=0 \"./**/*.ts\"", 23 | "format": "eslint --fix \"./**/*.ts\"", 24 | "setup:rust": "npm run setup:rust:rustup && npm run setup:rust:cargo", 25 | "setup:rust:ci": "npm run setup:rust:cargo", 26 | "setup:rust:rustup": "rustup toolchain install stable --profile default --no-self-update && rustup target add wasm32-unknown-unknown && rustup target add wasm32-wasi", 27 | "setup:rust:cargo": "cargo install --git https://github.com/bytecodealliance/cargo-component?rev=e57d1d14#e57d1d1405ed2d76f1f3d8647480dea700379ff8 --locked cargo-component", 28 | "test:jco": "node ./hello/hello-test.mjs", 29 | "test:usage": "node ./usage.mjs && node ./usage2.mjs", 30 | "test:ci": "jest", 31 | "test:unix": "NODE_OPTIONS=--experimental-vm-modules jest --watch", 32 | "test:win": "set NODE_OPTIONS=--experimental-vm-modules && jest --watch", 33 | "test:cover": "set NODE_OPTIONS=--experimental-vm-modules && jest --coverage" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/pavelsavara/jsco.git" 38 | }, 39 | "keywords": [ 40 | "WASM", 41 | "WIT", 42 | "WASI", 43 | "Component", 44 | "WebAssembly", 45 | "polyfill" 46 | ], 47 | "author": "Pavel Savara ", 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/pavelsavara/jsco/issues" 51 | }, 52 | "homepage": "https://github.com/pavelsavara/jsco#readme", 53 | "devDependencies": { 54 | "@bytecodealliance/jco": "^0.11.1", 55 | "@rollup/plugin-eslint": "^9.0.4", 56 | "@rollup/plugin-node-resolve": "^15.2.1", 57 | "@rollup/plugin-terser": "^0.4.3", 58 | "@rollup/plugin-typescript": "^11.1.3", 59 | "@rollup/plugin-virtual": "^3.0.1", 60 | "@swc/core": "^1.3.83", 61 | "@swc/jest": "^0.2.29", 62 | "@types/jest": "^29.5.4", 63 | "@typescript-eslint/eslint-plugin": "^6.6.0", 64 | "@typescript-eslint/parser": "^6.6.0", 65 | "eslint": "^8.48.0", 66 | "git-commit-info": "^2.0.2", 67 | "jest": "^29.6.4", 68 | "rollup": "^3.29.0", 69 | "rollup-plugin-dts": "^6.0.1", 70 | "rollup-plugin-swc": "^0.2.1", 71 | "terser": "^5.19.4", 72 | "typescript": "^5.2.2" 73 | }, 74 | "dependencies": { 75 | "@thi.ng/leb128": "^3.0.34", 76 | "just-camel-case": "^6.2.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 4 | import terser from '@rollup/plugin-terser'; 5 | import virtual from '@rollup/plugin-virtual'; 6 | import * as path from 'path'; 7 | import dts from 'rollup-plugin-dts'; 8 | import gitCommitInfo from 'git-commit-info'; 9 | 10 | const configuration = process.env.Configuration ?? 'Debug'; 11 | const isDebug = configuration !== 'Release'; 12 | const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === 'true' ? true : false; 13 | let gitHash = (() => { 14 | try { 15 | const gitInfo = gitCommitInfo(); 16 | return gitInfo.hash; 17 | } catch (e) { 18 | return 'unknown'; 19 | } 20 | })(); 21 | 22 | const constants = { 23 | 'env:configuration': `export default "${configuration}"`, 24 | 'env:gitHash': `export default "${gitHash}"`, 25 | }; 26 | const plugins = isDebug ? [] : [terser({ 27 | ecma: 2022, 28 | compress: { 29 | defaults: true, 30 | module: true, 31 | ecma: 2022, 32 | toplevel : true, 33 | passes: 4 34 | }, 35 | mangle: { 36 | module: true, 37 | toplevel : true, 38 | // TODO properties:{ reserved:['leb128DecodeU64', 'leb128DecodeI64', 'leb128EncodeU64', 'leb128EncodeI64', 'buf', 'memory'] } 39 | }, 40 | })]; 41 | const banner = '//! Pavel Savara licenses this file to you under the MIT license.\n'; 42 | const externalDependencies = ['module', 'fs', 'gitHash']; 43 | const jsco = { 44 | treeshake: !isDebug, 45 | input: './src/index.ts', 46 | output: [ 47 | { 48 | format: 'es', 49 | file: 'dist/index.js', 50 | banner, 51 | plugins, 52 | sourcemap: true, 53 | sourcemapPathTransform, 54 | } 55 | ], 56 | external: externalDependencies, 57 | plugins: [ 58 | virtual(constants), 59 | nodeResolve({ 60 | extensions: ['.ts'], 61 | }), 62 | typescript() 63 | ] 64 | }; 65 | const jscoTypes = { 66 | input: './src/index.ts', 67 | output: [ 68 | { 69 | format: 'es', 70 | file: 'dist/index.d.ts', 71 | banner: banner, 72 | } 73 | ], 74 | external: externalDependencies, 75 | plugins: [dts()], 76 | }; 77 | 78 | export default defineConfig([ 79 | jsco, 80 | jscoTypes, 81 | ]); 82 | 83 | 84 | const locationCache = {}; 85 | function sourcemapPathTransform(relativeSourcePath, sourcemapPath) { 86 | let res = locationCache[relativeSourcePath]; 87 | if (res === undefined) { 88 | if (!isContinuousIntegrationBuild) { 89 | const sourcePath = path.resolve( 90 | path.dirname(sourcemapPath), 91 | relativeSourcePath 92 | ); 93 | res = `file:///${sourcePath.replace(/\\/g, '/')}`; 94 | } else { 95 | relativeSourcePath = relativeSourcePath.substring(12); 96 | res = `https://raw.githubusercontent.com/pavelsavara/jsco/${gitHash}/${relativeSourcePath}`; 97 | } 98 | locationCache[relativeSourcePath] = res; 99 | } 100 | return res; 101 | } 102 | -------------------------------------------------------------------------------- /src/.types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "env:*" { 2 | //Constant that will be inlined by Rollup and rollup-plugin-consts. 3 | const constant: any; 4 | export default constant; 5 | } 6 | 7 | declare module "@bytecodealliance/jco" { 8 | export function parse(wat: string): Promise; 9 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | 4 | import gitHash from 'env:gitHash'; 5 | import configuration from 'env:configuration'; 6 | import { setConfiguration } from './utils/assert'; 7 | 8 | export type { WITModel } from './parser'; 9 | export { parse } from './parser'; 10 | export { instantiateComponent, createComponent } from './resolver'; 11 | export { createLifting, createLowering } from './resolver/binding'; 12 | 13 | export function getBuildInfo() { 14 | return { 15 | gitHash, 16 | configuration, 17 | }; 18 | } 19 | 20 | setConfiguration(configuration); -------------------------------------------------------------------------------- /src/model/README.md: -------------------------------------------------------------------------------- 1 | adopted from https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasmparser/src/readers/component under Apache-2.0 license -------------------------------------------------------------------------------- /src/model/aliases.ts: -------------------------------------------------------------------------------- 1 | import { CanonicalFunctionLift, CanonicalFunctionLower } from './canonicals'; 2 | import { u32, ExternalKind } from './core'; 3 | import { ComponentExternalKind } from './exports'; 4 | import { IndexedElement, ModelTag } from './tags'; 5 | 6 | /// Represents the kind of an outer alias in a WebAssembly component. 7 | export const enum ComponentOuterAliasKind { 8 | /// The alias is to an outer core module. 9 | CoreModule = 'coremodule', 10 | /// The alias is to an outer core type. 11 | CoreType = 'coretype', 12 | /// The alias is to an outer type. 13 | Type = 'type', 14 | /// The alias is to an outer component. 15 | Component = 'component', 16 | } 17 | 18 | /// Represents an alias in a WebAssembly component. 19 | export type ComponentAlias = 20 | | ComponentAliasInstanceExport 21 | | ComponentAliasCoreInstanceExport 22 | | ComponentAliasOuter 23 | 24 | export type ComponentFunction = 25 | | CanonicalFunctionLift 26 | | ComponentAliasInstanceExport 27 | 28 | /// The alias is to an export of a component instance. 29 | export type ComponentAliasInstanceExport = IndexedElement & { 30 | tag: ModelTag.ComponentAliasInstanceExport, 31 | /// The alias kind. 32 | kind: ComponentExternalKind, 33 | /// The instance index. 34 | instance_index: u32, 35 | /// The export name. 36 | name: string, 37 | } 38 | 39 | /// The alias is to an export of a module instance. 40 | export type ComponentAliasCoreInstanceExport = IndexedElement & { 41 | tag: ModelTag.ComponentAliasCoreInstanceExport, 42 | /// The alias kind. 43 | kind: ExternalKind, 44 | /// The instance index. 45 | instance_index: u32, 46 | /// The export name. 47 | name: string, 48 | } 49 | 50 | export type CoreFunction = 51 | | ComponentAliasCoreInstanceExport 52 | | CanonicalFunctionLower 53 | 54 | /// The alias is to an outer item. 55 | export type ComponentAliasOuter = { 56 | tag: ModelTag.ComponentAliasOuter, 57 | /// The alias kind. 58 | kind: ComponentOuterAliasKind, 59 | /// The outward count, starting at zero for the current component. 60 | count: u32, 61 | /// The index of the item within the outer component. 62 | index: u32, 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/model/canonicals.ts: -------------------------------------------------------------------------------- 1 | import { u32 } from './core'; 2 | import { IndexedElement, ModelTag } from './tags'; 3 | 4 | /// Represents options for component functions. 5 | export type CanonicalOption = 6 | | CanonicalOptionUTF8 7 | | CanonicalOptionUTF16 8 | | CanonicalOptionCompactUTF16 9 | | CanonicalOptionMemory 10 | | CanonicalOptionRealloc 11 | | CanonicalOptionPostReturn 12 | 13 | /// The string types in the function signature are UTF-8 encoded. 14 | export type CanonicalOptionUTF8 = { 15 | tag: ModelTag.CanonicalOptionUTF8 16 | } 17 | 18 | /// The string types in the function signature are UTF-16 encoded. 19 | export type CanonicalOptionUTF16 = { 20 | tag: ModelTag.CanonicalOptionUTF16 21 | } 22 | 23 | /// The string types in the function signature are compact UTF-16 encoded. 24 | export type CanonicalOptionCompactUTF16 = { 25 | tag: ModelTag.CanonicalOptionCompactUTF16 26 | } 27 | 28 | /// The memory to use if the lifting or lowering of a function requires memory access. 29 | /// 30 | /// The value is an index to a core memory. 31 | export type CanonicalOptionMemory = { 32 | tag: ModelTag.CanonicalOptionMemory 33 | value: u32 34 | } 35 | /// The realloc function to use if the lifting or lowering of a function requires memory 36 | /// allocation. 37 | /// 38 | /// The value is an index to a core function of type `(func (param i32 i32 i32 i32) (result i32))`. 39 | export type CanonicalOptionRealloc = { 40 | tag: ModelTag.CanonicalOptionRealloc 41 | value: u32 42 | } 43 | 44 | /// The post-return function to use if the lifting of a function requires 45 | /// cleanup after the function returns. 46 | export type CanonicalOptionPostReturn = { 47 | tag: ModelTag.CanonicalOptionPostReturn 48 | value: u32 49 | } 50 | 51 | /// Represents a canonical function in a WebAssembly component. 52 | export type CanonicalFunction = 53 | | CanonicalFunctionLift // this is component function 54 | | CanonicalFunctionLower // this is core function 55 | | CanonicalFunctionResourceNew 56 | | CanonicalFunctionResourceDrop 57 | | CanonicalFunctionResourceRep 58 | 59 | /// The function lifts a core WebAssembly function to the canonical ABI. 60 | export type CanonicalFunctionLift = IndexedElement & { 61 | tag: ModelTag.CanonicalFunctionLift 62 | /// The index of the core WebAssembly function to lift. 63 | core_func_index: u32, 64 | /// The index of the lifted function's type. 65 | type_index: u32, 66 | /// The canonical options for the function. 67 | options: CanonicalOption[], 68 | } 69 | 70 | /// The function lowers a canonical ABI function to a core WebAssembly function. 71 | export type CanonicalFunctionLower = IndexedElement & { 72 | tag: ModelTag.CanonicalFunctionLower 73 | /// The index of the function to lower. 74 | func_index: u32, 75 | /// The canonical options for the function. 76 | options: CanonicalOption[], 77 | } 78 | 79 | /// A function which creates a new owned handle to a resource. 80 | export type CanonicalFunctionResourceNew = { 81 | tag: ModelTag.CanonicalFunctionResourceNew 82 | /// The type index of the resource that's being created. 83 | resource: u32, 84 | } 85 | 86 | /// A function which is used to drop resource handles of the specified type. 87 | export type CanonicalFunctionResourceDrop = { 88 | tag: ModelTag.CanonicalFunctionResourceDrop 89 | /// The type index of the resource that's being dropped. 90 | resource: u32, 91 | } 92 | 93 | /// A function which returns the underlying i32-based representation of the 94 | /// specified resource. 95 | export type CanonicalFunctionResourceRep = { 96 | tag: ModelTag.CanonicalFunctionResourceRep 97 | /// The type index of the resource that's being accessed. 98 | resource: u32, 99 | } -------------------------------------------------------------------------------- /src/model/core.ts: -------------------------------------------------------------------------------- 1 | export type u32 = number; 2 | export type u64 = number; 3 | export type u8 = number; 4 | export type usize = number; 5 | export type RefType = number; 6 | import { ModelTag } from './tags'; 7 | 8 | /// External types as defined https://webassembly.github.io/spec/core/syntax/types.html#external-types. 9 | export const enum ExternalKind { 10 | /// The external kind is a function. 11 | Func = 'func', 12 | /// The external kind if a table. 13 | Table = 'table', 14 | /// The external kind is a memory. 15 | Memory = 'memory', 16 | /// The external kind is a global. 17 | Global = 'global', 18 | /// The external kind is a tag. 19 | Tag = 'tag', 20 | } 21 | 22 | /// Represents a reference to a type definition in a WebAssembly module. 23 | export type TypeRef = 24 | | TypeRefFunc 25 | | TypeRefTable 26 | | TypeRefMemory 27 | | TypeRefGlobal 28 | | TypeRefTag 29 | 30 | 31 | /// The type is a function. 32 | /// 33 | /// The value is an index into the type section. 34 | export type TypeRefFunc = { 35 | tag: ModelTag.TypeRefFunc 36 | value: u32 37 | } 38 | /// The type is a table. 39 | export type TypeRefTable = TableType & { 40 | tag: ModelTag.TypeRefTable 41 | } 42 | 43 | /// The type is a memory. 44 | export type TypeRefMemory = MemoryType & { 45 | tag: ModelTag.TypeRefMemory 46 | } 47 | 48 | /// The type is a global. 49 | export type TypeRefGlobal = GlobalType & { 50 | tag: ModelTag.TypeRefGlobal 51 | } 52 | 53 | /// The type is a tag. 54 | /// 55 | /// This variant is only used for the exception handling proposal. 56 | /// 57 | /// The value is an index in the types index space. 58 | export type TypeRefTag = { 59 | tag: ModelTag.TypeRefTag 60 | value: u32 61 | } 62 | 63 | /// Represents an import in a WebAssembly module. 64 | export type Import = { 65 | /// The module being imported from. 66 | module: string, 67 | /// The name of the imported item. 68 | name: string, 69 | /// The type of the imported item. 70 | ty: TypeRef, 71 | } 72 | 73 | 74 | export type Export = { 75 | /// The name of the exported item. 76 | name: string, 77 | /// The kind of the export. 78 | kind: ExternalKind, 79 | /// The index of the exported item. 80 | index: u32, 81 | } 82 | 83 | export type NameMap = Naming[]; 84 | 85 | export type Naming = { 86 | /// The index being named. 87 | index: u32, 88 | /// The name for the index. 89 | name: string, 90 | } 91 | 92 | /// Represents the types of values in a WebAssembly module. 93 | export type ValType = 94 | | ValTypeI32 95 | | ValTypeI64 96 | | ValTypeF32 97 | | ValTypeF64 98 | | ValTypeV128 99 | | ValTypeRef 100 | 101 | /// The value type is i32. 102 | export type ValTypeI32 = { 103 | tag: ModelTag.ValTypeI32 104 | } 105 | 106 | /// The value type is i64. 107 | export type ValTypeI64 = { 108 | tag: ModelTag.ValTypeI64 109 | } 110 | 111 | /// The value type is f32. 112 | export type ValTypeF32 = { 113 | tag: ModelTag.ValTypeF32 114 | } 115 | 116 | /// The value type is f64. 117 | export type ValTypeF64 = { 118 | tag: ModelTag.ValTypeF64 119 | } 120 | 121 | /// The value type is v128. 122 | export type ValTypeV128 = { 123 | tag: ModelTag.ValTypeV128 124 | } 125 | 126 | /// The value type is a reference. 127 | export type ValTypeRef = { 128 | tag: ModelTag.ValTypeRef 129 | value: RefType 130 | } 131 | 132 | /// Represents a global's type. 133 | export type GlobalType = { 134 | /// The global's type. 135 | content_type: ValType, 136 | /// Whether or not the global is mutable. 137 | mutable: boolean, 138 | } 139 | 140 | /// Represents a memory's type. 141 | export type MemoryType = { 142 | /// Whether or not this is a 64-bit memory, using i64 as an index. If this 143 | /// is false it's a 32-bit memory using i32 as an index. 144 | /// 145 | /// This is part of the memory64 proposal in WebAssembly. 146 | memory64: boolean, 147 | 148 | /// Whether or not this is a "shared" memory, indicating that it should be 149 | /// send-able across threads and the `maximum` field is always present for 150 | /// valid types. 151 | /// 152 | /// This is part of the threads proposal in WebAssembly. 153 | shared: boolean, 154 | 155 | /// Initial size of this memory, in wasm pages. 156 | /// 157 | /// For 32-bit memories (when `memory64` is `false`) this is guaranteed to 158 | /// be at most `u32::MAX` for valid types. 159 | initial: u64, 160 | 161 | /// Optional maximum size of this memory, in wasm pages. 162 | /// 163 | /// For 32-bit memories (when `memory64` is `false`) this is guaranteed to 164 | /// be at most `u32::MAX` for valid types. This field is always present for 165 | /// valid wasm memories when `shared` is `true`. 166 | maximum?: u64, 167 | } 168 | 169 | /// Represents a subtype of possible other types in a WebAssembly module. 170 | export type SubType = { 171 | /// Is the subtype final. 172 | is_final: boolean, 173 | /// The list of supertype indexes. As of GC MVP, there can be at most one supertype. 174 | supertype_idx?: u32, 175 | /// The structural type of the subtype. 176 | structural_type: StructuralType, 177 | } 178 | 179 | 180 | /// Represents a structural type in a WebAssembly module. 181 | export type StructuralType = 182 | | StructuralTypeFunc 183 | | StructuralTypeArray 184 | | StructuralTypeStruct 185 | 186 | 187 | /// The type is for a function. 188 | export type StructuralTypeFunc = FuncType & { 189 | tag: ModelTag.StructuralTypeFunc 190 | } 191 | 192 | /// The type is for an array. 193 | export type StructuralTypeArray = ArrayType & { 194 | tag: ModelTag.StructuralTypeArray 195 | } 196 | 197 | /// The type is for a struct. 198 | export type StructuralTypeStruct = StructType & { 199 | tag: ModelTag.StructuralTypeStruct 200 | } 201 | 202 | /// Represents a table's type. 203 | export type TableType = { 204 | /// The table's element type. 205 | element_type: RefType, 206 | /// Initial size of this table, in elements. 207 | initial: u32, 208 | /// Optional maximum size of the table, in elements. 209 | maximum?: u32, 210 | } 211 | 212 | /// Represents a type of a struct in a WebAssembly module. 213 | export type StructType = { 214 | /// Struct fields. 215 | fields: FieldType[], 216 | } 217 | 218 | /// Represents a field type of an array or a struct. 219 | export type FieldType = { 220 | /// Array element type. 221 | element_type: StorageType, 222 | /// Are elements mutable. 223 | mutable: boolean, 224 | } 225 | 226 | /// Represents storage types introduced in the GC spec for array and struct fields. 227 | export type StorageType = 228 | | StorageTypeI8 229 | | StorageTypeI16 230 | | StorageTypeVal 231 | 232 | /// The storage type is i8. 233 | export type StorageTypeI8 = { 234 | tag: ModelTag.StorageTypeI8 235 | } 236 | 237 | /// The storage type is i16. 238 | export type StorageTypeI16 = { 239 | tag: ModelTag.StorageTypeI16 240 | } 241 | 242 | /// The storage type is a value type. 243 | export type StorageTypeVal = { 244 | tag: ModelTag.StorageTypeVal 245 | type: ValType 246 | } 247 | 248 | /// Represents a type of an array in a WebAssembly module. 249 | export type ArrayType = { 250 | value: FieldType 251 | } 252 | 253 | /// Represents a type of a function in a WebAssembly module. 254 | export type FuncType = { 255 | /// The combined parameters and result types. 256 | params_results: ValType[], 257 | /// The number of parameter types. 258 | len_params: usize, 259 | } 260 | -------------------------------------------------------------------------------- /src/model/exports.ts: -------------------------------------------------------------------------------- 1 | import { u32 } from './core'; 2 | import { ComponentExternName, ComponentTypeRef } from './imports'; 3 | import { IndexedElement, ModelTag } from './tags'; 4 | 5 | /// Represents the kind of an external items of a WebAssembly component. 6 | export const enum ComponentExternalKind { 7 | /// The external kind is a core module. 8 | Module = 'module', 9 | /// The external kind is a function. 10 | Func = 'func', 11 | /// The external kind is a value. 12 | Value = 'value', 13 | /// The external kind is a type. 14 | Type = 'type', 15 | /// The external kind is an instance. 16 | Instance = 'instance', 17 | /// The external kind is a component. 18 | Component = 'component', 19 | } 20 | 21 | /// Represents an export in a WebAssembly component. 22 | export type ComponentExport = IndexedElement & { 23 | tag: ModelTag.ComponentExport, 24 | /// The name of the exported item. 25 | name: ComponentExternName, 26 | /// The kind of the export. 27 | kind: ComponentExternalKind, 28 | /// The index of the exported item. 29 | index: u32, 30 | /// An optionally specified type ascribed to this export. 31 | ty?: ComponentTypeRef, 32 | } 33 | -------------------------------------------------------------------------------- /src/model/imports.ts: -------------------------------------------------------------------------------- 1 | import { u32 } from './core'; 2 | import { ComponentValType } from './types'; 3 | import { IndexedElement, ModelTag } from './tags'; 4 | 5 | /// Represents the type bounds for imports and exports. 6 | export type TypeBounds = 7 | | TypeBoundsEq 8 | | TypeBoundsSubResource 9 | 10 | /// The type is bounded by equality. 11 | export type TypeBoundsEq = { 12 | tag: ModelTag.TypeBoundsEq 13 | value: u32 14 | } 15 | 16 | /// A fresh resource type, 17 | export type TypeBoundsSubResource = { 18 | tag: ModelTag.TypeBoundsSubResource 19 | } 20 | 21 | /// Represents a reference to a component type. 22 | export type ComponentTypeRef = 23 | | ComponentTypeRefModule 24 | | ComponentTypeRefFunc 25 | | ComponentTypeRefValue 26 | | ComponentTypeRefType 27 | | ComponentTypeRefInstance 28 | | ComponentTypeRefComponent 29 | 30 | /// The reference is to a core module type. 31 | export type ComponentTypeRefModule = { 32 | tag: ModelTag.ComponentTypeRefModule, 33 | /// The index is expected to be core type index to a core module type. 34 | value: u32 35 | } 36 | /// The reference is to a function type. 37 | /// 38 | export type ComponentTypeRefFunc = { 39 | tag: ModelTag.ComponentTypeRefFunc 40 | /// The index is expected to be a type index to a function type. 41 | value: u32 42 | } 43 | /// The reference is to a value type. 44 | export type ComponentTypeRefValue = { 45 | tag: ModelTag.ComponentTypeRefValue, 46 | value: ComponentValType 47 | } 48 | /// The reference is to a bounded type. 49 | /// 50 | /// The index is expected to be a type index. 51 | export type ComponentTypeRefType = { 52 | tag: ModelTag.ComponentTypeRefType 53 | value: TypeBounds 54 | } 55 | /// The reference is to an instance type. 56 | /// 57 | /// The index is a type index to an instance type. 58 | export type ComponentTypeRefInstance = { 59 | tag: ModelTag.ComponentTypeRefInstance 60 | value: u32 61 | } 62 | /// The reference is to a component type. 63 | /// 64 | /// The index is a type index to a component type. 65 | export type ComponentTypeRefComponent = { 66 | tag: ModelTag.ComponentTypeRefComponent 67 | value: u32 68 | } 69 | 70 | /// Represents an import in a WebAssembly component 71 | export type ComponentImport = IndexedElement & { 72 | tag: ModelTag.ComponentImport, 73 | /// The name of the imported item. 74 | name: ComponentExternName, 75 | /// The type reference for the import. 76 | ty: ComponentTypeRef, 77 | } 78 | 79 | /// Represents an export in a WebAssembly component. 80 | export type ComponentExternName = 81 | | ComponentExternNameKebab 82 | | ComponentExternNameInterface 83 | 84 | 85 | export type ComponentExternNameKebab = { 86 | tag: ModelTag.ComponentExternNameKebab 87 | name: string 88 | } 89 | 90 | export type ComponentExternNameInterface = { 91 | tag: ModelTag.ComponentExternNameInterface 92 | name: string 93 | } 94 | -------------------------------------------------------------------------------- /src/model/instances.ts: -------------------------------------------------------------------------------- 1 | import { Export, u32 } from './core'; 2 | import { ComponentExport, ComponentExternalKind } from './exports'; 3 | import { IndexedElement, ModelTag } from './tags'; 4 | import { ComponentTypeInstance } from './types'; 5 | 6 | /// Represents the kind of an instantiation argument for a core instance. 7 | export const enum InstantiationArgKind { 8 | /// The instantiation argument is a core instance. 9 | Instance = 'instance', 10 | } 11 | 12 | /// Represents an argument to instantiating a WebAssembly module. 13 | export type InstantiationArg = { 14 | /// The name of the module argument. 15 | name: string, 16 | /// The kind of the module argument. 17 | kind: InstantiationArgKind, 18 | /// The index of the argument item. 19 | index: u32, 20 | } 21 | 22 | /// Represents an instance of a WebAssembly module. 23 | export type CoreInstance = 24 | | CoreInstanceInstantiate 25 | | CoreInstanceFromExports 26 | 27 | 28 | /// The instance is from instantiating a WebAssembly module. 29 | export type CoreInstanceInstantiate = IndexedElement & { 30 | tag: ModelTag.CoreInstanceInstantiate, 31 | /// The module index. 32 | module_index: u32, 33 | /// The module's instantiation arguments. 34 | args: InstantiationArg[], 35 | } 36 | 37 | /// The instance is a from exporting local items. 38 | export type CoreInstanceFromExports = IndexedElement & { 39 | tag: ModelTag.CoreInstanceFromExports, 40 | exports: Export[], 41 | } 42 | 43 | /// Represents an argument to instantiating a WebAssembly component. 44 | export type ComponentInstantiationArg = { 45 | /// The name of the component argument. 46 | name: string, 47 | /// The kind of the component argument. 48 | kind: ComponentExternalKind, 49 | /// The index of the argument item. 50 | index: u32, 51 | } 52 | 53 | /// Represents an instance in a WebAssembly component. 54 | export type ComponentInstance = 55 | | ComponentInstanceInstantiate 56 | | ComponentInstanceFromExports 57 | | ComponentTypeInstance 58 | 59 | /// The instance is from instantiating a WebAssembly component. 60 | export type ComponentInstanceInstantiate = IndexedElement & { 61 | tag: ModelTag.ComponentInstanceInstantiate, 62 | /// The component index. 63 | component_index: u32, 64 | /// The component's instantiation arguments. 65 | args: ComponentInstantiationArg[], 66 | } 67 | 68 | /// The instance is a from exporting local items. 69 | export type ComponentInstanceFromExports = IndexedElement & { 70 | tag: ModelTag.ComponentInstanceFromExports, 71 | exports: ComponentExport[] 72 | } 73 | -------------------------------------------------------------------------------- /src/model/names.ts: -------------------------------------------------------------------------------- 1 | import { NameMap, u8 } from './core'; 2 | import { ModelTag } from './tags'; 3 | 4 | /// Represents a name read from the names custom section. 5 | export type ComponentName = 6 | | ComponentNameComponent 7 | | ComponentNameCoreFuncs 8 | | ComponentNameCoreGlobals 9 | | ComponentNameCoreMemories 10 | | ComponentNameCoreTables 11 | | ComponentNameCoreModules 12 | | ComponentNameCoreInstances 13 | | ComponentNameCoreTypes 14 | | ComponentNameTypes 15 | | ComponentNameInstances 16 | | ComponentNameComponents 17 | | ComponentNameFuncs 18 | | ComponentNameValues 19 | | ComponentNameUnknown 20 | 21 | 22 | export type ComponentNameComponent = { 23 | name: string, 24 | name_range: any // TODO type 25 | } 26 | export type ComponentNameCoreFuncs = { 27 | tag: ModelTag.ComponentNameCoreFuncs, 28 | names: NameMap, 29 | } 30 | export type ComponentNameCoreGlobals = { 31 | tag: ModelTag.ComponentNameCoreGlobals, 32 | names: NameMap, 33 | } 34 | export type ComponentNameCoreMemories = { 35 | tag: ModelTag.ComponentNameCoreMemories, 36 | names: NameMap, 37 | } 38 | export type ComponentNameCoreTables = { 39 | tag: ModelTag.ComponentNameCoreTables, 40 | names: NameMap, 41 | } 42 | export type ComponentNameCoreModules = { 43 | tag: ModelTag.ComponentNameCoreModules, 44 | names: NameMap, 45 | } 46 | export type ComponentNameCoreInstances = { 47 | tag: ModelTag.ComponentNameCoreInstances, 48 | names: NameMap, 49 | } 50 | export type ComponentNameCoreTypes = { 51 | tag: ModelTag.ComponentNameCoreTypes, 52 | names: NameMap, 53 | } 54 | export type ComponentNameTypes = { 55 | tag: ModelTag.ComponentNameTypes, 56 | names: NameMap, 57 | } 58 | export type ComponentNameInstances = { 59 | tag: ModelTag.ComponentNameInstances, 60 | names: NameMap, 61 | } 62 | export type ComponentNameComponents = { 63 | tag: ModelTag.ComponentNameComponents, 64 | names: NameMap, 65 | } 66 | export type ComponentNameFuncs = { 67 | tag: ModelTag.ComponentNameFuncs, 68 | names: NameMap, 69 | } 70 | export type ComponentNameValues = { 71 | tag: ModelTag.ComponentNameValues, 72 | names: NameMap, 73 | } 74 | 75 | /// An unknown [name subsection](https://webassembly.github.io/spec/core/appendix/custom.html#subsections). 76 | export type ComponentNameUnknown = { 77 | /// The identifier for this subsection. 78 | ty: u8, 79 | /// The contents of this subsection. 80 | data: u8[], 81 | /// The range of bytes, relative to the start of the original data 82 | /// stream, that the contents of this subsection reside in. 83 | range: any // TODO type 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/model/start.ts: -------------------------------------------------------------------------------- 1 | import { u32 } from './core'; 2 | import { IndexedElement, ModelTag } from './tags'; 3 | 4 | export type ComponentStartFunction = IndexedElement & { 5 | tag: ModelTag.ComponentStartFunction 6 | /// The index to the start function. 7 | func_index: u32, 8 | /// The start function arguments. 9 | /// 10 | /// The arguments are specified by value index. 11 | arguments: u32[], 12 | /// The number of expected results for the start function. 13 | results: u32, 14 | } 15 | -------------------------------------------------------------------------------- /src/model/tags.ts: -------------------------------------------------------------------------------- 1 | import { CustomSection, SkippedSection, ComponentSection, CoreModule } from '../parser/types'; 2 | import { ComponentAlias } from './aliases'; 3 | import { CanonicalFunction } from './canonicals'; 4 | import { ComponentExport } from './exports'; 5 | import { ComponentImport } from './imports'; 6 | import { ComponentInstance, CoreInstance } from './instances'; 7 | import { ComponentType } from './types'; 8 | 9 | export const enum ModelTag { 10 | Model = 'Model', 11 | ModelElement = 'ModelElement', 12 | 13 | /// sections 14 | CustomSection = 'CustomSection', 15 | CoreModule = 'CoreModule', 16 | SkippedSection = 'SkippedSection', 17 | ComponentSection = 'ComponentSection', 18 | ComponentStartFunction = 'ComponentStartFunction', 19 | ComponentImport = 'ComponentImport', 20 | ComponentExport = 'ComponentExport', 21 | ComponentAliasCoreInstanceExport = 'ComponentAliasCoreInstanceExport', 22 | ComponentAliasInstanceExport = 'ComponentAliasInstanceExport', 23 | ComponentAliasOuter = 'ComponentAliasOuter', 24 | ComponentInstanceFromExports = 'ComponentInstanceFromExports', 25 | ComponentInstanceInstantiate = 'ComponentInstanceInstantiate', 26 | CoreInstanceFromExports = 'CoreInstanceFromExports', 27 | CoreInstanceInstantiate = 'CoreInstanceInstantiate', 28 | 29 | CanonicalFunctionLift = 'CanonicalFunctionLift', 30 | CanonicalFunctionLower = 'CanonicalFunctionLower', 31 | CanonicalFunctionResourceDrop = 'CanonicalFunctionResourceDrop', 32 | CanonicalFunctionResourceNew = 'CanonicalFunctionResourceNew', 33 | CanonicalFunctionResourceRep = 'CanonicalFunctionResourceRep', 34 | CanonicalOptionCompactUTF16 = 'CanonicalOptionCompactUTF16', 35 | CanonicalOptionMemory = 'CanonicalOptionMemory', 36 | CanonicalOptionPostReturn = 'CanonicalOptionPostReturn', 37 | CanonicalOptionRealloc = 'CanonicalOptionRealloc', 38 | CanonicalOptionUTF16 = 'CanonicalOptionUTF16', 39 | CanonicalOptionUTF8 = 'CanonicalOptionUTF8', 40 | ComponentTypeDefinedBorrow = 'ComponentTypeDefinedBorrow', 41 | ComponentTypeDefinedEnum = 'ComponentTypeDefinedEnum', 42 | ComponentTypeDefinedFlags = 'ComponentTypeDefinedFlags', 43 | ComponentTypeDefinedList = 'ComponentTypeDefinedList', 44 | ComponentTypeDefinedOption = 'ComponentTypeDefinedOption', 45 | ComponentTypeDefinedOwn = 'ComponentTypeDefinedOwn', 46 | ComponentTypeDefinedPrimitive = 'ComponentTypeDefinedPrimitive', 47 | ComponentTypeDefinedRecord = 'ComponentTypeDefinedRecord', 48 | ComponentTypeDefinedResult = 'ComponentTypeDefinedResult', 49 | ComponentTypeDefinedTuple = 'ComponentTypeDefinedTuple', 50 | ComponentTypeDefinedVariant = 'ComponentTypeDefinedVariant', 51 | ComponentExternNameInterface = 'ComponentExternNameInterface', 52 | ComponentExternNameKebab = 'ComponentExternNameKebab', 53 | ComponentFuncResultNamed = 'ComponentFuncResultNamed', 54 | ComponentFuncResultUnnamed = 'ComponentFuncResultUnnamed', 55 | ComponentNameComponents = 'ComponentNameComponents', 56 | ComponentNameCoreFuncs = 'ComponentNameCoreFuncs', 57 | ComponentNameCoreGlobals = 'ComponentNameCoreGlobals', 58 | ComponentNameCoreInstances = 'ComponentNameCoreInstances', 59 | ComponentNameCoreMemories = 'ComponentNameCoreMemories', 60 | ComponentNameCoreModules = 'ComponentNameCoreModules', 61 | ComponentNameCoreTables = 'ComponentNameCoreTables', 62 | ComponentNameCoreTypes = 'ComponentNameCoreTypes', 63 | ComponentNameFuncs = 'ComponentNameFuncs', 64 | ComponentNameInstances = 'ComponentNameInstances', 65 | ComponentNameTypes = 'ComponentNameTypes', 66 | ComponentNameValues = 'ComponentNameValues', 67 | ComponentTypeComponent = 'ComponentTypeComponent', 68 | ComponentTypeDeclarationAlias = 'ComponentTypeDeclarationAlias', 69 | ComponentTypeDeclarationExport = 'ComponentTypeDeclarationExport', 70 | ComponentTypeDeclarationCoreType = 'ComponentTypeDeclarationCoreType', 71 | ComponentTypeDeclarationImport = 'ComponentTypeDeclarationImport', 72 | ComponentTypeDeclarationType = 'ComponentTypeDeclarationType', 73 | ComponentTypeFunc = 'ComponentTypeFunc', 74 | ComponentTypeInstance = 'ComponentTypeInstance', 75 | ComponentTypeResource = 'ComponentTypeResource', 76 | ComponentValTypePrimitive = 'ComponentValTypePrimitive', 77 | ComponentValTypeType = 'ComponentValTypeType', 78 | CoreTypeFunc = 'CoreTypeFunc', 79 | CoreTypeModule = 'CoreTypeModule', 80 | InstanceTypeDeclarationAlias = 'InstanceTypeDeclarationAlias', 81 | InstanceTypeDeclarationExport = 'InstanceTypeDeclarationExport', 82 | InstanceTypeDeclarationCoreType = 'InstanceTypeDeclarationCoreType', 83 | InstanceTypeDeclarationType = 'InstanceTypeDeclarationType', 84 | InstantiationArgKindInstance = 'InstantiationArgKindInstance', 85 | ModuleTypeDeclarationType = 'ModuleTypeDeclarationType', 86 | ModuleTypeDeclarationExport = 'ModuleTypeDeclarationExport', 87 | ModuleTypeDeclarationOuterAlias = 'ModuleTypeDeclarationOuterAlias', 88 | ModuleTypeDeclarationImport = 'ModuleTypeDeclarationImport', 89 | OuterAliasKindType = 'OuterAliasKindType', 90 | StorageTypeI16 = 'StorageTypeI16', 91 | StorageTypeI8 = 'StorageTypeI8', 92 | StorageTypeVal = 'StorageTypeVal', 93 | StructuralTypeArray = 'StructuralTypeArray', 94 | StructuralTypeFunc = 'StructuralTypeFunc', 95 | StructuralTypeStruct = 'StructuralTypeStruct', 96 | ComponentTypeRefModule = 'ComponentTypeRefModule', 97 | ComponentTypeRefFunc = 'ComponentTypeRefFunc', 98 | ComponentTypeRefValue = 'ComponentTypeRefValue', 99 | ComponentTypeRefType = 'ComponentTypeRefType', 100 | ComponentTypeRefInstance = 'ComponentTypeRefInstance', 101 | ComponentTypeRefComponent = 'ComponentTypeRefComponent', 102 | TypeBoundsEq = 'TypeBoundsEq', 103 | TypeBoundsSubResource = 'TypeBoundsSubResource', 104 | TypeRefFunc = 'TypeRefFunc', 105 | TypeRefGlobal = 'TypeRefGlobal', 106 | TypeRefMemory = 'TypeRefMemory', 107 | TypeRefTable = 'TypeRefTable', 108 | TypeRefTag = 'TypeRefTag', 109 | ValTypeF32 = 'ValTypeF32', 110 | ValTypeF64 = 'ValTypeF64', 111 | ValTypeI32 = 'ValTypeI32', 112 | ValTypeI64 = 'ValTypeI64', 113 | ValTypeRef = 'ValTypeRef', 114 | ValTypeV128 = 'ValTypeV128', 115 | } 116 | 117 | export type ModelElement = any; 118 | 119 | export type TaggedElement = { 120 | tag: ModelTag 121 | } 122 | 123 | export type BrandedElement = { 124 | __brand: string // this is purely TS type system trickery, it has no runtime effect 125 | } 126 | 127 | export type IndexedElement = { 128 | selfSortIndex?: number 129 | } 130 | 131 | export type WITSection = 132 | | CustomSection 133 | | SkippedSection 134 | | ComponentSection 135 | | ComponentImport 136 | | ComponentExport 137 | | ComponentAlias 138 | | CanonicalFunction 139 | | ComponentType 140 | | ComponentInstance 141 | | CoreModule 142 | | CoreInstance 143 | 144 | -------------------------------------------------------------------------------- /src/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ComponentSection } from '../parser/types'; 2 | import { ComponentAlias, ComponentAliasInstanceExport } from './aliases'; 3 | import { FuncType, Import, SubType, TypeRef, ValType, u32 } from './core'; 4 | import { ComponentExternName, ComponentImport, ComponentTypeRef } from './imports'; 5 | import { IndexedElement, ModelTag } from './tags'; 6 | 7 | /// Represents the kind of an outer core alias in a WebAssembly component. 8 | export type OuterAliasKind = 9 | | OuterAliasKindType 10 | 11 | export type OuterAliasKindType = { 12 | tag: ModelTag.OuterAliasKindType 13 | } 14 | 15 | /// Represents a core type in a WebAssembly component. 16 | export type CoreType = 17 | | CoreTypeFunc 18 | | CoreTypeModule 19 | 20 | 21 | /// The type is for a core function. 22 | export type CoreTypeFunc = FuncType & { 23 | tag: ModelTag.CoreTypeFunc 24 | } 25 | 26 | /// The type is for a core module. 27 | export type CoreTypeModule = { 28 | tag: ModelTag.CoreTypeModule 29 | declarations: ModuleTypeDeclaration[] 30 | } 31 | 32 | /// Represents a module type declaration in a WebAssembly component. 33 | export type ModuleTypeDeclaration = 34 | | ModuleTypeDeclarationType 35 | | ModuleTypeDeclarationExport 36 | | ModuleTypeDeclarationOuterAlias 37 | | ModuleTypeDeclarationImport 38 | 39 | /// The module type definition is for a type. 40 | export type ModuleTypeDeclarationType = SubType & { 41 | tag: ModelTag.ModuleTypeDeclarationType 42 | } 43 | 44 | /// The module type definition is for an export. 45 | export type ModuleTypeDeclarationExport = { 46 | /// The name of the exported item. 47 | name: string, 48 | /// The type reference of the export. 49 | ty: TypeRef, 50 | } 51 | 52 | /// The module type declaration is for an outer alias. 53 | export type ModuleTypeDeclarationOuterAlias = { 54 | /// The alias kind. 55 | kind: OuterAliasKind, 56 | /// The outward count, starting at zero for the current type. 57 | count: u32, 58 | /// The index of the item within the outer type. 59 | index: u32, 60 | } 61 | 62 | /// The module type definition is for an import. 63 | export type ModuleTypeDeclarationImport = Import & { 64 | tag: ModelTag.ModuleTypeDeclarationImport 65 | } 66 | 67 | /// Represents a value type in a WebAssembly component. 68 | export type ComponentValType = 69 | | ComponentValTypePrimitive 70 | | ComponentValTypeType 71 | 72 | /// The value type is a primitive type. 73 | export type ComponentValTypePrimitive = { 74 | tag: ModelTag.ComponentValTypePrimitive 75 | value: PrimitiveValType 76 | } 77 | 78 | /// The value type is a reference to a defined type. 79 | export type ComponentValTypeType = { 80 | tag: ModelTag.ComponentValTypeType 81 | value: u32 82 | } 83 | 84 | /// Represents a primitive value type. 85 | export const enum PrimitiveValType { 86 | /// The type is a boolean. 87 | Bool = 'bool', 88 | /// The type is a signed 8-bit integer. 89 | S8 = 's8', 90 | /// The type is an unsigned 8-bit integer. 91 | U8 = 'u8', 92 | /// The type is a signed 16-bit integer. 93 | S16 = 's16', 94 | /// The type is an unsigned 16-bit integer. 95 | U16 = 'u16', 96 | /// The type is a signed 32-bit integer. 97 | S32 = 's32', 98 | /// The type is an unsigned 32-bit integer. 99 | U32 = 'u32', 100 | /// The type is a signed 64-bit integer. 101 | S64 = 's64', 102 | /// The type is an unsigned 64-bit integer. 103 | U64 = 'u64', 104 | /// The type is a 32-bit floating point number. 105 | Float32 = 'f32', 106 | /// The type is a 64-bit floating point number. 107 | Float64 = 'f64', 108 | /// The type is a Unicode character. 109 | Char = 'char', 110 | /// The type is a string. 111 | String = 'string', 112 | } 113 | 114 | /// Represents a type in a WebAssembly component. 115 | export type ComponentType = 116 | | ComponentTypeDefined 117 | | ComponentTypeFunc 118 | | ComponentTypeComponent 119 | | ComponentTypeInstance 120 | | ComponentTypeResource 121 | | ComponentSection 122 | | ComponentAliasInstanceExport 123 | 124 | /// The type is a function type. 125 | export type ComponentTypeFunc = IndexedElement & ComponentFuncType & { 126 | tag: ModelTag.ComponentTypeFunc 127 | } 128 | 129 | /// The type is a component type. 130 | export type ComponentTypeComponent = IndexedElement & { 131 | tag: ModelTag.ComponentTypeComponent 132 | declarations: ComponentTypeDeclaration[] 133 | } 134 | 135 | /// The type is an instance type. 136 | export type ComponentTypeInstance = IndexedElement & { 137 | tag: ModelTag.ComponentTypeInstance 138 | declarations: InstanceTypeDeclaration[] 139 | } 140 | 141 | /// The type is a fresh new resource type. 142 | export type ComponentTypeResource = IndexedElement & { 143 | tag: ModelTag.ComponentTypeResource 144 | /// The representation of this resource type in core WebAssembly. 145 | rep: ValType, 146 | /// An optionally-specified destructor to use for when this resource is 147 | /// no longer needed. 148 | dtor?: u32, 149 | } 150 | 151 | 152 | /// Represents part of a component type declaration in a WebAssembly component. 153 | export type ComponentTypeDeclaration = 154 | | ComponentTypeDeclarationCoreType 155 | | ComponentTypeDeclarationType 156 | | ComponentTypeDeclarationAlias 157 | | ComponentTypeDeclarationExport 158 | | ComponentTypeDeclarationImport 159 | 160 | 161 | /// The component type declaration is for a core type. 162 | export type ComponentTypeDeclarationCoreType = { 163 | tag: ModelTag.ComponentTypeDeclarationCoreType 164 | value: CoreType, 165 | } 166 | 167 | /// The component type declaration is for a type. 168 | export type ComponentTypeDeclarationType = { 169 | tag: ModelTag.ComponentTypeDeclarationType 170 | value: ComponentType, 171 | } 172 | 173 | /// The component type declaration is for an alias. 174 | export type ComponentTypeDeclarationAlias = { 175 | tag: ModelTag.ComponentTypeDeclarationAlias 176 | value: ComponentAlias, 177 | } 178 | 179 | /// The component type declaration is for an export. 180 | export type ComponentTypeDeclarationExport = { 181 | tag: ModelTag.ComponentTypeDeclarationExport 182 | /// The name of the export. 183 | name: ComponentExternName, 184 | /// The type reference for the export. 185 | ty: ComponentTypeRef, 186 | } 187 | 188 | /// The component type declaration is for an import. 189 | export type ComponentTypeDeclarationImport = ComponentImport & { 190 | } 191 | 192 | 193 | /// Represents an instance type declaration in a WebAssembly component. 194 | export type InstanceTypeDeclaration = 195 | | InstanceTypeDeclarationCoreType 196 | | InstanceTypeDeclarationType 197 | | InstanceTypeDeclarationAlias 198 | | InstanceTypeDeclarationExport 199 | 200 | /// The component type declaration is for a core type. 201 | export type InstanceTypeDeclarationCoreType = { 202 | tag: ModelTag.InstanceTypeDeclarationCoreType, 203 | value: CoreType, 204 | } 205 | 206 | /// The instance type declaration is for a type. 207 | export type InstanceTypeDeclarationType = { 208 | tag: ModelTag.InstanceTypeDeclarationType, 209 | value: ComponentType, 210 | } 211 | 212 | /// The instance type declaration is for an alias. 213 | export type InstanceTypeDeclarationAlias = { 214 | tag: ModelTag.InstanceTypeDeclarationAlias, 215 | value: ComponentAlias, 216 | } 217 | 218 | /// The instance type declaration is for an export. 219 | export type InstanceTypeDeclarationExport = { 220 | tag: ModelTag.InstanceTypeDeclarationExport 221 | /// The name of the export. 222 | name: ComponentExternName, 223 | /// The type reference for the export. 224 | ty: ComponentTypeRef, 225 | } 226 | 227 | 228 | /// Represents the result type of a component function. 229 | export type ComponentFuncResult = 230 | | ComponentFuncResultUnnamed 231 | | ComponentFuncResultNamed 232 | 233 | 234 | /// The function returns a singular, unnamed type. 235 | export type ComponentFuncResultUnnamed = { 236 | tag: ModelTag.ComponentFuncResultUnnamed, 237 | type: ComponentValType, 238 | } 239 | 240 | export type NamedValue = { 241 | name: string, 242 | type: ComponentValType 243 | } 244 | 245 | /// The function returns zero or more named types. 246 | export type ComponentFuncResultNamed = { 247 | tag: ModelTag.ComponentFuncResultNamed, 248 | values: NamedValue[] 249 | } 250 | 251 | /// Represents a type of a function in a WebAssembly component. 252 | export type ComponentFuncType = { 253 | /// The function parameters. 254 | params: NamedValue[], 255 | /// The function result. 256 | results: ComponentFuncResult, 257 | } 258 | 259 | /// Represents a case in a variant type. 260 | export type VariantCase = { 261 | /// The name of the variant case. 262 | name: string, 263 | /// The value type of the variant case. 264 | ty?: ComponentValType, 265 | /// The index of the variant case that is refined by this one. 266 | refines?: u32, 267 | } 268 | 269 | /// Represents a defined type in a WebAssembly component. 270 | export type ComponentTypeDefined = 271 | | ComponentTypeDefinedPrimitive 272 | | ComponentTypeDefinedRecord 273 | | ComponentTypeDefinedVariant 274 | | ComponentTypeDefinedList 275 | | ComponentTypeDefinedTuple 276 | | ComponentTypeDefinedFlags 277 | | ComponentTypeDefinedEnum 278 | | ComponentTypeDefinedOption 279 | | ComponentTypeDefinedResult 280 | | ComponentTypeDefinedOwn 281 | | ComponentTypeDefinedBorrow 282 | 283 | /// The type is one of the primitive value types. 284 | export type ComponentTypeDefinedPrimitive = IndexedElement & { 285 | tag: ModelTag.ComponentTypeDefinedPrimitive, 286 | value: PrimitiveValType, 287 | } 288 | 289 | /// The type is a record with the given fields. 290 | export type ComponentTypeDefinedRecord = IndexedElement & { 291 | tag: ModelTag.ComponentTypeDefinedRecord, 292 | members: { name: string, type: ComponentValType }[], 293 | } 294 | 295 | /// The type is a variant with the given cases. 296 | export type ComponentTypeDefinedVariant = IndexedElement & { 297 | tag: ModelTag.ComponentTypeDefinedVariant, 298 | variants: VariantCase[], 299 | } 300 | 301 | /// The type is a list of the given value type. 302 | export type ComponentTypeDefinedList = IndexedElement & { 303 | tag: ModelTag.ComponentTypeDefinedList, 304 | value: ComponentValType, 305 | } 306 | 307 | /// The type is a tuple of the given value types. 308 | export type ComponentTypeDefinedTuple = IndexedElement & { 309 | tag: ModelTag.ComponentTypeDefinedTuple, 310 | members: ComponentValType[], 311 | } 312 | 313 | /// The type is flags with the given names. 314 | export type ComponentTypeDefinedFlags = IndexedElement & { 315 | tag: ModelTag.ComponentTypeDefinedFlags, 316 | members: string[], 317 | } 318 | /// The type is an enum with the given tags. 319 | export type ComponentTypeDefinedEnum = IndexedElement & { 320 | tag: ModelTag.ComponentTypeDefinedEnum, 321 | members: string[], 322 | } 323 | 324 | /// The type is an option of the given value type. 325 | export type ComponentTypeDefinedOption = IndexedElement & { 326 | tag: ModelTag.ComponentTypeDefinedOption, 327 | value: ComponentValType, 328 | } 329 | 330 | /// The type is a result type. 331 | export type ComponentTypeDefinedResult = IndexedElement & { 332 | tag: ModelTag.ComponentTypeDefinedResult, 333 | /// The type returned for success. 334 | ok?: ComponentValType, 335 | /// The type returned for failure. 336 | err?: ComponentValType, 337 | } 338 | 339 | /// An owned handle to a resource. 340 | export type ComponentTypeDefinedOwn = IndexedElement & { 341 | tag: ModelTag.ComponentTypeDefinedOwn, 342 | value: u32, 343 | } 344 | 345 | /// A borrowed handle to a resource. 346 | export type ComponentTypeDefinedBorrow = IndexedElement & { 347 | tag: ModelTag.ComponentTypeDefinedBorrow, 348 | value: u32, 349 | } 350 | -------------------------------------------------------------------------------- /src/parser/README.md: -------------------------------------------------------------------------------- 1 | inspired by https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasmparser/src/readers under Apache-2.0 license -------------------------------------------------------------------------------- /src/parser/alias.test.ts: -------------------------------------------------------------------------------- 1 | import { aliasExportType3 } from '../../tests/hello'; 2 | import { expectModelToEqualWat } from './jest-utils'; 3 | 4 | describe('export', () => { 5 | test('parse alias', async () => { 6 | await expectModelToEqualWat('(alias export 0 "city-info" (type (;3;)))', [aliasExportType3]); 7 | }); 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /src/parser/alias.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ComponentAlias, ComponentAliasInstanceExport, ComponentAliasCoreInstanceExport, ComponentAliasOuter } from '../model/aliases'; 3 | import { ParserContext } from './types'; 4 | import { readU32, parseAsExternalKind, parseAsComponentExternalKind, parseAsComponentOuterAliasKind, readName } from './values'; 5 | import { ModelTag } from '../model/tags'; 6 | 7 | // see also https://github.com/bytecodealliance/wasm-tools/blob/e2af293273db65712b6f31da85f7aa5eb31abfde/crates/wasmparser/src/readers/component/exports.rs#L86 8 | // https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#alias-definitions 9 | export function parseSectionAlias( 10 | ctx: ParserContext, 11 | src: SyncSource, 12 | ): ComponentAlias[] { 13 | const count = readU32(src); 14 | const aliases: ComponentAlias[] = []; 15 | for (let i = 0; i < count; i++) { 16 | // We don't know what type of alias it is yet, so just read the sort bytes 17 | const b1 = readU32(src); 18 | const b2 = (b1 === 0) ? readU32(src) : undefined; 19 | const alias = parseAliasTarget(src, b1, b2); 20 | aliases.push(alias); 21 | } 22 | return aliases; 23 | } 24 | 25 | function parseAliasTarget(src: SyncSource, b1: number, b2?: number,) { 26 | const k1 = readU32(src); 27 | switch (k1) { 28 | case 0x00: 29 | return { 30 | tag: ModelTag.ComponentAliasInstanceExport, 31 | kind: parseAsComponentExternalKind(b1, b2), 32 | instance_index: readU32(src), 33 | name: readName(src) 34 | } as ComponentAliasInstanceExport; 35 | case 0x01: 36 | return { 37 | tag: ModelTag.ComponentAliasCoreInstanceExport, 38 | kind: parseAsExternalKind(b2!), 39 | instance_index: readU32(src), 40 | name: readName(src) 41 | } as ComponentAliasCoreInstanceExport; 42 | case 0x02: 43 | return { 44 | tag: ModelTag.ComponentAliasOuter, 45 | kind: parseAsComponentOuterAliasKind(b1, b2), 46 | count: readU32(src), 47 | index: readU32(src) 48 | } as ComponentAliasOuter; 49 | default: 50 | throw new Error(`unknown target type. ${k1}`); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/parser/canon.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ParserContext } from './types'; 3 | import { readU32, readCanonicalFunction } from './values'; 4 | import { CanonicalFunction } from '../model/canonicals'; 5 | 6 | export function parseSectionCanon( 7 | ctx: ParserContext, 8 | src: SyncSource, 9 | ): CanonicalFunction[] { 10 | const canonFunctions: CanonicalFunction[] = []; 11 | const count = readU32(src); 12 | for (let i = 0; i < count; i++) { 13 | const canonicalFun: CanonicalFunction = readCanonicalFunction(src); 14 | canonFunctions.push(canonicalFun); 15 | } 16 | return canonFunctions; 17 | } -------------------------------------------------------------------------------- /src/parser/coreInstance.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ParserContext } from './types'; 3 | import { readU32, readCoreInstance } from './values'; 4 | import { CoreInstance } from '../model/instances'; 5 | 6 | export function parseSectionCoreInstance( 7 | ctx: ParserContext, 8 | src: SyncSource, 9 | ): CoreInstance[] { 10 | const coreInstances: CoreInstance[] = []; 11 | const count = readU32(src); 12 | for (let i = 0; i < count; i++) { 13 | const coreInstance: CoreInstance = readCoreInstance(src); 14 | coreInstances.push(coreInstance); 15 | } 16 | return coreInstances; 17 | } -------------------------------------------------------------------------------- /src/parser/export.test.ts: -------------------------------------------------------------------------------- 1 | import { expectModelToEqualWat } from './jest-utils'; 2 | import { ComponentExternalKind } from '../model/exports'; 3 | import { ModelTag } from '../model/tags'; 4 | 5 | describe('export', () => { 6 | test('parse export', async () => { 7 | await expectModelToEqualWat('(export (;2;) (interface "hello:city/greeter") (instance 1))', [{ 8 | tag: ModelTag.ComponentExport, 9 | name: { tag: ModelTag.ComponentExternNameInterface, name: 'hello:city/greeter' }, 10 | kind: ComponentExternalKind.Instance, 11 | index: 1, 12 | ty: undefined 13 | }]); 14 | }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /src/parser/export.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ComponentExport } from '../model/exports'; 3 | import { ParserContext } from './types'; 4 | import { readU32, readComponentExternalKind, readComponentExternName, readComponentTypeRef } from './values'; 5 | import { ModelTag } from '../model/tags'; 6 | 7 | // see also https://github.com/bytecodealliance/wasm-tools/blob/e2af293273db65712b6f31da85f7aa5eb31abfde/crates/wasmparser/src/readers/component/exports.rs#L86 8 | // https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#import-and-export-definitions 9 | export function parseSectionExport( 10 | ctx: ParserContext, 11 | src: SyncSource, 12 | ): ComponentExport[] { 13 | const sections: ComponentExport[] = []; 14 | const count = readU32(src); 15 | for (let i = 0; i < count; i++) { 16 | const section: ComponentExport = { 17 | tag: ModelTag.ComponentExport, 18 | name: readComponentExternName(src), 19 | kind: readComponentExternalKind(src), 20 | index: readU32(src), 21 | ty: readU32(src) === 0 ? undefined : readComponentTypeRef(src) 22 | }; 23 | sections.push(section); 24 | } 25 | return sections; 26 | } 27 | -------------------------------------------------------------------------------- /src/parser/hello.test.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './index'; 2 | import { expectModelToEqual } from './jest-utils'; 3 | import { expectedModel } from '../../tests/hello'; 4 | import { CoreModule } from './types'; 5 | import { ModelTag } from '../model/tags'; 6 | //import { writeToFile } from '../../tests/utils'; 7 | 8 | describe('hello', () => { 9 | 10 | test('parse method compiles modules', async () => { 11 | // build it with `npm run build:hello` 12 | const actualModel = await parse('./hello/wasm/hello.wasm'); 13 | 14 | const moduleSections: CoreModule[] = actualModel.filter((section) => section.tag === ModelTag.CoreModule) as CoreModule[]; 15 | 16 | expect(moduleSections.length).toBe(3); 17 | expect(moduleSections[0].module).toBeInstanceOf(Promise); 18 | expect(moduleSections[1].module).toBeInstanceOf(Promise); 19 | expect(moduleSections[2].module).toBeInstanceOf(Promise); 20 | 21 | const modules = await Promise.all(moduleSections.map(async (m) => m.module)); 22 | expect(modules[0]).toBeInstanceOf(WebAssembly.Module); 23 | expect(modules[1]).toBeInstanceOf(WebAssembly.Module); 24 | expect(modules[1]).toBeInstanceOf(WebAssembly.Module); 25 | }); 26 | 27 | test('parsed model matches hand written model', async () => { 28 | const actualModel = await parse('./hello/wasm/hello.wasm'); 29 | //writeToFile('actual-hello.json', JSON.stringify(actualModel, null, 2)); 30 | //writeToFile('expected-hello.json', JSON.stringify(expectedModel, null, 2)); 31 | expectModelToEqual(actualModel, expectedModel); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/parser/import.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ParserContext } from './types'; 3 | import { readU32, readComponentExternName, readComponentTypeRef } from './values'; 4 | import { ModelTag } from '../model/tags'; 5 | import { ComponentImport } from '../model/imports'; 6 | 7 | export function parseSectionImport( 8 | ctx: ParserContext, 9 | src: SyncSource, 10 | ): ComponentImport[] { 11 | const sections: ComponentImport[] = []; 12 | const count = readU32(src); 13 | for (let i = 0; i < count; i++) { 14 | const section: ComponentImport = { 15 | tag: ModelTag.ComponentImport, 16 | name: readComponentExternName(src), 17 | ty: readComponentTypeRef(src) 18 | }; 19 | sections.push(section); 20 | } 21 | return sections; 22 | } 23 | -------------------------------------------------------------------------------- /src/parser/index.test.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './index'; 2 | 3 | describe('parser test', () => { 4 | test('to fail on invalid header', async () => { 5 | const wasm = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 6 | expect(async () => await parse(wasm)).rejects.toThrowError('unexpected magic, version or layer.'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | import type { WITModel, ParserContext, ParserOptions, ComponentSection } from './types'; 2 | import { fetchLike, getBodyIfResponse } from '../utils/fetch-like'; 3 | import { SyncSource, bufferToHex, Closeable, Source, newSource } from '../utils/streaming'; 4 | import { parseSectionCustom, skipSection } from './otherSection'; 5 | import { parseSectionExport } from './export'; 6 | import { parseModule } from './module'; 7 | import { readU32Async } from './values'; 8 | import { parseSectionAlias } from './alias'; 9 | import { parseSectionImport } from './import'; 10 | import { parseSectionType } from './type'; 11 | import { parseSectionCanon } from './canon'; 12 | import { parseSectionCoreInstance } from './coreInstance'; 13 | import { parseSectionInstance } from './instance'; 14 | import { ModelTag, WITSection } from '../model/tags'; 15 | 16 | export type { WITModel }; 17 | 18 | export const WIT_MAGIC = [0x00, 0x61, 0x73, 0x6d]; 19 | export const WIT_VERSION = [0x0D, 0x00]; 20 | export const WIT_LAYER = [0x01, 0x00]; 21 | 22 | export async function parse( 23 | componentOrUrl: 24 | | string 25 | | ArrayLike 26 | | ReadableStream 27 | | Response 28 | | PromiseLike, 29 | options?: ParserOptions 30 | ): Promise { 31 | let input = componentOrUrl as any; 32 | if (typeof componentOrUrl === 'string') { 33 | input = fetchLike(componentOrUrl); 34 | } 35 | input = await getBodyIfResponse(input); 36 | const src = newSource(input); 37 | const sections = await parseWIT(src, options); 38 | return sections; 39 | } 40 | 41 | async function parseWIT(src: Source & Closeable, options?: ParserOptions): Promise { 42 | try { 43 | await checkPreamble(src); 44 | 45 | const ctx: ParserContext = { 46 | otherSectionData: options?.otherSectionData ?? false, 47 | compileStreaming: options?.compileStreaming ?? WebAssembly.compileStreaming, 48 | processCustomSection: options?.processCustomSection ?? undefined, 49 | }; 50 | 51 | const model: WITSection[] = []; 52 | for (; ;) { 53 | const sections = await parseSection(ctx, src); 54 | if (sections === null) { 55 | break; 56 | } 57 | for (const s of sections) { 58 | model.push(s); 59 | } 60 | } 61 | 62 | return model; 63 | } 64 | finally { 65 | src.close(); 66 | } 67 | } 68 | 69 | async function checkPreamble(src: Source): Promise { 70 | const magic = await src.readExact(WIT_MAGIC.length); 71 | const version = await src.readExact(WIT_VERSION.length); 72 | const layer = await src.readExact(WIT_LAYER.length); 73 | 74 | const ok = magic.every((v, i) => v === WIT_MAGIC[i]) 75 | && version.every((v, i) => v === WIT_VERSION[i]) 76 | && layer.every((v, i) => v === WIT_LAYER[i]); 77 | if (!ok) { 78 | throw new Error('unexpected magic, version or layer.'); 79 | } 80 | } 81 | 82 | async function parseSection(ctx: ParserContext, src: Source): Promise { 83 | const type = await src.read(true); // byte will be enough for type 84 | if (type === null) { 85 | return null; 86 | } 87 | const size = await readU32Async(src); 88 | const start = src.pos; 89 | const asyncSub: Source | undefined = type == 1 || type == 4 ? src.subSource(size) : undefined; // if this is module, we need to stream it 90 | const sub: SyncSource | undefined = type != 1 && type != 4 ? await src.subSyncSource(size) : undefined; // otherwise it's not worth all the async overhead 91 | const sections = await (() => { 92 | switch (type) { 93 | /// 94 | /// https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#component-definitions 95 | /// 96 | case 0: return parseSectionCustom(ctx, sub!, size); 97 | case 1: return parseModule(ctx, asyncSub!, size); 98 | case 2: return parseSectionCoreInstance(ctx, sub!); 99 | case 4: return parseSectionComponent(ctx, asyncSub!, size); 100 | case 5: return parseSectionInstance(ctx, sub!); 101 | case 6: return parseSectionAlias(ctx, sub!); 102 | case 7: return parseSectionType(ctx, sub!); 103 | case 8: return parseSectionCanon(ctx, sub!); 104 | case 10: return parseSectionImport(ctx, sub!); 105 | case 11: return parseSectionExport(ctx, sub!); 106 | 107 | //TODO: to implement 108 | case 3: // core type - we don't have it in the sample 109 | case 9: // start 110 | return skipSection(ctx, sub!, type, size); // this is all TODO 111 | default: 112 | throw new Error(`unknown section: ${type}`); 113 | } 114 | })(); 115 | if (sub && sub.remaining !== 0) { 116 | const absoluteActual = start + sub.pos; 117 | const absoluteExpected = start + size; 118 | const remaining = sub.remaining; 119 | const data = sub.readExact(remaining); 120 | const hex = bufferToHex(data); 121 | throw new Error(`invalid size after reading section ${type}: \n` 122 | + `actual position: 0x${absoluteActual.toString(16)} vs. expected position 0x${absoluteExpected.toString(16)}, remaining ${remaining}\n` 123 | + `section: ${JSON.stringify(sections)}\n` 124 | + 'remaining: ' + hex); 125 | } 126 | 127 | return sections; 128 | } 129 | 130 | async function parseSectionComponent( 131 | ctx: ParserContext, 132 | src: Source, 133 | size: number 134 | ): Promise { 135 | const end = src.pos + size; 136 | await checkPreamble(src); 137 | let model: WITSection[] = []; 138 | for (; ;) { 139 | if (src.pos == end) { 140 | break; 141 | } 142 | const sections = await parseSection(ctx, src); 143 | if (sections === null) { 144 | break; 145 | } 146 | model = [...model, ...sections]; 147 | } 148 | return [{ 149 | tag: ModelTag.ComponentSection, 150 | sections: model, 151 | }]; 152 | } 153 | -------------------------------------------------------------------------------- /src/parser/instance.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ParserContext } from './types'; 3 | import { readU32, readComponentInstantiationArgs } from './values'; 4 | import { ComponentInstance } from '../model/instances'; 5 | import { ModelTag } from '../model/tags'; 6 | import { parseSectionExport } from './export'; 7 | 8 | export function parseSectionInstance( 9 | ctx: ParserContext, 10 | src: SyncSource, 11 | ): ComponentInstance[] { 12 | const sections: ComponentInstance[] = []; 13 | const count = readU32(src); 14 | for (let i = 0; i < count; i++) { 15 | const section: ComponentInstance = (() => { 16 | const type = readU32(src); 17 | switch (type) 18 | { 19 | case 0x00: { 20 | return { 21 | tag: ModelTag.ComponentInstanceInstantiate, 22 | component_index: readU32(src), 23 | args: readComponentInstantiationArgs(src), 24 | }; 25 | } 26 | case 0x01: { 27 | return { 28 | tag: ModelTag.ComponentInstanceFromExports, 29 | exports: parseSectionExport(ctx, src) 30 | }; 31 | } 32 | default: throw new Error(`Unrecognized type in parseSectionInstance: ${type}`); 33 | } 34 | })(); 35 | sections.push(section); 36 | } 37 | return sections; 38 | } -------------------------------------------------------------------------------- /src/parser/jest-utils.ts: -------------------------------------------------------------------------------- 1 | import * as jco from '@bytecodealliance/jco'; 2 | import { WITModel, parse } from '.'; 3 | import { ModelTag } from '../model/tags'; 4 | 5 | export function expectModelToEqual(actualModel: WITModel, expectedModel: WITModel) { 6 | const noModules = actualModel.map((section) => { 7 | if (section.tag === ModelTag.CoreModule) { 8 | delete section.module; 9 | } 10 | return section; 11 | }); 12 | expect(noModules).toEqual(expectedModel); 13 | } 14 | 15 | export function jcoCompileWat(wat: string): Promise { 16 | return jco.parse(wat); 17 | } 18 | 19 | export async function expectModelToEqualWat(watSections: string, expectedModel: WITModel) { 20 | const wasmBuffer = await jcoCompileWat(`(component ${watSections})`); 21 | expectModelToEqualWasm(wasmBuffer, expectedModel); 22 | } 23 | 24 | export async function expectModelToEqualWasm(wasmBuffer: Uint8Array, expectedModel: WITModel) { 25 | const model = await parse(wasmBuffer); 26 | expectModelToEqual(model, expectedModel); 27 | } -------------------------------------------------------------------------------- /src/parser/module.ts: -------------------------------------------------------------------------------- 1 | import { ModelTag } from '../model/tags'; 2 | import { Source } from '../utils/streaming'; 3 | import { ParserContext, CoreModule } from './types'; 4 | 5 | export async function parseModule( 6 | ctx: ParserContext, 7 | src: Source, 8 | size: number, 9 | ): Promise { 10 | const res: CoreModule = { 11 | tag: ModelTag.CoreModule, 12 | }; 13 | 14 | if (ctx.compileStreaming) { 15 | const whileReading = new Promise((resolve) => { 16 | const response = toWasmResponse(src, size, resolve); 17 | const module = ctx.compileStreaming(response); 18 | res.module = module; 19 | }); 20 | await whileReading; 21 | } 22 | else { 23 | const data = await src.readExact(size); 24 | res.data = data; 25 | } 26 | return [res]; 27 | } 28 | 29 | function toWasmResponse( 30 | src: Source, 31 | size: number, 32 | resolveWhenDoneReading: (_: any) => void 33 | ) { 34 | let remaining = size; 35 | const pull = async (controller: ReadableByteStreamController): Promise => { 36 | const data = await src.readAvailable(remaining); 37 | if (data === null) { 38 | resolveWhenDoneReading(undefined); 39 | controller.close(); 40 | } 41 | else { 42 | // copy, otherwise WebAssembly.compileStreaming will detach the underlying buffer. 43 | const copy = data.slice(); 44 | controller.enqueue(copy); 45 | remaining -= data.length; 46 | if (remaining === 0) { 47 | resolveWhenDoneReading(undefined); 48 | controller.close(); 49 | } 50 | } 51 | }; 52 | const rs = new ReadableStream({ 53 | type: 'bytes', pull, 54 | }); 55 | const headers = new Headers(); 56 | headers.append('Content-Type', 'application/wasm'); 57 | headers.append('Content-Length', '' + size); 58 | const response = new Response(rs, { 59 | headers, 60 | status: 200, 61 | statusText: 'OK', 62 | }); 63 | 64 | return response; 65 | } 66 | -------------------------------------------------------------------------------- /src/parser/otherSection.ts: -------------------------------------------------------------------------------- 1 | import { ModelTag } from '../model/tags'; 2 | import { SyncSource } from '../utils/streaming'; 3 | import { ParserContext, CustomSection, SkippedSection } from './types'; 4 | import { readName } from './values'; 5 | 6 | export function parseSectionCustom( 7 | ctx: ParserContext, 8 | src: SyncSource, 9 | size: number, 10 | ): CustomSection[] { 11 | const start = src.pos; 12 | const name = readName(src); 13 | const nameSize = src.pos - start; 14 | const data = src.readExact(size - nameSize); 15 | let section: CustomSection = { 16 | tag: ModelTag.CustomSection, 17 | name, 18 | data: ctx.otherSectionData ? data : undefined, 19 | }; 20 | if (ctx.processCustomSection) { 21 | section = ctx.processCustomSection(section); 22 | } 23 | return [section]; 24 | } 25 | 26 | export function skipSection( 27 | ctx: ParserContext, 28 | src: SyncSource, 29 | type: number, 30 | size: number, 31 | ): SkippedSection[] { 32 | const data = src.readExact(size); 33 | const section: SkippedSection = { 34 | tag: ModelTag.SkippedSection, 35 | type, 36 | data: ctx.otherSectionData ? data : undefined, 37 | }; 38 | return [section]; 39 | } -------------------------------------------------------------------------------- /src/parser/type.ts: -------------------------------------------------------------------------------- 1 | import { SyncSource } from '../utils/streaming'; 2 | import { ParserContext } from './types'; 3 | import { readU32, readComponentType } from './values'; 4 | import { ComponentType } from '../model/types'; 5 | 6 | export function parseSectionType( 7 | ctx: ParserContext, 8 | src: SyncSource, 9 | ): ComponentType[] { 10 | const sections: ComponentType[] = []; 11 | const count = readU32(src); 12 | for (let i = 0; i < count; i++) { 13 | const section: ComponentType = readComponentType(src); 14 | sections.push(section); 15 | } 16 | return sections; 17 | } -------------------------------------------------------------------------------- /src/parser/types.ts: -------------------------------------------------------------------------------- 1 | import { IndexedElement, ModelTag, WITSection } from '../model/tags'; 2 | 3 | export type CoreModule = IndexedElement & { 4 | tag: ModelTag.CoreModule 5 | data?: Uint8Array 6 | module?: Promise 7 | } 8 | 9 | export type CustomSection = { 10 | tag: ModelTag.CustomSection 11 | name: string 12 | data?: Uint8Array 13 | } 14 | 15 | export type SkippedSection = { 16 | tag: ModelTag.SkippedSection 17 | type: number 18 | data?: Uint8Array 19 | } 20 | 21 | export type ComponentSection = IndexedElement & { 22 | tag: ModelTag.ComponentSection 23 | sections: WITSection[] 24 | } 25 | 26 | export type WITModel = WITSection[]; 27 | 28 | export type ParserContext = { 29 | otherSectionData: boolean 30 | compileStreaming: typeof WebAssembly.compileStreaming 31 | processCustomSection?: (section: CustomSection) => CustomSection 32 | } 33 | 34 | export type ParserOptions = { 35 | otherSectionData?: boolean 36 | compileStreaming?: typeof WebAssembly.compileStreaming 37 | processCustomSection?: (section: CustomSection) => CustomSection 38 | } 39 | -------------------------------------------------------------------------------- /src/parser/zoo.test.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './index'; 2 | import { expectModelToEqual } from './jest-utils'; 3 | import { expectedModel } from '../../tests/zoo'; 4 | import { CoreModule } from './types'; 5 | import { writeToFile } from '../../tests/utils'; 6 | import { ModelTag } from '../model/tags'; 7 | 8 | describe('zoo', () => { 9 | 10 | test('parse method compiles zoo modules', async () => { 11 | // build it with `npm run build:zoo` 12 | const actualModel = await parse('./zoo/wasm/zoo.wasm'); 13 | 14 | const moduleSections: CoreModule[] = actualModel.filter((section) => section.tag === ModelTag.CoreModule) as CoreModule[]; 15 | 16 | expect(moduleSections.length).toBe(3); 17 | expect(moduleSections[0].module).toBeInstanceOf(Promise); 18 | expect(moduleSections[1].module).toBeInstanceOf(Promise); 19 | expect(moduleSections[2].module).toBeInstanceOf(Promise); 20 | 21 | const modules = await Promise.all(moduleSections.map(async (m) => m.module)); 22 | expect(modules[0]).toBeInstanceOf(WebAssembly.Module); 23 | expect(modules[1]).toBeInstanceOf(WebAssembly.Module); 24 | expect(modules[1]).toBeInstanceOf(WebAssembly.Module); 25 | }); 26 | 27 | test('parsed model matches hand written zoo model', async () => { 28 | const actualModel = await parse('./zoo/wasm/zoo.wasm'); 29 | // writeToFile('actual-zoo.json', JSON.stringify(actualModel, null, 2)); 30 | //writeToFile('expected-zoo.json', JSON.stringify(expectedModel, null, 2)); 31 | expectModelToEqual(actualModel, expectedModel); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/resolver/README.md: -------------------------------------------------------------------------------- 1 | # See also 2 | https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/decoding.rs 3 | https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/linking.rs 4 | https://github.com/bytecodealliance/jco/blob/main/crates/js-component-bindgen/src/function_bindgen.rs 5 | -------------------------------------------------------------------------------- /src/resolver/_temp.ts: -------------------------------------------------------------------------------- 1 | import { ModelTag, TaggedElement } from '../model/tags'; 2 | import { jsco_assert } from '../utils/assert'; 3 | import { Resolver, BinderRes } from './types'; 4 | 5 | type ModelElement = {} & TaggedElement 6 | type TempRes = {} 7 | 8 | export const resolveTemp: Resolver = (rctx, rargs) => { 9 | const tempElem = rargs.element; 10 | jsco_assert(tempElem && tempElem.tag == ModelTag.ModelElement, () => `Wrong element type '${tempElem?.tag}'`); 11 | return { 12 | callerElement: rargs.callerElement, 13 | element: tempElem, 14 | binder: async (bctx, bargs): Promise => { 15 | const binderResult = { 16 | missingRes: rargs.element.tag, 17 | result: { 18 | missingResRes: rargs.element.tag, 19 | } as TempRes 20 | }; 21 | return binderResult; 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/resolver/api-types.ts: -------------------------------------------------------------------------------- 1 | 2 | export type JsInterface = Record; 3 | export type JsInterfaceCollection = Record; 4 | 5 | export type WasmComponentInstance = { 6 | exports: JsExports 7 | abort: () => void 8 | } 9 | export type JsExports = TJSExports & JsInterfaceCollection 10 | export type JsImports = JsInterfaceCollection 11 | 12 | export type WasmComponent = { 13 | instantiate: WasmComponentFactory 14 | } 15 | export type WasmComponentFactory = (imports?: JsImports) => Promise> -------------------------------------------------------------------------------- /src/resolver/binding/cache.ts: -------------------------------------------------------------------------------- 1 | const memoizeCache = new Map(); 2 | 3 | export function memoize(key: K, factory: () => V): V { 4 | let res = memoizeCache.get(key); 5 | if (res !== undefined) { 6 | return res; 7 | } 8 | res = factory(); 9 | memoizeCache.set(key, res!); 10 | return res!; 11 | } 12 | -------------------------------------------------------------------------------- /src/resolver/binding/index.ts: -------------------------------------------------------------------------------- 1 | export { createLifting, createFunctionLifting } from './to-abi'; 2 | export { createLowering, createFunctionLowering } from './to-js'; 3 | 4 | -------------------------------------------------------------------------------- /src/resolver/binding/to-abi.ts: -------------------------------------------------------------------------------- 1 | import { ComponentAliasInstanceExport } from '../../model/aliases'; 2 | import { ModelTag } from '../../model/tags'; 3 | import { ComponentTypeDefined, ComponentTypeDefinedRecord, ComponentTypeFunc, ComponentTypeInstance, ComponentValType, InstanceTypeDeclaration, PrimitiveValType } from '../../model/types'; 4 | import { BindingContext, ResolverContext } from '../types'; 5 | import { jsco_assert } from '../../utils/assert'; 6 | import { memoize } from './cache'; 7 | import { createLowering } from './to-js'; 8 | import { LiftingFromJs, WasmPointer, FnLiftingCallFromJs, JsFunction, WasmSize, WasmValue, WasmFunction, JsValue } from './types'; 9 | 10 | 11 | export function createFunctionLifting(rctx: ResolverContext, importModel: ComponentTypeFunc): FnLiftingCallFromJs { 12 | return memoize(importModel, () => { 13 | const paramLifters: Function[] = []; 14 | for (const param of importModel.params) { 15 | const lifter = createLifting(rctx, param.type); 16 | paramLifters.push(lifter); 17 | } 18 | const resultLowerers: Function[] = []; 19 | switch (importModel.results.tag) { 20 | case ModelTag.ComponentFuncResultNamed: { 21 | for (const res of importModel.results.values) { 22 | const lowerer = createLowering(rctx, res.type); 23 | resultLowerers.push(lowerer); 24 | } 25 | break; 26 | } 27 | case ModelTag.ComponentFuncResultUnnamed: { 28 | const lowerer = createLowering(rctx, importModel.results.type); 29 | resultLowerers.push(lowerer); 30 | } 31 | } 32 | 33 | return (ctx: BindingContext, wasmFunction: WasmFunction): JsFunction => { 34 | function liftingTrampoline(...args: any[]): any { 35 | let covertedArgs: any[] = []; 36 | for (let i = 0; i < paramLifters.length; i++) { 37 | const lifter = paramLifters[i]; 38 | const value = args[i]; 39 | const converted = lifter(ctx, value); 40 | // TODO do not always spill into stack 41 | covertedArgs = [...covertedArgs, ...converted]; 42 | } 43 | const resJs = wasmFunction(...covertedArgs); 44 | if (resultLowerers.length === 1) { 45 | resultLowerers[0](resJs); 46 | } 47 | } 48 | return liftingTrampoline as JsFunction; 49 | }; 50 | }); 51 | } 52 | 53 | 54 | export function createLifting(rctx: ResolverContext, typeModel: ComponentValType | ComponentTypeInstance | InstanceTypeDeclaration | ComponentTypeDefined | ComponentAliasInstanceExport): LiftingFromJs { 55 | return memoize(typeModel, () => { 56 | switch (typeModel.tag) { 57 | case ModelTag.ComponentValTypePrimitive: 58 | switch (typeModel.value) { 59 | case PrimitiveValType.String: 60 | return createStringLifting(); 61 | case PrimitiveValType.U32: 62 | return createU32Lifting(); 63 | case PrimitiveValType.S64: 64 | return rctx.usesNumberForInt64 65 | ? createS64LiftingNumber() 66 | : createS64LiftingBigInt(); 67 | default: 68 | throw new Error('Not implemented'); 69 | } 70 | case ModelTag.ComponentAliasInstanceExport: { 71 | const resolved = rctx.indexes.componentInstances[typeModel.instance_index]; 72 | return createLifting(rctx, resolved as any); 73 | } 74 | case ModelTag.ComponentTypeInstance: { 75 | const resolved = typeModel.declarations[0]; 76 | return createLifting(rctx, resolved as any); 77 | } 78 | case ModelTag.InstanceTypeDeclarationType: { 79 | const resolved = typeModel.value; 80 | jsco_assert(resolved.tag === ModelTag.ComponentTypeDefinedRecord, () => `expected ComponentTypeDefinedRecord, got ${resolved.tag}`); 81 | return createRecordLifting(rctx, resolved); 82 | } 83 | case ModelTag.ComponentValTypeType: { 84 | const resolved = rctx.indexes.componentTypes[typeModel.value]; 85 | return createLifting(rctx, resolved as any); 86 | } 87 | default: 88 | //return createRecordLifting(rctx, typeModel.value); 89 | throw new Error('Not implemented ' + typeModel.tag); 90 | } 91 | }); 92 | } 93 | 94 | function createRecordLifting(rctx: ResolverContext, recordModel: ComponentTypeDefinedRecord): LiftingFromJs { 95 | const lifters: { name: string, lifter: LiftingFromJs }[] = []; 96 | for (const member of recordModel.members) { 97 | const lifter = createLifting(rctx, member.type); 98 | lifters.push({ name: member.name, lifter }); 99 | } 100 | return (ctx: BindingContext, srcJsRecord: JsValue): WasmValue[] => { 101 | // this is spilling into stack 102 | // TODO allocate on heap 103 | let args: any = []; 104 | for (const { name, lifter } of lifters) { 105 | const jsValue = srcJsRecord[name]; 106 | const wasmValue = lifter(ctx, jsValue); 107 | args = [...args, ...wasmValue]; 108 | } 109 | return args; 110 | }; 111 | } 112 | 113 | function createU32Lifting(): LiftingFromJs { 114 | return (_: BindingContext, srcJsValue: JsValue): WasmValue[] => { 115 | const num = srcJsValue as number; 116 | return [num >>> 0]; 117 | }; 118 | } 119 | 120 | function createS64LiftingNumber(): LiftingFromJs { 121 | return (ctx: BindingContext, srcJsValue: JsValue): WasmValue[] => { 122 | const num = srcJsValue as bigint; 123 | return [Number(BigInt.asIntN(52, num))]; 124 | }; 125 | } 126 | 127 | function createS64LiftingBigInt(): LiftingFromJs { 128 | return (ctx: BindingContext, srcJsValue: JsValue): WasmValue[] => { 129 | const num = srcJsValue as bigint; 130 | return [BigInt.asIntN(52, num)]; 131 | }; 132 | } 133 | 134 | function createStringLifting(): LiftingFromJs { 135 | return (ctx: BindingContext, srcJsValue: JsValue): any[] => { 136 | let str = srcJsValue as string; 137 | if (typeof str !== 'string') throw new TypeError('expected a string'); 138 | if (str.length === 0) { 139 | return [0, 0]; 140 | } 141 | let allocLen: WasmSize = 0 as any; 142 | let ptr: WasmPointer = 0 as any; 143 | let writtenTotal = 0; 144 | while (str.length > 0) { 145 | ptr = ctx.realloc(ptr, allocLen, 1 as any, allocLen + str.length as any); 146 | allocLen += str.length as any; 147 | const { read, written } = ctx.utf8Encoder.encodeInto( 148 | str, 149 | ctx.getViewU8(ptr + writtenTotal, allocLen - writtenTotal) 150 | ); 151 | writtenTotal += written; 152 | str = str.slice(read); 153 | } 154 | if (allocLen > writtenTotal) 155 | ptr = ctx.realloc(ptr, allocLen, 1 as any, writtenTotal as any); 156 | return [ptr, writtenTotal]; 157 | }; 158 | } 159 | -------------------------------------------------------------------------------- /src/resolver/binding/to-js.ts: -------------------------------------------------------------------------------- 1 | import { ModelTag } from '../../model/tags'; 2 | import { ComponentTypeFunc, ComponentValType, PrimitiveValType } from '../../model/types'; 3 | import { BindingContext, ResolverContext } from '../types'; 4 | import { memoize } from './cache'; 5 | import { createLifting } from './to-abi'; 6 | import { LoweringToJs, FnLoweringCallToJs, WasmFunction, WasmPointer, JsFunction, WasmSize, WasmValue } from './types'; 7 | 8 | 9 | export function createFunctionLowering(rctx: ResolverContext, exportModel: ComponentTypeFunc): FnLoweringCallToJs { 10 | return memoize(exportModel, () => { 11 | return (ctx: BindingContext, jsFunction: JsFunction): WasmFunction => { 12 | 13 | const paramLowerers: Function[] = []; 14 | for (const param of exportModel.params) { 15 | const lowerer = createLowering(rctx, param.type); 16 | paramLowerers.push(lowerer); 17 | } 18 | const resultLifters: Function[] = []; 19 | switch (exportModel.results.tag) { 20 | case ModelTag.ComponentFuncResultNamed: { 21 | for (const res of exportModel.results.values) { 22 | const lifter = createLifting(rctx, res.type); 23 | resultLifters.push(lifter); 24 | } 25 | break; 26 | } 27 | case ModelTag.ComponentFuncResultUnnamed: { 28 | const lifter = createLifting(rctx, exportModel.results.type); 29 | resultLifters.push(lifter); 30 | } 31 | } 32 | 33 | function loweringTrampoline(...args: any[]): any { 34 | let covertedArgs: any[] = []; 35 | // TODO do not always read spilled stack 36 | for (let i = 0; i < paramLowerers.length;) { 37 | const lowerer = paramLowerers[i]; 38 | const spill = (lowerer as any).spill; 39 | const values = args.slice(i, i + spill); 40 | const converted = lowerer(ctx, ...values); 41 | i += spill; 42 | covertedArgs = [...covertedArgs, converted]; 43 | } 44 | const resJs = jsFunction(...covertedArgs); 45 | if (resultLifters.length === 1) { 46 | resultLifters[0](resJs); 47 | } 48 | } 49 | return loweringTrampoline as WasmFunction; 50 | }; 51 | }); 52 | } 53 | 54 | export function createLowering(rctx: ResolverContext, typeModel: ComponentValType): LoweringToJs { 55 | return memoize(typeModel, () => { 56 | switch (typeModel.tag) { 57 | case ModelTag.ComponentValTypePrimitive: 58 | switch (typeModel.value) { 59 | case PrimitiveValType.String: 60 | return createStringLowering(rctx); 61 | default: 62 | throw new Error('Not implemented'); 63 | } 64 | default: 65 | throw new Error('Not implemented'); 66 | } 67 | }); 68 | } 69 | 70 | function createStringLowering(rctx: ResolverContext): LoweringToJs { 71 | const fn = (ctx: BindingContext, ...args: WasmValue[]) => { 72 | const pointer = args[0] as WasmPointer; 73 | const len = args[1] as WasmSize; 74 | const view = ctx.getView(pointer, len); 75 | const res = ctx.utf8Decoder.decode(view); 76 | return res; 77 | }; 78 | fn.spill = 2; 79 | return fn; 80 | } 81 | -------------------------------------------------------------------------------- /src/resolver/binding/types.ts: -------------------------------------------------------------------------------- 1 | import { BindingContext } from '../types'; 2 | 3 | export type WasmPointer = number; 4 | export type WasmNumber = number | bigint; 5 | export type WasmSize = number; 6 | export type WasmFunction = Function; 7 | export type WasmValue = WasmPointer | WasmSize | WasmNumber; 8 | export type JsFunction = Function; 9 | export type JsString = string; 10 | export type JsBoolean = boolean; 11 | export type JsNumber = number | bigint; 12 | export type JsValue = JsNumber | JsString | JsBoolean | any; 13 | 14 | 15 | export type FnLoweringCallToJs = (ctx: BindingContext, jsExport: JsFunction) => WasmFunction; 16 | export type FnLiftingCallFromJs = (ctx: BindingContext, wasmFunction: WasmFunction) => JsFunction; 17 | 18 | export type LoweringToJs = (ctx: BindingContext, ...args: WasmValue[]) => JsValue; 19 | export type LiftingFromJs = (ctx: BindingContext, srcJsValue: JsValue) => WasmValue[]; 20 | 21 | export type TCabiRealloc = (oldPtr: WasmPointer, oldSize: WasmSize, align: WasmSize, newSize: WasmSize) => WasmPointer; 22 | -------------------------------------------------------------------------------- /src/resolver/component-exports.ts: -------------------------------------------------------------------------------- 1 | import { ComponentExport, ComponentExternalKind } from '../model/exports'; 2 | import { ModelTag } from '../model/tags'; 3 | import { debugStack, jsco_assert } from '../utils/assert'; 4 | import { resolveComponentFunction } from './component-functions'; 5 | import { resolveComponentInstance } from './component-instances'; 6 | import { Resolver } from './types'; 7 | 8 | export const resolveComponentExport: Resolver = (rctx, rargs) => { 9 | const componentExport = rargs.element; 10 | jsco_assert(componentExport && componentExport.tag == ModelTag.ComponentExport, () => `Wrong element type '${componentExport?.tag}'`); 11 | 12 | // TODO componentExport.ty ? 13 | switch (componentExport.kind) { 14 | case ComponentExternalKind.Func: { 15 | const func = rctx.indexes.componentFunctions[componentExport.index]; 16 | const functionResolution = resolveComponentFunction(rctx, { element: func, callerElement: componentExport }); 17 | return { 18 | callerElement: rargs.callerElement, 19 | element: componentExport, 20 | binder: async (bctx, bargs) => { 21 | const args = { 22 | arguments: [componentExport.name.name], 23 | imports: bargs.imports, 24 | callerArgs: bargs, 25 | }; 26 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.name.name + ':' + rargs.element.kind); 27 | 28 | const exportResult = await functionResolution.binder(bctx, args); 29 | const binderResult = { 30 | result: exportResult.result 31 | }; 32 | return binderResult; 33 | } 34 | }; 35 | } 36 | case ComponentExternalKind.Instance: { 37 | const instance = rctx.indexes.componentInstances[componentExport.index]; 38 | const instanceResolution = resolveComponentInstance(rctx, { element: instance, callerElement: componentExport }); 39 | return { 40 | callerElement: rargs.callerElement, 41 | element: componentExport, 42 | binder: async (bctx, bargs) => { 43 | const args = { 44 | arguments: bargs.arguments, 45 | imports: bargs.imports, 46 | callerArgs: bargs, 47 | }; 48 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.name.name + ':' + rargs.element.kind); 49 | 50 | const instanceResult = await instanceResolution.binder(bctx, args); 51 | const ifc: any = {}; 52 | ifc[componentExport.name.name] = instanceResult.result; 53 | const binderResult = { 54 | result: ifc 55 | }; 56 | return binderResult; 57 | } 58 | }; 59 | } 60 | case ComponentExternalKind.Type: { 61 | throw new Error('TODO types'); 62 | } 63 | case ComponentExternalKind.Component: 64 | case ComponentExternalKind.Module: 65 | case ComponentExternalKind.Value: 66 | default: 67 | throw new Error(`${componentExport.kind} not implemented`); 68 | } 69 | }; -------------------------------------------------------------------------------- /src/resolver/component-functions.ts: -------------------------------------------------------------------------------- 1 | import { ComponentAliasInstanceExport, ComponentFunction } from '../model/aliases'; 2 | import { CanonicalFunctionLift } from '../model/canonicals'; 3 | import { ComponentExternalKind } from '../model/exports'; 4 | import { ModelTag } from '../model/tags'; 5 | import { debugStack, jsco_assert } from '../utils/assert'; 6 | import { createFunctionLifting } from './binding'; 7 | import { resolveComponentInstance } from './component-instances'; 8 | import { resolveCoreFunction } from './core-functions'; 9 | import { Resolver } from './types'; 10 | import camelCase from 'just-camel-case'; 11 | 12 | export const resolveComponentFunction: Resolver = (rctx, rargs) => { 13 | const coreInstance = rargs.element; 14 | switch (coreInstance.tag) { 15 | case ModelTag.CanonicalFunctionLift: return resolveCanonicalFunctionLift(rctx, rargs as any); 16 | case ModelTag.ComponentAliasInstanceExport: return resolveComponentAliasInstanceExport(rctx, rargs as any); 17 | default: throw new Error(`"${(coreInstance as any).tag}" not implemented`); 18 | } 19 | }; 20 | 21 | export const resolveCanonicalFunctionLift: Resolver = (rctx, rargs) => { 22 | const canonicalFunctionLift = rargs.element; 23 | jsco_assert(canonicalFunctionLift && canonicalFunctionLift.tag == ModelTag.CanonicalFunctionLift, () => `Wrong element type '${canonicalFunctionLift?.tag}'`); 24 | 25 | const coreFuntion = rctx.indexes.coreFunctions[canonicalFunctionLift.core_func_index]; 26 | const coreFunctionResolution = resolveCoreFunction(rctx, { element: coreFuntion, callerElement: canonicalFunctionLift }); 27 | 28 | const sectionFunType = rctx.indexes.componentTypes[canonicalFunctionLift.type_index]; 29 | jsco_assert(sectionFunType.tag === ModelTag.ComponentTypeFunc, () => `expected ComponentTypeFunc, got ${sectionFunType.tag}`); 30 | 31 | // TODO canonicalFunctionLift.options 32 | const liftingBinder = createFunctionLifting(rctx, sectionFunType); 33 | 34 | return { 35 | callerElement: rargs.callerElement, 36 | element: canonicalFunctionLift, 37 | binder: async (bctx, bargs) => { 38 | const args = { 39 | arguments: bargs.arguments, 40 | imports: bargs.imports, 41 | callerArgs: bargs, 42 | }; 43 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 44 | const functionResult = await coreFunctionResolution.binder(bctx, args); 45 | 46 | const jsFunction = liftingBinder(bctx, functionResult.result); 47 | 48 | const binderResult = { 49 | result: jsFunction 50 | }; 51 | return binderResult; 52 | } 53 | }; 54 | }; 55 | 56 | export const resolveComponentAliasInstanceExport: Resolver = (rctx, rargs) => { 57 | const componentAliasInstanceExport = rargs.element; 58 | jsco_assert(componentAliasInstanceExport && componentAliasInstanceExport.tag == ModelTag.ComponentAliasInstanceExport, () => `Wrong element type '${componentAliasInstanceExport?.tag}'`); 59 | 60 | if (componentAliasInstanceExport.kind === ComponentExternalKind.Type) { 61 | // TODO types 62 | return { 63 | callerElement: rargs.callerElement, 64 | element: componentAliasInstanceExport, 65 | binder: async (bctx, bargs) => { 66 | const binderResult = { 67 | missingRes: rargs.element.tag, 68 | confused: 1, 69 | result: { 70 | missingResTypes: rargs.element.tag, 71 | } 72 | }; 73 | return binderResult; 74 | } 75 | }; 76 | } 77 | if (componentAliasInstanceExport.kind !== ComponentExternalKind.Func) { 78 | throw new Error(`"${componentAliasInstanceExport.kind}" not implemented`); 79 | } 80 | 81 | const instance = rctx.indexes.componentInstances[componentAliasInstanceExport.instance_index]; 82 | const instanceResolution = resolveComponentInstance(rctx, { element: instance, callerElement: componentAliasInstanceExport }); 83 | 84 | return { 85 | callerElement: rargs.callerElement, 86 | element: componentAliasInstanceExport, 87 | binder: async (bctx, bargs) => { 88 | const args = { 89 | arguments: bargs.arguments, 90 | imports: bargs.imports, 91 | callerArgs: bargs, 92 | }; 93 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 94 | const instanceResult = await instanceResolution.binder(bctx, args) as any; 95 | 96 | // TODO resolve type as well 97 | let fn; 98 | const askedName = args.arguments?.[0] as string; 99 | if (askedName) { 100 | fn = instanceResult.result.exports[askedName]; 101 | } else { 102 | const ccName = camelCase(componentAliasInstanceExport.name); 103 | fn = instanceResult.result.imports[ccName]; 104 | } 105 | 106 | const binderResult = { 107 | result: fn 108 | }; 109 | return binderResult; 110 | } 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /src/resolver/component-imports.ts: -------------------------------------------------------------------------------- 1 | import { ComponentImport } from '../model/imports'; 2 | import { ModelTag } from '../model/tags'; 3 | import { jsco_assert } from '../utils/assert'; 4 | import { lookupComponentInstance } from './component-instances'; 5 | import { Resolver } from './types'; 6 | 7 | export const resolveComponentImport: Resolver = (rctx, rargs) => { 8 | const componentImport = rargs.element; 9 | jsco_assert(componentImport && componentImport.tag == ModelTag.ComponentImport, () => `Wrong element type '${componentImport?.tag}'`); 10 | 11 | switch (componentImport.ty.tag) { 12 | case ModelTag.ComponentTypeRefComponent: { 13 | return { 14 | callerElement: rargs.callerElement, 15 | element: componentImport, 16 | binder: async (bctx, bargs) => { 17 | // TODO this instance index is probably wrong! 18 | const binderResult = lookupComponentInstance(bctx, componentImport.selfSortIndex!); 19 | const imprt = bargs.imports[componentImport.name.name]; 20 | Object.assign(binderResult.result.imports, imprt); 21 | return binderResult; 22 | } 23 | }; 24 | } 25 | case ModelTag.ComponentTypeRefFunc: 26 | case ModelTag.ComponentTypeRefType: 27 | default: 28 | throw new Error(`${componentImport.ty.tag} not implemented`); 29 | 30 | } 31 | }; -------------------------------------------------------------------------------- /src/resolver/component-instances.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'just-camel-case'; 2 | import { ComponentExternalKind } from '../model/exports'; 3 | import { ComponentInstance, ComponentInstanceInstantiate, ComponentInstantiationArg } from '../model/instances'; 4 | import { ModelTag } from '../model/tags'; 5 | import { ComponentTypeInstance } from '../model/types'; 6 | import { debugStack, jsco_assert } from '../utils/assert'; 7 | import { JsInterfaceCollection } from './api-types'; 8 | import { resolveComponentFunction } from './component-functions'; 9 | import { resolveComponentType } from './component-types'; 10 | import { BinderRes, BindingContext, Resolver, ResolverRes } from './types'; 11 | 12 | export const resolveComponentInstance: Resolver = (rctx, rargs) => { 13 | const coreInstance = rargs.element; 14 | switch (coreInstance.tag) { 15 | case ModelTag.ComponentInstanceInstantiate: return resolveComponentInstanceInstantiate(rctx, rargs as any); 16 | // case ModelTag.ComponentInstanceFromExports: return resolveComponentInstanceFromExports(rctx, rargs as any); 17 | case ModelTag.ComponentTypeInstance: return resolveComponentTypeInstance(rctx, rargs as any); 18 | default: throw new Error(`"${(coreInstance as any).tag}" not implemented`); 19 | } 20 | }; 21 | 22 | export const resolveComponentInstanceInstantiate: Resolver = (rctx, rargs) => { 23 | const componentInstanceInstantiate = rargs.element; 24 | jsco_assert(componentInstanceInstantiate && componentInstanceInstantiate.tag == ModelTag.ComponentInstanceInstantiate, () => `Wrong element type '${componentInstanceInstantiate?.tag}'`); 25 | const componentSectionIndex = componentInstanceInstantiate.component_index; 26 | const componentSection = rctx.indexes.componentTypes[componentSectionIndex]; 27 | const componentSectionResolution = resolveComponentType(rctx, { element: componentSection, callerElement: componentInstanceInstantiate }); 28 | const argResolutions: ResolverRes[] = []; 29 | for (const arg of componentInstanceInstantiate.args) { 30 | switch (arg.kind) { 31 | case ComponentExternalKind.Func: { 32 | const componentFunction = rctx.indexes.componentFunctions[arg.index]; 33 | const resolver = resolveComponentFunction(rctx, { element: componentFunction, callerElement: arg }); 34 | argResolutions.push(resolver as any); 35 | break; 36 | } 37 | case ComponentExternalKind.Instance: { 38 | const componentInstance = rctx.indexes.componentInstances[arg.index]; 39 | const resolver = resolveComponentInstance(rctx, { element: componentInstance, callerElement: arg }); 40 | argResolutions.push(resolver as any); 41 | break; 42 | } 43 | case ComponentExternalKind.Type: { 44 | // TODO types 45 | break; 46 | } 47 | case ComponentExternalKind.Component: 48 | case ComponentExternalKind.Module: 49 | case ComponentExternalKind.Value: 50 | default: 51 | throw new Error(`"${arg.kind}" not implemented`); 52 | } 53 | } 54 | 55 | return { 56 | callerElement: rargs.callerElement, 57 | element: componentInstanceInstantiate, 58 | binder: async (bctx, bargs) => { 59 | const binderResult = lookupComponentInstance(bctx, componentInstanceInstantiate.selfSortIndex!); 60 | Object.assign(binderResult.result, bargs.imports); 61 | 62 | const componentArgs = {} as any; 63 | for (const argResolution of argResolutions) { 64 | const callerElement = argResolution.callerElement as ComponentInstantiationArg; 65 | 66 | const args = { 67 | arguments: bargs.arguments, 68 | imports: bargs.imports, 69 | callerArgs: bargs, 70 | }; 71 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 72 | debugStack(args, args, 'ComponentInstantiationArg:' + callerElement.index + ':' + callerElement.name); 73 | const argResult = await argResolution.binder(bctx, args); 74 | let argName = callerElement.name; 75 | // TODO is this prefix a convention ? 76 | if (argName.startsWith('import-func-')) { 77 | argName = argName.substring('import-func-'.length); 78 | } 79 | argName = camelCase(argName); 80 | componentArgs[argName] = argResult.result; 81 | } 82 | Object.assign(binderResult.result.exports, componentArgs); 83 | 84 | const args = { 85 | imports: componentArgs, 86 | callerArgs: bargs, 87 | }; 88 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 89 | const componentSectionResult = await componentSectionResolution.binder(bctx, args); 90 | 91 | binderResult.result = componentSectionResult.result as JsInterfaceCollection; 92 | return binderResult; 93 | } 94 | }; 95 | }; 96 | 97 | export const resolveComponentTypeInstance: Resolver = (rctx, rargs) => { 98 | const componentTypeInstance = rargs.element; 99 | jsco_assert(componentTypeInstance && componentTypeInstance.tag == ModelTag.ComponentTypeInstance, () => `Wrong element type '${componentTypeInstance?.tag}'`); 100 | 101 | return { 102 | callerElement: rargs.callerElement, 103 | element: componentTypeInstance, 104 | binder: async (bctx, bargs) => { 105 | const binderResult = lookupComponentInstance(bctx, componentTypeInstance.selfSortIndex!); 106 | Object.assign(binderResult.result.exports, bargs.imports); 107 | Object.assign(binderResult.result.types, componentTypeInstance.declarations); 108 | return binderResult; 109 | } 110 | }; 111 | }; 112 | 113 | export function lookupComponentInstance(bctx: BindingContext, instanceIndex: number): BinderRes { 114 | let binderResult = bctx.componentInstances[instanceIndex] as any; 115 | if (!binderResult) { 116 | binderResult = { 117 | result: { 118 | instanceIndex, 119 | imports: {}, 120 | exports: {}, 121 | types: {} 122 | } 123 | }; 124 | bctx.componentInstances[instanceIndex] = binderResult; 125 | } 126 | return binderResult; 127 | } -------------------------------------------------------------------------------- /src/resolver/component-types.ts: -------------------------------------------------------------------------------- 1 | import { ComponentExport, ComponentExternalKind } from '../model/exports'; 2 | import { ModelTag, WITSection } from '../model/tags'; 3 | import { ComponentType } from '../model/types'; 4 | import { ComponentSection } from '../parser/types'; 5 | import { debugStack, jsco_assert } from '../utils/assert'; 6 | import { resolveComponentExport } from './component-exports'; 7 | import { resolveComponentAliasInstanceExport } from './component-functions'; 8 | import { BinderRes, Resolver, ResolverRes } from './types'; 9 | 10 | export const resolveComponentType: Resolver = (rctx, rargs) => { 11 | const coreInstance = rargs.element; 12 | if (!coreInstance) { 13 | throw new Error('Wrong element type '); 14 | } 15 | switch (coreInstance.tag) { 16 | case ModelTag.ComponentSection: return resolveComponentSection(rctx, rargs as any); 17 | case ModelTag.ComponentAliasInstanceExport: return resolveComponentAliasInstanceExport(rctx, rargs as any); 18 | default: throw new Error(`"${(coreInstance as any).tag}" not implemented`); 19 | } 20 | }; 21 | 22 | export const resolveComponentSection: Resolver = (rctx, rargs) => { 23 | const componentSection = rargs.element; 24 | jsco_assert(componentSection && componentSection.tag == ModelTag.ComponentSection, () => `Wrong element type '${componentSection?.tag}'`); 25 | 26 | const exportResolutions: ResolverRes[] = []; 27 | for (const declaration of componentSection.sections) { 28 | switch (declaration.tag) { 29 | case ModelTag.ComponentExport: { 30 | if (declaration.kind === ComponentExternalKind.Func) { 31 | const exportResolution = resolveComponentExport(rctx, { element: declaration, callerElement: declaration }); 32 | exportResolutions.push(exportResolution); 33 | } else if (declaration.kind !== ComponentExternalKind.Type) { 34 | throw new Error('Not implemented'); 35 | } 36 | break; 37 | } 38 | case ModelTag.ComponentImport: { 39 | // declaration.name 40 | // declaration.name.name 41 | // rctx.indexes.componentTypes[declaration.ty.value]; 42 | // throw new Error('Not implemented' + declaration.name.name); 43 | break; 44 | } 45 | case ModelTag.ComponentTypeFunc: 46 | case ModelTag.ComponentTypeDefinedRecord: 47 | case ModelTag.ComponentTypeDefinedTuple: 48 | case ModelTag.ComponentTypeDefinedEnum: 49 | case ModelTag.ComponentTypeDefinedVariant: 50 | // TODO types 51 | break; 52 | default: 53 | throw new Error(`${declaration.tag} not implemented`); 54 | } 55 | } 56 | 57 | return { 58 | callerElement: rargs.callerElement, 59 | element: componentSection, 60 | binder: async (bctx, bargs) => { 61 | const exports = {} as any; 62 | for (const exportResolution of exportResolutions) { 63 | const callerElement = exportResolution.callerElement as ComponentExport; 64 | const args = { 65 | arguments: bargs.arguments, 66 | imports: bargs.imports, 67 | callerArgs: bargs, 68 | debugSource: callerElement.tag + ':' + callerElement.name.name 69 | }; 70 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 71 | debugStack(args, args, callerElement.tag + ':' + callerElement.name.name); 72 | 73 | const argResult = await exportResolution.binder(bctx, args); 74 | 75 | exports[callerElement.name.name] = argResult.result as any; 76 | } 77 | const binderResult: BinderRes = { 78 | result: exports 79 | }; 80 | return binderResult; 81 | } 82 | }; 83 | }; 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/resolver/context.ts: -------------------------------------------------------------------------------- 1 | import { WITModel } from '../parser'; 2 | import { IndexedElement, ModelTag, TaggedElement } from '../model/tags'; 3 | import { ExternalKind } from '../model/core'; 4 | import { ComponentExternalKind } from '../model/exports'; 5 | import { configuration } from '../utils/assert'; 6 | import { BindingContext, ComponentFactoryOptions, ResolverContext } from './types'; 7 | import { TCabiRealloc, WasmPointer, WasmSize } from './binding/types'; 8 | import { JsImports } from './api-types'; 9 | 10 | export function createResolverContext(sections: WITModel, options: ComponentFactoryOptions): ResolverContext { 11 | const rctx: ResolverContext = { 12 | usesNumberForInt64: (options.useNumberForInt64 === true) ? true : false, 13 | wasmInstantiate: options.wasmInstantiate ?? WebAssembly.instantiate, 14 | indexes: { 15 | componentExports: [], 16 | componentImports: [], 17 | componentFunctions: [], 18 | componentInstances: [], 19 | componentTypes: [], // this is 2 phase 20 | componentTypeResource: [], 21 | 22 | coreModules: [], 23 | coreInstances: [], 24 | coreFunctions: [], 25 | coreMemories: [], 26 | coreTables: [], 27 | coreGlobals: [], 28 | componentSections: [], 29 | }, 30 | }; 31 | 32 | const indexes = rctx.indexes; 33 | for (const section of sections) { 34 | // TODO: process all sections into model 35 | const bucket = bucketByTag(rctx, section.tag, false, (section as any).kind); 36 | bucket.push(section); 37 | } 38 | 39 | // indexed with imports first and then function definitions next 40 | // See https://github.com/bytecodealliance/wasm-interface-types/blob/main/BINARY.md 41 | rctx.indexes.componentTypes = [...rctx.indexes.componentSections, ...indexes.componentTypes]; 42 | setSelfIndex(rctx); 43 | return rctx; 44 | } 45 | 46 | export function setSelfIndex(rctx: ResolverContext) { 47 | function setSelfIndex(sort: IndexedElement[]) { 48 | for (let i = 0; i < sort.length; i++) { 49 | sort[i].selfSortIndex = i; 50 | } 51 | } 52 | setSelfIndex(rctx.indexes.componentExports); 53 | setSelfIndex(rctx.indexes.componentImports); 54 | setSelfIndex(rctx.indexes.componentFunctions); 55 | setSelfIndex(rctx.indexes.componentInstances); 56 | setSelfIndex(rctx.indexes.componentTypes); 57 | setSelfIndex(rctx.indexes.componentTypeResource); 58 | 59 | setSelfIndex(rctx.indexes.coreModules); 60 | setSelfIndex(rctx.indexes.coreInstances); 61 | setSelfIndex(rctx.indexes.coreFunctions); 62 | setSelfIndex(rctx.indexes.coreMemories); 63 | setSelfIndex(rctx.indexes.coreTables); 64 | setSelfIndex(rctx.indexes.coreGlobals); 65 | } 66 | 67 | export function createBindingContext(rctx: ResolverContext, componentImports: JsImports): BindingContext { 68 | let memory: WebAssembly.Memory = undefined as any; 69 | let cabi_realloc: TCabiRealloc = undefined as any; 70 | 71 | function initializeMemory(m: WebAssembly.Memory) { 72 | memory = m; 73 | } 74 | function initializeRealloc(realloc: TCabiRealloc) { 75 | cabi_realloc = realloc; 76 | } 77 | function getView(pointer?: number, len?: number) { 78 | return new DataView(memory.buffer, pointer, len); 79 | } 80 | function getViewU8(pointer?: number, len?: number) { 81 | return new Uint8Array(memory.buffer, pointer, len); 82 | } 83 | function getMemory() { 84 | return memory; 85 | } 86 | function realloc(oldPtr: WasmPointer, oldSize: WasmSize, align: WasmSize, newSize: WasmSize) { 87 | return cabi_realloc(oldPtr, oldSize, align, newSize); 88 | } 89 | function alloc(newSize: WasmSize, align: WasmSize) { 90 | return cabi_realloc(0 as any, 0 as any, align, newSize); 91 | } 92 | function readI32(ptr: WasmPointer) { 93 | return getView().getInt32(ptr); 94 | } 95 | function writeI32(ptr: WasmPointer, value: number) { 96 | return getView().setInt32(ptr, value); 97 | } 98 | function abort() { 99 | throw new Error('not implemented'); 100 | } 101 | const ctx: BindingContext = { 102 | componentImports, 103 | coreInstances: [], 104 | componentInstances: [], 105 | utf8Decoder: new TextDecoder(), 106 | utf8Encoder: new TextEncoder(), 107 | initializeMemory, 108 | initializeRealloc, 109 | getView, 110 | getViewU8, 111 | getMemory, 112 | realloc, 113 | alloc, 114 | readI32, 115 | writeI32, 116 | abort, 117 | }; 118 | if (configuration === 'Debug') { 119 | ctx.debugStack = []; 120 | } 121 | return ctx; 122 | } 123 | 124 | export function bucketByTag(rctx: ResolverContext, tag: ModelTag, read: boolean, kind?: ComponentExternalKind | ExternalKind): TaggedElement[] { 125 | switch (tag) { 126 | case ModelTag.CoreModule: 127 | return rctx.indexes.coreModules; 128 | case ModelTag.ComponentExport: 129 | return rctx.indexes.componentExports; 130 | case ModelTag.ComponentImport: 131 | return rctx.indexes.componentImports; 132 | break; 133 | case ModelTag.ComponentAliasCoreInstanceExport: { 134 | switch (kind) { 135 | case ExternalKind.Func: 136 | return rctx.indexes.coreFunctions; 137 | case ExternalKind.Table: 138 | return rctx.indexes.coreTables; 139 | case ExternalKind.Memory: 140 | return rctx.indexes.coreMemories; 141 | case ExternalKind.Global: 142 | return rctx.indexes.coreGlobals; 143 | case ExternalKind.Tag: 144 | default: 145 | throw new Error(`unexpected section tag: ${kind}`); 146 | } 147 | break; 148 | } 149 | case ModelTag.ComponentAliasInstanceExport: { 150 | switch (kind) { 151 | case ComponentExternalKind.Func: 152 | return rctx.indexes.componentFunctions; 153 | case ComponentExternalKind.Component: 154 | return rctx.indexes.componentTypes; 155 | case ComponentExternalKind.Type: 156 | return rctx.indexes.componentTypes; 157 | case ComponentExternalKind.Module: 158 | case ComponentExternalKind.Value: 159 | case ComponentExternalKind.Instance: 160 | default: 161 | throw new Error(`unexpected section tag: ${kind}`); 162 | } 163 | } 164 | case ModelTag.CoreInstanceFromExports: 165 | case ModelTag.CoreInstanceInstantiate: 166 | return rctx.indexes.coreInstances; 167 | case ModelTag.ComponentInstanceFromExports: 168 | case ModelTag.ComponentInstanceInstantiate: 169 | return rctx.indexes.componentInstances; 170 | case ModelTag.ComponentTypeFunc: 171 | return rctx.indexes.componentTypes; 172 | case ModelTag.ComponentSection: 173 | return read 174 | ? rctx.indexes.componentTypes 175 | : rctx.indexes.componentSections;//append later 176 | case ModelTag.ComponentTypeDefinedBorrow: 177 | case ModelTag.ComponentTypeDefinedEnum: 178 | case ModelTag.ComponentTypeDefinedFlags: 179 | case ModelTag.ComponentTypeDefinedList: 180 | case ModelTag.ComponentTypeDefinedOption: 181 | case ModelTag.ComponentTypeDefinedOwn: 182 | case ModelTag.ComponentTypeDefinedPrimitive: 183 | case ModelTag.ComponentTypeDefinedRecord: 184 | case ModelTag.ComponentTypeDefinedResult: 185 | case ModelTag.ComponentTypeDefinedTuple: 186 | case ModelTag.ComponentTypeDefinedVariant: 187 | return rctx.indexes.componentTypes; 188 | case ModelTag.ComponentTypeInstance: 189 | return rctx.indexes.componentInstances; 190 | case ModelTag.ComponentTypeResource: 191 | return rctx.indexes.componentTypeResource; 192 | case ModelTag.CanonicalFunctionLower: { 193 | return rctx.indexes.coreFunctions; 194 | } 195 | case ModelTag.CanonicalFunctionLift: { 196 | return rctx.indexes.componentFunctions; 197 | } 198 | 199 | case ModelTag.SkippedSection: 200 | case ModelTag.CustomSection: 201 | return [];//drop 202 | case ModelTag.ComponentAliasOuter: 203 | case ModelTag.CanonicalFunctionResourceDrop: 204 | case ModelTag.CanonicalFunctionResourceNew: 205 | case ModelTag.CanonicalFunctionResourceRep: 206 | default: 207 | throw new Error(`unexpected section tag: ${tag}`); 208 | } 209 | } 210 | 211 | export function elementByIndex(rctx: ResolverContext, template: TResult, index: number): TResult { 212 | const bucket = bucketByTag(rctx, template.tag, true, template.kind); 213 | return bucket[index] as TResult; 214 | } 215 | -------------------------------------------------------------------------------- /src/resolver/core-exports.ts: -------------------------------------------------------------------------------- 1 | import { ComponentAliasCoreInstanceExport } from '../model/aliases'; 2 | import { debugStack } from '../utils/assert'; 3 | import { resolveCoreInstance } from './core-instance'; 4 | import { Resolver, BinderRes } from './types'; 5 | 6 | type ExportResult = Function | WebAssembly.Memory | WebAssembly.Table 7 | 8 | export const resolveComponentAliasCoreInstanceExport: Resolver = (rctx, rargs) => { 9 | const componentAliasCoreInstanceExport = rargs.element; 10 | const coreInstanceIndex = componentAliasCoreInstanceExport.instance_index; 11 | const coreInstance = rctx.indexes.coreInstances[coreInstanceIndex]; 12 | const coreModuleResolution = resolveCoreInstance(rctx, { element: coreInstance, callerElement: componentAliasCoreInstanceExport }); 13 | 14 | return { 15 | callerElement: rargs.callerElement, 16 | element: componentAliasCoreInstanceExport, 17 | binder: async (bctx, bargs): Promise => { 18 | const args = { 19 | missing: rargs.element.tag, 20 | callerArgs: bargs, 21 | }; 22 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 23 | 24 | const moduleResult = await coreModuleResolution.binder(bctx, args); 25 | const result = moduleResult.result[componentAliasCoreInstanceExport.name] as ExportResult; 26 | const binderResult = { 27 | result 28 | }; 29 | return binderResult; 30 | } 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/resolver/core-functions.ts: -------------------------------------------------------------------------------- 1 | import { CoreFunction } from '../model/aliases'; 2 | import { CanonicalFunctionLower } from '../model/canonicals'; 3 | import { ModelTag } from '../model/tags'; 4 | import { ComponentTypeFunc, ComponentTypeInstance, InstanceTypeDeclarationType } from '../model/types'; 5 | import { debugStack, jsco_assert } from '../utils/assert'; 6 | import { createFunctionLowering } from './binding'; 7 | import { resolveComponentFunction } from './component-functions'; 8 | import { resolveComponentAliasCoreInstanceExport } from './core-exports'; 9 | import { Resolver, BinderRes, ResolverRes } from './types'; 10 | 11 | 12 | export const resolveCoreFunction: Resolver = (rctx, rargs) => { 13 | const coreInstance = rargs.element; 14 | switch (coreInstance.tag) { 15 | case ModelTag.ComponentAliasCoreInstanceExport: return resolveComponentAliasCoreInstanceExport(rctx, rargs as any) as ResolverRes; 16 | case ModelTag.CanonicalFunctionLower: return resolveCanonicalFunctionLower(rctx, rargs as any); 17 | default: throw new Error(`"${(coreInstance as any).tag}" not implemented`); 18 | } 19 | }; 20 | 21 | export const resolveCanonicalFunctionLower: Resolver = (rctx, rargs) => { 22 | const canonicalFunctionLowerElem = rargs.element; 23 | jsco_assert(canonicalFunctionLowerElem && canonicalFunctionLowerElem.tag == ModelTag.CanonicalFunctionLower, () => `Wrong element type '${canonicalFunctionLowerElem?.tag}'`); 24 | 25 | const componentFuntion = rctx.indexes.componentFunctions[canonicalFunctionLowerElem.func_index]; 26 | const componentFunctionResolution = resolveComponentFunction(rctx, { element: componentFuntion, callerElement: canonicalFunctionLowerElem }); 27 | 28 | // this is very fake 29 | const componentType = rctx.indexes.componentInstances[0] as ComponentTypeInstance; 30 | const instanceFunType = componentType.declarations[2] as InstanceTypeDeclarationType; 31 | const funcType = instanceFunType.value as ComponentTypeFunc; 32 | 33 | //TODO canonicalFunctionLowerElem.options 34 | const loweringBinder = createFunctionLowering(rctx, funcType); 35 | 36 | return { 37 | callerElement: rargs.callerElement, 38 | element: canonicalFunctionLowerElem, 39 | binder: async (bctx, bargs): Promise => { 40 | const args = { 41 | arguments: bargs.arguments, 42 | imports: bargs.imports, 43 | callerArgs: bargs, 44 | }; 45 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 46 | debugStack(args, args, componentFuntion.tag + ':' + componentFuntion.selfSortIndex); 47 | const functionResult = await componentFunctionResolution.binder(bctx, args); 48 | 49 | const wasmFunction = loweringBinder(bctx, functionResult.result); 50 | 51 | const binderResult = { 52 | result: wasmFunction 53 | }; 54 | return binderResult; 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /src/resolver/core-instance.ts: -------------------------------------------------------------------------------- 1 | import { Export, ExternalKind } from '../model/core'; 2 | import { CoreInstance, CoreInstanceFromExports, CoreInstanceInstantiate, InstantiationArg, InstantiationArgKind } from '../model/instances'; 3 | import { ModelTag } from '../model/tags'; 4 | import { debugStack, jsco_assert } from '../utils/assert'; 5 | import { resolveCoreFunction } from './core-functions'; 6 | import { resolveCoreModule } from './core-module'; 7 | import { Resolver, ResolverRes, BinderRes, BinderArgs } from './types'; 8 | 9 | export const resolveCoreInstance: Resolver = (rctx, rargs) => { 10 | const coreInstance = rargs.element; 11 | switch (coreInstance.tag) { 12 | case ModelTag.CoreInstanceFromExports: return resolveCoreInstanceFromExports(rctx, rargs as any); 13 | case ModelTag.CoreInstanceInstantiate: return resolveCoreInstanceInstantiate(rctx, rargs as any); 14 | default: throw new Error(`"${(coreInstance as any).tag}" not implemented`); 15 | } 16 | }; 17 | 18 | export const resolveCoreInstanceFromExports: Resolver = (rctx, rargs) => { 19 | const coreInstanceFromExports = rargs.element; 20 | jsco_assert(coreInstanceFromExports && coreInstanceFromExports.tag == ModelTag.CoreInstanceFromExports, () => `Wrong element type '${coreInstanceFromExports?.tag}'`); 21 | 22 | const exportResolutions: ResolverRes[] = []; 23 | for (const exp of coreInstanceFromExports.exports) { 24 | switch (exp.kind) { 25 | case ExternalKind.Func: { 26 | const func = rctx.indexes.coreFunctions[exp.index]; 27 | const exportResolution = resolveCoreFunction(rctx, { element: func, callerElement: exp }); 28 | exportResolutions.push(exportResolution); 29 | break; 30 | } 31 | case ExternalKind.Table: { 32 | const table = rctx.indexes.coreTables[exp.index]; 33 | const exportResolution = resolveCoreFunction(rctx, { element: table, callerElement: exp }); 34 | exportResolutions.push(exportResolution); 35 | break; 36 | } 37 | default: 38 | throw new Error(`"${exp.kind}" not implemented`); 39 | } 40 | } 41 | 42 | return { 43 | element: coreInstanceFromExports, 44 | callerElement: rargs.callerElement, 45 | binder: async (bctx, bargs) => { 46 | const exports = {} as WebAssembly.Imports; 47 | for (const exportResolution of exportResolutions) { 48 | const callerElement = exportResolution.callerElement as Export; 49 | const args = { 50 | arguments: bargs.arguments, 51 | imports: bargs.imports, 52 | callerArgs: bargs, 53 | }; 54 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 55 | debugStack(args, args, callerElement.kind + ':' + callerElement.name); 56 | 57 | const argResult = await exportResolution.binder(bctx, args); 58 | exports[callerElement.name] = argResult.result as any; 59 | } 60 | const binderResult: BinderRes = { 61 | result: exports as any as WebAssembly.Instance 62 | }; 63 | return binderResult; 64 | } 65 | }; 66 | }; 67 | 68 | export const resolveCoreInstanceInstantiate: Resolver = (rctx, rargs) => { 69 | const coreInstanceInstantiate = rargs.element; 70 | const coreInstanceIndex = coreInstanceInstantiate.selfSortIndex!; 71 | jsco_assert(coreInstanceInstantiate && coreInstanceInstantiate.tag == ModelTag.CoreInstanceInstantiate, () => `Wrong element type '${coreInstanceInstantiate?.tag}'`); 72 | const coreModuleIndex = coreInstanceInstantiate.module_index; 73 | const coreModule = rctx.indexes.coreModules[coreModuleIndex]; 74 | const coreModuleResolution = resolveCoreModule(rctx, { element: coreModule, callerElement: coreInstanceInstantiate }); 75 | const argResolutions: ResolverRes[] = []; 76 | for (const arg of coreInstanceInstantiate.args) { 77 | switch (arg.kind) { 78 | case InstantiationArgKind.Instance: { 79 | const argInstance = rctx.indexes.coreInstances[arg.index]; 80 | const resolution = resolveCoreInstance(rctx, { 81 | callerElement: arg, 82 | element: argInstance 83 | }); 84 | argResolutions.push(resolution); 85 | break; 86 | } 87 | default: 88 | throw new Error(`"${arg.kind}" not implemented`); 89 | } 90 | } 91 | 92 | return { 93 | element: coreInstanceInstantiate, 94 | callerElement: rargs.callerElement, 95 | binder: async (bctx, bargs): Promise => { 96 | let binderResult = bctx.coreInstances[coreInstanceIndex]; 97 | if (binderResult) { 98 | // TODO, do I need to validate that all calls got the same args ? 99 | return binderResult; 100 | } 101 | binderResult = {} as BinderRes; 102 | bctx.coreInstances[coreInstanceIndex] = binderResult; 103 | 104 | const wasmImports = { 105 | debugSource: rargs.element.tag 106 | } as any as WebAssembly.Imports; 107 | for (const argResolution of argResolutions) { 108 | const callerElement = argResolution.callerElement as InstantiationArg; 109 | 110 | const args = { 111 | arguments: bargs.arguments, 112 | imports: bargs.imports, 113 | callerArgs: bargs, 114 | }; 115 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 116 | debugStack(args, args, callerElement.index + ':' + callerElement.name); 117 | 118 | const argResult = await argResolution.binder(bctx, args); 119 | wasmImports[callerElement.name] = argResult.result as any; 120 | } 121 | 122 | const args: BinderArgs = { 123 | callerArgs: bargs, 124 | }; 125 | debugStack(bargs, args, rargs.element.tag + ':' + rargs.element.selfSortIndex); 126 | const moduleResult = await coreModuleResolution.binder(bctx, args); 127 | const module = moduleResult.result; 128 | const instance = await rctx.wasmInstantiate(module, wasmImports); 129 | // console.log('rctx.wasmInstantiate ' + coreInstanceIndex, Object.keys(instance.exports)); 130 | const exports = instance.exports; 131 | 132 | // TODO maybe there are WIT instructions telling that explicitly ? 133 | const memory = exports['memory'] as WebAssembly.Memory; 134 | if (memory) { 135 | bctx.initializeMemory(memory); 136 | } 137 | const cabi_realloc = exports['cabi_realloc'] as any; 138 | if (cabi_realloc) { 139 | bctx.initializeRealloc(cabi_realloc); 140 | } 141 | 142 | binderResult.result = exports; 143 | return binderResult; 144 | } 145 | }; 146 | }; 147 | 148 | -------------------------------------------------------------------------------- /src/resolver/core-module.ts: -------------------------------------------------------------------------------- 1 | import { ModelTag } from '../model/tags'; 2 | import { CoreModule } from '../parser/types'; 3 | import { jsco_assert } from '../utils/assert'; 4 | import { Resolver } from './types'; 5 | 6 | export const resolveCoreModule: Resolver = (rctx, rargs) => { 7 | const coreModule = rargs.element; 8 | jsco_assert(coreModule && coreModule.tag == ModelTag.CoreModule, () => `Wrong element type '${coreModule?.tag}'`); 9 | return { 10 | callerElement: rargs.callerElement, 11 | element: coreModule, 12 | binder: async (bctx, bargs) => { 13 | const binderResult = { 14 | result: await coreModule.module! 15 | }; 16 | return binderResult; 17 | } 18 | }; 19 | }; -------------------------------------------------------------------------------- /src/resolver/hello.test.ts: -------------------------------------------------------------------------------- 1 | import { expectedModel } from '../../tests/hello'; 2 | import { expectedContext, resolveJCO } from '../../tests/resolve-hello'; 3 | import { js } from '../../tests/hello-component'; 4 | import { createResolverContext, setSelfIndex } from './context'; 5 | import { createComponent, instantiateComponent } from './index'; 6 | import { ResolverContext } from './types'; 7 | import { parse } from '../parser'; 8 | import { setConfiguration } from '../utils/assert'; 9 | // import { writeToFile } from '../../tests/utils'; 10 | 11 | setConfiguration('Debug'); 12 | 13 | describe('resolver hello', () => { 14 | test('resolver compiles component from fake model', async () => { 15 | const component = await createComponent(expectedModel); 16 | expect(component).toBeDefined(); 17 | }); 18 | 19 | test('component hello.wasm could run', async () => { 20 | let actualMessage: string = undefined as any; 21 | 22 | const imports: js.NamedImports = { 23 | 'hello:city/city': { 24 | sendMessage: (message: string) => { 25 | //console.log('sendMessage in test ', message); 26 | actualMessage = message; 27 | } 28 | } 29 | }; 30 | 31 | const instance = await instantiateComponent('./hello/wasm/hello.wasm', imports); 32 | const run = instance.exports['hello:city/greeter'].run; 33 | 34 | run({ 35 | name: 'Prague', 36 | headCount: 1_000_000, 37 | budget: BigInt(200_000_000), 38 | }); 39 | expect(actualMessage).toBe('Welcome to Prague, we invite you for a drink!'); 40 | 41 | actualMessage = undefined as any; 42 | run({ 43 | name: 'Kladno', 44 | headCount: 100_000, 45 | budget: 0n, 46 | }); 47 | expect(actualMessage).toBe('Welcome to Kladno!'); 48 | 49 | }); 50 | 51 | test('manual resolve indexes', async () => { 52 | const actualContext = createResolverContext(expectedModel, {}); 53 | // writeToFile('actual-hello.json', JSON.stringify(actualContext, null, 2)); 54 | // writeToFile('expected-hello.json', JSON.stringify(expectedContext, null, 2)); 55 | const expectedContextCpy = { ...expectedContext } as ResolverContext; 56 | setSelfIndex(expectedContextCpy); 57 | expect(actualContext.indexes).toEqual(expectedContext.indexes); 58 | }); 59 | 60 | test('JCO rewrite works', async () => { 61 | const parsedModel = await parse('./hello/wasm/hello.wasm'); 62 | 63 | let actualMessage: string = undefined as any; 64 | const imports: js.NamedImports = { 65 | 'hello:city/city': { 66 | sendMessage: (message: string) => { 67 | actualMessage = message; 68 | } 69 | } 70 | }; 71 | 72 | const instance = await resolveJCO(parsedModel, imports); 73 | 74 | instance.exports['hello:city/greeter'].run({ 75 | name: 'Prague', 76 | headCount: 1_000_000, 77 | budget: BigInt(200_000_000), 78 | }); 79 | expect(actualMessage).toBe('Welcome to Prague, we invite you for a drink!'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /src/resolver/index.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '../parser'; 2 | import { ParserOptions } from '../parser/types'; 3 | import { isDebug } from '../utils/assert'; 4 | import { JsImports, WasmComponentInstance, WasmComponent } from './api-types'; 5 | import { resolveComponentExport } from './component-exports'; 6 | import { resolveComponentImport } from './component-imports'; 7 | import { createBindingContext, createResolverContext } from './context'; 8 | import { resolveCoreInstance } from './core-instance'; 9 | import { ComponentFactoryInput, ComponentFactoryOptions, ResolverContext, ResolverRes } from './types'; 10 | 11 | export async function instantiateComponent( 12 | modelOrComponentOrUrl: ComponentFactoryInput, 13 | imports?: JsImports, 14 | options?: ComponentFactoryOptions & ParserOptions, 15 | ): Promise> { 16 | let input = modelOrComponentOrUrl as any; 17 | if (typeof input !== 'object' || (Array.isArray(input) && input.length != 0 && typeof input[0] !== 'object')) { 18 | input = await parse(input, options ?? {}); 19 | } 20 | const component = await createComponent(input, options); 21 | return component.instantiate(imports); 22 | } 23 | 24 | export async function createComponent(modelOrComponentOrUrl: ComponentFactoryInput, options?: ComponentFactoryOptions & ParserOptions): Promise> { 25 | let input = modelOrComponentOrUrl as any; 26 | if (typeof input !== 'object' || (Array.isArray(input) && input.length != 0 && typeof input[0] !== 'object')) { 27 | input = await parse(input, options ?? {}); 28 | } 29 | 30 | const rctx: ResolverContext = createResolverContext(input, options ?? {}); 31 | 32 | for (const coreModule of rctx.indexes.coreModules) { 33 | await coreModule.module; 34 | } 35 | const coreInstanceResolutions: ResolverRes[] = []; 36 | for (const coreInstance of rctx.indexes.coreInstances) { 37 | const resolution = resolveCoreInstance(rctx, { element: coreInstance, callerElement: undefined }); 38 | coreInstanceResolutions.push(resolution); 39 | } 40 | const componentImportResolutions: ResolverRes[] = []; 41 | for (const componentImport of rctx.indexes.componentImports) { 42 | const resolution = resolveComponentImport(rctx, { element: componentImport, callerElement: undefined }); 43 | componentImportResolutions.push(resolution); 44 | } 45 | const componentExportResolutions: ResolverRes[] = []; 46 | for (const componentExport of rctx.indexes.componentExports) { 47 | const resolution = resolveComponentExport(rctx, { element: componentExport, callerElement: undefined }); 48 | componentExportResolutions.push(resolution); 49 | } 50 | 51 | async function instantiate(componentImports?: JsImports) { 52 | componentImports = componentImports ?? {}; 53 | const ctx = createBindingContext(rctx, componentImports); 54 | 55 | const imports = {}; 56 | for (const componentImportResolution of componentImportResolutions) { 57 | const args = { 58 | imports: componentImports 59 | }; 60 | if (isDebug) (args as any)['debugStack'] = []; 61 | const componentImportResult = await componentImportResolution.binder(ctx, args); 62 | Object.assign(imports, componentImportResult.result); 63 | } 64 | 65 | const exports = {}; 66 | for (const componentExportResolution of componentExportResolutions) { 67 | const args = {}; 68 | if (isDebug) (args as any)['debugStack'] = []; 69 | const componentExportResult = await componentExportResolution.binder(ctx, args); 70 | Object.assign(exports, componentExportResult.result); 71 | } 72 | 73 | // this is magic, because some core instances are not exported, but they are still needed 74 | // I think this is about $imports 75 | for (const instanceResolution of coreInstanceResolutions) { 76 | const args = {}; 77 | if (isDebug) (args as any)['debugStack'] = []; 78 | await instanceResolution.binder(ctx, args); 79 | } 80 | 81 | return { 82 | exports, 83 | abort: ctx.abort, 84 | } as any as WasmComponentInstance; 85 | } 86 | const component: WasmComponent = { 87 | instantiate, 88 | }; 89 | return component; 90 | } -------------------------------------------------------------------------------- /src/resolver/types.ts: -------------------------------------------------------------------------------- 1 | import { TCabiRealloc, WasmPointer, WasmSize } from './binding/types'; 2 | import { ComponentAliasCoreInstanceExport, ComponentFunction, CoreFunction } from '../model/aliases'; 3 | import { ComponentExport } from '../model/exports'; 4 | import { ComponentImport } from '../model/imports'; 5 | import { CoreInstance, ComponentInstance } from '../model/instances'; 6 | import { ComponentTypeResource, ComponentType } from '../model/types'; 7 | import { WITModel } from '../parser'; 8 | import { CoreModule, ComponentSection } from '../parser/types'; 9 | import { ModelElement } from '../model/tags'; 10 | import { JsImports, WasmComponentInstance } from './api-types'; 11 | 12 | export type ComponentFactoryOptions = { 13 | useNumberForInt64?: boolean 14 | wasmInstantiate?: typeof WebAssembly.instantiate 15 | } 16 | 17 | export type ComponentFactoryInput = WITModel 18 | | string 19 | | ArrayLike 20 | | ReadableStream 21 | | Response 22 | | PromiseLike 23 | 24 | 25 | export type IndexedModel = { 26 | coreModules: CoreModule[] 27 | coreInstances: CoreInstance[], 28 | coreFunctions: CoreFunction[] 29 | coreMemories: ComponentAliasCoreInstanceExport[] 30 | coreGlobals: ComponentAliasCoreInstanceExport[] 31 | coreTables: ComponentAliasCoreInstanceExport[] 32 | 33 | componentImports: ComponentImport[] 34 | componentExports: ComponentExport[] 35 | componentInstances: ComponentInstance[], 36 | componentTypeResource: ComponentTypeResource[], 37 | componentFunctions: ComponentFunction[], 38 | componentTypes: ComponentType[], 39 | componentSections: ComponentSection[]// append to componentTypes 40 | } 41 | 42 | export type ResolverContext = { 43 | indexes: IndexedModel; 44 | usesNumberForInt64: boolean 45 | wasmInstantiate: (moduleObject: WebAssembly.Module, importObject?: WebAssembly.Imports) => Promise 46 | } 47 | 48 | export type BindingContext = { 49 | componentImports: JsImports 50 | coreInstances: BinderRes[]; 51 | componentInstances: BinderRes[] 52 | initializeMemory(memory: WebAssembly.Memory): void; 53 | initializeRealloc(cabi_realloc: TCabiRealloc): void; 54 | utf8Decoder: TextDecoder; 55 | utf8Encoder: TextEncoder; 56 | getMemory: () => WebAssembly.Memory; 57 | getView: (ptr: WasmPointer, len: WasmSize) => DataView; 58 | getViewU8: (ptr: WasmPointer, len: WasmSize) => Uint8Array; 59 | alloc: (newSize: WasmSize, align: WasmSize) => WasmPointer; 60 | realloc: (oldPtr: WasmPointer, oldSize: WasmSize, align: WasmSize, newSize: WasmSize) => WasmPointer; 61 | readI32: (ptr: WasmPointer) => number; 62 | writeI32: (ptr: WasmPointer, value: number) => void; 63 | abort: () => void; 64 | debugStack?: string[]; 65 | } 66 | 67 | export type Resolver = (rctx: ResolverContext, args: ResolverArgs) => ResolverRes 68 | export type Binder = (bctx: BindingContext, args: BinderArgs) => Promise 69 | 70 | export type ResolverArgs = { 71 | callerElement: ModelElement 72 | element: TModelElement 73 | } 74 | 75 | export type ResolverRes = { 76 | callerElement: ModelElement 77 | element: ModelElement 78 | binder: Binder 79 | } 80 | 81 | export type BinderArgs = { 82 | callerArgs?: BinderArgs 83 | arguments?: any[] 84 | imports?: any 85 | debugStack?: string[] 86 | } 87 | 88 | export type BinderRes = { 89 | result: any 90 | } 91 | -------------------------------------------------------------------------------- /src/resolver/zoo.would-be-test.ts: -------------------------------------------------------------------------------- 1 | import { instantiateComponent } from './index'; 2 | import { setConfiguration } from '../utils/assert'; 3 | import type { ZooFoodFood } from '../../tests/zoo-food-food'; 4 | import type { ZooFoodEater } from '../../tests/zoo-food-eater'; 5 | 6 | type TFeed = typeof ZooFoodEater.feed 7 | type TZooFoodFood = typeof ZooFoodFood 8 | 9 | setConfiguration('Debug'); 10 | 11 | describe('resolver hello', () => { 12 | test.failing('component zoo.wasm could run', async () => { 13 | let actualMessage: string = undefined as any; 14 | let actualFood: any = undefined as any; 15 | 16 | const zooFood: TZooFoodFood = { 17 | hideFood: (food, message) => { 18 | actualMessage = message; 19 | actualFood = food; 20 | }, 21 | consumeFood: (foodinfo, packageinfo, message) => { 22 | actualMessage = message; 23 | }, 24 | openPackage: (packageinfo, message) => { 25 | actualMessage = message; 26 | }, 27 | trashPackage: (sealingstate, message) => { 28 | actualMessage = message; 29 | } 30 | }; 31 | const imports = { 32 | 'zoo:food/food': zooFood 33 | }; 34 | 35 | const instance = await instantiateComponent('./zoo/wasm/zoo.wasm', imports); 36 | const feed = instance.exports['zoo:food/eater'].feed as TFeed; 37 | 38 | feed({ 39 | name: 'apple', 40 | isoCode: 'cz', 41 | weight: 1, 42 | healthy: true, 43 | calories: 1n, 44 | cost: 1, 45 | rating: 1, 46 | pieces: 1, 47 | shelfTemperature: [1, 1], 48 | cookTimeInMinutes: 1, 49 | }, { 50 | nutrition: { 51 | percentage: 1, 52 | nutritionType: 'carbohyrdate', 53 | }, 54 | material: { tag: 'plastic-bag' }, 55 | sealing: 'closed', 56 | }); 57 | 58 | expect(actualMessage).toBe('Welcome to Prague, we invite you for a drink!'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/utils/assert.ts: -------------------------------------------------------------------------------- 1 | // TODO inline rollup macro 2 | export function jsco_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition { 3 | if (condition) return; 4 | const message = 'Assert failed: ' + (typeof messageFactory === 'function' 5 | ? messageFactory() 6 | : messageFactory); 7 | throw new Error(message); 8 | } 9 | 10 | // TODO figure out how to get jest to use virtual modules 11 | export let configuration = 'Debug'; 12 | export let isDebug = false; 13 | export function setConfiguration(value: string) { 14 | configuration = value; 15 | isDebug = value === 'Debug'; 16 | } 17 | 18 | export function debugStack(src: any, target: any, position: string) { 19 | if (!isDebug) return; 20 | const orig = src['debugStack'] ?? []; 21 | target['debugStack'] = [position, ...(orig)]; 22 | } -------------------------------------------------------------------------------- /src/utils/fetch-like.ts: -------------------------------------------------------------------------------- 1 | const isNode = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string'; 2 | 3 | export function fetchLike(url: string) { 4 | const isFileUrl = url.startsWith('file://'); 5 | const isHttpUrl = url.startsWith('https://') || url.startsWith('http://'); 6 | if (isNode && (isFileUrl || !isHttpUrl)) { 7 | return import('fs/promises').then((fs) => { 8 | return fs.readFile(url); 9 | }); 10 | } 11 | if (typeof globalThis.fetch !== 'function') { 12 | throw new Error('globalThis.fetch is not a function'); 13 | } 14 | return globalThis.fetch(url); 15 | } 16 | 17 | export async function getBodyIfResponse( 18 | input: 19 | | ArrayLike 20 | | ReadableStream 21 | | Response 22 | | PromiseLike, 23 | ): Promise | ReadableStream> { 24 | if ('length' in input || 'getReader' in input) { 25 | return input; 26 | } 27 | if ('body' in input) { 28 | return getBodyIfResponse(input.body!); 29 | } 30 | if ('then' in input) { 31 | return getBodyIfResponse(await input); 32 | } 33 | throw new Error('I got ' + typeof input); 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/streaming.ts: -------------------------------------------------------------------------------- 1 | // adapted from https://github.com/yskszk63/stream-wasm-parser by yusuke suzuki under MIT License 2 | 3 | export interface Closeable { 4 | close(): void; 5 | } 6 | 7 | export interface Source { 8 | get pos(): number; 9 | read(): Promise; 10 | read(eof: true): Promise; 11 | readExact(n: number): Promise; 12 | skip(n: number): Promise; 13 | subSource(limit: number): Source; 14 | subSyncSource(limit: number): Promise; 15 | readAvailable(limit: number): Promise; 16 | } 17 | 18 | export interface SyncSource { 19 | get pos(): number; 20 | get remaining(): number; 21 | read(): number; 22 | read(eof: true): number | null; 23 | readExact(n: number): Uint8Array; 24 | } 25 | 26 | export function newSource(items: ArrayLike): Source & Closeable; 27 | export function newSource(items: ReadableStream): Source & Closeable; 28 | export function newSource(items: ArrayLike | ReadableStream,): Source & Closeable; 29 | export function newSource(items: ArrayLike | ReadableStream,): Source & Closeable { 30 | if ('getReader' in items) { 31 | return new StreamSource(items); 32 | } else { 33 | return new ArraySource(items); 34 | } 35 | } 36 | 37 | class StreamSource { 38 | total: number; 39 | bpos: number; 40 | buf: Uint8Array | null; 41 | reader: ReadableStreamDefaultReader; 42 | 43 | constructor(stream: ReadableStream) { 44 | this.total = 0; 45 | this.bpos = 0; 46 | this.buf = null; 47 | this.reader = stream.getReader(); 48 | } 49 | 50 | get pos(): number { 51 | return this.total; 52 | } 53 | 54 | async fill(need: number): Promise<'EOF' | Uint8Array> { 55 | if (this.buf === null || this.buf.length - this.bpos < 1) { 56 | const result = await this.reader.read(); 57 | if (result.done) { 58 | return 'EOF'; 59 | } 60 | this.buf = result.value; 61 | this.bpos = 0; 62 | } 63 | 64 | const start = this.bpos; 65 | const len = Math.min(this.buf.length - start, need); 66 | this.bpos += len; 67 | this.total += len; 68 | return this.buf.subarray(start, start + len); 69 | } 70 | 71 | async readAvailable(limit: number): Promise { 72 | if (this.buf === null || this.buf.length - this.bpos < 1) { 73 | const result = await this.reader.read(); 74 | if (result.done) { 75 | return null; 76 | } 77 | this.buf = result.value; 78 | this.bpos = 0; 79 | } 80 | 81 | const start = this.bpos; 82 | const len = Math.min(this.buf.length - start, limit); 83 | this.bpos += len; 84 | this.total += len; 85 | return this.buf.subarray(start, start + len); 86 | } 87 | 88 | read(): Promise; 89 | read(eof: true): Promise; 90 | async read(eof?: true): Promise { 91 | const maybebuf = await this.fill(1); 92 | if (maybebuf === 'EOF') { 93 | if (eof === true) { 94 | return null; 95 | } else { 96 | throw new Error('unexpected EOF.'); 97 | } 98 | } 99 | return maybebuf[0]; 100 | } 101 | 102 | async readExact(n: number): Promise { 103 | const r = new Uint8Array(n); 104 | let rest = n; 105 | while (rest > 0) { 106 | const maybebuf = await this.fill(rest); 107 | if (maybebuf === 'EOF') { 108 | throw new Error('unexpected EOF.'); 109 | } 110 | r.set(maybebuf, r.length - rest); 111 | rest -= maybebuf.length; 112 | } 113 | return r; 114 | } 115 | 116 | async skip(n: number): Promise { 117 | if (n < 0) { 118 | throw new Error('illegal argument.'); 119 | } 120 | let rest = n; 121 | while (rest > 0) { 122 | const maybebuf = await this.fill(rest); 123 | if (maybebuf === 'EOF') { 124 | throw new Error('unexpected EOF.'); 125 | } 126 | rest -= maybebuf.length; 127 | } 128 | } 129 | 130 | subSource(limit: number): Source { 131 | return new SubSource(this, limit); 132 | } 133 | 134 | async subSyncSource(limit: number): Promise { 135 | const buf = await this.readExact(limit); 136 | return new SyncArraySource(buf); 137 | } 138 | 139 | close(): void { 140 | this.reader.releaseLock(); 141 | } 142 | } 143 | 144 | class ArraySource { 145 | _pos: number; 146 | items: Uint8Array; 147 | 148 | constructor(items: ArrayLike) { 149 | this._pos = 0; 150 | this.items = Uint8Array.from(items); 151 | } 152 | 153 | get pos(): number { 154 | return this._pos; 155 | } 156 | 157 | readAvailable(limit: number): Promise { 158 | if (this.items.byteLength === 0 || this._pos == this.items.byteLength || limit === 0) { 159 | return Promise.resolve(null); 160 | } 161 | const max = Math.min(this.items.byteLength - this._pos, limit); 162 | const r = this.items.subarray(this._pos, this._pos + max); 163 | this._pos += r.byteLength; 164 | return Promise.resolve(r); 165 | } 166 | 167 | read(): Promise; 168 | read(eof: true): Promise; 169 | read(eof?: true): Promise { 170 | try { 171 | if (this.items.length - this._pos < 1) { 172 | if (eof) { 173 | return Promise.resolve(null); 174 | } else { 175 | throw new Error('unexpected EOF.'); 176 | } 177 | } 178 | const r = this.items[this._pos]; 179 | this._pos += 1; 180 | return Promise.resolve(r); 181 | } catch (e) { 182 | return Promise.reject(e); 183 | } 184 | } 185 | 186 | readExact(n: number): Promise { 187 | try { 188 | if (n === 0) { 189 | return Promise.resolve(Uint8Array.from([])); 190 | } 191 | if (n < 1) { 192 | throw new Error('illegal argument.'); 193 | } 194 | if (this.items.length - this._pos < n) { 195 | throw new Error('unexpected EOF.'); 196 | } 197 | const r = this.items.subarray(this._pos, this._pos + n); 198 | this._pos += n; 199 | return Promise.resolve(r); 200 | } catch (e) { 201 | return Promise.reject(e); 202 | } 203 | } 204 | 205 | skip(n: number): Promise { 206 | try { 207 | if (n === 0) { 208 | return Promise.resolve(); 209 | } 210 | if (n < 1) { 211 | throw new Error('illegal argument.'); 212 | } 213 | if (this.items.length - this._pos < n) { 214 | throw new Error('unexpected EOF.'); 215 | } 216 | this._pos += n; 217 | return Promise.resolve(); 218 | } catch (e) { 219 | return Promise.reject(e); 220 | } 221 | } 222 | 223 | subSource(limit: number): Source { 224 | return new SubSource(this, limit); 225 | } 226 | 227 | async subSyncSource(limit: number): Promise { 228 | const buf = await this.readExact(limit); 229 | return new SyncArraySource(buf); 230 | } 231 | 232 | close(): void { 233 | // nop 234 | } 235 | } 236 | 237 | class SubSource { 238 | delegate: Source; 239 | rest: number; 240 | 241 | constructor(delegate: Source, limit: number) { 242 | if (limit < 0) { 243 | throw new Error('illegal argument.'); 244 | } 245 | this.delegate = delegate; 246 | this.rest = limit; 247 | } 248 | 249 | get pos(): number { 250 | return this.delegate.pos; 251 | } 252 | 253 | async readAvailable(limit: number): Promise { 254 | const max = Math.min(limit, this.rest); 255 | const data = await this.delegate.readAvailable(max); 256 | if (data) { 257 | this.rest -= data.byteLength; 258 | } 259 | return data; 260 | } 261 | 262 | read(): Promise; 263 | read(eof: true): Promise; 264 | async read(eof?: true): Promise { 265 | this.checkLimit(1); 266 | let r; 267 | if (eof === true) { 268 | r = await this.delegate.read(true); 269 | } else { 270 | r = await this.delegate.read(); 271 | } 272 | if (r) { 273 | this.rest -= 1; 274 | } 275 | return r; 276 | } 277 | 278 | async readExact(n: number): Promise { 279 | this.checkLimit(n); 280 | const r = await this.delegate.readExact(n); 281 | this.rest -= r.length; 282 | return r; 283 | } 284 | 285 | async skip(n: number): Promise { 286 | this.checkLimit(n); 287 | await this.delegate.skip(n); 288 | this.rest -= n; 289 | } 290 | 291 | subSource(limit: number): Source { 292 | return new SubSource(this, limit); 293 | } 294 | 295 | async subSyncSource(limit: number): Promise { 296 | const buf = await this.readExact(limit); 297 | return new SyncArraySource(buf); 298 | } 299 | 300 | checkLimit(needs: number) { 301 | if (this.rest < needs) { 302 | throw new Error('limit reached.'); 303 | } 304 | } 305 | } 306 | 307 | class SyncArraySource implements SyncSource { 308 | _pos: number; 309 | items: Uint8Array; 310 | 311 | constructor(items: ArrayLike) { 312 | this._pos = 0; 313 | this.items = Uint8Array.from(items); 314 | } 315 | 316 | get pos(): number { 317 | return this._pos; 318 | } 319 | 320 | get remaining(): number { 321 | return this.items.length - this._pos; 322 | } 323 | 324 | read(): number; 325 | read(eof: true): number | null; 326 | read(eof?: true): number | null { 327 | if (this.items.length - this._pos < 1) { 328 | if (eof) { 329 | null; 330 | } else { 331 | throw new Error('unexpected EOF.'); 332 | } 333 | } 334 | const r = this.items[this._pos]; 335 | this._pos += 1; 336 | return r; 337 | } 338 | 339 | readExact(n: number): Uint8Array { 340 | if (n === 0) { 341 | return Uint8Array.from([]); 342 | } 343 | if (n < 1) { 344 | throw new Error('illegal argument.'); 345 | } 346 | if (this.items.length - this._pos < n) { 347 | throw new Error('unexpected EOF.'); 348 | } 349 | const r = this.items.subarray(this._pos, this._pos + n); 350 | this._pos += n; 351 | return r; 352 | } 353 | 354 | skip(n: number): void { 355 | if (n === 0) { 356 | return; 357 | } 358 | if (n < 1) { 359 | throw new Error('illegal argument.'); 360 | } 361 | if (this.items.length - this._pos < n) { 362 | throw new Error('unexpected EOF.'); 363 | } 364 | this._pos += n; 365 | } 366 | } 367 | 368 | 369 | export function bufferToHex(data: Uint8Array) { 370 | return data.reduce((t, x) => t + ' ' + x.toString(16).padStart(2, '0'), ''); 371 | } -------------------------------------------------------------------------------- /tests/hello-component.d.ts: -------------------------------------------------------------------------------- 1 | import { AbiPointer, TCabiRealloc } from '../src/resolver/binding/types'; 2 | 3 | declare module js { 4 | export type CityInfo = { 5 | name: string, 6 | headCount: number, 7 | budget: bigint, 8 | } 9 | 10 | export type Imports = { 11 | sendMessage: (message: string) => void; 12 | } 13 | 14 | export type Exports = { 15 | run: (info: CityInfo) => void; 16 | } 17 | 18 | export type NamedImports = { 19 | 'hello:city/city': Imports 20 | } 21 | 22 | export type NamedExports = { 23 | 'hello:city/greeter': Exports 24 | } 25 | } 26 | 27 | declare module wasm { 28 | export type module0Exports = { 29 | memory: WebAssembly.Memory 30 | cabi_realloc: TCabiRealloc 31 | '__data_end': WebAssembly.Global 32 | '__heap_base': WebAssembly.Global 33 | // TODO budget: number is wrong, should be BigInt 34 | 'hello:city/greeter#run': (namePtr: AbiPointer, nameLen: AbiPointer, headCount: number, budget: number) => void, 35 | } 36 | 37 | export type module0Imports = { 38 | 'hello:city/city': { 39 | 'send-message': (prt: AbiPointer, len: AbiPointer) => void 40 | } 41 | } 42 | 43 | export type module2Imports = { 44 | '': { 45 | $imports: WebAssembly.Table 46 | '0': (prt: AbiPointer, len: AbiPointer) => void 47 | } 48 | } 49 | 50 | export type module1Exports = { 51 | '0': (prt: AbiPointer, len: AbiPointer) => void, 52 | $imports: WebAssembly.Table 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/resolve-hello.ts: -------------------------------------------------------------------------------- 1 | // this is a model written by hand, so that we can test the parser and resolver early on 2 | // it should match ./hello.wat (delta mistakes) 3 | 4 | import { BindingContext, ResolverContext } from '../src/resolver/types'; 5 | import { ModelTag } from '../src/model/tags'; 6 | import { js, wasm } from './hello-component'; 7 | import { TCabiRealloc, WasmPointer } from '../src/resolver/binding/types'; 8 | import { jsco_assert } from '../src/utils/assert'; 9 | import { 10 | aliasCoreExportFunc0, aliasCoreExportFunc1, aliasCoreExportFunc3, 11 | aliasCoreExportMemory0, aliasCoreExportTable0, aliasExport0, 12 | aliasExportType1, aliasExportType3, canonicalFuncLift1, canonicalFuncLower2, 13 | componentExport0, componentImport0, componentInstance1, componentTypeComponent0, 14 | componentTypeFunc2, componentTypeInstance0, 15 | coreInstance0, coreInstance1, coreInstance2, coreInstance3, coreInstance4, 16 | coreModule0, coreModule1, coreModule2 17 | } from './hello'; 18 | import { PrimitiveValType } from '../src/model/types'; 19 | import { createBindingContext, createResolverContext } from '../src/resolver/context'; 20 | import { WITModel } from '../src/parser'; 21 | import { createLifting, createLowering } from '../src/resolver/binding'; 22 | 23 | export const expectedContext: Partial = { 24 | usesNumberForInt64: false, 25 | indexes: { 26 | componentExports: [componentExport0], 27 | componentImports: [componentImport0], 28 | componentFunctions: [aliasExport0, canonicalFuncLift1], 29 | componentInstances: [componentTypeInstance0, componentInstance1], 30 | componentTypes: [componentTypeComponent0, aliasExportType1, componentTypeFunc2, aliasExportType3], 31 | componentTypeResource: [], 32 | 33 | coreModules: [coreModule0, coreModule1, coreModule2], 34 | coreInstances: [coreInstance0, coreInstance1, coreInstance2, coreInstance3, coreInstance4], 35 | coreFunctions: [aliasCoreExportFunc0, aliasCoreExportFunc1, canonicalFuncLower2, aliasCoreExportFunc3], 36 | coreMemories: [aliasCoreExportMemory0], 37 | coreTables: [aliasCoreExportTable0], 38 | coreGlobals: [], 39 | componentSections: [componentTypeComponent0], 40 | }, 41 | }; 42 | 43 | export async function resolveJCO(sections: WITModel, imports: any) { 44 | const rctx: ResolverContext = createResolverContext(sections, {}); 45 | const ctx: BindingContext = createBindingContext(rctx, imports); 46 | const wasmInstantiate = WebAssembly.instantiate; 47 | 48 | const componentImports = (imports ? imports : {}) as { 49 | 'hello:city/city': js.Imports, 50 | }; 51 | 52 | const { sendMessage } = componentImports['hello:city/city']; 53 | const stringToJs = createLowering(rctx, { 54 | tag: ModelTag.ComponentValTypePrimitive, 55 | value: PrimitiveValType.String, 56 | }); 57 | 58 | const stringFromJs = createLifting(rctx, { 59 | tag: ModelTag.ComponentValTypePrimitive, 60 | value: PrimitiveValType.String, 61 | }); 62 | 63 | const numberToUint32 = createLifting(rctx, { 64 | tag: ModelTag.ComponentValTypePrimitive, 65 | value: PrimitiveValType.U32, 66 | }); 67 | 68 | const bigIntToInt64 = createLifting(rctx, { 69 | tag: ModelTag.ComponentValTypePrimitive, 70 | value: PrimitiveValType.S64, 71 | }); 72 | 73 | function sendMessageFromAbi(ptr: WasmPointer, len: WasmPointer) { 74 | const ptr0 = ptr; 75 | const len0 = len; 76 | const result0 = stringToJs(ctx, ptr0, len0); 77 | sendMessage(result0 as any); 78 | } 79 | 80 | const module0: WebAssembly.Module = await rctx.indexes.coreModules[0].module!; 81 | const module1: WebAssembly.Module = await rctx.indexes.coreModules[1].module!; 82 | const module2: WebAssembly.Module = await rctx.indexes.coreModules[2].module!; 83 | 84 | const instance1 = await wasmInstantiate(module1); 85 | const exports1 = instance1.exports as wasm.module1Exports; 86 | 87 | const fn0 = exports1['0']; 88 | //console.log('fn0', fn0.length); 89 | 90 | const imports0: wasm.module0Imports = { 91 | 'hello:city/city': { 92 | 'send-message': (...args) => { 93 | const rr = fn0(...args); 94 | //console.log('send-message', args, rr); 95 | return rr; 96 | }, 97 | }, 98 | }; 99 | const instance0 = await wasmInstantiate(module0, imports0); 100 | const exports0 = instance0.exports as wasm.module0Exports; 101 | 102 | const memory0 = exports0.memory as WebAssembly.Memory; 103 | ctx.initializeMemory(memory0); 104 | const cabi_realloc: TCabiRealloc = exports0.cabi_realloc; 105 | ctx.initializeRealloc(cabi_realloc); 106 | 107 | const imports2: wasm.module2Imports = { 108 | '': { 109 | $imports: exports1.$imports, 110 | '0': sendMessageFromAbi, 111 | }, 112 | }; 113 | 114 | const instance2 = await wasmInstantiate(module2, imports2); 115 | 116 | function runToAbi(info: js.CityInfo) { 117 | const args = [ 118 | ...stringFromJs(ctx, info.name), 119 | numberToUint32(ctx, info.headCount), 120 | bigIntToInt64(ctx, info.budget), 121 | ]; 122 | exports0['hello:city/greeter#run'].apply(null, args as any); 123 | } 124 | 125 | const greeter0_1_0: js.Exports = { 126 | run: runToAbi, 127 | }; 128 | 129 | return { 130 | exports: { 131 | 'hello:city/greeter': greeter0_1_0, 132 | } 133 | }; 134 | } -------------------------------------------------------------------------------- /tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'node:fs'; 2 | 3 | export function writeToFile(name: string, content: string) { 4 | writeFileSync(`./tests/${name}`, content); 5 | } -------------------------------------------------------------------------------- /tests/zoo-food-eater.d.ts: -------------------------------------------------------------------------------- 1 | export namespace ZooFoodEater { 2 | export function feed(foodinfo: FoodInfo, packageinfo: PackageInfo): void; 3 | } 4 | import type { FoodInfo } from '../exports/zoo-food-food'; 5 | export { FoodInfo }; 6 | import type { PackageInfo } from '../exports/zoo-food-food'; 7 | export { PackageInfo }; 8 | -------------------------------------------------------------------------------- /tests/zoo-food-food.d.ts: -------------------------------------------------------------------------------- 1 | export namespace ZooFoodFood { 2 | export function hideFood(food: FoodInfo, message: string): void; 3 | export function consumeFood(foodinfo: FoodInfo, packageinfo: PackageInfo, message: string): void; 4 | export function openPackage(packageinfo: PackageInfo, message: string): void; 5 | export function trashPackage(sealingstate: SealingState, message: string): void; 6 | } 7 | export interface FoodInfo { 8 | name: string, 9 | isoCode: string, 10 | weight: number, 11 | healthy: boolean, 12 | calories: bigint, 13 | cost: number, 14 | rating: number, 15 | pieces: number, 16 | shelfTemperature: [number, number], 17 | cookTimeInMinutes: number, 18 | } 19 | /** 20 | * # Variants 21 | * 22 | * ## `"carbohyrdate"` 23 | * 24 | * ## `"protein"` 25 | * 26 | * ## `"vitamin"` 27 | */ 28 | export type NutritionType = 'carbohyrdate' | 'protein' | 'vitamin'; 29 | export interface NutritionInfo { 30 | percentage: number, 31 | nutritionType: NutritionType, 32 | } 33 | export type MaterialType = MaterialTypePlasticBag | MaterialTypeMetalCan; 34 | export interface MaterialTypePlasticBag { 35 | tag: 'plastic-bag', 36 | } 37 | export interface MaterialTypeMetalCan { 38 | tag: 'metal-can', 39 | } 40 | /** 41 | * # Variants 42 | * 43 | * ## `"opened"` 44 | * 45 | * ## `"closed"` 46 | * 47 | * ## `"damaged"` 48 | */ 49 | export type SealingState = 'opened' | 'closed' | 'damaged'; 50 | export interface PackageInfo { 51 | nutrition: NutritionInfo, 52 | material: MaterialType, 53 | sealing: SealingState, 54 | } 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "noEmitOnError": true, 5 | "removeComments": false, 6 | "module": "ES2022", 7 | "sourceMap": true, 8 | "target": "ES2022", 9 | "moduleResolution": "Node", 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "skipLibCheck": true, 13 | "outDir": "./dist", 14 | "strict": true, 15 | "lib": [ 16 | "esnext", 17 | "dom" 18 | ], 19 | "types": [ 20 | "node", 21 | "jest" 22 | ], 23 | "rootDirs": [ 24 | "src" 25 | ] 26 | }, 27 | "include": [ 28 | "src/**/*", 29 | "src/.types.d.ts", 30 | "hello/**/*.ts", 31 | "zoo/**/*.ts", 32 | "tests/*.ts", 33 | "tests/*.d.ts", 34 | "usage.ts" 35 | ], 36 | "exclude": [ 37 | "node_modules", 38 | "dist", 39 | ] 40 | } -------------------------------------------------------------------------------- /usage.mjs: -------------------------------------------------------------------------------- 1 | import { createComponent } from './dist/index.js'; 2 | 3 | // this is url of your component 4 | // `hello.wasm` sample was built using `npm run build:hello` 5 | // the implementation is in `./hello/src/lib.rs` 6 | const componentUrl='./hello/wasm/hello.wasm'; 7 | 8 | // this is the component instance, see also `instantiateComponent` 9 | const component = await createComponent(componentUrl); 10 | 11 | // these are the imports that the component expects 12 | const imports = { 13 | 'hello:city/city': { 14 | sendMessage: console.log 15 | } 16 | }; 17 | 18 | // it has the following API `./hello/wit/hello.wit` 19 | const instance = await component.instantiate(imports); 20 | 21 | // exported namespaces contain the functions 22 | const exports = instance.exports; 23 | 24 | // this is the function that we want to call 25 | const run = exports['hello:city/greeter'].run; 26 | 27 | // run expects a cityInfo parameter 28 | const cityInfo = { 29 | name: 'Prague', 30 | headCount: 1_000_000, 31 | budget: BigInt(200_000_000), 32 | }; 33 | 34 | // call the WASM component's function 35 | run(cityInfo); 36 | 37 | // result type is void 38 | // And we should see 'Welcome to Prague, we invite you for a drink!' in the console -------------------------------------------------------------------------------- /usage2.mjs: -------------------------------------------------------------------------------- 1 | import { instantiateComponent } from './dist/index.js'; 2 | const instance = await instantiateComponent('./hello/wasm/hello.wasm', { 3 | 'hello:city/city': { sendMessage: console.log } 4 | }); 5 | const run = instance.exports['hello:city/greeter'].run; 6 | run({ name: 'Kladno', headCount: 100000, budget: 0n}); 7 | // prints 'Welcome to Kladno!' to the console -------------------------------------------------------------------------------- /zoo/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | rustflags = ["-Clink-arg=-zstack-size=16384","-Clink-arg=--max-memory=655360"] 3 | # TODO "-Clink-arg=--initial-memory=655360" 4 | -------------------------------------------------------------------------------- /zoo/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /zoo/Cargo-component.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by cargo-component. 2 | # It is not intended for manual editing. 3 | version = 1 4 | -------------------------------------------------------------------------------- /zoo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zoo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["pavel.savara@gmail.com"] 6 | 7 | [dependencies] 8 | cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component", rev = "36c221e41db3e87dec4c82eadcb9bc8f37626533" } 9 | wee_alloc = { version = "0.4.5" } 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [package.metadata.component] 15 | package = "zoo:food" 16 | 17 | [package.metadata.component.target] 18 | path = "wit" 19 | 20 | [package.metadata.component.dependencies] 21 | 22 | [profile.release] 23 | lto = true 24 | opt-level = 's' 25 | 26 | [build] 27 | incremental = true 28 | -------------------------------------------------------------------------------- /zoo/README.md: -------------------------------------------------------------------------------- 1 | # Build with cargo_component_bindings 2 | 3 | ```shell 4 | cargo component build --release --target wasm32-unknown-unknown 5 | jco transpile --instantiation --no-wasi-shim -b 0 --out-dir target/js-jco target/wasm32-unknown-unknown/release/zoo.wasm 6 | ``` 7 | -------------------------------------------------------------------------------- /zoo/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustc", "rustfmt", "rust-std", "cargo" ] 4 | targets = [ "wasm32-unknown-unknown", "wasm32-wasi" ] 5 | profile = "default" -------------------------------------------------------------------------------- /zoo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[global_allocator] 2 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 3 | 4 | cargo_component_bindings::generate!({ 5 | implementor: Eater, 6 | }); 7 | 8 | use bindings::exports::zoo::food::eater::Guest; 9 | use bindings::zoo::food::food::{ 10 | hide_food, consume_food, open_package, trash_package, 11 | FoodInfo, PackageInfo, NutritionType, SealingState, MaterialType}; 12 | use std::fmt; 13 | 14 | struct Eater; 15 | 16 | impl std::fmt::Display for MaterialType { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | f.write_str(match self { 19 | MaterialType::PlasticBag => "plastic bag", 20 | MaterialType::MetalCan => "metal can", 21 | }) 22 | } 23 | } 24 | 25 | impl Guest for Eater { 26 | fn feed(food_info: FoodInfo, package_info: PackageInfo) { 27 | if package_info.sealing == SealingState::Closed { 28 | open_package(package_info.sealing, 29 | package_info, 30 | &format!("Package type {} is now opened. Enjoy.", package_info.material)); 31 | } 32 | else if package_info.sealing == SealingState::Damaged { 33 | let vec = [package_info]; 34 | let _success = trash_package(&vec, 35 | &format!("Package type {} was damaged, you cannot eat this food.", package_info.material)); 36 | return; 37 | } 38 | 39 | if food_info.healthy && food_info.calories > 1000 { 40 | if package_info.sealing == SealingState::Opened && package_info.nutrition.nutrition_type == NutritionType::Protein && package_info.nutrition.percentage > 30.0 41 | { 42 | consume_food(&food_info, 43 | package_info, 44 | &format!("Eating {}", food_info.name)) 45 | } 46 | else 47 | { 48 | hide_food(&food_info, &format!( 49 | "Yum, {} should be hidden for later.", 50 | food_info.name 51 | )); 52 | } 53 | 54 | } else if food_info.cost > 100 { 55 | hide_food(&food_info, &format!("{}, come and have a bear hug!", food_info.name)); 56 | } else { 57 | hide_food(&food_info, &format!("{}? Yuk!", food_info.name)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /zoo/wasm/zoo.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavelsavara/jsco/14742a63af10b86c93b949f56e9e6f42d9d3122b/zoo/wasm/zoo.wasm -------------------------------------------------------------------------------- /zoo/wit/zoo.wit: -------------------------------------------------------------------------------- 1 | package zoo:food 2 | 3 | interface food { 4 | enum nutrition-type { 5 | carbohyrdate, 6 | protein, 7 | vitamin, 8 | } 9 | 10 | record nutrition-info { 11 | percentage: float64, 12 | nutrition-type: nutrition-type, 13 | } 14 | 15 | variant material-type { 16 | plastic-bag, 17 | metal-can, 18 | } 19 | 20 | // here I wanted to use flag but it cannot be found 21 | enum sealing-state { 22 | opened, 23 | closed, 24 | damaged, 25 | } 26 | 27 | record package-info { 28 | nutrition: nutrition-info, 29 | material: material-type, 30 | sealing: sealing-state, 31 | } 32 | 33 | // flat record, do not nest: 34 | // u32 is tested in hello 35 | record food-info { 36 | name: string, 37 | iso-code: char, 38 | weight: float32, 39 | healthy: bool, 40 | calories: u64, 41 | cost: u16, 42 | rating: s16, 43 | pieces: u8, 44 | // tuple (Celcius, Fahrenheit) 45 | shelf-temperature: tuple, 46 | cook-time-in-minutes: s32, 47 | } 48 | 49 | hide-food: func(food: food-info, message: string) 50 | consume-food: func(foodinfo: food-info, packageinfo: package-info, message: string) 51 | open-package: func(sealingstate: sealing-state, packageinfo: package-info, message: string) 52 | trash-package: func(trashed: list, message: string) -> bool 53 | } 54 | 55 | interface eater { 56 | use food.{food-info, package-info} 57 | feed: func(foodinfo: food-info, packageinfo: package-info) 58 | } 59 | 60 | world zoo { 61 | import food 62 | export eater 63 | } 64 | -------------------------------------------------------------------------------- /zoo/zoo-test.mjs: -------------------------------------------------------------------------------- 1 | import { instantiate } from "./target/js-jco/zoo.js" 2 | 3 | const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; 4 | let _fs; 5 | async function fetchCompile(url) { 6 | let self = import.meta.url.substring("file://".length); 7 | if (self.indexOf(":") === 2) { 8 | self = self.substring(1); 9 | } 10 | const u2 = self.substring(0, self.lastIndexOf("/"))+"/target/js-jco/"+url; 11 | if (isNode) { 12 | _fs = _fs || await import('fs/promises'); 13 | return WebAssembly.compile(await _fs.readFile(u2)); 14 | } 15 | return fetch(u2).then(WebAssembly.compileStreaming); 16 | } 17 | 18 | const expectdMessage='Yum, bananas should be hidden for later.'; 19 | let actualMessage; 20 | const imports = { 21 | 'zoo:food/food': { 22 | sendMessage: (message) => { 23 | actualMessage = message; 24 | console.log(message); 25 | } 26 | } 27 | } 28 | const component = await instantiate(fetchCompile, imports, WebAssembly.instantiate); 29 | const exports = component['eater']; 30 | exports.run({ 31 | name: 'bananas', 32 | weight: 10000.5, 33 | healthy: true, 34 | calories: BigInt(200000000), 35 | cost: 1234567, 36 | }); 37 | 38 | if (actualMessage !== expectdMessage) { 39 | throw new Error(`sendMessage: expected "${expectdMessage}" actual "${actualMessage}"`); 40 | } --------------------------------------------------------------------------------