├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── examples ├── add.wasm ├── fib.wasm └── wat │ ├── add.wat │ ├── fib.wat │ ├── global.wat │ └── if.wat ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── fuzz_targets │ └── decode.rs ├── script └── release ├── src ├── decode │ ├── decoder.rs │ ├── error.rs │ └── mod.rs ├── from_le.rs ├── instance │ └── mod.rs ├── instruction.rs ├── lib.rs ├── main.rs ├── module │ └── mod.rs ├── opcode.rs ├── runtime │ ├── activation_stack.rs │ ├── error.rs │ ├── function_table.rs │ ├── label_stack.rs │ ├── memory.rs │ ├── mod.rs │ └── runtime_value.rs └── types │ └── mod.rs └── tests └── testsuite.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [k-nasa] 4 | custom: ['https://twitter.com/nasa_desu'] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | - main 9 | 10 | jobs: 11 | build_and_test: 12 | name: Build and test 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macOS-latest] 17 | rust: [nightly, beta, stable] 18 | 19 | steps: 20 | - uses: actions/checkout@master 21 | 22 | - name: Install ${{ matrix.rust }} 23 | uses: actions-rs/toolchain@master 24 | with: 25 | toolchain: ${{ matrix.rust }} 26 | override: true 27 | 28 | - name: init 29 | run: | 30 | git submodule update --init --recursive 31 | 32 | 33 | - name: build 34 | uses: actions-rs/cargo@master 35 | with: 36 | command: build 37 | 38 | - name: test 39 | uses: actions-rs/cargo@master 40 | with: 41 | command: test 42 | 43 | check_fmt_and_docs: 44 | name: Checking fmt and docs 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@master 48 | 49 | - uses: actions-rs/toolchain@master 50 | with: 51 | profile: minimal 52 | toolchain: nightly 53 | override: true 54 | components: rustfmt 55 | 56 | - name: setup 57 | run: | 58 | rustup component add rustfmt 59 | rustc --version 60 | 61 | - name: fmt 62 | run: cargo fmt --all -- --check 63 | 64 | # fuzzing: 65 | # name: Fuzzing 66 | # runs-on: ${{ matrix.os }} 67 | # strategy: 68 | # matrix: 69 | # os: [ubuntu-latest, macOS-latest] 70 | # rust: [nightly] 71 | # 72 | # steps: 73 | # - uses: actions/checkout@master 74 | # 75 | # - name: Install ${{ matrix.rust }} 76 | # uses: actions-rs/toolchain@master 77 | # with: 78 | # toolchain: ${{ matrix.rust }} 79 | # override: true 80 | # 81 | # - name: fuzzing 82 | # run: | 83 | # cargo install cargo-fuzz 84 | # cargo fuzz run decode --jobs 4 -- -runs=10000 85 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'version' 7 | required: true 8 | 9 | jobs: 10 | release: 11 | runs-on: [macos-latest] 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Release 16 | run: ./script/release ${{ github.event.inputs.version }} ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "testsuite"] 2 | path = testsuite 3 | url = https://github.com/WebAssembly/testsuite.git 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.71" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.3.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 25 | 26 | [[package]] 27 | name = "cc" 28 | version = "1.0.78" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 31 | 32 | [[package]] 33 | name = "cfg-if" 34 | version = "1.0.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 37 | 38 | [[package]] 39 | name = "clap" 40 | version = "4.1.6" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" 43 | dependencies = [ 44 | "bitflags", 45 | "clap_derive", 46 | "clap_lex", 47 | "is-terminal", 48 | "once_cell", 49 | "strsim", 50 | "termcolor", 51 | ] 52 | 53 | [[package]] 54 | name = "clap_derive" 55 | version = "4.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 58 | dependencies = [ 59 | "heck", 60 | "proc-macro-error", 61 | "proc-macro2", 62 | "quote", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "clap_lex" 68 | version = "0.3.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 71 | dependencies = [ 72 | "os_str_bytes", 73 | ] 74 | 75 | [[package]] 76 | name = "env_logger" 77 | version = "0.10.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" 80 | dependencies = [ 81 | "humantime", 82 | "is-terminal", 83 | "log", 84 | "regex", 85 | "termcolor", 86 | ] 87 | 88 | [[package]] 89 | name = "errno" 90 | version = "0.2.8" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 93 | dependencies = [ 94 | "errno-dragonfly", 95 | "libc", 96 | "winapi", 97 | ] 98 | 99 | [[package]] 100 | name = "errno-dragonfly" 101 | version = "0.1.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 104 | dependencies = [ 105 | "cc", 106 | "libc", 107 | ] 108 | 109 | [[package]] 110 | name = "heck" 111 | version = "0.4.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 114 | 115 | [[package]] 116 | name = "hermit-abi" 117 | version = "0.2.6" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 120 | dependencies = [ 121 | "libc", 122 | ] 123 | 124 | [[package]] 125 | name = "humantime" 126 | version = "2.1.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 129 | 130 | [[package]] 131 | name = "io-lifetimes" 132 | version = "1.0.3" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" 135 | dependencies = [ 136 | "libc", 137 | "windows-sys", 138 | ] 139 | 140 | [[package]] 141 | name = "is-terminal" 142 | version = "0.4.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" 145 | dependencies = [ 146 | "hermit-abi", 147 | "io-lifetimes", 148 | "rustix", 149 | "windows-sys", 150 | ] 151 | 152 | [[package]] 153 | name = "leb128" 154 | version = "0.2.5" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 157 | 158 | [[package]] 159 | name = "libc" 160 | version = "0.2.138" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 163 | 164 | [[package]] 165 | name = "linux-raw-sys" 166 | version = "0.1.4" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 169 | 170 | [[package]] 171 | name = "log" 172 | version = "0.4.17" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 175 | dependencies = [ 176 | "cfg-if", 177 | ] 178 | 179 | [[package]] 180 | name = "memchr" 181 | version = "2.4.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 184 | 185 | [[package]] 186 | name = "once_cell" 187 | version = "1.12.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 190 | 191 | [[package]] 192 | name = "os_str_bytes" 193 | version = "6.0.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 196 | 197 | [[package]] 198 | name = "proc-macro-error" 199 | version = "1.0.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 202 | dependencies = [ 203 | "proc-macro-error-attr", 204 | "proc-macro2", 205 | "quote", 206 | "syn", 207 | "version_check", 208 | ] 209 | 210 | [[package]] 211 | name = "proc-macro-error-attr" 212 | version = "1.0.4" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 215 | dependencies = [ 216 | "proc-macro2", 217 | "quote", 218 | "version_check", 219 | ] 220 | 221 | [[package]] 222 | name = "proc-macro2" 223 | version = "1.0.46" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" 226 | dependencies = [ 227 | "unicode-ident", 228 | ] 229 | 230 | [[package]] 231 | name = "quote" 232 | version = "1.0.10" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 235 | dependencies = [ 236 | "proc-macro2", 237 | ] 238 | 239 | [[package]] 240 | name = "regex" 241 | version = "1.5.4" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 244 | dependencies = [ 245 | "aho-corasick", 246 | "memchr", 247 | "regex-syntax", 248 | ] 249 | 250 | [[package]] 251 | name = "regex-syntax" 252 | version = "0.6.25" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 255 | 256 | [[package]] 257 | name = "rustix" 258 | version = "0.36.5" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" 261 | dependencies = [ 262 | "bitflags", 263 | "errno", 264 | "io-lifetimes", 265 | "libc", 266 | "linux-raw-sys", 267 | "windows-sys", 268 | ] 269 | 270 | [[package]] 271 | name = "strsim" 272 | version = "0.10.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 275 | 276 | [[package]] 277 | name = "syn" 278 | version = "1.0.82" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 281 | dependencies = [ 282 | "proc-macro2", 283 | "quote", 284 | "unicode-xid", 285 | ] 286 | 287 | [[package]] 288 | name = "termcolor" 289 | version = "1.1.2" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 292 | dependencies = [ 293 | "winapi-util", 294 | ] 295 | 296 | [[package]] 297 | name = "unicode-ident" 298 | version = "1.0.5" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 301 | 302 | [[package]] 303 | name = "unicode-width" 304 | version = "0.1.9" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 307 | 308 | [[package]] 309 | name = "unicode-xid" 310 | version = "0.2.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 313 | 314 | [[package]] 315 | name = "version_check" 316 | version = "0.9.3" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 319 | 320 | [[package]] 321 | name = "wai" 322 | version = "0.2.0" 323 | dependencies = [ 324 | "anyhow", 325 | "clap", 326 | "env_logger", 327 | "log", 328 | "wast", 329 | ] 330 | 331 | [[package]] 332 | name = "wast" 333 | version = "40.0.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "9bb4f48a8b083dbc50e291e430afb8f524092bb00428957bcc63f49f856c64ac" 336 | dependencies = [ 337 | "leb128", 338 | "memchr", 339 | "unicode-width", 340 | ] 341 | 342 | [[package]] 343 | name = "winapi" 344 | version = "0.3.9" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 347 | dependencies = [ 348 | "winapi-i686-pc-windows-gnu", 349 | "winapi-x86_64-pc-windows-gnu", 350 | ] 351 | 352 | [[package]] 353 | name = "winapi-i686-pc-windows-gnu" 354 | version = "0.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 357 | 358 | [[package]] 359 | name = "winapi-util" 360 | version = "0.1.5" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 363 | dependencies = [ 364 | "winapi", 365 | ] 366 | 367 | [[package]] 368 | name = "winapi-x86_64-pc-windows-gnu" 369 | version = "0.4.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 372 | 373 | [[package]] 374 | name = "windows-sys" 375 | version = "0.42.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 378 | dependencies = [ 379 | "windows_aarch64_gnullvm", 380 | "windows_aarch64_msvc", 381 | "windows_i686_gnu", 382 | "windows_i686_msvc", 383 | "windows_x86_64_gnu", 384 | "windows_x86_64_gnullvm", 385 | "windows_x86_64_msvc", 386 | ] 387 | 388 | [[package]] 389 | name = "windows_aarch64_gnullvm" 390 | version = "0.42.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 393 | 394 | [[package]] 395 | name = "windows_aarch64_msvc" 396 | version = "0.42.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 399 | 400 | [[package]] 401 | name = "windows_i686_gnu" 402 | version = "0.42.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 405 | 406 | [[package]] 407 | name = "windows_i686_msvc" 408 | version = "0.42.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 411 | 412 | [[package]] 413 | name = "windows_x86_64_gnu" 414 | version = "0.42.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 417 | 418 | [[package]] 419 | name = "windows_x86_64_gnullvm" 420 | version = "0.42.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 423 | 424 | [[package]] 425 | name = "windows_x86_64_msvc" 426 | version = "0.42.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 429 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wai" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["k-nasa "] 6 | description = "A simple wasm interpreter" 7 | repository = "https://github.com/k-nasa/wai" 8 | license-file = "LICENSE" 9 | readme = "README.md" 10 | categories = ["wasm", "command-line-interface"] 11 | keywords = ["wasm", "webassembly", "cli"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | anyhow = "1.0.71" 17 | clap = {version = "4.1.6", features = ["derive"]} 18 | env_logger = "0.10.0" 19 | log = "0.4.17" 20 | 21 | [dev-dependencies] 22 | wast = "40.0.0" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 nasa 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | cargo build --release 4 | 5 | .PHONY: fuzz 6 | fuzz: 7 | cargo fuzz run decode --jobs 4 -- -runs=1000 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/k-nasa/wai/actions/workflows/ci.yml/badge.svg)](https://github.com/k-nasa/wai/actions/workflows/ci.yml) 2 | 3 | # wai (WebAssembly interpreter) 4 | 5 | A simple wasm interpreter 6 | 7 | This is an ongoing project 8 | 9 | 10 | ## DEMO 11 | 12 | https://user-images.githubusercontent.com/23740172/123530111-d8775280-d731-11eb-9ddf-b4afd640ccdb.mov 13 | 14 | 15 | ## Install 16 | 17 | ### Install via Homebrew 18 | 19 | ```bash 20 | brew install k-nasa/tap/wai 21 | ``` 22 | 23 | ### Install via Cargo 24 | 25 | ```bash 26 | cargo install --git https://github.com/k-nasa/wai.git wai 27 | ``` 28 | 29 | 30 | ## Usage 31 | 32 | ```bash 33 | wai examples/add.wasm --invoke add -a 1 2 34 | 35 | wai examples/fib.wasm --invoke fib -a 10 36 | ``` 37 | 38 | 39 | ```bash 40 | :) % wai -h 41 | wai 0.2.0 42 | k-nasa 43 | A simple wasm interpreter 44 | 45 | USAGE: 46 | wai [OPTIONS] --invoke 47 | 48 | ARGS: 49 | 50 | 51 | FLAGS: 52 | -h, --help Prints help information 53 | -V, --version Prints version information 54 | 55 | OPTIONS: 56 | -a, --args ... 57 | -i, --invoke 58 | ``` 59 | 60 | ## TODO 61 | 62 | - [ ] Pass the [wasm testsuite](https://github.com/WebAssembly/testsuite) 63 | - [ ] Implement validator 64 | - [ ] no_std 65 | - [ ] Support [WASI](https://wasi.dev/) 66 | 67 | ## Licence 68 | 69 | [MIT](https://github.com/k-nasa/wai/blob/master/LICENSE) 70 | -------------------------------------------------------------------------------- /examples/add.wasm: -------------------------------------------------------------------------------- 1 | asm`add 2 |  j -------------------------------------------------------------------------------- /examples/fib.wasm: -------------------------------------------------------------------------------- 1 | asm`'fib fib_iterative fib_recursive 2 | j AMA Ak Akj KA!A!A! AL@A AL@A @  H@  j! ! ! Aj!   -------------------------------------------------------------------------------- /examples/wat/add.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32) (result i32))) 3 | (func $add (type 0) (param $lhs i32) (param $rhs i32) (result i32) 4 | local.get $lhs 5 | local.get $rhs 6 | i32.add) 7 | (export "add" (func $add)) 8 | ) 9 | 10 | (assert_return (invoke "add" (i32.const 5) (i32.const 10)) (i32.const 15)) 11 | -------------------------------------------------------------------------------- /examples/wat/fib.wat: -------------------------------------------------------------------------------- 1 | (module 2 | 3 | ;; recursive implementation 4 | 5 | (func $fib_recursive (param $N i32) (result i32) 6 | (if 7 | (i32.eq (get_local $N) (i32.const 1)) 8 | (then (return (i32.const 1))) 9 | ) 10 | (if 11 | (i32.eq (get_local $N) (i32.const 2)) 12 | (then (return (i32.const 1))) 13 | ) 14 | (i32.add 15 | (call $fib_recursive (i32.sub (local.get 0) (i32.const 2))) 16 | (call $fib_recursive (i32.sub (local.get 0) (i32.const 1))) 17 | ) 18 | ) 19 | 20 | ;; iterative implementation, avoids stack overflow 21 | 22 | (func $fib_iterative (param $N i32) (result i32) 23 | (local $n1 i32) 24 | (local $n2 i32) 25 | (local $tmp i32) 26 | (local $i i32) 27 | (set_local $n1 (i32.const 1)) 28 | (set_local $n2 (i32.const 1)) 29 | (set_local $i (i32.const 2)) 30 | 31 | 32 | ;; return 0 for N <= 0 33 | (if 34 | (i32.le_s (get_local $N) (i32.const 0)) 35 | (then (return (i32.const 0))) 36 | ) 37 | 38 | ;;since we normally return n2, handle n=1 case specially 39 | (if 40 | (i32.le_s (get_local $N) (i32.const 2)) 41 | (then (return (i32.const 1))) 42 | ) 43 | 44 | (loop $again 45 | (if 46 | (i32.lt_s (get_local $i) (get_local $N)) 47 | (then 48 | (set_local $tmp (i32.add (get_local $n1) (get_local $n2))) 49 | (set_local $n1 (get_local $n2)) 50 | (set_local $n2 (get_local $tmp)) 51 | (set_local $i (i32.add (get_local $i) (i32.const 1))) 52 | (br $again) 53 | ) 54 | ) 55 | ) 56 | 57 | (get_local $n2) 58 | ) 59 | 60 | ;; export fib_iterative as the main thing, because it's the fastest 61 | 62 | (export "fib" (func $fib_iterative)) 63 | (export "fib_iterative" (func $fib_iterative)) 64 | (export "fib_recursive" (func $fib_recursive)) 65 | ) 66 | 67 | (assert_return (invoke "fib" (i32.const 10)) (i32.const 55)) 68 | (assert_return (invoke "fib_recursive" (i32.const 10)) (i32.const 55)) 69 | -------------------------------------------------------------------------------- /examples/wat/global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $global (param $lhs i32) (result i32) (local i32) 3 | get_local $lhs 4 | if 5 | i32.const 100 6 | set_local 0 7 | else 8 | i32.const 1 9 | set_local 0 10 | end 11 | get_local 0) 12 | (export "if" (func $if)) 13 | ) 14 | -------------------------------------------------------------------------------- /examples/wat/if.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $if (param $lhs i32) (result i32) (local i32) 3 | get_local $lhs 4 | if 5 | i32.const 100 6 | set_local 0 7 | else 8 | i32.const 1 9 | set_local 0 10 | end 11 | get_local 0) 12 | (export "if" (func $if)) 13 | ) 14 | 15 | (assert_return (invoke "if" (i32.const 0) ) (i32.const 1)) 16 | (assert_return (invoke "if" (i32.const 1) ) (i32.const 100)) 17 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/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 = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.41" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" 19 | 20 | [[package]] 21 | name = "arbitrary" 22 | version = "1.0.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "237430fd6ed3740afe94eefcc278ae21e050285be882804e0d6e8695f0c94691" 25 | dependencies = [ 26 | "derive_arbitrary", 27 | ] 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 34 | dependencies = [ 35 | "hermit-abi", 36 | "libc", 37 | "winapi", 38 | ] 39 | 40 | [[package]] 41 | name = "autocfg" 42 | version = "1.0.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 45 | 46 | [[package]] 47 | name = "bitflags" 48 | version = "1.2.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.0.68" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "clap" 66 | version = "3.0.0-beta.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" 69 | dependencies = [ 70 | "atty", 71 | "bitflags", 72 | "clap_derive", 73 | "indexmap", 74 | "lazy_static", 75 | "os_str_bytes", 76 | "strsim", 77 | "termcolor", 78 | "textwrap", 79 | "unicode-width", 80 | "vec_map", 81 | ] 82 | 83 | [[package]] 84 | name = "clap_derive" 85 | version = "3.0.0-beta.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" 88 | dependencies = [ 89 | "heck", 90 | "proc-macro-error", 91 | "proc-macro2", 92 | "quote", 93 | "syn", 94 | ] 95 | 96 | [[package]] 97 | name = "derive_arbitrary" 98 | version = "1.0.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "5f1281ee141df08871db9fe261ab5312179eac32d1e314134ceaa8dd7c042f5a" 101 | dependencies = [ 102 | "proc-macro2", 103 | "quote", 104 | "syn", 105 | ] 106 | 107 | [[package]] 108 | name = "env_logger" 109 | version = "0.8.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 112 | dependencies = [ 113 | "atty", 114 | "humantime", 115 | "log", 116 | "regex", 117 | "termcolor", 118 | ] 119 | 120 | [[package]] 121 | name = "hashbrown" 122 | version = "0.9.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 125 | 126 | [[package]] 127 | name = "heck" 128 | version = "0.3.3" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 131 | dependencies = [ 132 | "unicode-segmentation", 133 | ] 134 | 135 | [[package]] 136 | name = "hermit-abi" 137 | version = "0.1.18" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 140 | dependencies = [ 141 | "libc", 142 | ] 143 | 144 | [[package]] 145 | name = "humantime" 146 | version = "2.1.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 149 | 150 | [[package]] 151 | name = "indexmap" 152 | version = "1.6.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" 155 | dependencies = [ 156 | "autocfg", 157 | "hashbrown", 158 | ] 159 | 160 | [[package]] 161 | name = "lazy_static" 162 | version = "1.4.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 165 | 166 | [[package]] 167 | name = "leb128" 168 | version = "0.2.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" 171 | 172 | [[package]] 173 | name = "libc" 174 | version = "0.2.97" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 177 | 178 | [[package]] 179 | name = "libfuzzer-sys" 180 | version = "0.4.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" 183 | dependencies = [ 184 | "arbitrary", 185 | "cc", 186 | "once_cell", 187 | ] 188 | 189 | [[package]] 190 | name = "log" 191 | version = "0.4.14" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 194 | dependencies = [ 195 | "cfg-if", 196 | ] 197 | 198 | [[package]] 199 | name = "memchr" 200 | version = "2.4.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 203 | 204 | [[package]] 205 | name = "once_cell" 206 | version = "1.8.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 209 | 210 | [[package]] 211 | name = "os_str_bytes" 212 | version = "2.4.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" 215 | 216 | [[package]] 217 | name = "proc-macro-error" 218 | version = "1.0.4" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 221 | dependencies = [ 222 | "proc-macro-error-attr", 223 | "proc-macro2", 224 | "quote", 225 | "syn", 226 | "version_check", 227 | ] 228 | 229 | [[package]] 230 | name = "proc-macro-error-attr" 231 | version = "1.0.4" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 234 | dependencies = [ 235 | "proc-macro2", 236 | "quote", 237 | "version_check", 238 | ] 239 | 240 | [[package]] 241 | name = "proc-macro2" 242 | version = "1.0.27" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 245 | dependencies = [ 246 | "unicode-xid", 247 | ] 248 | 249 | [[package]] 250 | name = "quote" 251 | version = "1.0.9" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 254 | dependencies = [ 255 | "proc-macro2", 256 | ] 257 | 258 | [[package]] 259 | name = "regex" 260 | version = "1.5.4" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 263 | dependencies = [ 264 | "aho-corasick", 265 | "memchr", 266 | "regex-syntax", 267 | ] 268 | 269 | [[package]] 270 | name = "regex-syntax" 271 | version = "0.6.25" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 274 | 275 | [[package]] 276 | name = "strsim" 277 | version = "0.10.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.73" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-xid", 290 | ] 291 | 292 | [[package]] 293 | name = "termcolor" 294 | version = "1.1.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 297 | dependencies = [ 298 | "winapi-util", 299 | ] 300 | 301 | [[package]] 302 | name = "textwrap" 303 | version = "0.12.1" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" 306 | dependencies = [ 307 | "unicode-width", 308 | ] 309 | 310 | [[package]] 311 | name = "unicode-segmentation" 312 | version = "1.7.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 315 | 316 | [[package]] 317 | name = "unicode-width" 318 | version = "0.1.8" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 321 | 322 | [[package]] 323 | name = "unicode-xid" 324 | version = "0.2.2" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 327 | 328 | [[package]] 329 | name = "vec_map" 330 | version = "0.8.2" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 333 | 334 | [[package]] 335 | name = "version_check" 336 | version = "0.9.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 339 | 340 | [[package]] 341 | name = "wai" 342 | version = "0.1.0" 343 | dependencies = [ 344 | "anyhow", 345 | "clap", 346 | "env_logger", 347 | "log", 348 | ] 349 | 350 | [[package]] 351 | name = "wai-fuzz" 352 | version = "0.0.0" 353 | dependencies = [ 354 | "libfuzzer-sys", 355 | "wai", 356 | "wasm-smith", 357 | ] 358 | 359 | [[package]] 360 | name = "wasm-encoder" 361 | version = "0.5.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "72b3fb3541f4cdc05f0eacd7ce5544d69a4cc3e922ef1e7f37c1e7cb8b1e8e66" 364 | dependencies = [ 365 | "leb128", 366 | ] 367 | 368 | [[package]] 369 | name = "wasm-smith" 370 | version = "0.5.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c0b73bf3c616211529547284f12db7bb1e4d9bca11c738533490546239bbd120" 373 | dependencies = [ 374 | "arbitrary", 375 | "indexmap", 376 | "leb128", 377 | "wasm-encoder", 378 | ] 379 | 380 | [[package]] 381 | name = "winapi" 382 | version = "0.3.9" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 385 | dependencies = [ 386 | "winapi-i686-pc-windows-gnu", 387 | "winapi-x86_64-pc-windows-gnu", 388 | ] 389 | 390 | [[package]] 391 | name = "winapi-i686-pc-windows-gnu" 392 | version = "0.4.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 395 | 396 | [[package]] 397 | name = "winapi-util" 398 | version = "0.1.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 401 | dependencies = [ 402 | "winapi", 403 | ] 404 | 405 | [[package]] 406 | name = "winapi-x86_64-pc-windows-gnu" 407 | version = "0.4.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 410 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "wai-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.4" 14 | wasm-smith = "0.5.0" 15 | 16 | [dependencies.wai] 17 | path = ".." 18 | 19 | # Prevent this from interfering with workspaces 20 | [workspace] 21 | members = ["."] 22 | 23 | [[bin]] 24 | name = "decode" 25 | path = "fuzz_targets/decode.rs" 26 | test = false 27 | doc = false 28 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/decode.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use wai::*; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | use wasm_smith::Module as M; 7 | 8 | fuzz_target!(|module: M| { 9 | let bytes = module.to_bytes(); 10 | 11 | Module::from_byte(bytes).unwrap(); 12 | }); 13 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is used for release binary 3 | # Usage: ./release version GITHUB_TOKEN 4 | 5 | 6 | info () { 7 | printf "\r [ \033[00;34mINFO\033[0m ] $1\n" 8 | } 9 | 10 | success () { 11 | printf "\r\033[2K [ \033[00;32mOK\033[0m ] $1\n" 12 | } 13 | 14 | install_cross() { 15 | cross --version > /dev/null 16 | if [ $? -ne 0 ]; then 17 | info 'cross not found. try install cross' 18 | 19 | cargo install cross 20 | 21 | success "success install cross" 22 | else 23 | info "cross already installed" 24 | fi 25 | } 26 | 27 | install_rgh() { 28 | rgh --version > /dev/null 29 | if [ $? -ne 0 ]; then 30 | info 'rgh not found. try install rgh' 31 | 32 | cargo install rgh 33 | 34 | success "success install rgh" 35 | else 36 | info "rgh already installed" 37 | fi 38 | } 39 | 40 | build_all() { 41 | rm -rf dist/ 42 | 43 | build x86_64-apple-darwin wai 44 | build x86_64-unknown-linux-gnu wai 45 | } 46 | 47 | build() { 48 | CRATE_NAME=wai 49 | MISC="README.md LICENSE" 50 | 51 | TARGET=$1 52 | BIN_NAME=$2 53 | DIRNAME=${CRATE_NAME}_${TARGET} 54 | 55 | info "start build ${TARGET} ${BIN_NAME}" 56 | 57 | cross build --target ${TARGET} --release 58 | mkdir -p ${DIRNAME} 59 | \ 60 | cp ./target/${TARGET}/release/${BIN_NAME} ${DIRNAME} 61 | cp ${MISC} ${DIRNAME} 62 | \ 63 | mkdir -p dist 64 | tar czf dist/${DIRNAME}.tar.gz ${DIRNAME} 65 | rm -rf ${DIRNAME} 66 | 67 | success "success build ${TARGET} ${BIN_NAME}" 68 | } 69 | 70 | release() { 71 | VERSION=$1 72 | GITHUB_TOKEN=$2 73 | rgh $VERSION ./dist --token $GITHUB_TOKEN 74 | } 75 | 76 | main() { 77 | install_cross 78 | install_rgh 79 | 80 | build_all 81 | release $1 $2 82 | } 83 | 84 | main $1 $2 85 | -------------------------------------------------------------------------------- /src/decode/decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::error::DecodeError; 2 | use crate::instruction::Instruction; 3 | use crate::module::{Section, SectionType}; 4 | use crate::opcode::Opcode; 5 | use crate::types::*; 6 | use std::convert::TryFrom; 7 | use std::io::Cursor; 8 | use std::io::Read; 9 | 10 | const MAGIC_NUMBER: &[u8] = b"\0asm"; 11 | const FUNC_TYPE: u8 = 0x60; 12 | 13 | pub(crate) struct Decoder<'a> { 14 | reader: Cursor<&'a [u8]>, 15 | } 16 | 17 | impl<'a> Decoder<'a> { 18 | pub(crate) fn new(reader: Cursor<&'a [u8]>) -> Self { 19 | Self { reader } 20 | } 21 | 22 | pub(crate) fn is_end(&self) -> bool { 23 | self.reader.position() == self.reader.get_ref().len() as u64 24 | } 25 | 26 | /// wasmバイナリのマジックナンバーを見て適切なファイルか(本当にwasmか)をチェックする 27 | pub(crate) fn validate_wasm_format(&mut self) -> Result<(), DecodeError> { 28 | let mut magic_number = [0; 4]; 29 | self.reader.read_exact(&mut magic_number)?; 30 | 31 | if magic_number != *MAGIC_NUMBER { 32 | return Err(DecodeError::InvalidWasmFile); 33 | } 34 | 35 | Ok(()) 36 | } 37 | 38 | pub(crate) fn decode_version(&mut self) -> Result { 39 | let mut version = [0; 4]; 40 | self.reader.read_exact(&mut version)?; 41 | 42 | Ok(u32::from_le_bytes(version)) 43 | } 44 | 45 | pub(crate) fn decode_section_type(&mut self) -> Result<(SectionType, u32), DecodeError> { 46 | let mut section_number = [0; 1]; 47 | self.reader.read_exact(&mut section_number)?; 48 | 49 | let section_size = self.decode_ver_uint_n()?; 50 | let section_type = SectionType::from(section_number[0]); 51 | 52 | Ok((section_type, section_size.into())) 53 | } 54 | 55 | pub(crate) fn decode_section( 56 | &mut self, 57 | section_type: SectionType, 58 | section_size: u32, 59 | ) -> Result { 60 | let section = match section_type { 61 | SectionType::Custom => self.decode_custom_section(section_size)?, 62 | SectionType::Type => self.decode_type_section(section_size)?, 63 | SectionType::Import => self.decode_import_section(section_size)?, 64 | SectionType::Function => self.decode_function_section(section_size)?, 65 | SectionType::Table => self.decode_table_section(section_size)?, 66 | SectionType::Memory => self.decode_memory_section(section_size)?, 67 | SectionType::Global => self.decode_global_section(section_size)?, 68 | SectionType::Export => self.decode_export_section(section_size)?, 69 | SectionType::Start => self.decode_start_section(section_size)?, 70 | SectionType::Element => self.decode_element_section(section_size)?, 71 | SectionType::Code => self.decode_code_section(section_size)?, 72 | SectionType::Data => self.decode_data_section(section_size)?, 73 | SectionType::Unsuport => todo!(), 74 | }; 75 | 76 | Ok(section) 77 | } 78 | 79 | fn decode_custom_section(&mut self, size: u32) -> Result { 80 | let mut custom_section = vec![0; size as usize]; 81 | self.reader.read_exact(&mut custom_section)?; 82 | 83 | Ok(Section::Custom(())) // TODO implement! 84 | } 85 | 86 | fn decode_type_section(&mut self, size: u32) -> Result { 87 | let mut type_section = vec![0; size as usize]; 88 | self.reader.read_exact(&mut type_section)?; 89 | 90 | let mut type_section_decoder = Decoder::new(Cursor::new(&type_section)); 91 | let mut type_section = TypeSection { 92 | entries: Vec::new(), 93 | }; 94 | 95 | let entry_count = type_section_decoder.decode_ver_uint_n()?; 96 | for _ in 0..entry_count.into() { 97 | let func_type = type_section_decoder.read_next()?; 98 | if func_type != FUNC_TYPE { 99 | return Err(DecodeError::Unexpected(String::new())); 100 | } 101 | 102 | let mut func_type = FuncType { 103 | params: vec![], 104 | returns: vec![], 105 | }; 106 | 107 | let arg_count = type_section_decoder.decode_ver_uint_n()?; 108 | for _ in 0..arg_count.into() { 109 | let t = type_section_decoder.decode_ver_uint_n()?; 110 | func_type.params.push(ValueType::from(t)); 111 | } 112 | 113 | let returns_count = type_section_decoder.decode_ver_uint_n()?; 114 | for _ in 0..returns_count.into() { 115 | let t = type_section_decoder.decode_ver_uint_n()?; 116 | func_type.returns.push(ValueType::from(t)); 117 | } 118 | 119 | type_section.entries.push(func_type) 120 | } 121 | 122 | Ok(Section::Type(type_section)) 123 | } 124 | 125 | fn decode_import_section(&mut self, size: u32) -> Result { 126 | let mut custom_section = vec![0; size as usize]; 127 | self.reader.read_exact(&mut custom_section)?; 128 | 129 | Ok(Section::Import(())) 130 | } 131 | 132 | fn decode_function_section(&mut self, size: u32) -> Result { 133 | let mut func_section = vec![0; size as usize]; 134 | self.reader.read_exact(&mut func_section)?; 135 | 136 | let mut func_section_decoder = Decoder::new(Cursor::new(&func_section)); 137 | let mut func_section = FunctionSection { types: Vec::new() }; 138 | 139 | let count: u32 = func_section_decoder.decode_ver_uint_n()?.into(); 140 | for _ in 0..count { 141 | let t = func_section_decoder.decode_ver_uint_n()?; 142 | func_section.types.push(t.into()); 143 | } 144 | 145 | Ok(Section::Function(func_section)) 146 | } 147 | 148 | fn decode_table_section(&mut self, size: u32) -> Result { 149 | let mut custom_section = vec![0; size as usize]; 150 | self.reader.read_exact(&mut custom_section)?; 151 | 152 | Ok(Section::Table(())) 153 | } 154 | 155 | fn decode_memory_section(&mut self, size: u32) -> Result { 156 | let mut custom_section = vec![0; size as usize]; 157 | self.reader.read_exact(&mut custom_section)?; 158 | 159 | Ok(Section::Memory(())) 160 | } 161 | 162 | fn decode_global_section(&mut self, size: u32) -> Result { 163 | let mut custom_section = vec![0; size as usize]; 164 | self.reader.read_exact(&mut custom_section)?; 165 | 166 | Ok(Section::Global(())) 167 | } 168 | 169 | fn decode_export_section(&mut self, size: u32) -> Result { 170 | let mut section = vec![0; size as usize]; 171 | self.reader.read_exact(&mut section)?; 172 | 173 | let mut export_section_decoder = Decoder::new(Cursor::new(§ion)); 174 | let mut export_section = ExportSection { 175 | entries: Vec::new(), 176 | }; 177 | 178 | let count: u32 = export_section_decoder.decode_ver_uint_n()?.into(); 179 | for _ in 0..count { 180 | let field_len = export_section_decoder.decode_ver_uint_n()?; 181 | let field_str = 182 | String::from_utf8(export_section_decoder.read_byte(field_len.into())?).unwrap(); 183 | let kind = ExternalKind::from(export_section_decoder.decode_ver_uint_n()?); 184 | let index = export_section_decoder.decode_ver_uint_n()?.into(); 185 | 186 | let entry = ExportEntry { 187 | field_str, 188 | kind, 189 | index, 190 | }; 191 | export_section.entries.push(entry); 192 | } 193 | 194 | Ok(Section::Export(export_section)) 195 | } 196 | 197 | fn decode_start_section(&mut self, size: u32) -> Result { 198 | let mut custom_section = vec![0; size as usize]; 199 | self.reader.read_exact(&mut custom_section)?; 200 | 201 | Ok(Section::Start(())) 202 | } 203 | 204 | fn decode_element_section(&mut self, size: u32) -> Result { 205 | let mut custom_section = vec![0; size as usize]; 206 | self.reader.read_exact(&mut custom_section)?; 207 | 208 | Ok(Section::Element(())) 209 | } 210 | 211 | fn decode_code_section(&mut self, size: u32) -> Result { 212 | let mut section = vec![0; size as usize]; 213 | self.reader.read_exact(&mut section)?; 214 | 215 | let mut code_section_decoder = Decoder::new(Cursor::new(§ion)); 216 | let mut code_section = CodeSection { bodies: Vec::new() }; 217 | 218 | let count: u32 = code_section_decoder.decode_ver_uint_n()?.into(); 219 | 220 | for _ in 0..count { 221 | let body_size = code_section_decoder.decode_ver_uint_n()?; 222 | let body_bytes = &code_section_decoder.read_byte(body_size.into())?; 223 | let mut body = Decoder::new(Cursor::new(body_bytes)); 224 | 225 | let local_count = body.decode_ver_uint_n()?; 226 | 227 | let mut function_body = FunctionBody { 228 | locales: Vec::new(), 229 | code: Vec::new(), 230 | }; 231 | 232 | for _ in 0..local_count.into() { 233 | let count = body.decode_ver_uint_n()?; 234 | let t = body.decode_ver_uint_n()?; 235 | let local_entry = LocalEntry { 236 | count: count.into(), 237 | value_type: ValueType::from(t), 238 | }; 239 | 240 | function_body.locales.push(local_entry); 241 | } 242 | 243 | function_body.code = body.decode_function_body()?; 244 | code_section.bodies.push(function_body); 245 | } 246 | 247 | Ok(Section::Code(code_section)) 248 | } 249 | 250 | fn decode_data_section(&mut self, size: u32) -> Result { 251 | let mut section = vec![0; size as usize]; 252 | self.reader.read_exact(&mut section)?; 253 | 254 | let mut data_section_decoder = Decoder::new(Cursor::new(§ion)); 255 | 256 | let count = data_section_decoder.decode_ver_uint_n()?; 257 | 258 | let mut segments = vec![]; 259 | for _ in 0..count.into() { 260 | let index = data_section_decoder.decode_ver_uint_n()?; 261 | let _opcode = data_section_decoder.read_next()?; 262 | 263 | let offset = data_section_decoder.decode_ver_uint_n()?; 264 | 265 | // NOTE END命令が入っているので読み飛ばしてしまう 266 | // 本来であればopcodeを見て、ENDが来るまでdecodeするのがいいが、その必要性がないので雑に実装する 267 | data_section_decoder.read_next()?; 268 | 269 | let size = data_section_decoder.decode_ver_uint_n()?; 270 | 271 | let data = data_section_decoder.read_byte(usize::from(size))?; 272 | 273 | segments.push(DataSegment { 274 | data, 275 | index: index.into(), 276 | offset: offset.into(), 277 | }) 278 | } 279 | 280 | Ok(Section::Data(DataSection { segments })) 281 | } 282 | 283 | fn decode_ver_uint_n(&mut self) -> Result { 284 | let mut value = 0; 285 | let mut i = 0; 286 | loop { 287 | let bytes = u32::from(self.read_next()?); 288 | value += (bytes & 0x7f).checked_shl(i * 7).ok_or_else(|| { 289 | DecodeError::InvalidNumeric(format!("value is {}, byte is {:x}", value, bytes)) 290 | })?; 291 | 292 | i += 1; 293 | 294 | if bytes & 0x80 == 0 { 295 | break; 296 | } 297 | } 298 | 299 | Ok(VerUintN::from(value)) 300 | } 301 | 302 | fn decode_i64(&mut self) -> Result { 303 | let mut value = 0; 304 | let mut i = 0; 305 | loop { 306 | let bytes = i64::from(self.read_next()?); 307 | value += (bytes & 0x7f).checked_shl(i * 7).ok_or_else(|| { 308 | DecodeError::InvalidNumeric(format!("value is {}, byte is {:x}", value, bytes)) 309 | })?; 310 | 311 | i += 1; 312 | 313 | if bytes & 0x80 == 0 { 314 | break; 315 | } 316 | } 317 | 318 | Ok(value) 319 | } 320 | 321 | fn decode_function_body(&mut self) -> Result, DecodeError> { 322 | let mut instructions = Vec::new(); 323 | loop { 324 | let opcode = match Opcode::try_from(self.read_next()?) { 325 | Ok(v) => v, 326 | Err(e) => { 327 | return Err(DecodeError::Unexpected(format!( 328 | "error: {}\nnow decoded instructions is {:?}", 329 | e, instructions 330 | ))) 331 | } 332 | }; 333 | 334 | if self.is_end() { 335 | break; 336 | } 337 | 338 | let instruction = match opcode { 339 | // expect BlockType 340 | Opcode::Block => Instruction::Block(BlockType::try_from(self.read_next()?)?), 341 | Opcode::Loop => Instruction::Loop(BlockType::try_from(self.read_next()?)?), 342 | Opcode::If => Instruction::If(BlockType::try_from(self.read_next()?)?), 343 | 344 | // expect VerUintN 345 | Opcode::Br => Instruction::Br(self.decode_ver_uint_n()?), 346 | Opcode::BrIf => Instruction::BrIf(self.decode_ver_uint_n()?), 347 | Opcode::GetLocal => Instruction::GetLocal(self.decode_ver_uint_n()?), 348 | Opcode::SetLocal => Instruction::SetLocal(self.decode_ver_uint_n()?), 349 | Opcode::TeeLocal => Instruction::TeeLocal(self.decode_ver_uint_n()?), 350 | Opcode::GetGlobal => Instruction::GetGlobal(self.decode_ver_uint_n()?), 351 | Opcode::SetGlobal => Instruction::SetGlobal(self.decode_ver_uint_n()?), 352 | Opcode::Call => Instruction::Call(self.decode_ver_uint_n()?), 353 | Opcode::CurrentMemory => Instruction::CurrentMemory(self.decode_ver_uint_n()?), 354 | Opcode::GrowMemory => Instruction::GrowMemory(self.decode_ver_uint_n()?), 355 | 356 | Opcode::BrTable => { 357 | let target_count = self.decode_ver_uint_n()?; 358 | let mut target_tables = vec![]; 359 | for _ in 0..u32::from(target_count) { 360 | target_tables.push(self.decode_ver_uint_n()?); 361 | } 362 | let default_target = self.decode_ver_uint_n()?; 363 | 364 | Instruction::BrTable(target_tables, default_target) 365 | } 366 | Opcode::CallIndirect => { 367 | let type_index = self.decode_ver_uint_n()?; 368 | let reserved = self.decode_ver_uint_n()?; 369 | 370 | Instruction::CallIndirect(type_index, reserved) 371 | } 372 | 373 | Opcode::I32Load => Instruction::I32Load( 374 | u32::from(self.decode_ver_uint_n()?), 375 | u32::from(self.decode_ver_uint_n()?), 376 | ), 377 | Opcode::I64Load => Instruction::I64Load( 378 | u32::from(self.decode_ver_uint_n()?), 379 | u32::from(self.decode_ver_uint_n()?), 380 | ), 381 | Opcode::F32Load => Instruction::F32Load( 382 | u32::from(self.decode_ver_uint_n()?), 383 | u32::from(self.decode_ver_uint_n()?), 384 | ), 385 | Opcode::F64Load => Instruction::F64Load( 386 | u32::from(self.decode_ver_uint_n()?), 387 | u32::from(self.decode_ver_uint_n()?), 388 | ), 389 | Opcode::I32Load8S => Instruction::I32Load8S( 390 | u32::from(self.decode_ver_uint_n()?), 391 | u32::from(self.decode_ver_uint_n()?), 392 | ), 393 | Opcode::I32Load8U => Instruction::I32Load8U( 394 | u32::from(self.decode_ver_uint_n()?), 395 | u32::from(self.decode_ver_uint_n()?), 396 | ), 397 | Opcode::I32Load16S => Instruction::I32Load16S( 398 | u32::from(self.decode_ver_uint_n()?), 399 | u32::from(self.decode_ver_uint_n()?), 400 | ), 401 | Opcode::I32Load16U => Instruction::I32Load16U( 402 | u32::from(self.decode_ver_uint_n()?), 403 | u32::from(self.decode_ver_uint_n()?), 404 | ), 405 | Opcode::I64Load8S => Instruction::I64Load8S( 406 | u32::from(self.decode_ver_uint_n()?), 407 | u32::from(self.decode_ver_uint_n()?), 408 | ), 409 | Opcode::I64Load8U => Instruction::I64Load8U( 410 | u32::from(self.decode_ver_uint_n()?), 411 | u32::from(self.decode_ver_uint_n()?), 412 | ), 413 | Opcode::I64Load16S => Instruction::I64Load16S( 414 | u32::from(self.decode_ver_uint_n()?), 415 | u32::from(self.decode_ver_uint_n()?), 416 | ), 417 | Opcode::I64Load16U => Instruction::I64Load16U( 418 | u32::from(self.decode_ver_uint_n()?), 419 | u32::from(self.decode_ver_uint_n()?), 420 | ), 421 | Opcode::I64Load32S => Instruction::I64Load32S( 422 | u32::from(self.decode_ver_uint_n()?), 423 | u32::from(self.decode_ver_uint_n()?), 424 | ), 425 | Opcode::I64Load32U => Instruction::I64Load32U( 426 | u32::from(self.decode_ver_uint_n()?), 427 | u32::from(self.decode_ver_uint_n()?), 428 | ), 429 | Opcode::I32Store => Instruction::I32Store( 430 | u32::from(self.decode_ver_uint_n()?), 431 | u32::from(self.decode_ver_uint_n()?), 432 | ), 433 | Opcode::I64Store => Instruction::I64Store( 434 | u32::from(self.decode_ver_uint_n()?), 435 | u32::from(self.decode_ver_uint_n()?), 436 | ), 437 | Opcode::F32Store => Instruction::F32Store( 438 | u32::from(self.decode_ver_uint_n()?), 439 | u32::from(self.decode_ver_uint_n()?), 440 | ), 441 | Opcode::F64Store => Instruction::F64Store( 442 | u32::from(self.decode_ver_uint_n()?), 443 | u32::from(self.decode_ver_uint_n()?), 444 | ), 445 | Opcode::I32Store8 => Instruction::I32Store8( 446 | u32::from(self.decode_ver_uint_n()?), 447 | u32::from(self.decode_ver_uint_n()?), 448 | ), 449 | Opcode::I32Store16 => Instruction::I32Store16( 450 | u32::from(self.decode_ver_uint_n()?), 451 | u32::from(self.decode_ver_uint_n()?), 452 | ), 453 | Opcode::I64Store8 => Instruction::I64Store8( 454 | u32::from(self.decode_ver_uint_n()?), 455 | u32::from(self.decode_ver_uint_n()?), 456 | ), 457 | Opcode::I64Store16 => Instruction::I64Store16( 458 | u32::from(self.decode_ver_uint_n()?), 459 | u32::from(self.decode_ver_uint_n()?), 460 | ), 461 | Opcode::I64Store32 => Instruction::I64Store32( 462 | u32::from(self.decode_ver_uint_n()?), 463 | u32::from(self.decode_ver_uint_n()?), 464 | ), 465 | 466 | Opcode::I32Const => Instruction::I32Const(i32::from(self.decode_ver_uint_n()?)), 467 | Opcode::I64Const => Instruction::I64Const(self.decode_i64()?), 468 | Opcode::F32Const => { 469 | let v = self.read_u32()?; 470 | let v = f32::from_bits(v); 471 | Instruction::F32Const(v) 472 | } 473 | 474 | Opcode::F64Const => { 475 | let v = self.read_u64()?; 476 | let v = f64::from_bits(v); 477 | Instruction::F64Const(v) 478 | } 479 | Opcode::Prefix => Instruction::Prefix(self.decode_ver_uint_n()?), 480 | _ => Instruction::from(opcode), 481 | }; 482 | 483 | instructions.push(instruction); 484 | } 485 | 486 | Ok(instructions) 487 | } 488 | 489 | fn read_next(&mut self) -> Result { 490 | let mut buf = [0u8; 1]; 491 | self.reader.read_exact(&mut buf)?; 492 | 493 | Ok(buf[0]) 494 | } 495 | 496 | fn read_byte(&mut self, size: usize) -> Result, DecodeError> { 497 | let mut buf = vec![0; size]; 498 | self.reader.read_exact(&mut buf)?; 499 | 500 | Ok(buf) 501 | } 502 | 503 | pub fn read_u32(&mut self) -> Result { 504 | Ok(self 505 | .read_byte(4)? 506 | .iter() 507 | .rev() 508 | .fold(0, |x, &i| x << 8 | u32::from(i))) 509 | } 510 | 511 | pub fn read_u64(&mut self) -> Result { 512 | Ok(self 513 | .read_byte(8)? 514 | .iter() 515 | .rev() 516 | .fold(0, |x, &i| x << 8 | u64::from(i))) 517 | } 518 | 519 | // fn read_to_end(&mut self) -> Result, DecodeError> { 520 | // let mut buf = vec![]; 521 | // self.reader.read_to_end(&mut buf)?; 522 | // 523 | // Ok(buf) 524 | // } 525 | } 526 | -------------------------------------------------------------------------------- /src/decode/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::{self, Display}; 3 | 4 | #[derive(Debug)] 5 | pub enum DecodeError { 6 | InvalidWasmFile, 7 | InvalidNumeric(String), 8 | Unexpected(String), 9 | IOError(std::io::Error), 10 | } 11 | 12 | impl Error for DecodeError {} 13 | impl Display for DecodeError { 14 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 15 | use self::DecodeError::*; 16 | match self { 17 | InvalidWasmFile => write!(f, "invalid input file. not wasm file"), 18 | InvalidNumeric(s) => write!(f, "invalid numeric: {}", s), 19 | Unexpected(s) => write!(f, "unexpected byte. details: {}", s), 20 | IOError(i) => write!(f, "io error: {}", i), 21 | } 22 | } 23 | } 24 | 25 | impl From for DecodeError { 26 | fn from(error: std::io::Error) -> Self { 27 | Self::IOError(error) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/decode/mod.rs: -------------------------------------------------------------------------------- 1 | mod decoder; 2 | pub mod error; 3 | 4 | use decoder::Decoder; 5 | pub use error::DecodeError; 6 | 7 | use crate::module::Module; 8 | use std::io::Cursor; 9 | 10 | /// wasmバイナリをデコードしてwasm moduleを返す 11 | pub fn decode(buf: &[u8]) -> Result { 12 | let mut decoder = Decoder::new(Cursor::new(buf)); 13 | 14 | let mut m = Module::default(); 15 | 16 | decoder.validate_wasm_format()?; 17 | m.version = decoder.decode_version()?; 18 | 19 | loop { 20 | if decoder.is_end() { 21 | break; 22 | } 23 | 24 | let (section_type, section_size) = decoder.decode_section_type()?; 25 | let section = decoder.decode_section(section_type, section_size)?; 26 | 27 | m.take_in(section); 28 | } 29 | 30 | Ok(m) 31 | } 32 | -------------------------------------------------------------------------------- /src/from_le.rs: -------------------------------------------------------------------------------- 1 | pub trait FromLe { 2 | fn from_le_bytes(b: &[u8]) -> Self; 3 | } 4 | 5 | impl FromLe for u8 { 6 | fn from_le_bytes(byte: &[u8]) -> Self { 7 | let mut b: [u8; 1] = Default::default(); 8 | b.copy_from_slice(&byte[0..1]); 9 | Self::from_le_bytes(b) 10 | } 11 | } 12 | impl FromLe for u16 { 13 | fn from_le_bytes(byte: &[u8]) -> Self { 14 | let mut b: [u8; 2] = Default::default(); 15 | b.copy_from_slice(&byte[0..2]); 16 | Self::from_le_bytes(b) 17 | } 18 | } 19 | 20 | impl FromLe for u32 { 21 | fn from_le_bytes(byte: &[u8]) -> Self { 22 | let mut b: [u8; 4] = Default::default(); 23 | b.copy_from_slice(&byte[0..4]); 24 | Self::from_le_bytes(b) 25 | } 26 | } 27 | 28 | impl FromLe for i32 { 29 | fn from_le_bytes(byte: &[u8]) -> Self { 30 | let mut b: [u8; 4] = Default::default(); 31 | b.copy_from_slice(&byte[0..4]); 32 | Self::from_le_bytes(b) 33 | } 34 | } 35 | impl FromLe for u64 { 36 | fn from_le_bytes(byte: &[u8]) -> Self { 37 | let mut b: [u8; 8] = Default::default(); 38 | b.copy_from_slice(&byte[0..8]); 39 | Self::from_le_bytes(b) 40 | } 41 | } 42 | impl FromLe for i64 { 43 | fn from_le_bytes(byte: &[u8]) -> Self { 44 | let mut b: [u8; 8] = Default::default(); 45 | b.copy_from_slice(&byte[0..8]); 46 | Self::from_le_bytes(b) 47 | } 48 | } 49 | impl FromLe for f32 { 50 | fn from_le_bytes(byte: &[u8]) -> Self { 51 | let mut b: [u8; 4] = Default::default(); 52 | b.copy_from_slice(&byte[0..4]); 53 | Self::from_le_bytes(b) 54 | } 55 | } 56 | 57 | impl FromLe for f64 { 58 | fn from_le_bytes(byte: &[u8]) -> Self { 59 | let mut b: [u8; 8] = Default::default(); 60 | b.copy_from_slice(&byte[0..8]); 61 | Self::from_le_bytes(b) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/instance/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::module::Module; 2 | use crate::runtime::{error::RuntimeError, FunctionTable, Memory, Runtime, RuntimeValue}; 3 | use crate::types::*; 4 | 5 | #[derive(Debug)] 6 | pub struct Instance { 7 | module: Module, 8 | } 9 | 10 | type ValueStack = Vec; 11 | 12 | impl Instance { 13 | pub fn new(module: Module) -> Self { 14 | Self { module } 15 | } 16 | 17 | pub fn invoke( 18 | &self, 19 | name: impl AsRef, 20 | args: Vec, 21 | ) -> Result { 22 | let index = self.resolve_function_name(name.as_ref()); 23 | let index = match index { 24 | None => return Err(RuntimeError::NotFound(name.as_ref().to_string())), 25 | Some(i) => i, 26 | }; 27 | 28 | let function_table = self.function_table(); 29 | 30 | let func = function_table.get(index).unwrap(); 31 | let return_length = func.returns.len(); 32 | 33 | println!("exec func info: {:?}", func); 34 | 35 | Instance::validate(&func.params, &args)?; // argsとfunc_type.paramsの個数、型をチェックする + errorをいい感じに表示してあげたい 36 | let memory = Memory::new(self.init_memory()?); 37 | 38 | let mut runtime = Runtime::new(function_table, memory); 39 | let mut stack = runtime.execute(index, &args)?; 40 | 41 | stack.reverse(); 42 | let ret = &stack[0..return_length]; 43 | Ok(ret.to_vec()) 44 | } 45 | 46 | pub fn function_table(&self) -> FunctionTable { 47 | FunctionTable::from_module(&self.module) 48 | } 49 | 50 | fn resolve_function_name(&self, name: impl AsRef) -> Option { 51 | let export_section = &self.module.export_section; 52 | 53 | let exports = match export_section { 54 | None => return None, 55 | Some(e) => &e.entries, 56 | }; 57 | 58 | let entry = exports.iter().find(|x| x.field_str == name.as_ref()); 59 | 60 | entry.map(|x| x.index as usize) 61 | } 62 | 63 | fn init_memory(&self) -> Result, RuntimeError> { 64 | let section = self.module.data_section.as_ref(); 65 | if section.is_none() { 66 | return Ok(vec![]); 67 | } 68 | let init_memory = section.unwrap().segments.get(0).unwrap().data.clone(); 69 | 70 | Ok(init_memory) 71 | } 72 | 73 | fn validate(func_type: &[ValueType], args: &[RuntimeValue]) -> Result<(), RuntimeError> { 74 | let args_types: Vec<_> = args.iter().map(RuntimeValue::to_type).collect(); 75 | 76 | let expect = func_type; 77 | if expect != args_types { 78 | return Err(RuntimeError::InvalidArgs(expect.to_vec(), args_types)); 79 | } 80 | 81 | Ok(()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/instruction.rs: -------------------------------------------------------------------------------- 1 | use crate::opcode::Opcode; 2 | use crate::types::*; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub enum Instruction { 6 | Reserved, 7 | Prefix(VerUintN), 8 | Unreachable, 9 | Nop, 10 | Block(BlockType), 11 | Loop(BlockType), 12 | If(BlockType), 13 | Else, 14 | End, 15 | Br(VerUintN), 16 | BrIf(VerUintN), 17 | BrTable(Vec, VerUintN), 18 | Return, 19 | Call(VerUintN), 20 | CallIndirect(VerUintN, VerUintN), 21 | Drop, 22 | Select, 23 | GetLocal(VerUintN), 24 | SetLocal(VerUintN), 25 | TeeLocal(VerUintN), 26 | GetGlobal(VerUintN), 27 | SetGlobal(VerUintN), 28 | I32Load(u32, u32), 29 | I64Load(u32, u32), 30 | F32Load(u32, u32), 31 | F64Load(u32, u32), 32 | I32Load8S(u32, u32), 33 | I32Load8U(u32, u32), 34 | I32Load16S(u32, u32), 35 | I32Load16U(u32, u32), 36 | I64Load8S(u32, u32), 37 | I64Load8U(u32, u32), 38 | I64Load16S(u32, u32), 39 | I64Load16U(u32, u32), 40 | I64Load32S(u32, u32), 41 | I64Load32U(u32, u32), 42 | I32Store(u32, u32), 43 | I64Store(u32, u32), 44 | F32Store(u32, u32), 45 | F64Store(u32, u32), 46 | I32Store8(u32, u32), 47 | I32Store16(u32, u32), 48 | I64Store8(u32, u32), 49 | I64Store16(u32, u32), 50 | I64Store32(u32, u32), 51 | CurrentMemory(VerUintN), 52 | GrowMemory(VerUintN), 53 | I32Const(i32), 54 | I64Const(i64), 55 | F32Const(f32), 56 | F64Const(f64), 57 | I32Eqz, 58 | I32Eq, 59 | I32Ne, 60 | I32LtS, 61 | I32LtU, 62 | I32GtS, 63 | I32GtU, 64 | I32LeS, 65 | I32LeU, 66 | I32GeS, 67 | I32GeU, 68 | I64Eqz, 69 | I64Eq, 70 | I64Ne, 71 | I64LtS, 72 | I64LtU, 73 | I64GtS, 74 | I64GtU, 75 | I64LeS, 76 | I64LeU, 77 | I64GeS, 78 | I64GeU, 79 | F32Eq, 80 | F32Ne, 81 | F32Lt, 82 | F32Gt, 83 | F32Le, 84 | F32Ge, 85 | F64Eq, 86 | F64Ne, 87 | F64Lt, 88 | F64Gt, 89 | F64Le, 90 | F64Ge, 91 | I32Clz, 92 | I32Ctz, 93 | I32Popcnt, 94 | I32Add, 95 | I32Sub, 96 | I32Mul, 97 | I32DivS, 98 | I32DivU, 99 | I32RemS, 100 | I32RemU, 101 | I32And, 102 | I32Or, 103 | I32Xor, 104 | I32Shl, 105 | I32ShrS, 106 | I32ShrU, 107 | I32Rotl, 108 | I32Rotr, 109 | I64Clz, 110 | I64Ctz, 111 | I64Popcnt, 112 | I64Add, 113 | I64Sub, 114 | I64Mul, 115 | I64DivS, 116 | I64DivU, 117 | I64RemS, 118 | I64RemU, 119 | I64And, 120 | I64Or, 121 | I64Xor, 122 | I64Shl, 123 | I64ShrS, 124 | I64ShrU, 125 | I64Rotl, 126 | I64Rotr, 127 | F32Abs, 128 | F32Neg, 129 | F32Ceil, 130 | F32Floor, 131 | F32Trunc, 132 | F32Nearest, 133 | F32Sqrt, 134 | F32Add, 135 | F32Sub, 136 | F32Mul, 137 | F32Div, 138 | F32Min, 139 | F32Max, 140 | F32Copysign, 141 | F64Abs, 142 | F64Neg, 143 | F64Ceil, 144 | F64Floor, 145 | F64Trunc, 146 | F64Nearest, 147 | F64Sqrt, 148 | F64Add, 149 | F64Sub, 150 | F64Mul, 151 | F64Div, 152 | F64Min, 153 | F64Max, 154 | F64Copysign, 155 | I32WrapI64, 156 | I32TruncSF32, 157 | I32TruncUF32, 158 | I32TruncSF64, 159 | I32TruncUF64, 160 | I64ExtendSI32, 161 | I64ExtendUI32, 162 | I64TruncSF32, 163 | I64TruncUF32, 164 | I64TruncSF64, 165 | I64TruncUF64, 166 | F32ConvertSI32, 167 | F32ConvertUI32, 168 | F32ConvertSI64, 169 | F32ConvertUI64, 170 | F32DemoteF64, 171 | F64ConvertSI32, 172 | F64ConvertUI32, 173 | F64ConvertSI64, 174 | F64ConvertUI64, 175 | F64PromoteF32, 176 | I32ReinterpretF32, 177 | I64ReinterpretF64, 178 | F32ReinterpretI32, 179 | F64ReinterpretI64, 180 | } 181 | 182 | impl From for Instruction { 183 | fn from(opcode: Opcode) -> Self { 184 | use Instruction::*; 185 | match opcode { 186 | Opcode::Unreachable => Unreachable, 187 | Opcode::Nop => Nop, 188 | Opcode::Else => Else, 189 | Opcode::End => End, 190 | Opcode::Return => Return, 191 | Opcode::Drop => Drop, 192 | Opcode::Select => Select, 193 | Opcode::I32Eqz => I32Eqz, 194 | Opcode::I32Eq => I32Eq, 195 | Opcode::I32Ne => I32Ne, 196 | Opcode::I32LtS => I32LtS, 197 | Opcode::I32LtU => I32LtU, 198 | Opcode::I32GtS => I32GtS, 199 | Opcode::I32GtU => I32GtU, 200 | Opcode::I32LeS => I32LeS, 201 | Opcode::I32LeU => I32LeU, 202 | Opcode::I32GeS => I32GeS, 203 | Opcode::I32GeU => I32GeU, 204 | Opcode::I64Eqz => I64Eqz, 205 | Opcode::I64Eq => I64Eq, 206 | Opcode::I64Ne => I64Ne, 207 | Opcode::I64LtS => I64LtS, 208 | Opcode::I64LtU => I64LtU, 209 | Opcode::I64GtS => I64GtS, 210 | Opcode::I64GtU => I64GtU, 211 | Opcode::I64LeS => I64LeS, 212 | Opcode::I64LeU => I64LeU, 213 | Opcode::I64GeS => I64GeS, 214 | Opcode::I64GeU => I64GeU, 215 | Opcode::F32Eq => F32Eq, 216 | Opcode::F32Ne => F32Ne, 217 | Opcode::F32Lt => F32Lt, 218 | Opcode::F32Gt => F32Gt, 219 | Opcode::F32Le => F32Le, 220 | Opcode::F32Ge => F32Ge, 221 | Opcode::F64Eq => F64Eq, 222 | Opcode::F64Ne => F64Ne, 223 | Opcode::F64Lt => F64Lt, 224 | Opcode::F64Gt => F64Gt, 225 | Opcode::F64Le => F64Le, 226 | Opcode::F64Ge => F64Ge, 227 | Opcode::I32Clz => I32Clz, 228 | Opcode::I32Ctz => I32Ctz, 229 | Opcode::I32Popcnt => I32Popcnt, 230 | Opcode::I32Add => I32Add, 231 | Opcode::I32Sub => I32Sub, 232 | Opcode::I32Mul => I32Mul, 233 | Opcode::I32DivS => I32DivS, 234 | Opcode::I32DivU => I32DivU, 235 | Opcode::I32RemS => I32RemS, 236 | Opcode::I32RemU => I32RemU, 237 | Opcode::I32And => I32And, 238 | Opcode::I32Or => I32Or, 239 | Opcode::I32Xor => I32Xor, 240 | Opcode::I32Shl => I32Shl, 241 | Opcode::I32ShrS => I32ShrS, 242 | Opcode::I32ShrU => I32ShrU, 243 | Opcode::I32Rotl => I32Rotl, 244 | Opcode::I32Rotr => I32Rotr, 245 | Opcode::I64Clz => I64Clz, 246 | Opcode::I64Ctz => I64Ctz, 247 | Opcode::I64Popcnt => I64Popcnt, 248 | Opcode::I64Add => I64Add, 249 | Opcode::I64Sub => I64Sub, 250 | Opcode::I64Mul => I64Mul, 251 | Opcode::I64DivS => I64DivS, 252 | Opcode::I64DivU => I64DivU, 253 | Opcode::I64RemS => I64RemS, 254 | Opcode::I64RemU => I64RemU, 255 | Opcode::I64And => I64And, 256 | Opcode::I64Or => I64Or, 257 | Opcode::I64Xor => I64Xor, 258 | Opcode::I64Shl => I64Shl, 259 | Opcode::I64ShrS => I64ShrS, 260 | Opcode::I64ShrU => I64ShrU, 261 | Opcode::I64Rotl => I64Rotl, 262 | Opcode::I64Rotr => I64Rotr, 263 | Opcode::F32Abs => F32Abs, 264 | Opcode::F32Neg => F32Neg, 265 | Opcode::F32Ceil => F32Ceil, 266 | Opcode::F32Floor => F32Floor, 267 | Opcode::F32Trunc => F32Trunc, 268 | Opcode::F32Nearest => F32Nearest, 269 | Opcode::F32Sqrt => F32Sqrt, 270 | Opcode::F32Add => F32Add, 271 | Opcode::F32Sub => F32Sub, 272 | Opcode::F32Mul => F32Mul, 273 | Opcode::F32Div => F32Div, 274 | Opcode::F32Min => F32Min, 275 | Opcode::F32Max => F32Max, 276 | Opcode::F32Copysign => F32Copysign, 277 | Opcode::F64Abs => F64Abs, 278 | Opcode::F64Neg => F64Neg, 279 | Opcode::F64Ceil => F64Ceil, 280 | Opcode::F64Floor => F64Floor, 281 | Opcode::F64Trunc => F64Trunc, 282 | Opcode::F64Nearest => F64Nearest, 283 | Opcode::F64Sqrt => F64Sqrt, 284 | Opcode::F64Add => F64Add, 285 | Opcode::F64Sub => F64Sub, 286 | Opcode::F64Mul => F64Mul, 287 | Opcode::F64Div => F64Div, 288 | Opcode::F64Min => F64Min, 289 | Opcode::F64Max => F64Max, 290 | Opcode::F64Copysign => F64Copysign, 291 | Opcode::I32WrapI64 => I32WrapI64, 292 | Opcode::I32TruncSF32 => I32TruncSF32, 293 | Opcode::I32TruncUF32 => I32TruncUF32, 294 | Opcode::I32TruncSF64 => I32TruncSF64, 295 | Opcode::I32TruncUF64 => I32TruncUF64, 296 | Opcode::I64ExtendSI32 => I64ExtendSI32, 297 | Opcode::I64ExtendUI32 => I64ExtendUI32, 298 | Opcode::I64TruncSF32 => I64TruncSF32, 299 | Opcode::I64TruncUF32 => I64TruncUF32, 300 | Opcode::I64TruncSF64 => I64TruncSF64, 301 | Opcode::I64TruncUF64 => I64TruncUF64, 302 | Opcode::F32ConvertSI32 => F32ConvertSI32, 303 | Opcode::F32ConvertUI32 => F32ConvertUI32, 304 | Opcode::F32ConvertSI64 => F32ConvertSI64, 305 | Opcode::F32ConvertUI64 => F32ConvertUI64, 306 | Opcode::F32DemoteF64 => F32DemoteF64, 307 | Opcode::F64ConvertSI32 => F64ConvertSI32, 308 | Opcode::F64ConvertUI32 => F64ConvertUI32, 309 | Opcode::F64ConvertSI64 => F64ConvertSI64, 310 | Opcode::F64ConvertUI64 => F64ConvertUI64, 311 | Opcode::F64PromoteF32 => F64PromoteF32, 312 | Opcode::I32ReinterpretF32 => I32ReinterpretF32, 313 | Opcode::I64ReinterpretF64 => I64ReinterpretF64, 314 | Opcode::F32ReinterpretI32 => F32ReinterpretI32, 315 | Opcode::F64ReinterpretI64 => F64ReinterpretI64, 316 | Opcode::Reserved => Reserved, 317 | _ => todo!("{:x?}", opcode), 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod decode; 2 | mod from_le; 3 | mod instance; 4 | mod instruction; 5 | mod module; 6 | mod opcode; 7 | mod runtime; 8 | mod types; 9 | 10 | pub use runtime::{RuntimeError, RuntimeValue}; 11 | pub use {instance::Instance, module::Module}; 12 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use wai::*; 3 | 4 | #[derive(Parser)] 5 | #[clap( 6 | version = "0.2.0", 7 | author = "k-nasa ", 8 | about = "A simple wasm interpreter" 9 | )] 10 | struct Opts { 11 | file_path: String, 12 | 13 | #[clap(short, long)] 14 | invoke: String, 15 | 16 | #[clap(short, long)] 17 | args: Vec, 18 | } 19 | 20 | fn main() -> anyhow::Result<()> { 21 | std::env::set_var("RUST_LOG", "debug"); 22 | env_logger::init(); 23 | 24 | let opts: Opts = Opts::parse(); 25 | 26 | let filename = opts.file_path; 27 | let bytes = std::fs::read(filename)?; 28 | 29 | log::info!("start exec {:?}, args {:?}", opts.invoke, opts.args); 30 | 31 | let result = run_wasm(bytes, opts.invoke, opts.args)?; 32 | log::info!("return value is {:?}", result); 33 | 34 | Ok(()) 35 | } 36 | 37 | fn run_wasm( 38 | wasm_bytes: Vec, 39 | entory_point: String, 40 | args: Vec, 41 | ) -> anyhow::Result> { 42 | let m = Module::from_byte(wasm_bytes)?; 43 | log::debug!("module: {:#?}", m); 44 | let instance = Instance::new(m); 45 | 46 | let values = instance.invoke(&entory_point, args)?; 47 | Ok(values) 48 | } 49 | -------------------------------------------------------------------------------- /src/module/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::decode; 2 | use crate::types::*; 3 | 4 | #[derive(Debug, Clone, Default, PartialEq)] 5 | pub struct Module { 6 | pub(crate) version: u32, 7 | pub(crate) custom_section: Option<()>, 8 | pub(crate) type_section: Option, 9 | pub(crate) import_section: Option<()>, 10 | pub(crate) function_section: Option, 11 | pub(crate) table_section: Option<()>, 12 | pub(crate) memory_section: Option<()>, 13 | pub(crate) global_section: Option<()>, 14 | pub(crate) export_section: Option, 15 | pub(crate) element_section: Option<()>, 16 | pub(crate) start_section: Option<()>, 17 | pub(crate) code_section: Option, 18 | pub(crate) data_section: Option, 19 | } 20 | 21 | impl Module { 22 | pub fn from_byte(byte: impl AsRef<[u8]>) -> Result { 23 | decode::decode(byte.as_ref()) 24 | } 25 | 26 | // TODO refactor, section_typeとsectionの両方を取る必要はない 27 | pub(crate) fn take_in(&mut self, section: Section) { 28 | use Section::*; 29 | 30 | match section { 31 | Custom(i) => self.custom_section = Some(i), 32 | Type(i) => self.type_section = Some(i), 33 | Import(i) => self.import_section = Some(i), 34 | Function(i) => self.function_section = Some(i), 35 | Table(i) => self.table_section = Some(i), 36 | Memory(i) => self.memory_section = Some(i), 37 | Global(i) => self.global_section = Some(i), 38 | Export(i) => self.export_section = Some(i), 39 | Start(i) => self.start_section = Some(i), 40 | Element(i) => self.element_section = Some(i), 41 | Code(i) => self.code_section = Some(i), 42 | Data(i) => self.data_section = Some(i), 43 | } 44 | } 45 | } 46 | 47 | #[derive(Clone, Copy, Debug)] 48 | pub enum SectionType { 49 | Custom, 50 | Type, 51 | Import, 52 | Function, 53 | Table, 54 | Memory, 55 | Global, 56 | Export, 57 | Start, 58 | Element, 59 | Code, 60 | Data, 61 | Unsuport, 62 | } 63 | 64 | impl From for SectionType { 65 | fn from(x: u8) -> Self { 66 | use self::SectionType::*; 67 | match x { 68 | 0x0 => Custom, 69 | 0x1 => Type, 70 | 0x2 => Import, 71 | 0x3 => Function, 72 | 0x4 => Table, 73 | 0x5 => Memory, 74 | 0x6 => Global, 75 | 0x7 => Export, 76 | 0x8 => Start, 77 | 0x9 => Element, 78 | 0xA => Code, 79 | 0xB => Data, 80 | _ => Unsuport, 81 | } 82 | } 83 | } 84 | 85 | #[derive(Clone, Debug)] 86 | pub enum Section { 87 | Custom(()), 88 | Type(TypeSection), 89 | Import(()), 90 | Function(FunctionSection), 91 | Table(()), 92 | Memory(()), 93 | Global(()), 94 | Export(ExportSection), 95 | Start(()), 96 | Element(()), 97 | Code(CodeSection), 98 | Data(DataSection), 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::*; 104 | 105 | #[test] 106 | fn module_default_test() { 107 | let m = Module::default(); 108 | let expect = Module { 109 | version: 0, 110 | custom_section: None, 111 | type_section: None, 112 | import_section: None, 113 | function_section: None, 114 | table_section: None, 115 | memory_section: None, 116 | global_section: None, 117 | export_section: None, 118 | element_section: None, 119 | start_section: None, 120 | code_section: None, 121 | data_section: None, 122 | }; 123 | 124 | assert_eq!(expect, m); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/opcode.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::error::DecodeError; 2 | use std::convert::TryFrom; 3 | 4 | #[derive(Clone, Copy, Debug, PartialEq)] 5 | pub enum Opcode { 6 | Unreachable, 7 | Nop, 8 | Block, 9 | Loop, 10 | If, 11 | Else, 12 | End, 13 | Br, 14 | BrIf, 15 | BrTable, 16 | Return, 17 | Call, 18 | CallIndirect, 19 | Drop, 20 | Select, 21 | GetLocal, 22 | SetLocal, 23 | TeeLocal, 24 | GetGlobal, 25 | SetGlobal, 26 | I32Load, 27 | I64Load, 28 | F32Load, 29 | F64Load, 30 | I32Load8S, 31 | I32Load8U, 32 | I32Load16S, 33 | I32Load16U, 34 | I64Load8S, 35 | I64Load8U, 36 | I64Load16S, 37 | I64Load16U, 38 | I64Load32S, 39 | I64Load32U, 40 | I32Store, 41 | I64Store, 42 | F32Store, 43 | F64Store, 44 | I32Store8, 45 | I32Store16, 46 | I64Store8, 47 | I64Store16, 48 | I64Store32, 49 | CurrentMemory, 50 | GrowMemory, 51 | I32Const, 52 | I64Const, 53 | F32Const, 54 | F64Const, 55 | I32Eqz, 56 | I32Eq, 57 | I32Ne, 58 | I32LtS, 59 | I32LtU, 60 | I32GtS, 61 | I32GtU, 62 | I32LeS, 63 | I32LeU, 64 | I32GeS, 65 | I32GeU, 66 | I64Eqz, 67 | I64Eq, 68 | I64Ne, 69 | I64LtS, 70 | I64LtU, 71 | I64GtS, 72 | I64GtU, 73 | I64LeS, 74 | I64LeU, 75 | I64GeS, 76 | I64GeU, 77 | F32Eq, 78 | F32Ne, 79 | F32Lt, 80 | F32Gt, 81 | F32Le, 82 | F32Ge, 83 | F64Eq, 84 | F64Ne, 85 | F64Lt, 86 | F64Gt, 87 | F64Le, 88 | F64Ge, 89 | I32Clz, 90 | I32Ctz, 91 | I32Popcnt, 92 | I32Add, 93 | I32Sub, 94 | I32Mul, 95 | I32DivS, 96 | I32DivU, 97 | I32RemS, 98 | I32RemU, 99 | I32And, 100 | I32Or, 101 | I32Xor, 102 | I32Shl, 103 | I32ShrS, 104 | I32ShrU, 105 | I32Rotl, 106 | I32Rotr, 107 | I64Clz, 108 | I64Ctz, 109 | I64Popcnt, 110 | I64Add, 111 | I64Sub, 112 | I64Mul, 113 | I64DivS, 114 | I64DivU, 115 | I64RemS, 116 | I64RemU, 117 | I64And, 118 | I64Or, 119 | I64Xor, 120 | I64Shl, 121 | I64ShrS, 122 | I64ShrU, 123 | I64Rotl, 124 | I64Rotr, 125 | F32Abs, 126 | F32Neg, 127 | F32Ceil, 128 | F32Floor, 129 | F32Trunc, 130 | F32Nearest, 131 | F32Sqrt, 132 | F32Add, 133 | F32Sub, 134 | F32Mul, 135 | F32Div, 136 | F32Min, 137 | F32Max, 138 | F32Copysign, 139 | F64Abs, 140 | F64Neg, 141 | F64Ceil, 142 | F64Floor, 143 | F64Trunc, 144 | F64Nearest, 145 | F64Sqrt, 146 | F64Add, 147 | F64Sub, 148 | F64Mul, 149 | F64Div, 150 | F64Min, 151 | F64Max, 152 | F64Copysign, 153 | I32WrapI64, 154 | I32TruncSF32, 155 | I32TruncUF32, 156 | I32TruncSF64, 157 | I32TruncUF64, 158 | I64ExtendSI32, 159 | I64ExtendUI32, 160 | I64TruncSF32, 161 | I64TruncUF32, 162 | I64TruncSF64, 163 | I64TruncUF64, 164 | F32ConvertSI32, 165 | F32ConvertUI32, 166 | F32ConvertSI64, 167 | F32ConvertUI64, 168 | F32DemoteF64, 169 | F64ConvertSI32, 170 | F64ConvertUI32, 171 | F64ConvertSI64, 172 | F64ConvertUI64, 173 | F64PromoteF32, 174 | I32ReinterpretF32, 175 | I64ReinterpretF64, 176 | F32ReinterpretI32, 177 | F64ReinterpretI64, 178 | Reserved, 179 | Prefix, 180 | } 181 | 182 | impl TryFrom for Opcode { 183 | type Error = DecodeError; 184 | 185 | fn try_from(x: u8) -> Result { 186 | use Opcode::*; 187 | 188 | let opcode = match x { 189 | 0x00 => Unreachable, 190 | 0x01 => Nop, 191 | 0x02 => Block, 192 | 0x03 => Loop, 193 | 0x04 => If, 194 | 0x05 => Else, 195 | 0x0B => End, 196 | 0x0C => Br, 197 | 0x0D => BrIf, 198 | 0x0E => BrTable, 199 | 0x0F => Return, 200 | 0x10 => Call, 201 | 0x11 => CallIndirect, 202 | 0x1A => Drop, 203 | 0x1B => Select, 204 | 0x20 => GetLocal, 205 | 0x21 => SetLocal, 206 | 0x22 => TeeLocal, 207 | 0x23 => GetGlobal, 208 | 0x24 => SetGlobal, 209 | 0x28 => I32Load, 210 | 0x29 => I64Load, 211 | 0x2A => F32Load, 212 | 0x2B => F64Load, 213 | 0x2C => I32Load8S, 214 | 0x2D => I32Load8U, 215 | 0x2E => I32Load16S, 216 | 0x2F => I32Load16U, 217 | 0x30 => I64Load8S, 218 | 0x31 => I64Load8U, 219 | 0x32 => I64Load16S, 220 | 0x33 => I64Load16U, 221 | 0x34 => I64Load32S, 222 | 0x35 => I64Load32U, 223 | 0x36 => I32Store, 224 | 0x37 => I64Store, 225 | 0x38 => F32Store, 226 | 0x39 => F64Store, 227 | 0x3A => I32Store8, 228 | 0x3B => I32Store16, 229 | 0x3C => I64Store8, 230 | 0x3D => I64Store16, 231 | 0x3E => I64Store32, 232 | 0x3F => CurrentMemory, 233 | 0x40 => GrowMemory, 234 | 0x41 => I32Const, 235 | 0x42 => I64Const, 236 | 0x43 => F32Const, 237 | 0x44 => F64Const, 238 | 0x45 => I32Eqz, 239 | 0x46 => I32Eq, 240 | 0x47 => I32Ne, 241 | 0x48 => I32LtS, 242 | 0x49 => I32LtU, 243 | 0x4A => I32GtS, 244 | 0x4B => I32GtU, 245 | 0x4C => I32LeS, 246 | 0x4D => I32LeU, 247 | 0x4E => I32GeS, 248 | 0x4F => I32GeU, 249 | 0x50 => I64Eqz, 250 | 0x51 => I64Eq, 251 | 0x52 => I64Ne, 252 | 0x53 => I64LtS, 253 | 0x54 => I64LtU, 254 | 0x55 => I64GtS, 255 | 0x56 => I64GtU, 256 | 0x57 => I64LeS, 257 | 0x58 => I64LeU, 258 | 0x59 => I64GeS, 259 | 0x5A => I64GeU, 260 | 0x5B => F32Eq, 261 | 0x5C => F32Ne, 262 | 0x5D => F32Lt, 263 | 0x5E => F32Gt, 264 | 0x5F => F32Le, 265 | 0x60 => F32Ge, 266 | 0x61 => F64Eq, 267 | 0x62 => F64Ne, 268 | 0x63 => F64Lt, 269 | 0x64 => F64Gt, 270 | 0x65 => F64Le, 271 | 0x66 => F64Ge, 272 | 0x67 => I32Clz, 273 | 0x68 => I32Ctz, 274 | 0x69 => I32Popcnt, 275 | 0x6A => I32Add, 276 | 0x6B => I32Sub, 277 | 0x6C => I32Mul, 278 | 0x6D => I32DivS, 279 | 0x6E => I32DivU, 280 | 0x6F => I32RemS, 281 | 0x70 => I32RemU, 282 | 0x71 => I32And, 283 | 0x72 => I32Or, 284 | 0x73 => I32Xor, 285 | 0x74 => I32Shl, 286 | 0x75 => I32ShrS, 287 | 0x76 => I32ShrU, 288 | 0x77 => I32Rotl, 289 | 0x78 => I32Rotr, 290 | 0x79 => I64Clz, 291 | 0x7A => I64Ctz, 292 | 0x7B => I64Popcnt, 293 | 0x7C => I64Add, 294 | 0x7D => I64Sub, 295 | 0x7E => I64Mul, 296 | 0x7F => I64DivS, 297 | 0x80 => I64DivU, 298 | 0x81 => I64RemS, 299 | 0x82 => I64RemU, 300 | 0x83 => I64And, 301 | 0x84 => I64Or, 302 | 0x85 => I64Xor, 303 | 0x86 => I64Shl, 304 | 0x87 => I64ShrS, 305 | 0x88 => I64ShrU, 306 | 0x89 => I64Rotl, 307 | 0x8A => I64Rotr, 308 | 0x8B => F32Abs, 309 | 0x8C => F32Neg, 310 | 0x8D => F32Ceil, 311 | 0x8E => F32Floor, 312 | 0x8F => F32Trunc, 313 | 0x90 => F32Nearest, 314 | 0x91 => F32Sqrt, 315 | 0x92 => F32Add, 316 | 0x93 => F32Sub, 317 | 0x94 => F32Mul, 318 | 0x95 => F32Div, 319 | 0x96 => F32Min, 320 | 0x97 => F32Max, 321 | 0x98 => F32Copysign, 322 | 0x99 => F64Abs, 323 | 0x9A => F64Neg, 324 | 0x9B => F64Ceil, 325 | 0x9C => F64Floor, 326 | 0x9D => F64Trunc, 327 | 0x9E => F64Nearest, 328 | 0x9F => F64Sqrt, 329 | 0xA0 => F64Add, 330 | 0xA1 => F64Sub, 331 | 0xA2 => F64Mul, 332 | 0xA3 => F64Div, 333 | 0xA4 => F64Min, 334 | 0xA5 => F64Max, 335 | 0xA6 => F64Copysign, 336 | 0xA7 => I32WrapI64, 337 | 0xA8 => I32TruncSF32, 338 | 0xA9 => I32TruncUF32, 339 | 0xAA => I32TruncSF64, 340 | 0xAB => I32TruncUF64, 341 | 0xAC => I64ExtendSI32, 342 | 0xAD => I64ExtendUI32, 343 | 0xAE => I64TruncSF32, 344 | 0xAF => I64TruncUF32, 345 | 0xB0 => I64TruncSF64, 346 | 0xB1 => I64TruncUF64, 347 | 0xB2 => F32ConvertSI32, 348 | 0xB3 => F32ConvertUI32, 349 | 0xB4 => F32ConvertSI64, 350 | 0xB5 => F32ConvertUI64, 351 | 0xB6 => F32DemoteF64, 352 | 0xB7 => F64ConvertSI32, 353 | 0xB8 => F64ConvertUI32, 354 | 0xB9 => F64ConvertSI64, 355 | 0xBA => F64ConvertUI64, 356 | 0xBB => F64PromoteF32, 357 | 0xBC => I32ReinterpretF32, 358 | 0xBD => I64ReinterpretF64, 359 | 0xBE => F32ReinterpretI32, 360 | 0xBF => F64ReinterpretI64, 361 | 362 | 0x6 => Reserved, 363 | 0x7 => Reserved, 364 | 0x8 => Reserved, 365 | 0x9 => Reserved, 366 | 0xA => Reserved, 367 | 368 | 0x12 => Reserved, 369 | 0x13 => Reserved, 370 | 0x1C => Reserved, 371 | 372 | 0x25 => Reserved, 373 | 0x26 => Reserved, 374 | 375 | 0xC0 => Reserved, 376 | 0xC1 => Reserved, 377 | 0xC2 => Reserved, 378 | 0xC3 => Reserved, 379 | 0xC4 => Reserved, 380 | 381 | 0xD0 => Reserved, 382 | 0xD1 => Reserved, 383 | 0xD2 => Reserved, 384 | 0xD3 => Reserved, 385 | 386 | 0xFF => Reserved, 387 | 0xFE => Reserved, 388 | 0xFD => Reserved, 389 | 0xFC => Prefix, 390 | 391 | opcode => { 392 | return Err(DecodeError::Unexpected(format!( 393 | "unexpected opcode {:x}", 394 | opcode 395 | ))) 396 | } 397 | }; 398 | 399 | Ok(opcode) 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/runtime/activation_stack.rs: -------------------------------------------------------------------------------- 1 | use crate::runtime::RuntimeError; 2 | use crate::runtime::RuntimeValue; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug)] 6 | pub struct ActivationStack(Vec); 7 | 8 | pub type Locals = HashMap; 9 | 10 | impl ActivationStack { 11 | pub fn new() -> Self { 12 | Self(vec![]) 13 | } 14 | 15 | pub fn init(function_index: usize, locals_vec: Vec) -> Self { 16 | let mut locals: Locals = HashMap::new(); 17 | 18 | for (i, _) in locals_vec.iter().enumerate() { 19 | locals.insert(i, locals_vec[i]); 20 | } 21 | 22 | Self(vec![Activation::new(function_index, locals)]) 23 | } 24 | 25 | pub fn pc(&mut self) -> Result { 26 | let activation = match self.last_mut() { 27 | None => return Err(RuntimeError::ExpectActivationStack), 28 | Some(v) => v, 29 | }; 30 | 31 | Ok(activation.pc) 32 | } 33 | 34 | pub fn pop(&mut self) -> Option { 35 | self.0.pop() 36 | } 37 | 38 | pub fn len(&mut self) -> usize { 39 | self.0.len() 40 | } 41 | 42 | pub fn push(&mut self, activation: Activation) { 43 | self.0.push(activation) 44 | } 45 | 46 | pub fn set_pc(&mut self, pc: usize) -> Result<(), RuntimeError> { 47 | let activation = match self.last_mut() { 48 | None => return Err(RuntimeError::ExpectActivationStack), 49 | Some(v) => v, 50 | }; 51 | activation.pc = pc; 52 | Ok(()) 53 | } 54 | 55 | pub fn increment_pc(&mut self) -> Result<(), RuntimeError> { 56 | let activation = match self.last_mut() { 57 | None => return Err(RuntimeError::ExpectActivationStack), 58 | Some(v) => v, 59 | }; 60 | 61 | activation.pc += 1; 62 | 63 | Ok(()) 64 | } 65 | 66 | pub fn get_local(&mut self, i: usize) -> Result<&RuntimeValue, RuntimeError> { 67 | let locals = match self.locales_mut() { 68 | None => return Err(RuntimeError::ExpectActivationStack), 69 | Some(v) => v, 70 | }; 71 | 72 | match locals.get(&i) { 73 | None => Err(RuntimeError::NotFound("local".to_string())), 74 | Some(v) => Ok(v), 75 | } 76 | } 77 | 78 | pub fn set_local(&mut self, index: usize, v: RuntimeValue) -> Result<(), RuntimeError> { 79 | let locals = match self.locales_mut() { 80 | None => return Err(RuntimeError::ExpectActivationStack), 81 | Some(v) => v, 82 | }; 83 | 84 | locals.insert(index, v); 85 | 86 | Ok(()) 87 | } 88 | 89 | pub fn last(&self) -> Option<&Activation> { 90 | self.0.last() 91 | } 92 | 93 | fn last_mut(&mut self) -> Option<&mut Activation> { 94 | self.0.last_mut() 95 | } 96 | 97 | fn locales_mut(&mut self) -> Option<&mut Locals> { 98 | self.last_mut().map(|a| &mut a.locals) 99 | } 100 | } 101 | 102 | #[derive(Debug)] 103 | pub struct Activation { 104 | pub pc: usize, 105 | pub function_index: usize, 106 | 107 | pub locals: Locals, 108 | } 109 | 110 | impl Activation { 111 | pub fn new(function_index: usize, locals: Locals) -> Self { 112 | Self { 113 | function_index, 114 | locals, 115 | pc: 0, 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/runtime/error.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | use std::error::Error; 3 | use std::fmt::{self, Display}; 4 | 5 | #[derive(Debug)] 6 | pub enum RuntimeError { 7 | NotFound(String), 8 | ExpectCodeSection, 9 | ExpectValueStack, 10 | ExpectLabelStack, 11 | ExpectActivationStack, 12 | Unimplemented, 13 | InvalidArgs(Vec, Vec), 14 | IOError(std::io::Error), 15 | Custom(String), 16 | } 17 | 18 | impl Error for RuntimeError {} 19 | impl Display for RuntimeError { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | use self::RuntimeError::*; 22 | match self { 23 | NotFound(name) => write!(f, "'{}'' is not found", name), 24 | ExpectValueStack => write!(f, "expect value stack, but nothing"), 25 | ExpectActivationStack => write!(f, "expect activation stack, but nothing"), 26 | ExpectLabelStack => write!(f, "expect label stack, but nothing"), 27 | Unimplemented => write!(f, "unimplemented"), 28 | InvalidArgs(expect, actual) => write!( 29 | f, 30 | "Invalid argument: expect {:?},but got {:?}", 31 | expect, actual 32 | ), 33 | ExpectCodeSection => { 34 | write!(f, "not found code section. wai is expected code section") 35 | } 36 | IOError(i) => write!(f, "io error: {}", i), 37 | Custom(s) => write!(f, "{}", s), 38 | } 39 | } 40 | } 41 | 42 | impl From for RuntimeError { 43 | fn from(error: std::io::Error) -> Self { 44 | Self::IOError(error) 45 | } 46 | } 47 | 48 | impl PartialEq for RuntimeError { 49 | // TODO implement 50 | fn eq(&self, other: &Self) -> bool { 51 | if self == &RuntimeError::Unimplemented && other == &RuntimeError::Unimplemented { 52 | return true; 53 | } 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/runtime/function_table.rs: -------------------------------------------------------------------------------- 1 | use crate::module::Module; 2 | use crate::types::*; 3 | 4 | pub struct FunctionTable(Vec); 5 | 6 | impl FunctionTable { 7 | fn empty() -> Self { 8 | Self(vec![]) 9 | } 10 | 11 | pub fn from_module(m: &Module) -> Self { 12 | let funcs = match m.function_section.as_ref() { 13 | Some(c) => c, 14 | None => return Self::empty(), 15 | }; 16 | 17 | let types = match m.type_section.as_ref() { 18 | Some(c) => c, 19 | None => return Self::empty(), 20 | }; 21 | 22 | let codes = match m.code_section.as_ref() { 23 | Some(c) => c, 24 | None => return Self::empty(), 25 | }; 26 | 27 | let mut f = vec![]; 28 | 29 | // NOTE codes, funcの長さは同じはず。なのでどれの長さを取ってループを回しても良い 30 | for i in 0..codes.bodies.len() { 31 | let type_index = match funcs.types.get(i) { 32 | None => return Self::empty(), 33 | Some(v) => v, 34 | }; 35 | 36 | let func_body = match codes.bodies.get(i) { 37 | None => return Self::empty(), 38 | Some(v) => v, 39 | }; 40 | 41 | let t = match types.entries.get(*type_index as usize) { 42 | None => return Self::empty(), 43 | Some(v) => v, 44 | }; 45 | 46 | f.push(Function::new( 47 | t.params.clone(), 48 | t.returns.clone(), 49 | func_body.code.clone(), 50 | )) 51 | } 52 | 53 | Self(f) 54 | } 55 | 56 | pub fn get(&self, i: usize) -> Option<&Function> { 57 | self.0.get(i) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct Function { 63 | pub params: Vec, 64 | pub returns: Vec, 65 | pub code: Vec, 66 | } 67 | 68 | impl Function { 69 | pub fn new(params: Vec, returns: Vec, code: Vec) -> Self { 70 | Self { 71 | params, 72 | returns, 73 | code, 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/runtime/label_stack.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | 3 | pub type LabelStack = Vec