├── .config └── dotnet-tools.json ├── .github └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── Benchmarks.sln ├── Directory.Build.props ├── Examples.sln ├── LICENSE ├── README.md ├── Wasmtime.sln ├── benchmarks └── simple │ ├── Program.cs │ └── simple.csproj ├── docs ├── .gitignore ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── intro.md │ └── toc.yml ├── docfx.json ├── index.md ├── templates │ └── darkfx │ │ ├── partials │ │ └── head.tmpl.partial │ │ └── styles │ │ └── main.css ├── toc.yml └── wasm │ └── intro │ └── hello.wasm ├── examples ├── consumefuel │ ├── Program.cs │ ├── consumefuel.csproj │ └── consumefuel.wat ├── externref │ ├── Program.cs │ ├── externref.csproj │ └── externref.wat ├── funcref │ ├── Program.cs │ ├── funcref.csproj │ └── funcref.wat ├── global │ ├── Program.cs │ ├── global.csproj │ └── global.wat ├── hello │ ├── Program.cs │ ├── hello.csproj │ └── hello.wat ├── memory │ ├── Program.cs │ ├── memory.csproj │ └── memory.wat ├── storedata │ ├── Program.cs │ ├── storedata.csproj │ └── storedata.wat └── table │ ├── Program.cs │ ├── table.csproj │ └── table.wat ├── src ├── AssemblyAttributes.cs ├── Caller.cs ├── Config.cs ├── Delegates.cs ├── Engine.cs ├── Export.cs ├── Extensions.cs ├── Externs.cs ├── Function.FromCallback.cs ├── Function.FromCallback.tt ├── Function.Wrap.cs ├── Function.Wrap.tt ├── Function.cs ├── FunctionCallbackOverloadTemplates.t4 ├── Global.cs ├── Import.cs ├── Instance.cs ├── Linker.DefineFunction.cs ├── Linker.DefineFunction.tt ├── Linker.cs ├── Memory.cs ├── Module.cs ├── README.md ├── Result.cs ├── ReturnTypeFactory.cs ├── Store.cs ├── Table.cs ├── TemporaryAllocation.cs ├── TrapException.cs ├── V128.cs ├── Value.cs ├── ValueBox.cs ├── ValueRaw.cs ├── WasiConfiguration.cs ├── Wasmtime.csproj └── WasmtimeException.cs └── tests ├── CallExportFromImportTests.cs ├── CallerTests.cs ├── ConfigTests.cs ├── EngineTests.cs ├── EpochInterruptionTests.cs ├── ErrorTests.cs ├── ExitErrorTests.cs ├── ExternRefTests.cs ├── FuelConsumptionTests.cs ├── FuncRefTests.cs ├── FunctionExportsTests.cs ├── FunctionImportsTests.cs ├── FunctionTests.cs ├── GlobalExportsTests.cs ├── GlobalImportBindingTests.cs ├── GlobalImportsTests.cs ├── InstanceTests.cs ├── InvalidModuleTests.cs ├── LinkerFunctionsTests.cs ├── Memory64AccessTests.cs ├── MemoryAccessTests.cs ├── MemoryExportsTests.cs ├── MemoryImportBindingTests.cs ├── MemoryImportFromModuleTests.cs ├── MemoryImportNoUpperBoundTests.cs ├── MemoryImportWithUpperBoundTests.cs ├── ModuleFixture.cs ├── ModuleLoadTests.cs ├── ModuleSerializationTests.cs ├── Modules ├── BulkMemory.wat ├── CallExportFromImport.wat ├── Caller.wat ├── Error.wat ├── ExitError.wat ├── ExternRef.wat ├── FuelConsumption.wat ├── FuncRef.wat ├── FunctionExports.wat ├── FunctionImports.wat ├── Functions.wat ├── GlobalExports.wat ├── GlobalImportBindings.wat ├── GlobalImports.wat ├── Interrupt.wat ├── Memory64Access.wat ├── MemoryAccess.wat ├── MemoryExports.wat ├── MemoryImportBinding.wat ├── MemoryImportFromModule.wat ├── MemoryImportNoUpperBound.wat ├── MemoryImportWithUpperBound.wat ├── MultiValue.wat ├── RelaxedSIMD.wat ├── SIMD.wat ├── SharedMemory.wat ├── TableExports.wat ├── TableImportBinding.wat ├── TableImports.wat ├── Trap.wat ├── Wasi.wat ├── hello.wasm └── hello.wat ├── MultiMemoryTests.cs ├── StoreDataTests.cs ├── StoreFixture.cs ├── StoreTests.cs ├── TableExportsTests.cs ├── TableImportBindingTests.cs ├── TableImportsTests.cs ├── TempFile.cs ├── TrapTests.cs ├── ValueBoxTests.cs ├── WasiTests.cs └── Wasmtime.Tests.csproj /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-t4": { 6 | "version": "2.3.1", 7 | "commands": [ 8 | "t4" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main, 'release-*'] 5 | tags-ignore: [dev] 6 | pull_request: 7 | branches: [main, 'release-*'] 8 | schedule: 9 | - cron: '0 0 * * *' # run at 00:00 UTC 10 | 11 | jobs: 12 | build: 13 | name: Test .NET embedding of Wasmtime 14 | runs-on: ${{ matrix.os }} 15 | env: 16 | DevBuild: 'false' 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | build: [linux-debug, linux-release, macos-debug, macos-release, windows-debug, windows-release] 21 | include: 22 | - build: linux-debug 23 | os: ubuntu-latest 24 | config: debug 25 | - build: linux-release 26 | os: ubuntu-latest 27 | config: release 28 | - build: macos-debug 29 | os: macos-latest 30 | config: debug 31 | - build: macos-release 32 | os: macos-latest 33 | config: release 34 | - build: windows-debug 35 | os: windows-2019 36 | config: debug 37 | - build: windows-release 38 | os: windows-2019 39 | config: release 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: actions/setup-dotnet@v4 43 | with: 44 | dotnet-version: '8.0.x' 45 | # workaround for actions/setup-dotnet#155 46 | - name: Clear package cache 47 | run: dotnet clean Wasmtime.sln && dotnet nuget locals all --clear 48 | - name: Enable development builds for the main branch 49 | if: github.ref == 'refs/heads/main' || github.base_ref == 'main' 50 | shell: bash 51 | run: | 52 | echo "DevBuild=true" >> $GITHUB_ENV 53 | - name: Restore packages 54 | run: dotnet restore Wasmtime.sln 55 | - name: Build 56 | run: dotnet build Wasmtime.sln -c ${{ matrix.config }} --no-restore 57 | - name: Test 58 | run: dotnet test Wasmtime.sln -c ${{ matrix.config }} 59 | - name: Benchmark 60 | if: matrix.config == 'release' 61 | run: dotnet run -c ${{ matrix.config }} --project benchmarks/simple/simple.csproj 62 | - name: Run examples 63 | shell: bash 64 | env: 65 | EXAMPLES: externref funcref global hello memory table consumefuel 66 | run: | 67 | for e in $EXAMPLES; do cd examples/$e && dotnet run -c ${{ matrix.config }} && cd ../..; done 68 | - name: Create package 69 | run: | 70 | cd src 71 | dotnet pack -c ${{ matrix.config }} /p:Packing=true 72 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # The purpose of this workflow is to publish the packages to NuGet 2 | # whenever a tag is created. 3 | 4 | name: "Publish to NuGet" 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | publish: 13 | name: "Publish NuGet Package" 14 | if: github.repository == 'bytecodealliance/wasmtime-dotnet' 15 | runs-on: ubuntu-latest 16 | env: 17 | DevBuild: 'false' 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Create NuGet package 21 | run: | 22 | cd src 23 | dotnet pack -c Release /p:Packing=true 24 | - name: Publish NuGet Package 25 | run: | 26 | cd src/bin/Release 27 | dotnet nuget push Wasmtime.${GITHUB_REF_NAME:1}.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | .DS_Store 4 | 5 | .vs/ 6 | .vscode 7 | 8 | bin/ 9 | obj/ 10 | 11 | BenchmarkDotNet.Artifacts/ 12 | -------------------------------------------------------------------------------- /Benchmarks.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasmtime", "src\Wasmtime.csproj", "{5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "simple", "benchmarks\simple\simple.csproj", "{9264D423-A3AA-4765-9EBF-7A62BCE58CD8}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9264D423-A3AA-4765-9EBF-7A62BCE58CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9264D423-A3AA-4765-9EBF-7A62BCE58CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9264D423-A3AA-4765-9EBF-7A62BCE58CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9264D423-A3AA-4765-9EBF-7A62BCE58CD8}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F5AC35E5-1373-49E6-97DC-68CB5E0369E0} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 22.0.0 5 | 6 | $(WasmtimeVersion)$(WasmtimeDotnetVersion)-dev 7 | $(WasmtimeVersion)$(WasmtimeDotnetVersion) 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "externref", "examples\externref\externref.csproj", "{080F8792-A298-4BF4-BC5E-46BD305ACE70}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "funcref", "examples\funcref\funcref.csproj", "{E984109E-9068-4617-85D8-A2FF75490198}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "global", "examples\global\global.csproj", "{26FE6450-FCAE-471D-AA6C-6510248602E9}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello", "examples\hello\hello.csproj", "{D3D124D2-AE6F-49F5-81F4-8FE41451856B}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "memory", "examples\memory\memory.csproj", "{9F2BF53D-F96F-4E74-82D1-DC1F940B7B54}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "table", "examples\table\table.csproj", "{14C0359D-E39E-4CD6-BD25-486B37079E1C}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasmtime", "src\Wasmtime.csproj", "{82B01E4A-1CAD-44BB-B923-11FA37173BD7}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "consumefuel", "examples\consumefuel\consumefuel.csproj", "{F79FAA27-3CA6-4CC3-BB50-CE27191770F1}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "storedata", "examples\storedata\storedata.csproj", "{E8749BAF-9D8B-4CF9-ACFF-490E86D52A20}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {080F8792-A298-4BF4-BC5E-46BD305ACE70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {080F8792-A298-4BF4-BC5E-46BD305ACE70}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {080F8792-A298-4BF4-BC5E-46BD305ACE70}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {080F8792-A298-4BF4-BC5E-46BD305ACE70}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {E984109E-9068-4617-85D8-A2FF75490198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {E984109E-9068-4617-85D8-A2FF75490198}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {E984109E-9068-4617-85D8-A2FF75490198}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {E984109E-9068-4617-85D8-A2FF75490198}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {26FE6450-FCAE-471D-AA6C-6510248602E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {26FE6450-FCAE-471D-AA6C-6510248602E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {26FE6450-FCAE-471D-AA6C-6510248602E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {26FE6450-FCAE-471D-AA6C-6510248602E9}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {D3D124D2-AE6F-49F5-81F4-8FE41451856B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {D3D124D2-AE6F-49F5-81F4-8FE41451856B}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {D3D124D2-AE6F-49F5-81F4-8FE41451856B}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {D3D124D2-AE6F-49F5-81F4-8FE41451856B}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {9F2BF53D-F96F-4E74-82D1-DC1F940B7B54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {9F2BF53D-F96F-4E74-82D1-DC1F940B7B54}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {9F2BF53D-F96F-4E74-82D1-DC1F940B7B54}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {9F2BF53D-F96F-4E74-82D1-DC1F940B7B54}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {14C0359D-E39E-4CD6-BD25-486B37079E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {14C0359D-E39E-4CD6-BD25-486B37079E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {14C0359D-E39E-4CD6-BD25-486B37079E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {14C0359D-E39E-4CD6-BD25-486B37079E1C}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {82B01E4A-1CAD-44BB-B923-11FA37173BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {82B01E4A-1CAD-44BB-B923-11FA37173BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {82B01E4A-1CAD-44BB-B923-11FA37173BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {82B01E4A-1CAD-44BB-B923-11FA37173BD7}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {F79FAA27-3CA6-4CC3-BB50-CE27191770F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {F79FAA27-3CA6-4CC3-BB50-CE27191770F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {F79FAA27-3CA6-4CC3-BB50-CE27191770F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {F79FAA27-3CA6-4CC3-BB50-CE27191770F1}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {E8749BAF-9D8B-4CF9-ACFF-490E86D52A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {E8749BAF-9D8B-4CF9-ACFF-490E86D52A20}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {E8749BAF-9D8B-4CF9-ACFF-490E86D52A20}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {E8749BAF-9D8B-4CF9-ACFF-490E86D52A20}.Release|Any CPU.Build.0 = Release|Any CPU 66 | EndGlobalSection 67 | GlobalSection(SolutionProperties) = preSolution 68 | HideSolutionNode = FALSE 69 | EndGlobalSection 70 | GlobalSection(ExtensibilityGlobals) = postSolution 71 | SolutionGuid = {F5AC35E5-1373-49E6-97DC-68CB5E0369E0} 72 | EndGlobalSection 73 | EndGlobal 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

wasmtime-dotnet

3 | 4 |

5 | .NET embedding of 6 | Wasmtime 7 |

8 | 9 | A Bytecode Alliance project 10 | 11 |

12 | 13 | CI status 14 | 15 | 16 | Latest Version 17 | 18 | 19 | Documentation 20 | 21 |

22 | 23 |
24 | 25 | ## Installation 26 | 27 | You can add a package reference with the [.NET SDK](https://dotnet.microsoft.com/): 28 | 29 | ```text 30 | $ dotnet add package wasmtime 31 | ``` 32 | 33 | ## Introduction 34 | 35 | For this introduction, we'll be using a simple WebAssembly module that imports 36 | a `hello` function and exports a `run` function: 37 | 38 | ```wat 39 | (module 40 | (func $hello (import "" "hello")) 41 | (func (export "run") (call $hello)) 42 | ) 43 | ``` 44 | 45 | To use this module from .NET, create a new console project: 46 | 47 | ``` 48 | $ mkdir wasmintro 49 | $ cd wasmintro 50 | $ dotnet new console 51 | ``` 52 | 53 | Next, add a reference to the [Wasmtime package](https://www.nuget.org/packages/Wasmtime): 54 | 55 | ``` 56 | $ dotnet add package wasmtime 57 | ``` 58 | 59 | Replace the contents of `Program.cs` with the following code: 60 | 61 | ```c# 62 | using System; 63 | using Wasmtime; 64 | 65 | using var engine = new Engine(); 66 | 67 | using var module = Module.FromText( 68 | engine, 69 | "hello", 70 | "(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))" 71 | ); 72 | 73 | using var linker = new Linker(engine); 74 | using var store = new Store(engine); 75 | 76 | linker.Define( 77 | "", 78 | "hello", 79 | Function.FromCallback(store, () => Console.WriteLine("Hello from C#!")) 80 | ); 81 | 82 | var instance = linker.Instantiate(store, module); 83 | var run = instance.GetAction("run")!; 84 | run(); 85 | ``` 86 | 87 | An `Engine` is created and then a WebAssembly module is loaded from a string in 88 | WebAssembly text format. 89 | 90 | A `Linker` defines a function called `hello` that simply prints a hello message. 91 | 92 | The module is instantiated and the instance's `run` export is invoked. 93 | 94 | To run the application, simply use `dotnet`: 95 | 96 | ``` 97 | $ dotnet run 98 | ``` 99 | 100 | This should print `Hello from C#!`. 101 | 102 | ## Contributing 103 | 104 | ### Building 105 | 106 | Use `dotnet` to build the repository: 107 | 108 | ``` 109 | $ dotnet build Wasmtime.sln 110 | ``` 111 | 112 | This will download the latest development snapshot of Wasmtime for your 113 | platform. 114 | 115 | ### Testing 116 | 117 | Use `dotnet` to run the unit tests: 118 | 119 | ``` 120 | $ dotnet test Wasmtime.sln 121 | ``` 122 | 123 | ### Creating the NuGet package 124 | 125 | Use `dotnet` to create a NuGet package: 126 | 127 | ``` 128 | $ cd src 129 | $ dotnet pack Wasmtime.sln -c Release /p:Packing=true 130 | ``` 131 | 132 | This will create a `.nupkg` file in `src/bin/Release`. 133 | 134 | By default, local builds will use a `-dev` suffix for the package to 135 | differentiate between official packages and development packages. 136 | 137 | ### Updating Wasmtime for a release 138 | 139 | To update the Wasmtime library used for a new release, change `WasmtimeVersion` 140 | in `Directory.Build.props`: 141 | 142 | ```xml 143 | $VERSION 144 | ``` 145 | 146 | ### Publishing the Wasmtime .NET NuGet package 147 | 148 | GitHub actions is used to automatically publish a package to NuGet when a tag 149 | is pushed to the repository. 150 | 151 | To publish a new release, create a release in GitHub and add the relevant 152 | release notes. 153 | 154 | Use a tag of the format `v$VERSION` where `$VERSION` matches the Wasmtime 155 | version used by the .NET package; ensure the tagged commit matches the last 156 | commit to make for the release. 157 | 158 | When the release is published on GitHub, an action should automatically start 159 | to build and publish the package to NuGet. 160 | -------------------------------------------------------------------------------- /Wasmtime.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasmtime", "src\Wasmtime.csproj", "{5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasmtime.Tests", "tests\Wasmtime.Tests.csproj", "{8A200114-1D0B-4F90-9F82-1FFE47C207DD}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5EB63C51-5286-4DDF-BF7F-4110CC6D80B8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {8A200114-1D0B-4F90-9F82-1FFE47C207DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {8A200114-1D0B-4F90-9F82-1FFE47C207DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {8A200114-1D0B-4F90-9F82-1FFE47C207DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8A200114-1D0B-4F90-9F82-1FFE47C207DD}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F5AC35E5-1373-49E6-97DC-68CB5E0369E0} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /benchmarks/simple/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using BenchmarkDotNet.Attributes; 4 | using BenchmarkDotNet.Configs; 5 | using BenchmarkDotNet.Jobs; 6 | using BenchmarkDotNet.Running; 7 | using BenchmarkDotNet.Toolchains.InProcess.Emit; 8 | using Wasmtime; 9 | 10 | namespace Simple 11 | { 12 | [Config(typeof(Config))] 13 | public class Benchmark 14 | { 15 | private class Config : ManualConfig 16 | { 17 | public Config() 18 | { 19 | AddJob(Job.MediumRun 20 | .WithLaunchCount(1) 21 | .WithToolchain(InProcessEmitToolchain.Instance) 22 | .WithId("InProcess")); 23 | } 24 | } 25 | 26 | public Benchmark() 27 | { 28 | _engine = new Engine(); 29 | _module = Module.FromText( 30 | _engine, 31 | "hello", 32 | @" 33 | (module 34 | (type $t0 (func)) 35 | (import """" ""hello"" (func $.hello (type $t0))) 36 | (func $run 37 | call $.hello 38 | ) 39 | (export ""run"" (func $run)) 40 | )" 41 | ); 42 | } 43 | 44 | [Benchmark] 45 | public void SayHello() 46 | { 47 | using var linker = new Linker(_engine); 48 | using var store = new Store(_engine); 49 | 50 | linker.Define("", "hello", Function.FromCallback(store, () => { })); 51 | linker.Define("", "memory", new Memory(store, 3)); 52 | 53 | var instance = linker.Instantiate(store, _module); 54 | var run = instance.GetFunction("run")!.WrapAction(); 55 | if (run == null) 56 | { 57 | throw new InvalidOperationException(); 58 | } 59 | 60 | run.Invoke(); 61 | } 62 | 63 | private readonly Engine _engine; 64 | private readonly Module _module; 65 | } 66 | 67 | class Program 68 | { 69 | static void Main(string[] args) 70 | { 71 | var summary = BenchmarkRunner.Run(); 72 | 73 | var report = summary[summary.BenchmarksCases.Single(c => c.Descriptor.Type == typeof(Benchmark))]; 74 | if (!(report is null)) 75 | { 76 | if (report.ExecuteResults.All(r => r.ExitCode == 0)) 77 | { 78 | return; 79 | } 80 | } 81 | 82 | Console.Error.WriteLine("Benchmark failed."); 83 | Environment.Exit(1); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /benchmarks/simple/simple.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | -------------------------------------------------------------------------------- /docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # .NET embedding of Wasmtime 2 | 3 | This is the [.NET API](https://github.com/bytecodealliance/wasmtime-dotnet) for [Wasmtime](https://github.com/bytecodealliance/wasmtime). 4 | 5 | See the [documentation](/api/Wasmtime.html) for the various .NET classes. 6 | 7 | See the [introduction](/articles/intro.html) for a simple walkthrough of using the .NET embedding for Wasmtime. -------------------------------------------------------------------------------- /docs/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to .NET embedding of Wasmtime 2 | 3 | [Wasmtime](https://github.com/bytecodealliance/wasmtime) is a standalone runtime capable of executing [WebAssembly](https://webassembly.org/) outside of a web browser. 4 | 5 | The [.NET embedding of Wasmtime](https://github.com/bytecodealliance/wasmtime-dotnet) enables .NET developers to easily instantiate and execute WebAssembly modules using Wasmtime. 6 | 7 | For this tutorial, we will create a WebAssembly module and use that WebAssembly module from a .NET 5 application. 8 | 9 | # A simple WebAssembly module 10 | 11 | One of the reasons why WebAssembly is so exciting is that [many languages are able to target WebAssembly](https://github.com/appcypher/awesome-wasm-langs). This means, for example, a plugin model based on WebAssembly could enable developers to write sandboxed, cross-platform plugins in any number of languages. 12 | 13 | For this introduction, however, we will use a WebAssembly module in [WebAssembly Text Format](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format): 14 | 15 | ```text 16 | (module 17 | (func $hello (import "" "hello")) 18 | (func (export "run") (call $hello)) 19 | ) 20 | ``` 21 | 22 | This module simply imports a `hello` function from the host and exports a `run` function that calls the imported function. 23 | 24 | # Using the WebAssembly module from .NET 25 | 26 | ## Installing a .NET 5 SDK 27 | 28 | Install a [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) for your platform if you haven't already. 29 | 30 | This will add a `dotnet` command to your PATH. 31 | 32 | ## Creating the .NET project 33 | 34 | The .NET program will be a simple console application, so create a new console project with `dotnet new`: 35 | 36 | ```text 37 | mkdir tutorial 38 | cd tutorial 39 | dotnet new console 40 | ``` 41 | 42 | ## Referencing the Wasmtime package 43 | 44 | To use the .NET embedding of Wasmtime from the project, we need to add a reference to the [Wasmtime NuGet package](https://www.nuget.org/packages/Wasmtime): 45 | 46 | ```text 47 | dotnet add package wasmtime 48 | ``` 49 | 50 | This will add a `PackageReference` to the project file so that .NET embedding for Wasmtime can be used. 51 | 52 | ## Implementing the .NET code 53 | 54 | Replace the contents of `Program.cs` with the following: 55 | 56 | ```c# 57 | using System; 58 | using Wasmtime; 59 | 60 | namespace Tutorial 61 | { 62 | class Program 63 | { 64 | static void Main(string[] args) 65 | { 66 | using var engine = new Engine(); 67 | 68 | using var module = Module.FromText( 69 | engine, 70 | "hello", 71 | "(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))" 72 | ); 73 | 74 | using var linker = new Linker(engine); 75 | using var store = new Store(engine); 76 | 77 | linker.Define( 78 | "", 79 | "hello", 80 | Function.FromCallback(store, () => Console.WriteLine("Hello from C#!")) 81 | ); 82 | 83 | var instance = linker.Instantiate(store, module); 84 | var run = instance.GetAction(store, "run"); 85 | run(); 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | The [`Linker`](https://bytecodealliance.github.io/wasmtime-dotnet/api/Wasmtime.Linker.html) class is responsible for linking in those defined functions, such as `hello` in this example. 92 | 93 | Here we are defining a function named `hello` that simply prints `Hello from C#!` when called from WebAssembly. 94 | 95 | A WebAssembly module _instantiation_ is the stateful representation of a module that can be executed. 96 | 97 | This code is calling the `run` function defined in WebAssembly that is exported by the instance; this function then calls the `hello` function defined in C#. 98 | 99 | ## Building the .NET application 100 | 101 | Use `dotnet build` to build the .NET application: 102 | 103 | ```text 104 | dotnet build 105 | ``` 106 | 107 | This will create a `tutorial` (or `tutorial.exe` on Windows) executable in the `bin/Debug/net5.0` directory that implements the .NET application. 108 | 109 | ## Running the .NET application 110 | 111 | To run the .NET application, simply invoke the executable file or use `dotnet`: 112 | 113 | ```text 114 | dotnet run 115 | ``` 116 | 117 | This should result in the following output: 118 | 119 | ```text 120 | Hello from C#! 121 | ``` 122 | -------------------------------------------------------------------------------- /docs/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction to the .NET embedding of Wasmtime 2 | href: intro.md 3 | -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [{ 3 | "src": [{ 4 | "src": "..", 5 | "files": [ 6 | "src/**.csproj" 7 | ] 8 | }], 9 | "dest": "api", 10 | "disableGitFeatures": false, 11 | "disableDefaultFilter": false 12 | }], 13 | "build": { 14 | "content": [{ 15 | "files": [ 16 | "api/**.yml", 17 | "api/index.md" 18 | ] 19 | }, 20 | { 21 | "files": [ 22 | "articles/**.md", 23 | "articles/**/toc.yml", 24 | "toc.yml", 25 | "*.md" 26 | ] 27 | } 28 | ], 29 | "resource": [{ 30 | "files": [ 31 | "images/**" 32 | ] 33 | }], 34 | "overwrite": [{ 35 | "files": [ 36 | "apidoc/**.md" 37 | ], 38 | "exclude": [ 39 | "obj/**", 40 | "_site/**" 41 | ] 42 | }], 43 | "dest": "_site", 44 | "globalMetadataFiles": [], 45 | "fileMetadataFiles": [], 46 | "template": [ 47 | "default", 48 | "templates/darkfx" 49 | ], 50 | "postProcessors": [], 51 | "markdownEngineName": "markdig", 52 | "noLangKeyword": false, 53 | "keepFileLink": false, 54 | "cleanupCacheHistory": false, 55 | "disableGitFeatures": false, 56 | "globalMetadata": { 57 | "_gitContribute": { 58 | "repo": "https://github.com/bytecodealliance/wasmtime-dotnet", 59 | "branch": "main" 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # .NET embedding of Wasmtime 2 | 3 | [Wasmtime](https://github.com/bytecodealliance/wasmtime) is a standalone runtime for [WebAssembly](https://webassembly.org/), using the Cranelift JIT compiler. 4 | 5 | The [.NET embedding of Wasmtime](https://github.com/bytecodealliance/wasmtime-dotnet) enables .NET code to instantiate WebAssembly modules and to interact with them in-process. 6 | -------------------------------------------------------------------------------- /docs/templates/darkfx/partials/head.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | 4 | 5 | 6 | {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} 7 | 8 | 9 | 10 | {{#_description}}{{/_description}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{#_noindex}}{{/_noindex}} 19 | {{#_enableSearch}}{{/_enableSearch}} 20 | {{#_enableNewTab}}{{/_enableNewTab}} 21 | -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: API Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | - name: GitHub Repo 7 | href: https://github.com/bytecodealliance/wasmtime-dotnet 8 | -------------------------------------------------------------------------------- /docs/wasm/intro/hello.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytecodealliance/wasmtime-dotnet/9a64e14aeb28991eb61f186fea9a745c0fd7f6a4/docs/wasm/intro/hello.wasm -------------------------------------------------------------------------------- /examples/consumefuel/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(new Config() 5 | .WithFuelConsumption(true)); 6 | using var module = Module.FromTextFile(engine, "consumefuel.wat"); 7 | using var linker = new Linker(engine); 8 | using var store = new Store(engine); 9 | 10 | linker.Define( 11 | "", 12 | "expensive", 13 | Function.FromCallback(store, (Caller caller) => 14 | { 15 | checked 16 | { 17 | caller.Fuel -= 1000UL; 18 | } 19 | 20 | var remaining = caller.Fuel; 21 | Console.WriteLine($"Called an expensive function which consumed 1000 fuel. {remaining} units of fuel remaining."); 22 | } 23 | )); 24 | 25 | var instance = linker.Instantiate(store, module); 26 | 27 | var expensive = instance.GetAction("expensive"); 28 | if (expensive is null) 29 | { 30 | Console.WriteLine("error: expensive export is missing"); 31 | return; 32 | } 33 | 34 | store.Fuel += 5000UL; 35 | Console.WriteLine("Added 5000 units of fuel"); 36 | 37 | for (var i = 0; i < 4; i++) 38 | { 39 | expensive(); 40 | } 41 | 42 | Console.WriteLine("Calling the expensive function one more time, which will throw an exception."); 43 | try 44 | { 45 | expensive(); 46 | } 47 | catch (WasmtimeException ex) 48 | { 49 | Console.WriteLine("Exception caught with the following message:"); 50 | Console.WriteLine(ex.Message); 51 | } 52 | -------------------------------------------------------------------------------- /examples/consumefuel/consumefuel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/consumefuel/consumefuel.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "expensive" (func $.expensive)) 3 | (func $expensive 4 | call $.expensive 5 | ) 6 | (export "expensive" (func $expensive)) 7 | ) 8 | -------------------------------------------------------------------------------- /examples/externref/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(new Config().WithReferenceTypes(true)); 5 | using var module = Module.FromTextFile(engine, "externref.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | linker.Define( 10 | "", 11 | "concat", 12 | Function.FromCallback(store, (string a, string b) => $"{a} {b}") 13 | ); 14 | 15 | var instance = linker.Instantiate(store, module); 16 | 17 | var run = instance.GetFunction("run"); 18 | if (run is null) 19 | { 20 | Console.WriteLine("error: run export is missing"); 21 | return; 22 | } 23 | 24 | Console.WriteLine(run("hello", "world!")); 25 | -------------------------------------------------------------------------------- /examples/externref/externref.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/externref/externref.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "concat" (func $.concat (param externref externref) (result externref))) 3 | (func (export "run") (param externref externref) (result externref) 4 | local.get 0 5 | local.get 1 6 | call $.concat 7 | ) 8 | ) -------------------------------------------------------------------------------- /examples/funcref/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(new Config().WithReferenceTypes(true)); 5 | using var module = Module.FromTextFile(engine, "funcref.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | linker.Define( 10 | "", 11 | "g", 12 | Function.FromCallback(store, (Caller caller, Function h) => { h.Invoke(); }) 13 | ); 14 | 15 | linker.Define( 16 | "", 17 | "i", 18 | Function.FromCallback(store, () => Console.WriteLine("Called via a function reference!")) 19 | ); 20 | 21 | var func1 = Function.FromCallback(store, (string s) => Console.WriteLine($"First callback: {s}")); 22 | var func2 = Function.FromCallback(store, (string s) => Console.WriteLine($"Second callback: {s}")); 23 | 24 | var instance = linker.Instantiate(store, module); 25 | 26 | var call = instance.GetAction("call"); 27 | if (call is null) 28 | { 29 | Console.WriteLine("error: `call` export is missing"); 30 | return; 31 | } 32 | 33 | var f = instance.GetAction("f"); 34 | if (f is null) 35 | { 36 | Console.WriteLine("error: `f` export is missing"); 37 | return; 38 | } 39 | 40 | call(func1, "Hello"); 41 | call(func2, "Hello"); 42 | f(); 43 | -------------------------------------------------------------------------------- /examples/funcref/funcref.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/funcref/funcref.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "g" (func $g (param funcref))) 3 | (import "" "i" (func $i)) 4 | (table $t 2 funcref) 5 | (elem declare func $h) 6 | (func (export "call") (param funcref externref) 7 | (table.set $t (i32.const 0) (local.get 0)) 8 | (call_indirect $t (param externref) (local.get 1) (i32.const 0)) 9 | ) 10 | (func $h 11 | (call $i) 12 | ) 13 | (func (export "f") 14 | (call $g (ref.func $h)) 15 | ) 16 | ) -------------------------------------------------------------------------------- /examples/global/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(); 5 | using var module = Module.FromTextFile(engine, "global.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | var global = new Global(store, ValueKind.Int32, 1, Mutability.Mutable); 10 | 11 | linker.Define("", "global", global); 12 | 13 | linker.Define( 14 | "", 15 | "print_global", 16 | Function.FromCallback(store, (Caller caller) => 17 | { 18 | Console.WriteLine($"The value of the global is: {global.GetValue()}."); 19 | } 20 | )); 21 | 22 | var instance = linker.Instantiate(store, module); 23 | 24 | var run = instance.GetAction("run"); 25 | if (run is null) 26 | { 27 | Console.WriteLine("error: run export is missing"); 28 | return; 29 | } 30 | 31 | run(20); 32 | -------------------------------------------------------------------------------- /examples/global/global.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/global/global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "print_global" (func $.print_global)) 3 | (import "" "global" (global $.global (mut i32))) 4 | (func $run (param i32) (local $i i32) 5 | loop $l1 6 | call $.print_global 7 | global.get $.global 8 | i32.const 2 9 | i32.mul 10 | global.set $.global 11 | local.get $i 12 | i32.const 1 13 | i32.add 14 | local.set $i 15 | local.get $i 16 | local.get 0 17 | i32.le_u 18 | br_if $l1 19 | end 20 | ) 21 | (export "run" (func $run)) 22 | ) 23 | -------------------------------------------------------------------------------- /examples/hello/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(); 5 | using var module = Module.FromTextFile(engine, "hello.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | linker.Define( 10 | "", 11 | "hello", 12 | Function.FromCallback(store, () => Console.WriteLine("Hello from C#, WebAssembly!")) 13 | ); 14 | 15 | var instance = linker.Instantiate(store, module); 16 | 17 | var run = instance.GetAction("run"); 18 | if (run is null) 19 | { 20 | Console.WriteLine("error: run export is missing"); 21 | return; 22 | } 23 | 24 | run(); 25 | -------------------------------------------------------------------------------- /examples/hello/hello.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/hello/hello.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func)) 3 | (import "" "hello" (func $.hello (type $t0))) 4 | (func $run 5 | call $.hello 6 | ) 7 | (export "run" (func $run)) 8 | ) 9 | -------------------------------------------------------------------------------- /examples/memory/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(); 5 | using var module = Module.FromTextFile(engine, "memory.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | linker.Define( 10 | "", 11 | "log", 12 | Function.FromCallback(store, (Caller caller, int address, int length) => 13 | { 14 | var message = caller.GetMemory("mem").ReadString(address, length); 15 | Console.WriteLine($"Message from WebAssembly: {message}"); 16 | } 17 | )); 18 | 19 | var instance = linker.Instantiate(store, module); 20 | 21 | var run = instance.GetAction("run"); 22 | if (run is null) 23 | { 24 | Console.WriteLine("error: run export is missing"); 25 | return; 26 | } 27 | 28 | run(); 29 | -------------------------------------------------------------------------------- /examples/memory/memory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/memory/memory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func (param i32 i32))) 3 | (import "" "log" (func $.log (type $t0))) 4 | (memory (export "mem") 1 2) 5 | (data (i32.const 0) "Hello World") 6 | (func $run 7 | i32.const 0 8 | i32.const 11 9 | call $.log 10 | ) 11 | (export "run" (func $run)) 12 | ) 13 | -------------------------------------------------------------------------------- /examples/storedata/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Wasmtime; 4 | 5 | using var engine = new Engine(new Config().WithReferenceTypes(true)); 6 | using var module = Module.FromTextFile(engine, "storedata.wat"); 7 | using var linker = new Linker(engine); 8 | 9 | var storeData = new StoreData("Hello, WASM", 0); 10 | using var store = new Store(engine, storeData); 11 | 12 | linker.DefineFunction("", "store_data", (Caller caller) => 13 | { 14 | var data = caller.GetData() as StoreData; 15 | Console.WriteLine(data); // 'ID 0: Hello, WASM' 16 | 17 | // Fully replace store data 18 | var newStoreData = new StoreData("Store data replaced", 1); 19 | caller.SetData(newStoreData); 20 | 21 | data = caller.GetData() as StoreData; 22 | Debug.Assert(data != null); 23 | Console.WriteLine(data); // 'ID 1: Store data replaced' 24 | 25 | // Change properties normally 26 | data.Message = "Properties changed"; 27 | data.Id = 2; 28 | }); 29 | 30 | var instance = linker.Instantiate(store, module); 31 | 32 | var run = instance.GetAction("run"); 33 | if (run is null) 34 | { 35 | Console.WriteLine("error: run export is missing"); 36 | return; 37 | } 38 | run(); 39 | 40 | // Retrieve final data from store directly 41 | var data = store.GetData() as StoreData; 42 | Console.WriteLine(data); // 'ID 2: Properties changed' 43 | 44 | class StoreData 45 | { 46 | public int Id { get; set; } 47 | 48 | public string Message { get; set; } 49 | 50 | public StoreData(string message, int id = 0) 51 | { 52 | Message = message; 53 | Id = id; 54 | } 55 | 56 | public override string ToString() 57 | { 58 | return $"ID {Id}: {Message}"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/storedata/storedata.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/storedata/storedata.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func)) 3 | (import "" "store_data" (func $.store_data (type $t0))) 4 | (func $run 5 | call $.store_data 6 | ) 7 | (export "run" (func $run)) 8 | ) 9 | -------------------------------------------------------------------------------- /examples/table/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasmtime; 3 | 4 | using var engine = new Engine(); 5 | using var module = Module.FromTextFile(engine, "table.wat"); 6 | using var linker = new Linker(engine); 7 | using var store = new Store(engine); 8 | 9 | var table = new Table(store, TableKind.FuncRef, null, 4); 10 | 11 | table.SetElement(0, Function.FromCallback(store, (int a, int b) => a + b)); 12 | table.SetElement(1, Function.FromCallback(store, (int a, int b) => a - b)); 13 | table.SetElement(2, Function.FromCallback(store, (int a, int b) => a * b)); 14 | table.SetElement(3, Function.FromCallback(store, (int a, int b) => a / b)); 15 | 16 | linker.Define("", "table", table); 17 | 18 | var instance = linker.Instantiate(store, module); 19 | 20 | var call_indirect = instance.GetFunction("call_indirect"); 21 | if (call_indirect is null) 22 | { 23 | Console.WriteLine("error: `call_indirect` export is missing"); 24 | return; 25 | } 26 | 27 | Console.WriteLine($"100 + 25 = {call_indirect(0, 100, 25)}"); 28 | Console.WriteLine($"100 - 25 = {call_indirect(1, 100, 25)}"); 29 | Console.WriteLine($"100 * 25 = {call_indirect(2, 100, 25)}"); 30 | Console.WriteLine($"100 / 25 = {call_indirect(3, 100, 25)}"); 31 | -------------------------------------------------------------------------------- /examples/table/table.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/table/table.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "table" (table $t 4 funcref)) 3 | (func (export "call_indirect") (param i32 i32 i32) (result i32) 4 | (call_indirect $t (param i32 i32) (result i32) (local.get 1) (local.get 2) (local.get 0)) 5 | ) 6 | ) 7 | -------------------------------------------------------------------------------- /src/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Wasmtime.Tests")] -------------------------------------------------------------------------------- /src/Delegates.cs: -------------------------------------------------------------------------------- 1 | namespace Wasmtime; 2 | 3 | /// 4 | /// Action accepting a caller 5 | /// 6 | public delegate void CallerAction(Caller caller); 7 | 8 | /// 9 | /// Action accepting a caller and 1 parameter 10 | /// 11 | public delegate void CallerAction(Caller caller, T arg); 12 | 13 | /// 14 | /// Action accepting a caller and 2 parameters 15 | /// 16 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2); 17 | 18 | /// 19 | /// Action accepting a caller and 3 parameters 20 | /// 21 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3); 22 | 23 | /// 24 | /// Action accepting a caller and 4 parameters 25 | /// 26 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4); 27 | 28 | /// 29 | /// Action accepting a caller and 5 parameters 30 | /// 31 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); 32 | 33 | /// 34 | /// Action accepting a caller and 6 parameters 35 | /// 36 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); 37 | 38 | /// 39 | /// Action accepting a caller and 7 parameters 40 | /// 41 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); 42 | 43 | /// 44 | /// Action accepting a caller and 8 parameters 45 | /// 46 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); 47 | 48 | /// 49 | /// Action accepting a caller and 9 parameters 50 | /// 51 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); 52 | 53 | /// 54 | /// Action accepting a caller and 10 parameters 55 | /// 56 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); 57 | 58 | /// 59 | /// Action accepting a caller and 11 parameters 60 | /// 61 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); 62 | 63 | /// 64 | /// Action accepting a caller and 12 parameters 65 | /// 66 | public delegate void CallerAction(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); 67 | 68 | /// 69 | /// Func accepting a caller 70 | /// 71 | public delegate TResult CallerFunc(Caller caller); 72 | 73 | /// 74 | /// Func accepting a caller and 1 parameter 75 | /// 76 | public delegate TResult CallerFunc(Caller caller, T arg); 77 | 78 | /// 79 | /// Func accepting a caller and 2 parameters 80 | /// 81 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2); 82 | 83 | /// 84 | /// Func accepting a caller and 3 parameters 85 | /// 86 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3); 87 | 88 | /// 89 | /// Func accepting a caller and 4 parameters 90 | /// 91 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4); 92 | 93 | /// 94 | /// Func accepting a caller and 5 parameters 95 | /// 96 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); 97 | 98 | /// 99 | /// Func accepting a caller and 6 parameters 100 | /// 101 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); 102 | 103 | /// 104 | /// Func accepting a caller and 7 parameters 105 | /// 106 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); 107 | 108 | /// 109 | /// Func accepting a caller and 8 parameters 110 | /// 111 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); 112 | 113 | /// 114 | /// Func accepting a caller and 9 parameters 115 | /// 116 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); 117 | 118 | /// 119 | /// Func accepting a caller and 10 parameters 120 | /// 121 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); 122 | 123 | /// 124 | /// Func accepting a caller and 11 parameters 125 | /// 126 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); 127 | 128 | /// 129 | /// Func accepting a caller and 12 parameters 130 | /// 131 | public delegate TResult CallerFunc(Caller caller, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); 132 | 133 | -------------------------------------------------------------------------------- /src/Engine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.Win32.SafeHandles; 4 | 5 | namespace Wasmtime 6 | { 7 | /// 8 | /// Represents the Wasmtime engine. 9 | /// 10 | public class Engine : IDisposable 11 | { 12 | internal const string LibraryName = "wasmtime"; 13 | 14 | /// 15 | /// Constructs a new default engine. 16 | /// 17 | public Engine() 18 | { 19 | handle = new Handle(Native.wasm_engine_new()); 20 | } 21 | 22 | /// 23 | /// Constructs a new engine using the given configuration. 24 | /// 25 | /// The configuration to use for the engine. 26 | /// This method will dispose the given configuration. 27 | public Engine(Config config) 28 | { 29 | handle = new Handle(Native.wasm_engine_new_with_config(config.NativeHandle)); 30 | config.NativeHandle.SetHandleAsInvalid(); 31 | } 32 | 33 | /// 34 | public void Dispose() 35 | { 36 | handle.Dispose(); 37 | } 38 | 39 | /// 40 | /// Increments the epoch for epoch-based interruption 41 | /// 42 | public void IncrementEpoch() 43 | { 44 | Native.wasmtime_engine_increment_epoch(handle); 45 | } 46 | 47 | internal Handle NativeHandle 48 | { 49 | get 50 | { 51 | if (handle.IsInvalid || handle.IsClosed) 52 | { 53 | throw new ObjectDisposedException(typeof(Engine).FullName); 54 | } 55 | 56 | return handle; 57 | } 58 | } 59 | 60 | internal class Handle : SafeHandleZeroOrMinusOneIsInvalid 61 | { 62 | public Handle(IntPtr handle) 63 | : base(true) 64 | { 65 | SetHandle(handle); 66 | } 67 | 68 | protected override bool ReleaseHandle() 69 | { 70 | Native.wasm_engine_delete(handle); 71 | return true; 72 | } 73 | } 74 | 75 | private static class Native 76 | { 77 | [DllImport(LibraryName)] 78 | public static extern IntPtr wasm_engine_new(); 79 | 80 | [DllImport(LibraryName)] 81 | public static extern IntPtr wasm_engine_new_with_config(Config.Handle config); 82 | 83 | [DllImport(LibraryName)] 84 | public static extern void wasm_engine_delete(IntPtr engine); 85 | 86 | [DllImport(LibraryName)] 87 | public static extern void wasmtime_engine_increment_epoch(Handle engine); 88 | } 89 | 90 | private readonly Handle handle; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace Wasmtime 7 | { 8 | internal static class Extensions 9 | { 10 | public const UnmanagedType LPUTF8Str = 11 | #if NETSTANDARD2_0 12 | (UnmanagedType)48; 13 | #else 14 | UnmanagedType.LPUTF8Str; 15 | #endif 16 | 17 | #if NETSTANDARD2_0 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static unsafe string GetString(this Encoding encoding, Span bytes) 20 | { 21 | fixed (byte* bytesPtr = bytes) 22 | { 23 | return encoding.GetString(bytesPtr, bytes.Length); 24 | } 25 | } 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public static unsafe string GetString(this Encoding encoding, ReadOnlySpan bytes) 29 | { 30 | fixed (byte* bytesPtr = bytes) 31 | { 32 | return encoding.GetString(bytesPtr, bytes.Length); 33 | } 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public static unsafe int GetBytes(this Encoding encoding, Span chars, Span bytes) 38 | { 39 | fixed (char* charsPtr = chars) 40 | fixed (byte* bytesPtr = bytes) 41 | { 42 | return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); 43 | } 44 | } 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan chars, Span bytes) 48 | { 49 | fixed (char* charsPtr = chars) 50 | fixed (byte* bytesPtr = bytes) 51 | { 52 | return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public static unsafe int GetBytes(this Encoding encoding, string chars, Span bytes) 58 | { 59 | fixed (char* charsPtr = chars) 60 | fixed (byte* bytesPtr = bytes) 61 | { 62 | return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); 63 | } 64 | } 65 | #endif 66 | 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | public static bool IsTupleType(this Type type) 69 | { 70 | #if NETSTANDARD2_0 71 | return type.FullName.StartsWith("System.ValueTuple`"); 72 | #else 73 | return typeof(ITuple).IsAssignableFrom(type); 74 | #endif 75 | } 76 | 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | public static float Int32BitsToSingle(int value) 79 | { 80 | #if NETSTANDARD2_0 81 | unsafe 82 | { 83 | return *(float*)&value; 84 | } 85 | #else 86 | return BitConverter.Int32BitsToSingle(value); 87 | #endif 88 | } 89 | 90 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 91 | public static int SingleToInt32Bits(float value) 92 | { 93 | #if NETSTANDARD2_0 94 | unsafe 95 | { 96 | return *(int*)&value; 97 | } 98 | #else 99 | return BitConverter.SingleToInt32Bits(value); 100 | #endif 101 | } 102 | 103 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 104 | public static string PtrToStringUTF8(IntPtr ptr, int byteLen) 105 | { 106 | #if NETSTANDARD2_0 107 | unsafe 108 | { 109 | return Encoding.UTF8.GetString((byte*)ptr, byteLen); 110 | } 111 | #else 112 | return Marshal.PtrToStringUTF8(ptr, byteLen); 113 | #endif 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/Externs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Wasmtime 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | internal struct ExternFunc 8 | { 9 | public ulong store; 10 | public nuint __private; 11 | } 12 | 13 | [StructLayout(LayoutKind.Sequential)] 14 | internal struct ExternTable 15 | { 16 | public ulong store; 17 | public nuint __private; 18 | } 19 | 20 | [StructLayout(LayoutKind.Sequential)] 21 | internal struct ExternMemory 22 | { 23 | public ulong store; 24 | public nuint __private; 25 | } 26 | 27 | [StructLayout(LayoutKind.Sequential)] 28 | internal struct ExternInstance 29 | { 30 | public ulong store; 31 | public nuint __private; 32 | } 33 | 34 | [StructLayout(LayoutKind.Sequential)] 35 | internal struct ExternGlobal 36 | { 37 | public ulong store; 38 | public nuint __private; 39 | } 40 | 41 | internal enum ExternKind : byte 42 | { 43 | Func, 44 | Global, 45 | Table, 46 | Memory, 47 | SharedMemory, 48 | } 49 | 50 | [StructLayout(LayoutKind.Explicit)] 51 | internal struct ExternUnion 52 | { 53 | [FieldOffset(0)] 54 | public ExternFunc func; 55 | 56 | [FieldOffset(0)] 57 | public ExternGlobal global; 58 | 59 | [FieldOffset(0)] 60 | public ExternTable table; 61 | 62 | [FieldOffset(0)] 63 | public ExternMemory memory; 64 | 65 | [FieldOffset(0)] 66 | public IntPtr sharedmemory; 67 | } 68 | 69 | [StructLayout(LayoutKind.Sequential)] 70 | internal struct Extern : IDisposable 71 | { 72 | public ExternKind kind; 73 | public ExternUnion of; 74 | 75 | public void Dispose() 76 | { 77 | Native.wasmtime_extern_delete(this); 78 | } 79 | 80 | private static class Native 81 | { 82 | [DllImport(Engine.LibraryName)] 83 | public static extern void wasmtime_extern_delete(in Extern self); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Function.FromCallback.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Collections.Generic" #> 4 | <#@ import namespace="System.Globalization" #> 5 | <#@ import namespace="System.Text" #> 6 | <#@ import namespace="System.Linq" #> 7 | <#@ output extension=".cs" encoding="us-ascii" #> 8 | <#@ include file="FunctionCallbackOverloadTemplates.t4" once="true" #> 9 | <# 10 | // Note: We specify ASCII as output encoding above to prevent different UTF-8 BOM behavior with VS and dotnet-t4. 11 | #> 12 | // 13 | // This file is automatically generated from a T4 text template (Function.FromCallback.tt) 14 | // when building the project. 15 | // Do not modify it directly. 16 | // 17 | 18 | #nullable enable 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Runtime.InteropServices; 23 | 24 | namespace Wasmtime 25 | { 26 | public partial class Function 27 | { 28 | <# 29 | // Generate overloads for different combinations of function parameter count, result count, 30 | // and hasCaller states. 31 | // Currently, we generate them for a maximum of 12 parameters and 4 result values, which means there 32 | // will be 2 * 13 * 5 = 130 overloads of DefineFunction accepting an Action/Func. 33 | // Note that more than 200 overloads can cause the C# compiler to no longer resolve the overloads 34 | // correctly (tested with Visual Studio 17.4.0 Preview 2.1). 35 | // For delegate types not covered by these overloads, users will need to use an untyped callback. 36 | foreach (var (hasCaller, resultCount, parameterCount, methodGenerics, delegateType, callbackParameterTypeExpressions, callbackReturnTypeExpression, parameterConverters, resultConverters) in EnumerateTypeCombinations()) 37 | { 38 | #> 39 | /// 40 | /// Creates a function given a callback. 41 | /// 42 | /// The store to create the function in. 43 | /// The callback for when the function is invoked. 44 | public static Function FromCallback<#= methodGenerics #>(Store store, <#= delegateType #> callback) 45 | { 46 | if (store is null) 47 | { 48 | throw new ArgumentNullException(nameof(store)); 49 | } 50 | 51 | if (callback is null) 52 | { 53 | throw new ArgumentNullException(nameof(callback)); 54 | } 55 | 56 | var (parameterKinds, resultKinds) = GetFunctionType(<# if (callbackParameterTypeExpressions.Length == 0) { #>Array.Empty()<# } else { #>new Type[] { <#= callbackParameterTypeExpressions #>}<# } #>, <#= callbackReturnTypeExpression #>, allowCaller: <#= hasCaller ? "true" : "false" #>, allowTuple: <#= (resultCount > 1) ? "true" : "false" #>); 57 | <#= parameterConverters 58 | #><#= resultConverters 59 | #> 60 | unsafe 61 | { 62 | Native.WasmtimeFuncUncheckedCallback func = (env, callerPtr, args_and_results, num_args_and_results) => 63 | { 64 | <# GenerateCallbackContent(hasCaller, resultCount, parameterCount, true); #> 65 | }; 66 | 67 | var funcType = CreateFunctionType(parameterKinds, resultKinds); 68 | ExternFunc externFunc; 69 | try 70 | { 71 | Native.wasmtime_func_new_unchecked( 72 | store.Context.handle, 73 | funcType, 74 | func, 75 | GCHandle.ToIntPtr(GCHandle.Alloc(func)), 76 | Finalizer, 77 | out externFunc 78 | ); 79 | } 80 | finally 81 | { 82 | Native.wasm_functype_delete(funcType); 83 | } 84 | 85 | GC.KeepAlive(store); 86 | 87 | return new Function(store, externFunc, parameterKinds, resultKinds); 88 | } 89 | } 90 | <# 91 | } 92 | #> 93 | } 94 | } -------------------------------------------------------------------------------- /src/Function.Wrap.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Collections.Generic" #> 4 | <#@ import namespace="System.Globalization" #> 5 | <#@ import namespace="System.Text" #> 6 | <#@ import namespace="System.Linq" #> 7 | <#@ output extension=".cs" encoding="us-ascii" #> 8 | <#@ include file="FunctionCallbackOverloadTemplates.t4" once="true" #> 9 | <# 10 | // Note: We specify ASCII as output encoding above to prevent different UTF-8 BOM behavior with VS and dotnet-t4. 11 | #> 12 | // 13 | // This file is automatically generated from a T4 text template (Function.Wrap.tt) 14 | // when building the project. 15 | // Do not modify it directly. 16 | // 17 | 18 | #nullable enable 19 | 20 | using System; 21 | 22 | namespace Wasmtime 23 | { 24 | public partial class Function 25 | { 26 | private object? _wrapperCache; 27 | 28 | <# 29 | // Generate overloads of WrapAction/WrapFunc for up to 16 parameters and up to one return type 30 | // (2 * 17 overloads). 31 | // Note: We only use up to one return type parameter here, because unlike e.g. 32 | // Function.FromCallback() which explicitely declares ValueTuple<...> overloads, here we 33 | // expect that the single return type parameter is implicitely used as ValueTuple<...> for 34 | // the return type factory. 35 | foreach (var (_, returnTypeCount, parameterCount, methodGenerics, delegateType, callbackParameterTypeExpressions, callbackReturnTypeExpression, parameterConverters, _) in EnumerateTypeCombinations(16, 1, canHaveCaller: false, delegateInputsNullable: false)) 36 | { 37 | #> 38 | /// 39 | /// Attempt to wrap this function as <#= returnTypeCount > 0 ? "a Func" : "an Action" #>. Wrapped <#= returnTypeCount > 0 ? "Func" : "Action" #> is faster than a normal Invoke call. 40 | /// 41 | /// A <#= returnTypeCount > 0 ? "Func" : "Action" #> to invoke this function, or null if the type signature is incompatible. 42 | public <#= delegateType #>? Wrap<#= returnTypeCount > 0 ? "Func" : "Action" #><#= methodGenerics #>() 43 | { 44 | if (store is null || IsNull) 45 | { 46 | throw new InvalidOperationException("Cannot wrap a null function reference."); 47 | } 48 | 49 | // Try to retrieve it from the cache. if it's cached it must have passed the type 50 | // signature check already, so it's safe to do this before CheckTypeSignature. 51 | if (_wrapperCache?.GetType() == typeof(<#= delegateType #>)) 52 | { 53 | return (<#= delegateType #>)_wrapperCache; 54 | } 55 | 56 | // Check that the requested type signature is compatible 57 | <# if (callbackParameterTypeExpressions.Length == 0) { #> 58 | var parameterTypes = Array.Empty(); 59 | <# } else { #> 60 | var parameterTypes = new Type[] { <#= callbackParameterTypeExpressions #>}; 61 | <# } #> 62 | 63 | // Check if the requested type signature is compatible with the function being wrapped 64 | if (!CheckTypeSignature(<#= callbackReturnTypeExpression #>, parameterTypes)) 65 | { 66 | return null; 67 | } 68 | 69 | // Fetch a converter for each parameter type to box it 70 | <#= parameterConverters #> 71 | <# 72 | if (returnTypeCount > 0) 73 | { 74 | #> 75 | // Create a factory for the return type 76 | var factory = ReturnTypeFactory.Create(); 77 | 78 | <# 79 | } 80 | #> 81 | // Determine how much space to allocate for params/results 82 | <# 83 | if (returnTypeCount == 0) 84 | { 85 | #> 86 | const int allocCount = <#= parameterCount.ToString(CultureInfo.InvariantCulture) #>; 87 | <# 88 | } else { 89 | #> 90 | var allocCount = Math.Max(<#= parameterCount.ToString(CultureInfo.InvariantCulture) #>, Results.Count); 91 | <# 92 | } 93 | #> 94 | 95 | <#= delegateType #> result = (<# 96 | for (int x = 0; x < parameterCount; x++) 97 | { 98 | if (x >= 1) 99 | { 100 | #>, <# 101 | } 102 | 103 | #>p<#= x.ToString(CultureInfo.InvariantCulture) #><# 104 | 105 | } 106 | #>) => 107 | { 108 | // Allocate space for both the arguments and the results. 109 | Span argsAndResults = stackalloc ValueRaw[allocCount]; 110 | var storeContext = store.Context; 111 | 112 | <# 113 | for (int x = 0; x < parameterCount; x++) 114 | { 115 | string xStr = x.ToString(CultureInfo.InvariantCulture); 116 | #> 117 | convT<#= parameterCount is 1 ? "" : (x + 1).ToString(CultureInfo.InvariantCulture) #>.Box(storeContext, store, ref argsAndResults[<#= xStr #>], p<#= xStr #>); 118 | <# 119 | } 120 | 121 | if (returnTypeCount > 0) 122 | { 123 | #> 124 | 125 | return InvokeWithReturn(argsAndResults, factory, storeContext); 126 | <# 127 | } 128 | else 129 | { 130 | #> 131 | 132 | InvokeWithoutReturn(argsAndResults, storeContext); 133 | <# 134 | } 135 | #> 136 | }; 137 | 138 | _wrapperCache = result; 139 | return result; 140 | } 141 | 142 | <# 143 | } 144 | #> 145 | } 146 | } -------------------------------------------------------------------------------- /src/Linker.DefineFunction.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Collections.Generic" #> 4 | <#@ import namespace="System.Globalization" #> 5 | <#@ import namespace="System.Text" #> 6 | <#@ import namespace="System.Linq" #> 7 | <#@ output extension=".cs" encoding="us-ascii" #> 8 | <#@ include file="FunctionCallbackOverloadTemplates.t4" once="true" #> 9 | <# 10 | // Note: We specify ASCII as output encoding above to prevent different UTF-8 BOM behavior with VS and dotnet-t4. 11 | #> 12 | // 13 | // This file is automatically generated from a T4 text template (Linker.DefineFunction.tt) 14 | // when building the project. 15 | // Do not modify it directly. 16 | // 17 | 18 | #nullable enable 19 | 20 | using System; 21 | using System.Buffers; 22 | using System.Runtime.InteropServices; 23 | using System.Text; 24 | 25 | namespace Wasmtime 26 | { 27 | public partial class Linker 28 | { 29 | <# 30 | // Generate overloads for different combinations of function parameter count, result count, 31 | // and hasCaller states. 32 | // Currently, we generate them for a maximum of 12 parameters and 4 result values, which means there 33 | // will be 2 * 13 * 5 = 130 overloads of DefineFunction accepting an Action/Func. 34 | // Note that more than 200 overloads can cause the C# compiler to no longer resolve the overloads 35 | // correctly (tested with Visual Studio 17.4.0 Preview 2.1). 36 | // For delegate types not covered by these overloads, users will need to use an untyped callback. 37 | foreach (var (hasCaller, resultCount, parameterCount, methodGenerics, delegateType, callbackParameterTypeExpressions, callbackReturnTypeExpression, parameterConverters, resultConverters) in EnumerateTypeCombinations()) 38 | { 39 | #> 40 | /// 41 | /// Defines a function in the linker. 42 | /// 43 | /// Functions defined with this method are store-independent. 44 | /// The module name of the function. 45 | /// The name of the function. 46 | /// The callback for when the function is invoked. 47 | public void DefineFunction<#= methodGenerics #>(string module, string name, <#= delegateType #> callback) 48 | { 49 | if (module is null) 50 | { 51 | throw new ArgumentNullException(nameof(module)); 52 | } 53 | 54 | if (name is null) 55 | { 56 | throw new ArgumentNullException(nameof(name)); 57 | } 58 | 59 | if (callback is null) 60 | { 61 | throw new ArgumentNullException(nameof(callback)); 62 | } 63 | 64 | var (parameterKinds, resultKinds) = Function.GetFunctionType(<# if (callbackParameterTypeExpressions.Length == 0) { #>Array.Empty()<# } else { #>new Type[] { <#= callbackParameterTypeExpressions #>}<# } #>, <#= callbackReturnTypeExpression #>, allowCaller: <#= hasCaller ? "true" : "false" #>, allowTuple: true); 65 | <#= parameterConverters 66 | #><#= resultConverters 67 | #> 68 | unsafe 69 | { 70 | Function.Native.WasmtimeFuncUncheckedCallback func = (env, callerPtr, args_and_results, num_args_and_results) => 71 | { 72 | <# GenerateCallbackContent(hasCaller, resultCount, parameterCount, false); #> 73 | }; 74 | 75 | const int StackallocThreshold = 256; 76 | 77 | byte[]? moduleBytesBuffer = null; 78 | var moduleLength = Encoding.UTF8.GetByteCount(module); 79 | Span moduleBytes = moduleLength <= StackallocThreshold ? stackalloc byte[moduleLength] : (moduleBytesBuffer = ArrayPool.Shared.Rent(moduleLength)).AsSpan()[..moduleLength]; 80 | Encoding.UTF8.GetBytes(module, moduleBytes); 81 | 82 | byte[]? nameBytesBuffer = null; 83 | var nameLength = Encoding.UTF8.GetByteCount(name); 84 | Span nameBytes = nameLength <= StackallocThreshold ? stackalloc byte[nameLength] : (nameBytesBuffer = ArrayPool.Shared.Rent(nameLength)).AsSpan()[..nameLength]; 85 | Encoding.UTF8.GetBytes(name, nameBytes); 86 | 87 | var funcType = Function.CreateFunctionType(parameterKinds, resultKinds); 88 | try 89 | { 90 | fixed (byte* modulePtr = moduleBytes, namePtr = nameBytes) 91 | { 92 | var error = Native.wasmtime_linker_define_func_unchecked( 93 | handle, 94 | modulePtr, 95 | (nuint)moduleBytes.Length, 96 | namePtr, 97 | (nuint)nameBytes.Length, 98 | funcType, 99 | func, 100 | GCHandle.ToIntPtr(GCHandle.Alloc(func)), 101 | Function.Finalizer 102 | ); 103 | 104 | if (error != IntPtr.Zero) 105 | { 106 | throw WasmtimeException.FromOwnedError(error); 107 | } 108 | } 109 | } 110 | finally 111 | { 112 | Function.Native.wasm_functype_delete(funcType); 113 | 114 | if (moduleBytesBuffer is not null) 115 | { 116 | ArrayPool.Shared.Return(moduleBytesBuffer); 117 | } 118 | if (nameBytesBuffer is not null) 119 | { 120 | ArrayPool.Shared.Return(nameBytesBuffer); 121 | } 122 | } 123 | } 124 | } 125 | 126 | <# 127 | } 128 | #> 129 | } 130 | } -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | You can add a package reference with the [.NET SDK](https://dotnet.microsoft.com/): 4 | 5 | ```text 6 | $ dotnet add package wasmtime 7 | ``` 8 | 9 | ## Introduction 10 | 11 | For this introduction, we'll be using a simple WebAssembly module that imports a `hello` function and exports a `run` function: 12 | 13 | ```wat 14 | (module 15 | (func $hello (import "" "hello")) 16 | (func (export "run") (call $hello)) 17 | ) 18 | ``` 19 | 20 | To use this module from .NET, create a new console project: 21 | 22 | ``` 23 | $ mkdir wasmintro 24 | $ cd wasmintro 25 | $ dotnet new console 26 | ``` 27 | 28 | Next, add a reference to the [Wasmtime package](https://www.nuget.org/packages/Wasmtime): 29 | 30 | ``` 31 | $ dotnet add package wasmtime 32 | ``` 33 | 34 | Replace the contents of `Program.cs` with the following code: 35 | 36 | ```csharp 37 | using System; 38 | using Wasmtime; 39 | 40 | using var engine = new Engine(); 41 | 42 | using var module = Module.FromText( 43 | engine, 44 | "hello", 45 | "(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))" 46 | ); 47 | 48 | using var linker = new Linker(engine); 49 | using var store = new Store(engine); 50 | 51 | linker.Define( 52 | "", 53 | "hello", 54 | Function.FromCallback(store, () => Console.WriteLine("Hello from C#!")) 55 | ); 56 | 57 | var instance = linker.Instantiate(store, module); 58 | var run = instance.GetAction("run")!; 59 | run(); 60 | ``` 61 | 62 | An `Engine` is created and then a WebAssembly module is loaded from a string in WebAssembly text format. 63 | 64 | A `Linker` defines a function called `hello` that simply prints a hello message. 65 | 66 | The module is instantiated and the instance's `run` export is invoked. 67 | 68 | To run the application, simply use `dotnet`: 69 | 70 | ``` 71 | $ dotnet run 72 | ``` 73 | 74 | This should print `Hello from C#!`. 75 | -------------------------------------------------------------------------------- /src/TemporaryAllocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Text; 4 | 5 | namespace Wasmtime 6 | { 7 | internal static class StringExtensions 8 | { 9 | public static TemporaryAllocation ToUTF8(this string value, Span bytes) 10 | { 11 | return TemporaryAllocation.FromString(value, bytes); 12 | } 13 | } 14 | 15 | internal readonly ref struct TemporaryAllocation 16 | { 17 | public readonly Span Span; 18 | private readonly byte[]? _rented; 19 | 20 | public int Length => Span.Length; 21 | 22 | private TemporaryAllocation(Span span, byte[]? rented) 23 | { 24 | Span = span; 25 | _rented = rented; 26 | } 27 | 28 | public static TemporaryAllocation FromString(string str, Span output) 29 | { 30 | var length = Encoding.UTF8.GetByteCount(str); 31 | 32 | if (length <= output.Length) 33 | { 34 | Encoding.UTF8.GetBytes(str, output); 35 | return new TemporaryAllocation(output[..length], null); 36 | } 37 | 38 | var rented = ArrayPool.Shared.Rent(length); 39 | Encoding.UTF8.GetBytes(str, rented); 40 | return new TemporaryAllocation(rented.AsSpan()[..length], rented); 41 | } 42 | 43 | /// 44 | /// Recycle rented memory 45 | /// 46 | public void Dispose() 47 | { 48 | if (_rented != null) 49 | { 50 | ArrayPool.Shared.Return(_rented); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/V128.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Wasmtime 5 | { 6 | /// 7 | /// A 128 bit value 8 | /// 9 | public struct V128 10 | { 11 | /// 12 | /// Get a V128 with all bits set to 1 13 | /// 14 | public static readonly V128 AllBitsSet = new V128( 15 | byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue, 16 | byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue, 17 | byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue, 18 | byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue 19 | ); 20 | 21 | #pragma warning disable CS0649 // "Field `bytes` is never assigned". Justification: This is assigned through the span returned from AsSpan(). 22 | #if NETSTANDARD2_0 23 | internal 24 | #else 25 | private 26 | #endif 27 | unsafe fixed byte bytes[16]; 28 | #pragma warning restore CS0649 29 | 30 | /// 31 | /// Construct a new V128 from a 16 element byte span 32 | /// 33 | /// The bytes to construct the V128 with. 34 | /// Thrown if the number of bytes in the span is not 16. 35 | public V128(ReadOnlySpan bytes) 36 | { 37 | if (bytes.Length != 16) 38 | { 39 | throw new ArgumentException("Must supply exactly 16 bytes to construct V128"); 40 | } 41 | 42 | #if NETSTANDARD2_0 43 | unsafe 44 | { 45 | fixed (byte* bytesPtr = this.bytes) 46 | { 47 | bytes.CopyTo(new Span(bytesPtr, 16)); 48 | } 49 | } 50 | #else 51 | bytes.CopyTo(AsSpan()); 52 | #endif 53 | } 54 | 55 | /// 56 | /// Construct a new V128 from 16 bytes 57 | /// 58 | /// First byte. 59 | /// Second byte. 60 | /// Third byte. 61 | /// Fourth byte. 62 | /// Fifth byte. 63 | /// Sixth byte. 64 | /// Seventh byte. 65 | /// Eighth byte. 66 | /// Ninth byte. 67 | /// Tenth byte. 68 | /// Eleventh byte. 69 | /// Twelfth byte. 70 | /// Thirteenth byte. 71 | /// Fourteenth byte. 72 | /// Fifteenth byte. 73 | /// Sixteenth byte. 74 | public V128(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8, byte b9, byte b10, byte b11, byte b12, byte b13, byte b14, byte b15) 75 | { 76 | unsafe 77 | { 78 | bytes[0] = b0; 79 | bytes[1] = b1; 80 | bytes[2] = b2; 81 | bytes[3] = b3; 82 | bytes[4] = b4; 83 | bytes[5] = b5; 84 | bytes[6] = b6; 85 | bytes[7] = b7; 86 | bytes[8] = b8; 87 | bytes[9] = b9; 88 | bytes[10] = b10; 89 | bytes[11] = b11; 90 | bytes[12] = b12; 91 | bytes[13] = b13; 92 | bytes[14] = b14; 93 | bytes[15] = b15; 94 | } 95 | } 96 | 97 | internal unsafe V128(byte* src) 98 | : this(new ReadOnlySpan(src, 16)) 99 | { 100 | } 101 | 102 | #if !NETSTANDARD2_0 103 | /// 104 | /// Creates a new writeable span over the bytes of this V128 105 | /// 106 | /// The span representation of this V128. 107 | public Span AsSpan() 108 | { 109 | unsafe 110 | { 111 | return MemoryMarshal.CreateSpan(ref bytes[0], 16); 112 | } 113 | } 114 | #endif 115 | 116 | internal unsafe void CopyTo(byte* dest) 117 | { 118 | var dst = new Span(dest, 16); 119 | CopyTo(dst); 120 | } 121 | 122 | /// 123 | /// Copy bytes into a span 124 | /// 125 | /// span to copy bytes into 126 | public void CopyTo(Span dest) 127 | { 128 | #if NETSTANDARD2_0 129 | unsafe 130 | { 131 | fixed (byte* bytesPtr = bytes) 132 | { 133 | new Span(bytesPtr, 16).CopyTo(dest); 134 | } 135 | } 136 | #else 137 | AsSpan().CopyTo(dest); 138 | #endif 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/WasmtimeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | 7 | namespace Wasmtime 8 | { 9 | /// 10 | /// The base type for Wasmtime exceptions. 11 | /// 12 | [Serializable] 13 | public class WasmtimeException : Exception 14 | { 15 | /// 16 | public WasmtimeException() { } 17 | 18 | /// 19 | public WasmtimeException(string message) : base(message) { } 20 | 21 | /// 22 | public WasmtimeException(string message, Exception? inner) : base(message, inner) { } 23 | 24 | /// 25 | /// Gets the error's frames. 26 | /// 27 | public IReadOnlyList? Frames { get; private protected set; } 28 | 29 | /// 30 | /// Gets the exit code when the error results from executing the WASI proc_exit function. 31 | /// 32 | /// The value is null if the error was not an exit error. 33 | /// 34 | public int? ExitCode { get; private set; } 35 | 36 | internal static WasmtimeException FromOwnedError(IntPtr error) 37 | { 38 | try 39 | { 40 | // Get the cause of the error if available (in case the error was caused by a 41 | // .NET exception thrown in a callback). 42 | var callbackErrorCause = Function.CallbackErrorCause; 43 | 44 | if (callbackErrorCause is not null) 45 | { 46 | // Clear the field as we consumed the value. 47 | Function.CallbackErrorCause = null; 48 | } 49 | 50 | int? exitStatus = null; 51 | if (Native.wasmtime_error_exit_status(error, out int localExitStatus)) 52 | { 53 | exitStatus = localExitStatus; 54 | } 55 | 56 | Native.wasmtime_error_message(error, out var bytes); 57 | 58 | using (bytes) 59 | { 60 | unsafe 61 | { 62 | var byteSpan = new ReadOnlySpan(bytes.data, checked((int)bytes.size)); 63 | 64 | Native.wasmtime_error_wasm_trace(error, out var frames); 65 | 66 | using (frames) 67 | { 68 | return new WasmtimeException(Encoding.UTF8.GetString(byteSpan), callbackErrorCause) 69 | { 70 | ExitCode = exitStatus, 71 | Frames = TrapException.GetFrames(frames) 72 | }; 73 | } 74 | } 75 | } 76 | } 77 | finally 78 | { 79 | Native.wasmtime_error_delete(error); 80 | } 81 | } 82 | 83 | internal static class Native 84 | { 85 | [DllImport(Engine.LibraryName)] 86 | public static extern void wasmtime_error_message(IntPtr error, out ByteArray message); 87 | 88 | [DllImport(Engine.LibraryName)] 89 | public static extern void wasmtime_error_wasm_trace(IntPtr error, out TrapException.Native.FrameArray frames); 90 | 91 | [DllImport(Engine.LibraryName)] 92 | public static extern void wasmtime_error_delete(IntPtr error); 93 | 94 | [DllImport(Engine.LibraryName)] 95 | [return: MarshalAs(UnmanagedType.I1)] 96 | public static extern bool wasmtime_error_exit_status(IntPtr error, out int exitStatus); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/CallExportFromImportTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class CallExportFromImportFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "CallExportFromImport.wat"; 10 | } 11 | 12 | public class CallExportFromImportTests : IClassFixture, IDisposable 13 | { 14 | private CallExportFromImportFixture Fixture { get; } 15 | private Store Store { get; } 16 | private Linker Linker { get; } 17 | 18 | public CallExportFromImportTests(CallExportFromImportFixture fixture) 19 | { 20 | Fixture = fixture; 21 | Store = new Store(Fixture.Engine); 22 | Linker = new Linker(Fixture.Engine); 23 | } 24 | 25 | [Fact] 26 | public void ItCallsExportedFunctionFromImportedFunction() 27 | { 28 | Linker.DefineFunction("env", "getInt", (Caller caller, int arg) => 29 | { 30 | var shiftLeftFunc = caller.GetFunction("shiftLeft"); 31 | 32 | return (int)shiftLeftFunc.Invoke(arg); 33 | }); 34 | 35 | var instance = Linker.Instantiate(Store, Fixture.Module); 36 | var testFunction = instance.GetFunction("testFunction"); 37 | 38 | var result = (int)testFunction.Invoke(2); 39 | result.Should().Be(2 << 1); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | Store?.Dispose(); 45 | Linker?.Dispose(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class ConfigTests 9 | { 10 | [Fact] 11 | public void ItSetsCompilerStrategy() 12 | { 13 | var config = new Config(); 14 | 15 | config.WithCompilerStrategy(CompilerStrategy.Cranelift); 16 | 17 | using var engine = new Engine(config); 18 | } 19 | 20 | [Fact] 21 | public void ItFailsSettingNonexistantCompilerStrategy() 22 | { 23 | var config = new Config(); 24 | 25 | var act = () => { config.WithCompilerStrategy((CompilerStrategy)123); }; 26 | act.Should().Throw(); 27 | } 28 | 29 | [Fact] 30 | public void ItSetsProfilingStrategy() 31 | { 32 | var config = new Config(); 33 | 34 | config.WithProfilingStrategy(ProfilingStrategy.None); 35 | 36 | using var engine = new Engine(config); 37 | } 38 | 39 | [Fact] 40 | public void ItFailsSettingNonexistantProfilingStrategy() 41 | { 42 | var config = new Config(); 43 | 44 | var act = () => { config.WithProfilingStrategy((ProfilingStrategy)123); }; 45 | act.Should().Throw(); 46 | } 47 | 48 | [Fact] 49 | public void ItSetsOptimizationLevel() 50 | { 51 | var config = new Config(); 52 | 53 | config.WithOptimizationLevel(OptimizationLevel.Speed); 54 | 55 | using var engine = new Engine(config); 56 | } 57 | 58 | [Fact] 59 | public void ItFailsSettingNonexistantOptimizationLevel() 60 | { 61 | var config = new Config(); 62 | 63 | var act = () => { config.WithOptimizationLevel((OptimizationLevel)123); }; 64 | act.Should().Throw(); 65 | } 66 | 67 | [Fact] 68 | public void ItSetsNanCanonicalization() 69 | { 70 | var config = new Config(); 71 | 72 | config.WithCraneliftNaNCanonicalization(true); 73 | 74 | using var engine = new Engine(config); 75 | } 76 | 77 | [Fact] 78 | public void ItSetsEpochInterruption() 79 | { 80 | var config = new Config(); 81 | 82 | config.WithEpochInterruption(true); 83 | 84 | using var engine = new Engine(config); 85 | } 86 | 87 | [Fact] 88 | public void ItSetsDebugInfo() 89 | { 90 | var config = new Config(); 91 | 92 | config.WithDebugInfo(true); 93 | 94 | using var engine = new Engine(config); 95 | } 96 | 97 | [Fact] 98 | public void ItSetsThreads() 99 | { 100 | var config = new Config(); 101 | config.WithWasmThreads(true); 102 | 103 | using var engine = new Engine(config); 104 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", "SharedMemory.wat")); 105 | } 106 | 107 | [Fact] 108 | public void ItSetsSIMD() 109 | { 110 | var config = new Config(); 111 | config.WithSIMD(false); 112 | config.WithRelaxedSIMD(false, false); 113 | 114 | Action act = () => 115 | { 116 | using var engine = new Engine(config); 117 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", "SIMD.wat")); 118 | }; 119 | 120 | act.Should().Throw(); 121 | } 122 | 123 | [Fact] 124 | public void ItSetsRelaxedSIMD() 125 | { 126 | var config = new Config(); 127 | config.WithRelaxedSIMD(false, false); 128 | 129 | Action act = () => 130 | { 131 | using var engine = new Engine(config); 132 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", "RelaxedSIMD.wat")); 133 | }; 134 | 135 | act.Should().Throw(); 136 | } 137 | 138 | [Fact] 139 | public void ItSetsBulkMemory() 140 | { 141 | var config = new Config(); 142 | config.WithBulkMemory(false); 143 | config.WithWasmThreads(false); 144 | config.WithReferenceTypes(false); 145 | 146 | Action act = () => 147 | { 148 | using var engine = new Engine(config); 149 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", "BulkMemory.wat")); 150 | }; 151 | 152 | act.Should().Throw(); 153 | } 154 | 155 | [Fact] 156 | public void ItSetsMultiValue() 157 | { 158 | var config = new Config(); 159 | config.WithMultiValue(false); 160 | 161 | Action act = () => 162 | { 163 | using var engine = new Engine(config); 164 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", "MultiValue.wat")); 165 | }; 166 | 167 | act.Should().Throw(); 168 | } 169 | 170 | [Fact] 171 | public void ItCannotBeAccessedOnceDisposed() 172 | { 173 | var config = new Config(); 174 | config.Dispose(); 175 | 176 | Assert.Throws(() => config.NativeHandle); 177 | Assert.Throws(() => config.WithBulkMemory(true)); 178 | Assert.Throws(() => config.WithCacheConfig(null)); 179 | Assert.Throws(() => config.WithEpochInterruption(true)); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /tests/EngineTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Wasmtime.Tests; 5 | 6 | public class EngineTests 7 | { 8 | [Fact] 9 | public void ItCannotBeAccessedOnceDisposed() 10 | { 11 | var engine = new Engine(); 12 | 13 | engine.Dispose(); 14 | 15 | Assert.Throws(() => engine.NativeHandle); 16 | Assert.Throws(() => engine.IncrementEpoch()); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/EpochInterruptionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests; 7 | 8 | public class EpochInterruptionFixture : ModuleFixture 9 | { 10 | protected override string ModuleFileName => "Interrupt.wat"; 11 | 12 | public override Config GetEngineConfig() 13 | { 14 | return base.GetEngineConfig() 15 | .WithEpochInterruption(true); 16 | } 17 | } 18 | 19 | public class EpochInterruptionTests : IClassFixture, IDisposable 20 | { 21 | public Store Store { get; set; } 22 | 23 | public Linker Linker { get; set; } 24 | 25 | public EpochInterruptionFixture Fixture { get; } 26 | 27 | public EpochInterruptionTests(EpochInterruptionFixture fixture) 28 | { 29 | Fixture = fixture; 30 | Linker = new Linker(Fixture.Engine); 31 | Store = new Store(Fixture.Engine); 32 | } 33 | 34 | [Fact] 35 | public void ItCanInterruptInfiniteLoop() 36 | { 37 | Store.SetEpochDeadline(1); 38 | 39 | var instance = Linker.Instantiate(Store, Fixture.Module); 40 | var run = instance.GetFunction("run"); 41 | 42 | var action = () => 43 | { 44 | using var cts = new CancellationTokenSource(100); 45 | using var tokenRegistration = cts.Token.Register(Fixture.Engine.IncrementEpoch); 46 | 47 | run.Invoke(); 48 | }; 49 | 50 | action.Should() 51 | .Throw() 52 | .WithMessage("*wasm trap: interrupt*"); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | Store.Dispose(); 58 | Linker.Dispose(); 59 | } 60 | } -------------------------------------------------------------------------------- /tests/ErrorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class ErrorFixture : ModuleFixture 9 | { 10 | protected override string ModuleFileName => "Error.wat"; 11 | } 12 | 13 | public class ErrorTests : IClassFixture, IDisposable 14 | { 15 | private ErrorFixture Fixture { get; set; } 16 | 17 | private Store Store { get; set; } 18 | 19 | private Linker Linker { get; set; } 20 | 21 | private Action ErrorFromHostExceptionCallback { get; set; } 22 | 23 | private Action HostCallback { get; set; } 24 | 25 | public ErrorTests(ErrorFixture fixture) 26 | { 27 | Fixture = fixture; 28 | Store = new Store(Fixture.Engine); 29 | Linker = new Linker(Fixture.Engine); 30 | 31 | Linker.DefineWasi(); 32 | 33 | Linker.Define("", "error_from_host_exception", Function.FromCallback( 34 | Store, 35 | () => ErrorFromHostExceptionCallback?.Invoke())); 36 | 37 | Linker.Define("", "call_host_callback", Function.FromCallback( 38 | Store, 39 | () => HostCallback?.Invoke())); 40 | } 41 | 42 | [Fact] 43 | public void ItPassesCallbackErrorCauseAsInnerException() 44 | { 45 | var instance = Linker.Instantiate(Store, Fixture.Module); 46 | var callError = instance.GetAction("error_from_host_exception"); 47 | var errorInWasm = instance.GetAction("error_in_wasm"); 48 | 49 | var exceptionToThrow = new IOException("My I/O exception."); 50 | 51 | ErrorFromHostExceptionCallback = () => throw exceptionToThrow; 52 | 53 | // Verify that the IOException thrown at the host callback is passed as 54 | // InnerException to the WasmtimeException thrown on the host-to-wasm transition. 55 | var action = callError; 56 | 57 | action 58 | .Should() 59 | .Throw() 60 | .Where(e => e.InnerException == exceptionToThrow); 61 | 62 | // After that, ensure that when invoking another function that causes an error 63 | // by setting the WASI exit code (so it cannot have a .NET exception as cause), 64 | // the WasmtimeException's InnerException is now null. 65 | action = errorInWasm; 66 | action 67 | .Should() 68 | .Throw() 69 | .Where(e => e.InnerException == null); 70 | } 71 | 72 | [Fact] 73 | public void ItPassesCallbackErrorCauseAsInnerExceptionOverTwoLevels() 74 | { 75 | var instance = Linker.Instantiate(Store, Fixture.Module); 76 | var callError = instance.GetAction("error_from_host_exception"); 77 | var callHostCallback = instance.GetAction("call_host_callback"); 78 | 79 | var exceptionToThrow = new IOException("My I/O exception."); 80 | 81 | ErrorFromHostExceptionCallback = () => throw exceptionToThrow; 82 | HostCallback = callError; 83 | 84 | // Verify that the IOException is passed as InnerException to the 85 | // WasmtimeException even after two levels of wasm-to-host transitions. 86 | var action = callHostCallback; 87 | 88 | action 89 | .Should() 90 | .Throw() 91 | .Where(e => e.InnerException == exceptionToThrow); 92 | } 93 | 94 | [Fact] 95 | public void ItPassesCallbackErrorCauseAsInnerExceptionWhenInstantiating() 96 | { 97 | var exceptionToThrow = new IOException("My I/O exception."); 98 | HostCallback = () => throw exceptionToThrow; 99 | 100 | var action = () => Linker.Instantiate(Store, Fixture.Module); 101 | 102 | action 103 | .Should() 104 | .Throw() 105 | .Where(e => e.InnerException == exceptionToThrow); 106 | } 107 | 108 | public void Dispose() 109 | { 110 | Store.Dispose(); 111 | Linker.Dispose(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/ExitErrorTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class ExitErrorTests 8 | { 9 | [Theory] 10 | [InlineData("ExitError.wat", 0)] 11 | [InlineData("ExitError.wat", 1)] 12 | [InlineData("ExitError.wat", -1)] 13 | public void ItReturnsExitCode(string path, int exitCode) 14 | { 15 | using var engine = new Engine(); 16 | using var module = Module.FromTextFile(engine, Path.Combine("Modules", path)); 17 | using var store = new Store(engine); 18 | using var linker = new Linker(engine); 19 | 20 | linker.DefineWasi(); 21 | 22 | store.SetWasiConfiguration(new WasiConfiguration()); 23 | var instance = linker.Instantiate(store, module); 24 | 25 | var memory = instance.GetMemory("memory"); 26 | memory.Should().NotBeNull(); 27 | 28 | var exit = instance.GetAction("exit")!; 29 | exit.Should().NotBeNull(); 30 | 31 | try 32 | { 33 | exit(exitCode); 34 | Assert.False(bool.Parse(bool.TrueString)); 35 | } 36 | catch (WasmtimeException ex) 37 | { 38 | if (exitCode < 0) 39 | { 40 | Assert.Null(ex.ExitCode); 41 | Assert.Contains("exit with invalid exit status", ex.Message); 42 | } 43 | else 44 | { 45 | Assert.NotNull(ex.ExitCode); 46 | Assert.Equal(exitCode, ex.ExitCode); 47 | Assert.Contains($"Exited with i32 exit status {exitCode}", ex.Message); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/ExternRefTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class ExternRefFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "ExternRef.wat"; 10 | } 11 | 12 | public class ExternRefTests : IClassFixture, IDisposable 13 | { 14 | public ExternRefTests(ExternRefFixture fixture) 15 | { 16 | Fixture = fixture; 17 | Linker = new Linker(Fixture.Engine); 18 | Store = new Store(Fixture.Engine); 19 | 20 | Linker.Define("", "inout", Function.FromCallback(Store, (object o) => o)); 21 | } 22 | 23 | private ExternRefFixture Fixture { get; set; } 24 | 25 | private Store Store { get; set; } 26 | 27 | private Linker Linker { get; set; } 28 | 29 | [Fact] 30 | public void ItReturnsTheSameDotnetReference() 31 | { 32 | var instance = Linker.Instantiate(Store, Fixture.Module); 33 | 34 | var inout = instance.GetFunction("inout"); 35 | inout.Should().NotBeNull(); 36 | 37 | var input = "input"; 38 | inout(input).Should().BeSameAs(input); 39 | } 40 | 41 | [Fact] 42 | public void ItAllowsToPassInterfaceToCallback() 43 | { 44 | Linker.AllowShadowing = true; 45 | Linker.Define("", "inout", Function.FromCallback(Store, (IComparable o) => o)); 46 | var instance = Linker.Instantiate(Store, Fixture.Module); 47 | 48 | // TODO: Currently, it seems to not be supported to use an interface type 49 | // as return type parameter when getting a instance function. 50 | var inout = instance.GetFunction("inout"); 51 | inout.Should().NotBeNull(); 52 | 53 | IComparable input = 1234; 54 | inout(input).Should().BeSameAs(input); 55 | } 56 | 57 | [Fact] 58 | public void ItHandlesNullReferences() 59 | { 60 | var instance = Linker.Instantiate(Store, Fixture.Module); 61 | 62 | var inout = instance.GetFunction("inout"); 63 | inout.Should().NotBeNull(); 64 | 65 | var nullref = instance.GetFunction("nullref"); 66 | inout.Should().NotBeNull(); 67 | 68 | (inout.Invoke(ValueBox.AsBox((object)null))).Should().BeNull(); 69 | (nullref.Invoke()).Should().BeNull(); 70 | } 71 | 72 | [Fact] 73 | public void ItReturnsBoxedValueTupleAsExternRef() 74 | { 75 | // Test for issue #158 76 | var instance = Linker.Instantiate(Store, Fixture.Module); 77 | 78 | var inout = instance.GetFunction("inout"); 79 | inout.Should().NotBeNull(); 80 | 81 | var input = (object)(1, 2, 3); 82 | inout(input).Should().BeSameAs(input); 83 | } 84 | 85 | unsafe class Value 86 | { 87 | internal Value(int* counter) 88 | { 89 | this.counter = counter; 90 | 91 | System.Threading.Interlocked.Increment(ref *counter); 92 | } 93 | 94 | ~Value() 95 | { 96 | System.Threading.Interlocked.Decrement(ref *counter); 97 | } 98 | 99 | int* counter; 100 | } 101 | 102 | [Fact] 103 | unsafe public void ItCollectsExternRefs() 104 | { 105 | var counter = 0; 106 | 107 | RunTest(&counter); 108 | 109 | GC.Collect(); 110 | GC.WaitForPendingFinalizers(); 111 | 112 | counter.Should().Be(0); 113 | 114 | void RunTest(int* counter) 115 | { 116 | var instance = Linker.Instantiate(Store, Fixture.Module); 117 | 118 | var inout = instance.GetFunction("inout"); 119 | inout.Should().NotBeNull(); 120 | for (int i = 0; i < 100; ++i) 121 | { 122 | inout.Invoke(ValueBox.AsBox(new Value(counter))); 123 | } 124 | 125 | Store.Dispose(); 126 | Store = null; 127 | } 128 | } 129 | 130 | [Fact] 131 | public void ItThrowsForMismatchedTypes() 132 | { 133 | Linker.AllowShadowing = true; 134 | Linker.Define("", "inout", Function.FromCallback(Store, (string o) => o)); 135 | 136 | var instance = Linker.Instantiate(Store, Fixture.Module); 137 | 138 | var inout = instance.GetFunction("inout"); 139 | inout.Should().NotBeNull(); 140 | 141 | Action action = () => inout.Invoke(ValueBox.AsBox((object)5)); 142 | 143 | action 144 | .Should() 145 | .Throw() 146 | .WithMessage("*Unable to cast object of type 'System.Int32' to type 'System.String'*"); 147 | } 148 | 149 | public void Dispose() 150 | { 151 | if (Store != null) 152 | { 153 | Store.Dispose(); 154 | } 155 | Linker.Dispose(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /tests/FuelConsumptionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class FuelConsumptionFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "FuelConsumption.wat"; 10 | 11 | public override Config GetEngineConfig() 12 | { 13 | return base.GetEngineConfig() 14 | .WithFuelConsumption(true); 15 | } 16 | } 17 | 18 | public class FuelConsumptionTests : IClassFixture, IDisposable 19 | { 20 | private Store Store { get; set; } 21 | 22 | private Linker Linker { get; set; } 23 | 24 | private FuelConsumptionFixture Fixture { get; } 25 | 26 | public FuelConsumptionTests(FuelConsumptionFixture fixture) 27 | { 28 | Fixture = fixture; 29 | Linker = new Linker(Fixture.Engine); 30 | Store = new Store(Fixture.Engine); 31 | 32 | Linker.Define("env", "free", Function.FromCallback(Store, () => 33 | { 34 | // do nothing 35 | })); 36 | Linker.Define("env", "expensive", Function.FromCallback(Store, (Caller caller) => 37 | { 38 | checked 39 | { 40 | caller.Fuel -= 100UL; 41 | } 42 | })); 43 | } 44 | 45 | [Fact] 46 | public void ItHasNoFuelBeforeFuelIsAdded() 47 | { 48 | Store.Fuel.Should().Be(0UL); 49 | } 50 | 51 | [Fact] 52 | public void ItCanSetAndGetFuel() 53 | { 54 | Store.Fuel = 1000UL; 55 | Store.Fuel.Should().Be(1000UL); 56 | } 57 | 58 | [Fact] 59 | public void ItCanAddFuel() 60 | { 61 | Store.Fuel = 1000UL; 62 | Store.Fuel += 1000UL; 63 | 64 | Store.Fuel.Should().Be(2000UL); 65 | } 66 | [Fact] 67 | public void ItCanRemoveFuel() 68 | { 69 | Store.Fuel = 1000UL; 70 | Store.Fuel -= 500UL; 71 | 72 | Store.Fuel.Should().Be(500UL); 73 | } 74 | 75 | [Fact] 76 | public void ItConsumesFuelWhenCallingImportMethods() 77 | { 78 | var instance = Linker.Instantiate(Store, Fixture.Module); 79 | var free = instance.GetFunction("free").WrapFunc(); 80 | 81 | Store.Fuel = 1000UL; 82 | 83 | free.Invoke().Type.Should().Be(ResultType.Ok); 84 | Store.Fuel.Should().Be(1000UL - 2UL); 85 | 86 | free.Invoke().Type.Should().Be(ResultType.Ok); 87 | Store.Fuel.Should().Be(1000UL - 4UL); 88 | } 89 | 90 | [Fact] 91 | public void ItConsumesFuelFromInsideAnImportMethod() 92 | { 93 | var instance = Linker.Instantiate(Store, Fixture.Module); 94 | var expensive = instance.GetFunction("expensive"); 95 | 96 | Store.Fuel = 1000UL; 97 | 98 | expensive.Invoke(); 99 | Store.Fuel.Should().Be(1000UL - 102UL); 100 | } 101 | 102 | [Fact] 103 | public void ItThrowsOnCallingImportMethodIfNoFuelAdded() 104 | { 105 | var instance = Linker.Instantiate(Store, Fixture.Module); 106 | var free = instance.GetFunction("free"); 107 | 108 | Action action = () => free.Invoke(); 109 | action 110 | .Should() 111 | .Throw() 112 | .Where(e => e.Type == TrapCode.OutOfFuel) 113 | .WithMessage("*all fuel consumed by WebAssembly*"); 114 | 115 | Store.Fuel.Should().Be(0UL); 116 | } 117 | 118 | [Fact] 119 | public void ItThrowsOnCallingImportMethodIfNotEnoughFuelAdded() 120 | { 121 | var instance = Linker.Instantiate(Store, Fixture.Module); 122 | var free = instance.GetFunction("free"); 123 | 124 | Store.Fuel = 4UL; 125 | 126 | free.Invoke(); 127 | Store.Fuel.Should().Be(2UL); 128 | 129 | free.Invoke(); 130 | Store.Fuel.Should().Be(0UL); 131 | 132 | Action action = () => free.Invoke(); 133 | action 134 | .Should() 135 | .Throw() 136 | .Where(e => e.Type == TrapCode.OutOfFuel) 137 | .WithMessage("*all fuel consumed by WebAssembly*"); 138 | 139 | Store.Fuel.Should().Be(0UL); 140 | } 141 | 142 | [Fact] 143 | public void ItThrowsWhenConsumingTooMuchFuelFromInsideAnImportMethod() 144 | { 145 | var instance = Linker.Instantiate(Store, Fixture.Module); 146 | var expensive = instance.GetFunction("expensive"); 147 | 148 | Store.Fuel = 50UL; 149 | 150 | Action action = () => expensive.Invoke(); 151 | action 152 | .Should() 153 | .Throw() 154 | .WithInnerException(); 155 | 156 | Store.Fuel.Should().Be(48UL); 157 | } 158 | 159 | [Fact] 160 | public void ItAddsAdditonalFuelAfterCallingImportMethods() 161 | { 162 | var instance = Linker.Instantiate(Store, Fixture.Module); 163 | var free = instance.GetFunction("free"); 164 | 165 | Store.Fuel = 4UL; 166 | 167 | free.Invoke(); 168 | Store.Fuel.Should().Be(2UL); 169 | 170 | free.Invoke(); 171 | Store.Fuel.Should().Be(0UL); 172 | 173 | Action action = () => free.Invoke(); 174 | action 175 | .Should() 176 | .Throw() 177 | .Where(e => e.Type == TrapCode.OutOfFuel) 178 | .WithMessage("*all fuel consumed by WebAssembly*"); 179 | 180 | Store.Fuel += 3UL; 181 | 182 | free.Invoke(); 183 | Store.Fuel.Should().Be(1UL); 184 | 185 | action 186 | .Should() 187 | .Throw() 188 | .Where(e => e.Type == TrapCode.OutOfFuel) 189 | .WithMessage("*all fuel consumed by WebAssembly*"); 190 | 191 | Store.Fuel.Should().Be(0UL); 192 | } 193 | 194 | public void Dispose() 195 | { 196 | Store.Dispose(); 197 | Linker.Dispose(); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/FuncRefTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class FuncRefFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "FuncRef.wat"; 10 | } 11 | 12 | public class FuncRefTests : IClassFixture, IDisposable 13 | { 14 | public FuncRefTests(FuncRefFixture fixture) 15 | { 16 | Fixture = fixture; 17 | Linker = new Linker(Fixture.Engine); 18 | Store = new Store(Fixture.Engine); 19 | 20 | Callback = Function.FromCallback(Store, (Caller caller, Function f) => f.Invoke("testing")); 21 | Assert = Function.FromCallback(Store, (string s) => { s.Should().Be("testing"); return "asserted!"; }); 22 | 23 | Linker.DefineFunction("", "return_funcref", () => ReturnFuncRefCallback()); 24 | Linker.DefineFunction("", "store_funcref", (Function funcRef) => StoreFuncRefCallback(funcRef)); 25 | 26 | Linker.Define("", "callback", Callback); 27 | Linker.Define("", "assert", Assert); 28 | } 29 | 30 | private FuncRefFixture Fixture { get; set; } 31 | 32 | private Store Store { get; set; } 33 | 34 | private Linker Linker { get; set; } 35 | 36 | private Function Callback { get; set; } 37 | 38 | private Function Assert { get; set; } 39 | 40 | private Func ReturnFuncRefCallback { get; set; } 41 | 42 | private Action StoreFuncRefCallback { get; set; } 43 | 44 | [Fact] 45 | public void ItPassesFunctionReferencesToWasm() 46 | { 47 | var f = Function.FromCallback(Store, (Caller caller, string s) => Assert.Invoke(s)); 48 | var instance = Linker.Instantiate(Store, Fixture.Module); 49 | 50 | var func = instance.GetFunction("call_nested"); 51 | func.Should().NotBeNull(); 52 | 53 | func(Callback, f).Should().Be("asserted!"); 54 | } 55 | 56 | [Fact] 57 | public void ItAcceptsFunctionReferences() 58 | { 59 | var instance = Linker.Instantiate(Store, Fixture.Module); 60 | 61 | var func = instance.GetFunction("call_callback"); 62 | func.Should().NotBeNull(); 63 | 64 | func().Should().Be("asserted!"); 65 | } 66 | 67 | [Fact] 68 | public void ItReturnsFunctionReferences() 69 | { 70 | ReturnFuncRefCallback = () => Assert; 71 | var instance = Linker.Instantiate(Store, Fixture.Module); 72 | 73 | var func = instance.GetFunction("return_funcref"); 74 | func.Should().NotBeNull(); 75 | 76 | var returnedFunc = func(); 77 | returnedFunc.Should().NotBeNull(); 78 | 79 | // We can't check whether the returnedFunc is the same as the Assert function 80 | // (as they will be different instances). 81 | var wrappedFunc = returnedFunc.WrapFunc(); 82 | wrappedFunc.Should().NotBeNull(); 83 | 84 | wrappedFunc("testing").Should().Be("asserted!"); 85 | } 86 | 87 | [Fact] 88 | public void ItReturnsNullFunctionReferences() 89 | { 90 | var instance = Linker.Instantiate(Store, Fixture.Module); 91 | 92 | var func = instance.GetFunction("return_funcref"); 93 | func.Should().NotBeNull(); 94 | 95 | ReturnFuncRefCallback = () => Function.Null; 96 | var returnedFunc = func(); 97 | returnedFunc.IsNull.Should().BeTrue(); 98 | 99 | ReturnFuncRefCallback = () => null; 100 | returnedFunc = func(); 101 | returnedFunc.IsNull.Should().BeTrue(); 102 | } 103 | 104 | [Fact] 105 | public void ItThrowsWhenReturningFunctionReferencesFromDifferentStore() 106 | { 107 | using var separateStore = new Store(Fixture.Engine); 108 | var separateStoreFunction = Function.FromCallback(separateStore, () => 123); 109 | 110 | ReturnFuncRefCallback = () => separateStoreFunction; 111 | var instance = Linker.Instantiate(Store, Fixture.Module); 112 | 113 | var func = instance.GetFunction("return_funcref"); 114 | func.Should().NotBeNull(); 115 | 116 | func 117 | .Should() 118 | .Throw() 119 | .Where(e => e.InnerException is InvalidOperationException) 120 | .WithMessage("*Returning a Function is only allowed when it belongs to the current store.*"); 121 | } 122 | 123 | [Fact] 124 | public void ItThrowsForInvokingANullFunctionReference() 125 | { 126 | var instance = Linker.Instantiate(Store, Fixture.Module); 127 | var func = instance.GetFunction("call_with_null"); 128 | func.Should().NotBeNull(); 129 | 130 | func 131 | .Should() 132 | .Throw() 133 | .WithMessage("*Cannot invoke a null function reference.*"); 134 | } 135 | 136 | [Fact] 137 | public void ItCanUseFunctionReferenceFromCallbackAfterReturning() 138 | { 139 | var localFuncRef = default(Function); 140 | StoreFuncRefCallback = funcRef => localFuncRef = funcRef; 141 | 142 | var instance = Linker.Instantiate(Store, Fixture.Module); 143 | var func = instance.GetAction("call_store_funcref"); 144 | func.Should().NotBeNull(); 145 | 146 | func(); 147 | 148 | var wrappedFunc = localFuncRef.WrapFunc(); 149 | 150 | wrappedFunc 151 | .Should() 152 | .NotBeNull(); 153 | 154 | wrappedFunc("testing").Should().Be("asserted!"); 155 | } 156 | 157 | public void Dispose() 158 | { 159 | Store.Dispose(); 160 | Linker.Dispose(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /tests/FunctionExportsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace Wasmtime.Tests 8 | { 9 | public class FunctionExportsFixture : ModuleFixture 10 | { 11 | protected override string ModuleFileName => "FunctionExports.wat"; 12 | } 13 | 14 | public class FunctionExportsTests : IClassFixture 15 | { 16 | private Store Store { get; set; } 17 | 18 | private Linker Linker { get; set; } 19 | 20 | public FunctionExportsTests(FunctionExportsFixture fixture) 21 | { 22 | Fixture = fixture; 23 | Store = new Store(Fixture.Engine); 24 | Linker = new Linker(Fixture.Engine); 25 | } 26 | 27 | private FunctionExportsFixture Fixture { get; set; } 28 | 29 | [Theory] 30 | [MemberData(nameof(GetFunctionExports))] 31 | public void ItHasTheExpectedFunctionExports(string exportName, ValueKind[] expectedParameters, ValueKind[] expectedResults) 32 | { 33 | var export = Fixture.Module.Exports.Where(f => f.Name == exportName).FirstOrDefault() as FunctionExport; 34 | export.Should().NotBeNull(); 35 | export.Parameters.Should().Equal(expectedParameters); 36 | export.Results.Should().Equal(expectedResults); 37 | } 38 | 39 | [Fact] 40 | public void ItHasTheExpectedNumberOfExportedFunctions() 41 | { 42 | GetFunctionExports().Count().Should().Be(Fixture.Module.Exports.Count(e => e is FunctionExport)); 43 | } 44 | 45 | [Fact] 46 | public void ItReturnsNullForNonExistantFunction() 47 | { 48 | var instance = Linker.Instantiate(Store, Fixture.Module); 49 | 50 | var i32 = instance.GetFunction("no_such_func"); 51 | i32.Should().BeNull(); 52 | } 53 | 54 | [Fact] 55 | public void ItReturnsNullForWrongTypeSignature() 56 | { 57 | var instance = Linker.Instantiate(Store, Fixture.Module); 58 | 59 | var i32 = instance.GetFunction("one_i32_param_no_results", typeof(float)); 60 | i32.Should().BeNull(); 61 | } 62 | 63 | public static IEnumerable GetFunctionExports() 64 | { 65 | yield return new object[] { 66 | "no_params_no_results", 67 | Array.Empty(), 68 | Array.Empty() 69 | }; 70 | 71 | yield return new object[] { 72 | "one_i32_param_no_results", 73 | new ValueKind[] { 74 | ValueKind.Int32 75 | }, 76 | Array.Empty() 77 | }; 78 | 79 | yield return new object[] { 80 | "one_i64_param_no_results", 81 | new ValueKind[] { 82 | ValueKind.Int64 83 | }, 84 | Array.Empty() 85 | }; 86 | 87 | yield return new object[] { 88 | "one_f32_param_no_results", 89 | new ValueKind[] { 90 | ValueKind.Float32 91 | }, 92 | Array.Empty() 93 | }; 94 | 95 | yield return new object[] { 96 | "one_f64_param_no_results", 97 | new ValueKind[] { 98 | ValueKind.Float64 99 | }, 100 | Array.Empty() 101 | }; 102 | 103 | yield return new object[] { 104 | "one_param_of_each_type", 105 | new ValueKind[] { 106 | ValueKind.Int32, 107 | ValueKind.Int64, 108 | ValueKind.Float32, 109 | ValueKind.Float64 110 | }, 111 | Array.Empty() 112 | }; 113 | 114 | yield return new object[] { 115 | "no_params_one_i32_result", 116 | Array.Empty(), 117 | new ValueKind[] { 118 | ValueKind.Int32, 119 | } 120 | }; 121 | 122 | yield return new object[] { 123 | "no_params_one_i64_result", 124 | Array.Empty(), 125 | new ValueKind[] { 126 | ValueKind.Int64, 127 | } 128 | }; 129 | 130 | yield return new object[] { 131 | "no_params_one_f32_result", 132 | Array.Empty(), 133 | new ValueKind[] { 134 | ValueKind.Float32, 135 | } 136 | }; 137 | 138 | yield return new object[] { 139 | "no_params_one_f64_result", 140 | Array.Empty(), 141 | new ValueKind[] { 142 | ValueKind.Float64, 143 | } 144 | }; 145 | 146 | yield return new object[] { 147 | "one_result_of_each_type", 148 | Array.Empty(), 149 | new ValueKind[] { 150 | ValueKind.Int32, 151 | ValueKind.Int64, 152 | ValueKind.Float32, 153 | ValueKind.Float64, 154 | } 155 | }; 156 | 157 | yield return new object[] { 158 | "one_param_and_result_of_each_type", 159 | new ValueKind[] { 160 | ValueKind.Int32, 161 | ValueKind.Int64, 162 | ValueKind.Float32, 163 | ValueKind.Float64, 164 | }, 165 | new ValueKind[] { 166 | ValueKind.Int32, 167 | ValueKind.Int64, 168 | ValueKind.Float32, 169 | ValueKind.Float64, 170 | } 171 | }; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/FunctionImportsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace Wasmtime.Tests 8 | { 9 | public class FunctionImportsFixture : ModuleFixture 10 | { 11 | protected override string ModuleFileName => "FunctionImports.wat"; 12 | } 13 | 14 | public class FunctionImportsTests : IClassFixture 15 | { 16 | public FunctionImportsTests(FunctionImportsFixture fixture) 17 | { 18 | Fixture = fixture; 19 | } 20 | 21 | private FunctionImportsFixture Fixture { get; set; } 22 | 23 | [Theory] 24 | [MemberData(nameof(GetFunctionImports))] 25 | public void ItHasTheExpectedFunctionImports(string importModule, string importName, ValueKind[] expectedParameters, ValueKind[] expectedResults) 26 | { 27 | var import = Fixture.Module.Imports.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault() as FunctionImport; 28 | import.Should().NotBeNull(); 29 | import.Parameters.Should().Equal(expectedParameters); 30 | import.Results.Should().Equal(expectedResults); 31 | } 32 | 33 | [Fact] 34 | public void ItHasTheExpectedNumberOfExportedFunctions() 35 | { 36 | GetFunctionImports().Count().Should().Be(Fixture.Module.Imports.Count(i => i is FunctionImport)); 37 | } 38 | 39 | public static IEnumerable GetFunctionImports() 40 | { 41 | yield return new object[] { 42 | "", 43 | "no_params_no_results", 44 | Array.Empty(), 45 | Array.Empty() 46 | }; 47 | 48 | yield return new object[] { 49 | "", 50 | "one_i32_param_no_results", 51 | new ValueKind[] { 52 | ValueKind.Int32 53 | }, 54 | Array.Empty() 55 | }; 56 | 57 | yield return new object[] { 58 | "", 59 | "one_i64_param_no_results", 60 | new ValueKind[] { 61 | ValueKind.Int64 62 | }, 63 | Array.Empty() 64 | }; 65 | 66 | yield return new object[] { 67 | "", 68 | "one_f32_param_no_results", 69 | new ValueKind[] { 70 | ValueKind.Float32 71 | }, 72 | Array.Empty() 73 | }; 74 | 75 | yield return new object[] { 76 | "", 77 | "one_f64_param_no_results", 78 | new ValueKind[] { 79 | ValueKind.Float64 80 | }, 81 | Array.Empty() 82 | }; 83 | 84 | yield return new object[] { 85 | "", 86 | "one_param_of_each_type", 87 | new ValueKind[] { 88 | ValueKind.Int32, 89 | ValueKind.Int64, 90 | ValueKind.Float32, 91 | ValueKind.Float64 92 | }, 93 | Array.Empty() 94 | }; 95 | 96 | yield return new object[] { 97 | "", 98 | "no_params_one_i32_result", 99 | Array.Empty(), 100 | new ValueKind[] { 101 | ValueKind.Int32, 102 | } 103 | }; 104 | 105 | yield return new object[] { 106 | "", 107 | "no_params_one_i64_result", 108 | Array.Empty(), 109 | new ValueKind[] { 110 | ValueKind.Int64, 111 | } 112 | }; 113 | 114 | yield return new object[] { 115 | "", 116 | "no_params_one_f32_result", 117 | Array.Empty(), 118 | new ValueKind[] { 119 | ValueKind.Float32, 120 | } 121 | }; 122 | 123 | yield return new object[] { 124 | "", 125 | "no_params_one_f64_result", 126 | Array.Empty(), 127 | new ValueKind[] { 128 | ValueKind.Float64, 129 | } 130 | }; 131 | 132 | yield return new object[] { 133 | "", 134 | "one_result_of_each_type", 135 | Array.Empty(), 136 | new ValueKind[] { 137 | ValueKind.Int32, 138 | ValueKind.Int64, 139 | ValueKind.Float32, 140 | ValueKind.Float64, 141 | } 142 | }; 143 | 144 | yield return new object[] { 145 | "", 146 | "one_param_and_result_of_each_type", 147 | new ValueKind[] { 148 | ValueKind.Int32, 149 | ValueKind.Int64, 150 | ValueKind.Float32, 151 | ValueKind.Float64, 152 | }, 153 | new ValueKind[] { 154 | ValueKind.Int32, 155 | ValueKind.Int64, 156 | ValueKind.Float32, 157 | ValueKind.Float64, 158 | } 159 | }; 160 | 161 | yield return new object[] { 162 | "other", 163 | "function_from_module", 164 | Array.Empty(), 165 | Array.Empty(), 166 | }; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/GlobalImportsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class GlobalImportsFixture : ModuleFixture 9 | { 10 | protected override string ModuleFileName => "GlobalImports.wat"; 11 | } 12 | 13 | public class GlobalImportsTests : IClassFixture 14 | { 15 | public GlobalImportsTests(GlobalImportsFixture fixture) 16 | { 17 | Fixture = fixture; 18 | } 19 | 20 | private GlobalImportsFixture Fixture { get; set; } 21 | 22 | [Theory] 23 | [MemberData(nameof(GetGlobalImports))] 24 | public void ItHasTheExpectedGlobalImports(string importModule, string importName, ValueKind expectedKind, Mutability expectedMutability) 25 | { 26 | var import = Fixture.Module.Imports.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault() as GlobalImport; 27 | import.Should().NotBeNull(); 28 | import.Kind.Should().Be(expectedKind); 29 | import.Mutability.Should().Be(expectedMutability); 30 | } 31 | 32 | [Fact] 33 | public void ItHasTheExpectedNumberOfExportedGlobals() 34 | { 35 | GetGlobalImports().Count().Should().Be(Fixture.Module.Imports.Count(i => i is GlobalImport)); 36 | } 37 | 38 | public static IEnumerable GetGlobalImports() 39 | { 40 | yield return new object[] { 41 | "", 42 | "global_i32", 43 | ValueKind.Int32, 44 | Mutability.Immutable 45 | }; 46 | 47 | yield return new object[] { 48 | "", 49 | "global_i32_mut", 50 | ValueKind.Int32, 51 | Mutability.Mutable 52 | }; 53 | 54 | yield return new object[] { 55 | "", 56 | "global_i64", 57 | ValueKind.Int64, 58 | Mutability.Immutable 59 | }; 60 | 61 | yield return new object[] { 62 | "", 63 | "global_i64_mut", 64 | ValueKind.Int64, 65 | Mutability.Mutable 66 | }; 67 | 68 | yield return new object[] { 69 | "", 70 | "global_f32", 71 | ValueKind.Float32, 72 | Mutability.Immutable 73 | }; 74 | 75 | yield return new object[] { 76 | "", 77 | "global_f32_mut", 78 | ValueKind.Float32, 79 | Mutability.Mutable 80 | }; 81 | 82 | yield return new object[] { 83 | "", 84 | "global_f64", 85 | ValueKind.Float64, 86 | Mutability.Immutable 87 | }; 88 | 89 | yield return new object[] { 90 | "", 91 | "global_f64_mut", 92 | ValueKind.Float64, 93 | Mutability.Mutable 94 | }; 95 | 96 | yield return new object[] { 97 | "other", 98 | "global_from_module", 99 | ValueKind.Int32, 100 | Mutability.Immutable 101 | }; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/InstanceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class InstanceFixture 9 | : ModuleFixture 10 | { 11 | protected override string ModuleFileName => "hello.wat"; 12 | } 13 | 14 | public class InstanceTests 15 | : IClassFixture, IDisposable 16 | { 17 | private Store Store { get; set; } 18 | 19 | private Linker Linker { get; set; } 20 | 21 | public InstanceTests(InstanceFixture fixture) 22 | { 23 | Fixture = fixture; 24 | Linker = new Linker(Fixture.Engine); 25 | Store = new Store(Fixture.Engine); 26 | 27 | Linker.DefineFunction("env", "add", (int x, int y) => x + y); 28 | Linker.DefineFunction("env", "swap", (int x, int y) => (y, x)); 29 | Linker.DefineFunction("", "hi", (int x, int y) => (y, x)); 30 | 31 | Linker.DefineFunction("", "hello", () => { }); 32 | } 33 | 34 | private InstanceFixture Fixture { get; } 35 | 36 | [Fact] 37 | public void ItGetsExportedFunctions() 38 | { 39 | var instance = Linker.Instantiate(Store, Fixture.Module); 40 | 41 | var results = instance.GetFunctions(); 42 | 43 | results.Single().Name.Should().Be("run"); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | Store.Dispose(); 49 | Linker.Dispose(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/InvalidModuleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class InvalidModuleTests 8 | { 9 | [Fact] 10 | public void ItThrowsWithErrorMessageForInvalidModules() 11 | { 12 | using var engine = new Engine(); 13 | 14 | Action action = () => Module.FromBytes(engine, "invalid", new byte[] { }); 15 | 16 | action 17 | .Should() 18 | .Throw() 19 | .WithMessage("WebAssembly module 'invalid' is not valid: failed to parse WebAssembly module*"); 20 | } 21 | 22 | [Fact] 23 | public void ItReturnsAnErrorWhenValidatingAnInvalidModule() 24 | { 25 | using var engine = new Engine(); 26 | Module.Validate(engine, Array.Empty()).Should().Be("unexpected end-of-file (at offset 0x0)"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Memory64AccessTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class Memory64AccessFixture : ModuleFixture 9 | { 10 | protected override string ModuleFileName => "Memory64Access.wat"; 11 | } 12 | 13 | public class Memory64AccessTests : IClassFixture, IDisposable 14 | { 15 | private Memory64AccessFixture Fixture { get; set; } 16 | 17 | private Store Store { get; set; } 18 | 19 | private Linker Linker { get; set; } 20 | 21 | public Memory64AccessTests(Memory64AccessFixture fixture) 22 | { 23 | Fixture = fixture; 24 | Store = new Store(Fixture.Engine); 25 | Linker = new Linker(Fixture.Engine); 26 | } 27 | 28 | [Fact(Skip = "Test consumes too much memory for CI")] 29 | public unsafe void ItCanAccessMemoryWith65537Pages() 30 | { 31 | var memoryExport = Fixture.Module.Exports.OfType().Single(); 32 | memoryExport.Minimum.Should().Be(0x10001); 33 | memoryExport.Maximum.Should().Be(0x1000000000000); 34 | memoryExport.Is64Bit.Should().BeTrue(); 35 | 36 | var instance = Linker.Instantiate(Store, Fixture.Module); 37 | var memory = instance.GetMemory("mem"); 38 | 39 | memory.Minimum.Should().Be(0x10001); 40 | memory.Maximum.Should().Be(0x1000000000000); 41 | memory.Is64Bit.Should().BeTrue(); 42 | memory.GetSize().Should().Be(0x10001); 43 | memory.GetLength().Should().Be(0x100010000); 44 | 45 | memory.ReadInt32(0).Should().Be(0); 46 | 47 | memory.WriteInt64(100, 1234); 48 | memory.ReadInt64(100).Should().Be(1234); 49 | 50 | memory.ReadByte(0x10000FFFF).Should().Be(0x63); 51 | memory.ReadInt16(0x10000FFFE).Should().Be(0x6364); 52 | memory.ReadInt32(0x10000FFFC).Should().Be(0x63646500); 53 | 54 | memory.ReadSingle(0x10000FFFC).Should().Be(4.2131355E+21F); 55 | 56 | var span = memory.GetSpan(0x10000FFFE, 2); 57 | span.SequenceEqual(new byte[] { 0x64, 0x63 }).Should().BeTrue(); 58 | 59 | var int16Span = memory.GetSpan(0x10000, int.MaxValue); 60 | int16Span[0x7FFFFFFE].Should().Be(0x6500); 61 | 62 | int16Span = memory.GetSpan(0x10002); 63 | int16Span[0x7FFFFFFE].Should().Be(0x6364); 64 | 65 | byte* ptr = (byte*)memory.GetPointer(); 66 | ptr += 0x10000FFFF; 67 | (*ptr).Should().Be(0x63); 68 | 69 | string str1 = "Hello World"; 70 | memory.WriteString(0x10000FFFF - str1.Length, str1); 71 | memory.ReadString(0x10000FFFF - str1.Length, str1.Length).Should().Be(str1); 72 | } 73 | 74 | [Fact(Skip = "Test consumes too much memory for CI")] 75 | public void ItThrowsForOutOfBoundsAccess() 76 | { 77 | var instance = Linker.Instantiate(Store, Fixture.Module); 78 | var memory = instance.GetMemory("mem"); 79 | 80 | #pragma warning disable CS0618 // Type or member is obsolete 81 | Action action = () => memory.GetSpan(); 82 | #pragma warning restore CS0618 // Type or member is obsolete 83 | action.Should().Throw(); 84 | 85 | action = () => memory.GetSpan(0x10000); 86 | action.Should().Throw(); 87 | 88 | action = () => memory.GetSpan(-1L, 0); 89 | action.Should().Throw(); 90 | 91 | action = () => memory.GetSpan(0L, -1); 92 | action.Should().Throw(); 93 | 94 | action = () => memory.ReadInt16(0x10000FFFF); 95 | action.Should().Throw(); 96 | 97 | action = () => memory.GetSpan(0x10000FFFF, 1); 98 | action.Should().Throw(); 99 | } 100 | 101 | public void Dispose() 102 | { 103 | Store.Dispose(); 104 | Linker.Dispose(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/MemoryAccessTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class MemoryAccessFixture : ModuleFixture 9 | { 10 | protected override string ModuleFileName => "MemoryAccess.wat"; 11 | } 12 | 13 | public class MemoryAccessTests : IClassFixture, IDisposable 14 | { 15 | private MemoryAccessFixture Fixture { get; set; } 16 | 17 | private Store Store { get; set; } 18 | 19 | private Linker Linker { get; set; } 20 | 21 | public MemoryAccessTests(MemoryAccessFixture fixture) 22 | { 23 | Fixture = fixture; 24 | Store = new Store(Fixture.Engine); 25 | Linker = new Linker(Fixture.Engine); 26 | } 27 | 28 | [Fact] 29 | public void ItGrows() 30 | { 31 | var memory = new Memory(Store, 1, 4); 32 | memory.GetSize().Should().Be(1); 33 | memory.Grow(1); 34 | memory.GetSize().Should().Be(2); 35 | memory.Grow(2); 36 | memory.GetSize().Should().Be(4); 37 | } 38 | 39 | [Fact] 40 | public void ItFailsToShrink() 41 | { 42 | var memory = new Memory(Store, 1, 4); 43 | memory.GetSize().Should().Be(1); 44 | memory.Grow(1); 45 | memory.GetSize().Should().Be(2); 46 | 47 | var act = () => { memory.Grow(-1); }; 48 | act.Should().Throw(); 49 | } 50 | 51 | [Fact] 52 | public void ItFailsToGrowOverLimit() 53 | { 54 | var memory = new Memory(Store, 1, 4); 55 | memory.GetSize().Should().Be(1); 56 | 57 | var act = () => { memory.Grow(10); }; 58 | act.Should().Throw(); 59 | } 60 | 61 | [Fact] 62 | public unsafe void ItCanAccessMemoryWith65536Pages() 63 | { 64 | var memoryExport = Fixture.Module.Exports.OfType().Single(); 65 | memoryExport.Minimum.Should().Be(0x10000); 66 | memoryExport.Maximum.Should().BeNull(); 67 | memoryExport.Is64Bit.Should().BeFalse(); 68 | 69 | var instance = Linker.Instantiate(Store, Fixture.Module); 70 | var memory = instance.GetMemory("mem"); 71 | 72 | memory.Minimum.Should().Be(0x10000); 73 | memory.Maximum.Should().BeNull(); 74 | memory.Is64Bit.Should().BeFalse(); 75 | memory.GetSize().Should().Be(0x10000); 76 | memory.GetLength().Should().Be(0x100000000); 77 | 78 | memory.ReadInt32(0).Should().Be(0); 79 | 80 | memory.WriteInt64(100, 1234); 81 | memory.ReadInt64(100).Should().Be(1234); 82 | 83 | memory.ReadByte(0xFFFFFFFF).Should().Be(0x63); 84 | memory.ReadInt16(0xFFFFFFFE).Should().Be(0x6364); 85 | memory.ReadInt32(0xFFFFFFFC).Should().Be(0x63646500); 86 | 87 | memory.ReadSingle(0xFFFFFFFC).Should().Be(4.2131355E+21F); 88 | 89 | var span = memory.GetSpan(0xFFFFFFFE, 2); 90 | span.SequenceEqual(new byte[] { 0x64, 0x63 }).Should().BeTrue(); 91 | 92 | var int16Span = memory.GetSpan(0, int.MaxValue); 93 | int16Span[0x7FFFFFFE].Should().Be(0x6500); 94 | 95 | int16Span = memory.GetSpan(2); 96 | int16Span[0x7FFFFFFE].Should().Be(0x6364); 97 | 98 | byte* ptr = (byte*)memory.GetPointer(); 99 | ptr += 0xFFFFFFFF; 100 | (*ptr).Should().Be(0x63); 101 | 102 | string str1 = "Hello World"; 103 | memory.WriteString(0xFFFFFFFF - str1.Length, str1); 104 | memory.ReadString(0xFFFFFFFF - str1.Length, str1.Length).Should().Be(str1); 105 | } 106 | 107 | [Fact] 108 | public void ItThrowsForOutOfBoundsAccess() 109 | { 110 | var instance = Linker.Instantiate(Store, Fixture.Module); 111 | var memory = instance.GetMemory("mem"); 112 | 113 | #pragma warning disable CS0618 // Type or member is obsolete 114 | Action action = () => memory.GetSpan(); 115 | #pragma warning restore CS0618 // Type or member is obsolete 116 | action.Should().Throw(); 117 | 118 | action = () => memory.GetSpan(0); 119 | action.Should().Throw(); 120 | 121 | action = () => memory.GetSpan(-1L, 0); 122 | action.Should().Throw(); 123 | 124 | action = () => memory.GetSpan(0L, -1); 125 | action.Should().Throw(); 126 | 127 | action = () => memory.ReadInt16(0xFFFFFFFF); 128 | action.Should().Throw(); 129 | 130 | action = () => memory.GetSpan(0xFFFFFFFF, 1); 131 | action.Should().Throw(); 132 | } 133 | 134 | public void Dispose() 135 | { 136 | Store.Dispose(); 137 | Linker.Dispose(); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/MemoryExportsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace Wasmtime.Tests 9 | { 10 | public class MemoryExportsFixture : ModuleFixture 11 | { 12 | protected override string ModuleFileName => "MemoryExports.wat"; 13 | } 14 | 15 | public class MemoryExportsTests : IClassFixture, IDisposable 16 | { 17 | private MemoryExportsFixture Fixture { get; set; } 18 | 19 | private Store Store { get; set; } 20 | 21 | private Linker Linker { get; set; } 22 | 23 | public MemoryExportsTests(MemoryExportsFixture fixture) 24 | { 25 | Fixture = fixture; 26 | Store = new Store(Fixture.Engine); 27 | Linker = new Linker(Fixture.Engine); 28 | } 29 | 30 | [Theory] 31 | [MemberData(nameof(GetMemoryExports))] 32 | public void ItHasTheExpectedMemoryExports(string exportName, long expectedMinimum, long? expectedMaximum, bool is64Bit) 33 | { 34 | var export = Fixture.Module.Exports.Where(m => m.Name == exportName).FirstOrDefault() as MemoryExport; 35 | export.Should().NotBeNull(); 36 | export.Minimum.Should().Be(expectedMinimum); 37 | export.Maximum.Should().Be(expectedMaximum); 38 | export.Is64Bit.Should().Be(is64Bit); 39 | } 40 | 41 | [Fact] 42 | public void ItReturnsNullForNonExistantMemory() 43 | { 44 | var instance = Linker.Instantiate(Store, Fixture.Module); 45 | 46 | var i32 = instance.GetMemory("no_such_mem"); 47 | i32.Should().BeNull(); 48 | } 49 | 50 | [Fact] 51 | public void ItHasTheExpectedNumberOfExportedTables() 52 | { 53 | GetMemoryExports().Count().Should().Be(Fixture.Module.Exports.Count(e => e is MemoryExport)); 54 | } 55 | 56 | [Fact] 57 | public void ItReadsAndWritesGenericTypes() 58 | { 59 | var instance = Linker.Instantiate(Store, Fixture.Module); 60 | var memory = instance.GetMemory("mem"); 61 | 62 | memory.Should().NotBeNull(); 63 | 64 | memory.Write(11, new TestStruct { A = 17, B = -34346 }); 65 | var result = memory.Read(11); 66 | 67 | result.A.Should().Be(17); 68 | result.B.Should().Be(-34346); 69 | } 70 | 71 | struct TestStruct 72 | { 73 | public byte A; 74 | public int B; 75 | } 76 | 77 | [Fact] 78 | public void ItCreatesExternsForTheMemories() 79 | { 80 | var instance = Linker.Instantiate(Store, Fixture.Module); 81 | var memory = instance.GetMemory("mem"); 82 | 83 | memory.Should().NotBeNull(); 84 | 85 | memory.ReadString(0, 11).Should().Be("Hello World"); 86 | int written = memory.WriteString(0, "WebAssembly Rocks!"); 87 | memory.ReadString(0, written).Should().Be("WebAssembly Rocks!"); 88 | 89 | memory.ReadByte(20).Should().Be(1); 90 | memory.WriteByte(20, 11); 91 | memory.ReadByte(20).Should().Be(11); 92 | 93 | memory.ReadInt16(21).Should().Be(2); 94 | memory.WriteInt16(21, 12); 95 | memory.ReadInt16(21).Should().Be(12); 96 | 97 | memory.ReadInt32(23).Should().Be(3); 98 | memory.WriteInt32(23, 13); 99 | memory.ReadInt32(23).Should().Be(13); 100 | 101 | memory.ReadInt64(27).Should().Be(4); 102 | memory.WriteInt64(27, 14); 103 | memory.ReadInt64(27).Should().Be(14); 104 | 105 | memory.ReadSingle(35).Should().Be(5); 106 | memory.WriteSingle(35, 15); 107 | memory.ReadSingle(35).Should().Be(15); 108 | 109 | memory.ReadDouble(39).Should().Be(6); 110 | memory.WriteDouble(39, 16); 111 | memory.ReadDouble(39).Should().Be(16); 112 | 113 | memory.ReadIntPtr(48).Should().Be((IntPtr)7); 114 | memory.WriteIntPtr(48, (IntPtr)17); 115 | memory.ReadIntPtr(48).Should().Be((IntPtr)17); 116 | } 117 | 118 | public static IEnumerable GetMemoryExports() 119 | { 120 | yield return new object[] { 121 | "mem", 122 | 1L, 123 | 2L, 124 | false 125 | }; 126 | } 127 | 128 | public void Dispose() 129 | { 130 | Store.Dispose(); 131 | Linker.Dispose(); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/MemoryImportBindingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class MemoryImportBindingFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "MemoryImportBinding.wat"; 10 | } 11 | 12 | public class MemoryImportBindingTests : IClassFixture, IDisposable 13 | { 14 | private MemoryImportBindingFixture Fixture { get; set; } 15 | 16 | private Store Store { get; set; } 17 | 18 | private Linker Linker { get; set; } 19 | 20 | public MemoryImportBindingTests(MemoryImportBindingFixture fixture) 21 | { 22 | Fixture = fixture; 23 | Store = new Store(Fixture.Engine); 24 | Linker = new Linker(Fixture.Engine); 25 | } 26 | 27 | [Fact] 28 | public void ItFailsToInstantiateWithMissingImport() 29 | { 30 | Action action = () => { Linker.Instantiate(Store, Fixture.Module); }; 31 | 32 | action 33 | .Should() 34 | .Throw() 35 | .WithMessage("unknown import: `::mem` has not been defined"); 36 | } 37 | 38 | [Fact] 39 | public void ItFailsToInstantiateWithNullStore() 40 | { 41 | Action action = () => { new Memory(null!, 0, 1); }; 42 | 43 | action 44 | .Should() 45 | .Throw() 46 | .WithMessage("Value cannot be null. (Parameter 'store')"); 47 | } 48 | 49 | [Fact] 50 | public void ItFailsToInstantiateWithDisposedStore() 51 | { 52 | var store = new Store(Fixture.Engine); 53 | store.Dispose(); 54 | 55 | Action action = () => { new Memory(store, 0, 1); }; 56 | 57 | action 58 | .Should() 59 | .Throw(); 60 | } 61 | 62 | [Fact] 63 | public void ItFailsToInstantiateWithNegativeMinimum() 64 | { 65 | Action action = () => { new Memory(Store, -1, 1); }; 66 | 67 | action 68 | .Should() 69 | .Throw() 70 | .WithMessage("Specified argument was out of the range of valid values. (Parameter 'minimum')"); 71 | } 72 | 73 | [Fact] 74 | public void ItFailsToInstantiateWithNegativeMaximum() 75 | { 76 | Action action = () => { new Memory(Store, 0, -1); }; 77 | 78 | action 79 | .Should() 80 | .Throw() 81 | .WithMessage("Specified argument was out of the range of valid values. (Parameter 'maximum')"); 82 | } 83 | 84 | [Fact] 85 | public void ItFailsToInstantiateWithMaximumLessThanMinimum() 86 | { 87 | Action action = () => { new Memory(Store, 20, 10); }; 88 | 89 | action 90 | .Should() 91 | .Throw() 92 | .WithMessage("The maximum cannot be less than the minimum. (Parameter 'maximum')"); 93 | } 94 | 95 | [Fact] 96 | public void ItBindsTheGlobalsCorrectly() 97 | { 98 | var mem = new Memory(Store, 1); 99 | Linker.Define("", "mem", mem); 100 | var instance = Linker.Instantiate(Store, Fixture.Module); 101 | var readByte = instance.GetFunction("ReadByte"); 102 | var readInt16 = instance.GetFunction("ReadInt16"); 103 | var readInt32 = instance.GetFunction("ReadInt32"); 104 | var readInt64 = instance.GetFunction("ReadInt64"); 105 | var readFloat32 = instance.GetFunction("ReadFloat32"); 106 | var readFloat64 = instance.GetFunction("ReadFloat64"); 107 | var readIntPtr = instance.GetFunction("ReadIntPtr"); 108 | 109 | mem.ReadString(0, 11).Should().Be("Hello World"); 110 | int written = mem.WriteString(0, "WebAssembly Rocks!"); 111 | mem.ReadString(0, written).Should().Be("WebAssembly Rocks!"); 112 | 113 | mem.ReadByte(20).Should().Be(1); 114 | mem.WriteByte(20, 11); 115 | mem.ReadByte(20).Should().Be(11); 116 | readByte.Invoke().Should().Be(11); 117 | 118 | mem.ReadInt16(21).Should().Be(2); 119 | mem.WriteInt16(21, 12); 120 | mem.ReadInt16(21).Should().Be(12); 121 | readInt16.Invoke().Should().Be(12); 122 | 123 | mem.ReadInt32(23).Should().Be(3); 124 | mem.WriteInt32(23, 13); 125 | mem.ReadInt32(23).Should().Be(13); 126 | readInt32.Invoke().Should().Be(13); 127 | 128 | mem.ReadInt64(27).Should().Be(4); 129 | mem.WriteInt64(27, 14); 130 | mem.ReadInt64(27).Should().Be(14); 131 | readInt64.Invoke().Should().Be(14); 132 | 133 | mem.ReadSingle(35).Should().Be(5); 134 | mem.WriteSingle(35, 15); 135 | mem.ReadSingle(35).Should().Be(15); 136 | readFloat32.Invoke().Should().Be(15); 137 | 138 | mem.ReadDouble(39).Should().Be(6); 139 | mem.WriteDouble(39, 16); 140 | mem.ReadDouble(39).Should().Be(16); 141 | readFloat64.Invoke().Should().Be(16); 142 | 143 | mem.ReadIntPtr(48).Should().Be((IntPtr)7); 144 | mem.WriteIntPtr(48, (IntPtr)17); 145 | mem.ReadIntPtr(48).Should().Be((IntPtr)17); 146 | readIntPtr.Invoke().Should().Be(17); 147 | } 148 | 149 | public void Dispose() 150 | { 151 | Store.Dispose(); 152 | Linker.Dispose(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tests/MemoryImportFromModuleTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class MemoryImportFromModuleFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "MemoryImportFromModule.wat"; 10 | } 11 | 12 | public class MemoryImportFromModuleTests : IClassFixture 13 | { 14 | public MemoryImportFromModuleTests(MemoryImportFromModuleFixture fixture) 15 | { 16 | Fixture = fixture; 17 | } 18 | 19 | private MemoryImportFromModuleFixture Fixture { get; set; } 20 | 21 | [Fact] 22 | public void ItHasTheExpectedImport() 23 | { 24 | Fixture.Module.Imports.Count(i => i is MemoryImport).Should().Be(1); 25 | 26 | var memory = Fixture.Module.Imports[0] as MemoryImport; 27 | 28 | memory.Should().NotBeNull(); 29 | memory.ModuleName.Should().Be("js"); 30 | memory.Name.Should().Be("mem"); 31 | memory.Minimum.Should().Be(1L); 32 | memory.Maximum.Should().Be(2L); 33 | memory.Is64Bit.Should().BeFalse(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/MemoryImportNoUpperBoundTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class MemoryImportNoUpperBoundFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "MemoryImportNoUpperBound.wat"; 10 | } 11 | 12 | public class MemoryImportNoUpperBoundTests : IClassFixture 13 | { 14 | public MemoryImportNoUpperBoundTests(MemoryImportNoUpperBoundFixture fixture) 15 | { 16 | Fixture = fixture; 17 | } 18 | 19 | private MemoryImportNoUpperBoundFixture Fixture { get; set; } 20 | 21 | [Fact] 22 | public void ItHasTheExpectedImport() 23 | { 24 | Fixture.Module.Imports.Count(i => i is MemoryImport).Should().Be(1); 25 | 26 | var memory = Fixture.Module.Imports[0] as MemoryImport; 27 | 28 | memory.Should().NotBeNull(); 29 | memory.ModuleName.Should().Be(""); 30 | memory.Name.Should().Be("mem"); 31 | memory.Minimum.Should().Be(1L); 32 | memory.Maximum.Should().BeNull(); 33 | memory.Is64Bit.Should().BeFalse(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/MemoryImportWithUpperBoundTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class MemoryImportWithUpperBoundFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "MemoryImportWithUpperBound.wat"; 10 | } 11 | 12 | public class MemoryImportWithUpperBoundTests : IClassFixture 13 | { 14 | public MemoryImportWithUpperBoundTests(MemoryImportWithUpperBoundFixture fixture) 15 | { 16 | Fixture = fixture; 17 | } 18 | 19 | private MemoryImportWithUpperBoundFixture Fixture { get; set; } 20 | 21 | [Fact] 22 | public void ItHasTheExpectedImport() 23 | { 24 | Fixture.Module.Imports.Count(i => i is MemoryImport).Should().Be(1); 25 | 26 | var memory = Fixture.Module.Imports[0] as MemoryImport; 27 | 28 | memory.Should().NotBeNull(); 29 | memory.ModuleName.Should().Be(""); 30 | memory.Name.Should().Be("mem"); 31 | memory.Minimum.Should().Be(10L); 32 | memory.Maximum.Should().Be(100L); 33 | memory.Is64Bit.Should().BeFalse(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/ModuleFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Wasmtime; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public abstract class ModuleFixture : IDisposable 8 | { 9 | public ModuleFixture() 10 | { 11 | Engine = new Engine(GetEngineConfig()); 12 | 13 | Module = Wasmtime.Module.FromTextFile(Engine, Path.Combine("Modules", ModuleFileName)); 14 | } 15 | 16 | public virtual Config GetEngineConfig() 17 | { 18 | return new Config() 19 | .WithMemory64(true); 20 | } 21 | 22 | public void Dispose() 23 | { 24 | if (!(Module is null)) 25 | { 26 | Module.Dispose(); 27 | Module = null; 28 | } 29 | 30 | if (!(Engine is null)) 31 | { 32 | Engine.Dispose(); 33 | Engine = null; 34 | } 35 | } 36 | 37 | public Engine Engine { get; set; } 38 | public Module Module { get; set; } 39 | 40 | protected abstract string ModuleFileName { get; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ModuleLoadTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using FluentAssertions; 4 | using Wasmtime; 5 | using Xunit; 6 | 7 | namespace Wasmtime.Tests 8 | { 9 | public class ModuleLoadTests 10 | { 11 | [Fact] 12 | public void ItLoadsModuleFromEmbeddedResource() 13 | { 14 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("hello.wasm"); 15 | stream.Should().NotBeNull(); 16 | 17 | using var engine = new Engine(); 18 | Module.FromStream(engine, "hello.wasm", stream).Should().NotBeNull(); 19 | 20 | // `LoadModule` is not supposed to close the supplied stream, 21 | // so the following statement should complete without throwing 22 | // `ObjectDisposedException` 23 | stream.Read(new byte[0], 0, 0); 24 | } 25 | 26 | [Fact] 27 | public void ItValidatesModuleFromEmbeddedResource() 28 | { 29 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("hello.wasm"); 30 | stream.Should().NotBeNull(); 31 | 32 | byte[] buffer = new byte[stream.Length]; 33 | 34 | stream.Read(buffer, 0, buffer.Length); 35 | 36 | using var engine = new Engine(); 37 | Module.Validate(engine, buffer).Should().BeNull(); 38 | } 39 | 40 | [Fact] 41 | public void ItLoadsModuleTextFromEmbeddedResource() 42 | { 43 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("hello.wat"); 44 | stream.Should().NotBeNull(); 45 | 46 | using var engine = new Engine(); 47 | Module.FromTextStream(engine, "hello.wat", stream).Should().NotBeNull(); 48 | 49 | // `LoadModuleText` is not supposed to close the supplied stream, 50 | // so the following statement should complete without throwing 51 | // `ObjectDisposedException` 52 | stream.Read(new byte[0], 0, 0); 53 | } 54 | 55 | [Fact] 56 | public void ItCannotBeAccessedOnceDisposed() 57 | { 58 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("hello.wasm"); 59 | stream.Should().NotBeNull(); 60 | 61 | using var engine = new Engine(); 62 | var module = Module.FromStream(engine, "hello.wasm", stream); 63 | 64 | module.Dispose(); 65 | 66 | Assert.Throws(() => module.NativeHandle); 67 | Assert.Throws(() => module.Serialize()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/ModuleSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class ModuleSerializationTests 9 | { 10 | [Fact] 11 | public void ItSerializesAndDeserializesAModule() 12 | { 13 | using var engine = new Engine(); 14 | 15 | using var original = Module.FromText(engine, "test", "(module)"); 16 | 17 | var bytes = original.Serialize(); 18 | bytes.Should().NotBeNull(); 19 | bytes.Length.Should().NotBe(0); 20 | 21 | using var deserialized = Module.Deserialize(engine, "test", bytes); 22 | deserialized.Should().NotBeNull(); 23 | } 24 | 25 | [Fact] 26 | public void ItDeserializesFromAFile() 27 | { 28 | using var engine = new Engine(); 29 | 30 | using var original = Module.FromText(engine, "test", "(module)"); 31 | 32 | var bytes = original.Serialize(); 33 | bytes.Should().NotBeNull(); 34 | bytes.Length.Should().NotBe(0); 35 | 36 | var path = Path.GetTempFileName(); 37 | 38 | File.WriteAllBytes(path, bytes); 39 | 40 | try 41 | { 42 | using var deserialized = Module.DeserializeFile(engine, "test", path); 43 | } 44 | finally 45 | { 46 | File.Delete(path); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Modules/BulkMemory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (param $dst i32) (param $src i32) (param $size i32) (result i32) 3 | local.get $dst 4 | local.get $src 5 | local.get $size 6 | memory.copy 7 | local.get $dst 8 | ) 9 | ) 10 | -------------------------------------------------------------------------------- /tests/Modules/CallExportFromImport.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $i32_=>_i32 (func (param i32) (result i32))) 3 | (import "env" "getInt" (func $assembly/env/getInt (param i32) (result i32))) 4 | (memory $0 1 1) 5 | (export "testFunction" (func $assembly/index/testFunction)) 6 | (export "shiftLeft" (func $assembly/index/shiftLeft)) 7 | (export "memory" (memory $0)) 8 | (func $assembly/index/testFunction (param $0 i32) (result i32) 9 | local.get $0 10 | call $assembly/env/getInt 11 | ) 12 | (func $assembly/index/shiftLeft (param $0 i32) (result i32) 13 | local.get $0 14 | i32.const 1 15 | i32.shl 16 | ) 17 | ) -------------------------------------------------------------------------------- /tests/Modules/Caller.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "callback" (func $callback)) 3 | 4 | (memory (export "memory") 1 1) 5 | 6 | (func (export "call_callback") 7 | (call $callback) 8 | ) 9 | (func (export "add") (param $lhs i32) (param $rhs i32) (result i32) 10 | local.get $lhs 11 | local.get $rhs 12 | i32.add 13 | ) 14 | ) -------------------------------------------------------------------------------- /tests/Modules/Error.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32))) 3 | (import "" "error_from_host_exception" (func $error_from_host_exception)) 4 | (import "" "call_host_callback" (func $call_host_callback)) 5 | (export "error_from_host_exception" (func $error_from_host_exception)) 6 | (export "call_host_callback" (func $call_host_callback)) 7 | (export "error_in_wasm" (func $error_in_wasm)) 8 | (start $start) 9 | 10 | (func $start 11 | (call $call_host_callback) 12 | ) 13 | (func $error_in_wasm 14 | i32.const 0 15 | call $exit 16 | ) 17 | ) 18 | -------------------------------------------------------------------------------- /tests/Modules/ExitError.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32))) 3 | (memory (export "memory") 1) 4 | (func (export "exit") (param i32) 5 | local.get 0 6 | call $exit) 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /tests/Modules/ExternRef.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "inout" (func $.inout (param externref) (result externref))) 3 | (func (export "inout") (param externref) (result externref) 4 | local.get 0 5 | call $.inout 6 | ) 7 | (func (export "nullref") (result externref) 8 | ref.null extern 9 | call $.inout 10 | ) 11 | ) -------------------------------------------------------------------------------- /tests/Modules/FuelConsumption.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "expensive" (func $env.expensive)) 3 | (import "env" "free" (func $env.free)) 4 | (func $expensive 5 | call $env.expensive 6 | ) 7 | (func $free 8 | call $env.free 9 | ) 10 | (export "expensive" (func $expensive)) 11 | (export "free" (func $free)) 12 | ) 13 | -------------------------------------------------------------------------------- /tests/Modules/FuncRef.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "callback" (func $callback (param funcref) (result externref))) 3 | (import "" "return_funcref" (func $return_funcref (result funcref))) 4 | (import "" "assert" (func $assert (param externref) (result externref))) 5 | (import "" "store_funcref" (func $store_funcref (param funcref))) 6 | (export "return_funcref" (func $return_funcref)) 7 | (table $t 1 funcref) 8 | (elem declare func $f) 9 | (elem declare func $assert) 10 | (func (export "call_nested") (param funcref funcref) (result externref) 11 | (table.set $t (i32.const 0) (local.get 0)) 12 | (call_indirect $t (param funcref) (result externref) (local.get 1) (i32.const 0)) 13 | ) 14 | (func $f (param externref) (result externref) 15 | (call $assert (local.get 0)) 16 | ) 17 | (func (export "call_callback") (result externref) 18 | (call $callback (ref.func $f)) 19 | ) 20 | (func (export "call_with_null") (result externref) 21 | (call $callback (ref.null func)) 22 | ) 23 | (func (export "call_store_funcref") 24 | (call $store_funcref (ref.func $assert)) 25 | ) 26 | ) -------------------------------------------------------------------------------- /tests/Modules/FunctionExports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $no_params_no_results) 3 | (func $one_i32_param_no_results (param i32)) 4 | (func $one_i64_param_no_results (param i64)) 5 | (func $one_f32_param_no_results (param f32)) 6 | (func $one_f64_param_no_results (param f64)) 7 | (func $one_param_of_each_type (param i32 i64 f32 f64)) 8 | (func $no_params_one_i32_result (result i32) i32.const 0) 9 | (func $no_params_one_i64_result (result i64) i64.const 0) 10 | (func $no_params_one_f32_result (result f32) f32.const 0) 11 | (func $no_params_one_f64_result (result f64) f64.const 0) 12 | (func $one_result_of_each_type (result i32 i64 f32 f64) i32.const 0 i64.const 0 f32.const 0 f64.const 0) 13 | (func $one_param_and_result_of_each_type (param i32 i64 f32 f64) (result i32 i64 f32 f64) i32.const 0 i64.const 0 f32.const 0 f64.const 0) 14 | (export "no_params_no_results" (func $no_params_no_results)) 15 | (export "one_i32_param_no_results" (func $one_i32_param_no_results)) 16 | (export "one_i64_param_no_results" (func $one_i64_param_no_results)) 17 | (export "one_f32_param_no_results" (func $one_f32_param_no_results)) 18 | (export "one_f64_param_no_results" (func $one_f64_param_no_results)) 19 | (export "one_param_of_each_type" (func $one_param_of_each_type)) 20 | (export "no_params_one_i32_result" (func $no_params_one_i32_result)) 21 | (export "no_params_one_i64_result" (func $no_params_one_i64_result)) 22 | (export "no_params_one_f32_result" (func $no_params_one_f32_result)) 23 | (export "no_params_one_f64_result" (func $no_params_one_f64_result)) 24 | (export "one_result_of_each_type" (func $one_result_of_each_type)) 25 | (export "one_param_and_result_of_each_type" (func $one_param_and_result_of_each_type)) 26 | ) 27 | -------------------------------------------------------------------------------- /tests/Modules/FunctionImports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func)) 3 | (type $t1 (func (param i32))) 4 | (type $t2 (func (param i64))) 5 | (type $t3 (func (param f32))) 6 | (type $t4 (func (param f64))) 7 | (type $t5 (func (param i32 i64 f32 f64))) 8 | (type $t6 (func (result i32))) 9 | (type $t7 (func (result i64))) 10 | (type $t8 (func (result f32))) 11 | (type $t9 (func (result f64))) 12 | (type $t10 (func (result i32 i64 f32 f64))) 13 | (type $t11 (func (param i32 i64 f32 f64) (result i32 i64 f32 f64))) 14 | (import "" "no_params_no_results" (func $.f0 (type $t0))) 15 | (import "" "one_i32_param_no_results" (func $.f1 (type $t1))) 16 | (import "" "one_i64_param_no_results" (func $.f2 (type $t2))) 17 | (import "" "one_f32_param_no_results" (func $.f3 (type $t3))) 18 | (import "" "one_f64_param_no_results" (func $.f4 (type $t4))) 19 | (import "" "one_param_of_each_type" (func $.f5 (type $t5))) 20 | (import "" "no_params_one_i32_result" (func $.f6 (type $t6))) 21 | (import "" "no_params_one_i64_result" (func $.f7 (type $t7))) 22 | (import "" "no_params_one_f32_result" (func $.f8 (type $t8))) 23 | (import "" "no_params_one_f64_result" (func $.f9 (type $t9))) 24 | (import "" "one_result_of_each_type" (func $.f10 (type $t10))) 25 | (import "" "one_param_and_result_of_each_type" (func $.f11 (type $t11))) 26 | (import "other" "function_from_module" (func $.f12 (type $t0))) 27 | ) 28 | -------------------------------------------------------------------------------- /tests/Modules/Functions.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "add" (func $env.add (param i32 i32) (result i32))) 3 | (import "env" "swap" (func $env.swap (param i32 i32) (result i32 i32))) 4 | (import "env" "do_throw" (func $env.do_throw)) 5 | (import "env" "check_string" (func $env.check_string (param i32 i32))) 6 | (import "env" "return_i32" (func $env.return_i32 (result i32))) 7 | (import "env" "return_15_values" (func $env.return_15_values (result i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) 8 | (import "env" "accept_15_values" (func $env.accept_15_values (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) 9 | (import "env" "return_all_types" (func $env.return_all_types (result i32 i64 f32 f64 v128 funcref externref))) 10 | (import "env" "accept_all_types" (func $env.accept_all_types (param i32 i64 f32 f64 v128 funcref externref))) 11 | (import "env" "pass_through_multiple_values1" (func $env.pass_through_multiple_values1 (param i64 f64 externref) (result i64 f64 externref))) 12 | (import "env" "pass_through_multiple_values2" (func $env.pass_through_multiple_values2 (param i64 f64 externref) (result i64 f64 externref))) 13 | (import "env" "pass_through_v128" (func $env.pass_through_v128 (param v128) (result v128))) 14 | (memory (export "mem") 1) 15 | (export "add" (func $add)) 16 | (export "swap" (func $swap)) 17 | (export "do_throw" (func $do_throw)) 18 | (export "check_string" (func $check_string)) 19 | (export "return_i32" (func $env.return_i32)) 20 | (export "get_and_pass_15_values" (func $get_and_pass_15_values)) 21 | (export "get_and_pass_all_types" (func $get_and_pass_all_types)) 22 | (export "pass_through_multiple_values1" (func $env.pass_through_multiple_values1)) 23 | (export "pass_through_multiple_values2" (func $env.pass_through_multiple_values2)) 24 | (export "pass_through_v128" (func $env.pass_through_v128)) 25 | (func $add (param i32 i32) (result i32) 26 | (call $env.add (local.get 0) (local.get 1)) 27 | ) 28 | (func $swap (param i32 i32) (result i32 i32) 29 | (call $env.swap (local.get 0) (local.get 1)) 30 | ) 31 | (func $do_throw 32 | (call $env.do_throw) 33 | ) 34 | (func $check_string 35 | (call $env.check_string (i32.const 0) (i32.const 11)) 36 | ) 37 | (func $get_and_pass_15_values 38 | call $env.return_15_values 39 | call $env.accept_15_values 40 | ) 41 | (func $get_and_pass_all_types 42 | call $env.return_all_types 43 | call $env.accept_all_types 44 | ) 45 | (data (i32.const 0) "Hello World") 46 | 47 | (func $return_funcref (result funcref) 48 | unreachable 49 | ) 50 | (export "return_funcref" (func $return_funcref)) 51 | 52 | (func (export "noop")) 53 | (func $echo_i32 (param i32) (result i32) local.get 0) 54 | (export "$echo_i32" (func $echo_i32)) 55 | (func $echo_i64 (param i64) (result i64) local.get 0) 56 | (export "$echo_i64" (func $echo_i64)) 57 | (func $echo_f32 (param f32) (result f32) local.get 0) 58 | (export "$echo_f32" (func $echo_f32)) 59 | (func $echo_f64 (param f64) (result f64) local.get 0) 60 | (export "$echo_f64" (func $echo_f64)) 61 | (func $echo_v128 (param v128) (result v128) local.get 0) 62 | (export "$echo_v128" (func $echo_v128)) 63 | (func $echo_funcref (param funcref) (result funcref) local.get 0) 64 | (export "$echo_funcref" (func $echo_funcref)) 65 | (func $echo_externref (param externref) (result externref) local.get 0) 66 | (export "$echo_externref" (func $echo_externref)) 67 | 68 | (func $echo_tuple2 (result i32 i32) i32.const 1 i32.const 2) 69 | (export "$echo_tuple2" (func $echo_tuple2)) 70 | (func $echo_tuple3 (result i32 i32 i32) i32.const 1 i32.const 2 i32.const 3) 71 | (export "$echo_tuple3" (func $echo_tuple3)) 72 | (func $echo_tuple4 (result i32 i32 i32 f32) i32.const 1 i32.const 2 i32.const 3 f32.const 3.141) 73 | (export "$echo_tuple4" (func $echo_tuple4)) 74 | (func $echo_tuple5 (result i32 i32 i32 f32 f64) i32.const 1 i32.const 2 i32.const 3 f32.const 3.141 f64.const 2.71828) 75 | (export "$echo_tuple5" (func $echo_tuple5)) 76 | (func $echo_tuple6 (result i32 i32 i32 f32 f64 i32) i32.const 1 i32.const 2 i32.const 3 f32.const 3.141 f64.const 2.71828 i32.const 6) 77 | (export "$echo_tuple6" (func $echo_tuple6)) 78 | (func $echo_tuple7 (result i32 i32 i32 f32 f64 i32 i32) i32.const 1 i32.const 2 i32.const 3 f32.const 3.141 f64.const 2.71828 i32.const 6 i32.const 7) 79 | (export "$echo_tuple7" (func $echo_tuple7)) 80 | (func $echo_tuple8 (result i32 i32 i32 f32 f64 i32 i32 i32) i32.const 1 i32.const 2 i32.const 3 f32.const 3.141 f64.const 2.71828 i32.const 6 i32.const 7 i32.const 8) 81 | (export "$echo_tuple8" (func $echo_tuple8)) 82 | ) 83 | -------------------------------------------------------------------------------- /tests/Modules/GlobalExports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (global $g1 i32 (i32.const 0)) 3 | (global $g2 (mut i32) (i32.const 1)) 4 | (global $g3 i64 (i64.const 2)) 5 | (global $g4 (mut i64) (i64.const 3)) 6 | (global $g5 f32 (f32.const 4)) 7 | (global $g6 (mut f32) (f32.const 5)) 8 | (global $g7 f64 (f64.const 6)) 9 | (global $g8 (mut f64) (f64.const 7)) 10 | (export "global_i32" (global $g1)) 11 | (export "global_i32_mut" (global $g2)) 12 | (export "global_i64" (global $g3)) 13 | (export "global_i64_mut" (global $g4)) 14 | (export "global_f32" (global $g5)) 15 | (export "global_f32_mut" (global $g6)) 16 | (export "global_f64" (global $g7)) 17 | (export "global_f64_mut" (global $g8)) 18 | ) 19 | -------------------------------------------------------------------------------- /tests/Modules/GlobalImportBindings.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "global_i32_mut" (global $global_i32_mut (mut i32))) 3 | (import "" "global_i32" (global $global_i32 i32)) 4 | (import "" "global_i64_mut" (global $global_i64_mut (mut i64))) 5 | (import "" "global_i64" (global $global_i64 i64)) 6 | (import "" "global_f32_mut" (global $global_f32_mut (mut f32))) 7 | (import "" "global_f32" (global $global_f32 f32)) 8 | (import "" "global_f64_mut" (global $global_f64_mut (mut f64))) 9 | (import "" "global_f64" (global $global_f64 f64)) 10 | (func (export "get_global_i32_mut") (result i32) (global.get $global_i32_mut)) 11 | (func (export "get_global_i32") (result i32) (global.get $global_i32)) 12 | (func (export "set_global_i32_mut") (param i32) (global.set $global_i32_mut (local.get 0))) 13 | (func (export "get_global_i64_mut") (result i64) (global.get $global_i64_mut)) 14 | (func (export "get_global_i64") (result i64) (global.get $global_i64)) 15 | (func (export "set_global_i64_mut") (param i64) (global.set $global_i64_mut (local.get 0))) 16 | (func (export "get_global_f32_mut") (result f32) (global.get $global_f32_mut)) 17 | (func (export "get_global_f32") (result f32) (global.get $global_f32)) 18 | (func (export "set_global_f32_mut") (param f32) (global.set $global_f32_mut (local.get 0))) 19 | (func (export "get_global_f64_mut") (result f64) (global.get $global_f64_mut)) 20 | (func (export "get_global_f64") (result f64) (global.get $global_f64)) 21 | (func (export "set_global_f64_mut") (param f64) (global.set $global_f64_mut (local.get 0))) 22 | ) 23 | -------------------------------------------------------------------------------- /tests/Modules/GlobalImports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (global $g1 (import "" "global_i32") i32) 3 | (global $g2 (import "" "global_i32_mut") (mut i32)) 4 | (global $g3 (import "" "global_i64") i64) 5 | (global $g4 (import "" "global_i64_mut") (mut i64)) 6 | (global $g5 (import "" "global_f32") f32) 7 | (global $g6 (import "" "global_f32_mut") (mut f32)) 8 | (global $g7 (import "" "global_f64") f64) 9 | (global $g8 (import "" "global_f64_mut") (mut f64)) 10 | (global $g9 (import "other" "global_from_module") i32) 11 | ) 12 | -------------------------------------------------------------------------------- /tests/Modules/Interrupt.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "run") 3 | (loop 4 | br 0) 5 | ) 6 | ) -------------------------------------------------------------------------------- /tests/Modules/Memory64Access.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory (export "mem") i64 0x10001 0x1000000000000) 3 | (func $start 4 | i64.const 0x10000FFFF 5 | i32.const 99 6 | i32.store8 7 | i64.const 0x10000FFFE 8 | i32.const 100 9 | i32.store8 10 | i64.const 0x10000FFFD 11 | i32.const 101 12 | i32.store8 13 | ) 14 | (start $start) 15 | ) 16 | -------------------------------------------------------------------------------- /tests/Modules/MemoryAccess.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory (export "mem") 0x10000) 3 | (func $start 4 | i32.const 0xFFFFFFFF 5 | i32.const 99 6 | i32.store8 7 | i32.const 0xFFFFFFFE 8 | i32.const 100 9 | i32.store8 10 | i32.const 0xFFFFFFFD 11 | i32.const 101 12 | i32.store8 13 | ) 14 | (start $start) 15 | ) 16 | -------------------------------------------------------------------------------- /tests/Modules/MemoryExports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory (export "mem") 1 2) 3 | (data (i32.const 0) "Hello World") 4 | (data (i32.const 20) "\01") 5 | (data (i32.const 21) "\02\00") 6 | (data (i32.const 23) "\03\00\00\00") 7 | (data (i32.const 27) "\04\00\00\00\00\00\00\00") 8 | (data (i32.const 35) "\00\00\a0\40") 9 | (data (i32.const 39) "\00\00\00\00\00\00\18\40") 10 | (data (i32.const 48) "\07\00\00\00\00\00\00\00") 11 | ) 12 | -------------------------------------------------------------------------------- /tests/Modules/MemoryImportBinding.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "mem" (memory 1)) 3 | (data (i32.const 0) "Hello World") 4 | (data (i32.const 20) "\01") 5 | (data (i32.const 21) "\02\00") 6 | (data (i32.const 23) "\03\00\00\00") 7 | (data (i32.const 27) "\04\00\00\00\00\00\00\00") 8 | (data (i32.const 35) "\00\00\a0\40") 9 | (data (i32.const 39) "\00\00\00\00\00\00\18\40") 10 | (data (i32.const 48) "\07\00\00\00\00\00\00\00") 11 | (func (export "ReadByte") (result i32) 12 | i32.const 20 13 | i32.load8_s 14 | ) 15 | (func (export "ReadInt16") (result i32) 16 | i32.const 21 17 | i32.load16_s 18 | ) 19 | (func (export "ReadInt32") (result i32) 20 | i32.const 23 21 | i32.load 22 | ) 23 | (func (export "ReadInt64") (result i64) 24 | i32.const 27 25 | i64.load 26 | ) 27 | (func (export "ReadFloat32") (result f32) 28 | i32.const 35 29 | f32.load 30 | ) 31 | (func (export "ReadFloat64") (result f64) 32 | i32.const 39 33 | f64.load 34 | ) 35 | (func (export "ReadIntPtr") (result i64) 36 | i32.const 48 37 | i64.load 38 | ) 39 | ) 40 | -------------------------------------------------------------------------------- /tests/Modules/MemoryImportFromModule.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "js" "mem" (memory 1 2)) 3 | ) 4 | -------------------------------------------------------------------------------- /tests/Modules/MemoryImportNoUpperBound.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "mem" (memory 1)) 3 | ) 4 | -------------------------------------------------------------------------------- /tests/Modules/MemoryImportWithUpperBound.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "mem" (memory 10 100)) 3 | ) 4 | -------------------------------------------------------------------------------- /tests/Modules/MultiValue.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $echo_tuple2 (result i32 i32) i32.const 1 i32.const 2) 3 | (export "$echo_tuple2" (func $echo_tuple2)) 4 | ) 5 | -------------------------------------------------------------------------------- /tests/Modules/RelaxedSIMD.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $madd_v128 (param v128) (result v128) 3 | local.get 0 4 | local.get 0 5 | local.get 0 6 | f32x4.relaxed_madd 7 | ) 8 | (export "$madd_v128" (func $madd_v128)) 9 | ) 10 | -------------------------------------------------------------------------------- /tests/Modules/SIMD.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $echo_v128 (param v128) (result v128) local.get 0) 3 | (export "$echo_v128" (func $echo_v128)) 4 | ) 5 | -------------------------------------------------------------------------------- /tests/Modules/SharedMemory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1 1 shared) 3 | ) 4 | -------------------------------------------------------------------------------- /tests/Modules/TableExports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (table $t0 1 10 funcref) 3 | (table $t1 10 funcref) 4 | (table $t2 100 1000 funcref) 5 | (export "table1" (table $t0)) 6 | (export "table2" (table $t1)) 7 | (export "table3" (table $t2)) 8 | ) 9 | -------------------------------------------------------------------------------- /tests/Modules/TableImportBinding.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "funcs" (table $funcs 10 funcref)) 3 | (import "" "externs" (table $externs 10 externref)) 4 | (import "" "assert" (func $assert (param externref externref))) 5 | (func (export "is_null_func") (param i32) (result i32) 6 | (table.get $funcs (local.get 0)) 7 | (ref.is_null) 8 | ) 9 | (func (export "is_null_extern") (param i32) (result i32) 10 | (table.get $externs (local.get 0)) 11 | (ref.is_null) 12 | ) 13 | (func (export "call") (param i32) 14 | (call_indirect $funcs (local.get 0)) 15 | ) 16 | (func (export "assert_extern") (param i32 externref) 17 | (call $assert (table.get $externs (local.get 0)) (local.get 1)) 18 | ) 19 | (func (export "grow_funcs") (param i32) 20 | (table.grow $funcs (ref.null func) (local.get 0)) 21 | (drop) 22 | ) 23 | (func (export "grow_externs") (param i32) 24 | (table.grow $externs (ref.null extern) (local.get 0)) 25 | (drop) 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /tests/Modules/TableImports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "table1" (table $t1 10 funcref)) 3 | (import "" "table2" (table $t2 15 funcref)) 4 | (import "other" "table3" (table $t3 1 funcref)) 5 | ) 6 | -------------------------------------------------------------------------------- /tests/Modules/Trap.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (export "ok" (func $ok)) 3 | (export "ok_value" (func $ok_value)) 4 | (export "run" (func $run)) 5 | (export "run_div_zero" (func $run_div_zero)) 6 | (export "run_div_zero_with_result" (func $run_div_zero_with_result)) 7 | 8 | (func $run 9 | (call $first) 10 | ) 11 | (func $first 12 | (call $second) 13 | ) 14 | (func $second 15 | (call $third) 16 | ) 17 | (func $third 18 | unreachable 19 | ) 20 | 21 | (func $run_div_zero_with_result (result i32) 22 | (i32.const 1) 23 | (i32.const 0) 24 | (i32.div_s) 25 | ) 26 | 27 | (func $run_div_zero 28 | (call $run_div_zero_with_result) 29 | (drop) 30 | ) 31 | 32 | (func $ok) 33 | (func $ok_value (result i32) 34 | (i32.const 1) 35 | ) 36 | ) 37 | -------------------------------------------------------------------------------- /tests/Modules/Wasi.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func (param i32 i32) (result i32))) 3 | (type $t1 (func (param i32 i32 i32 i32) (result i32))) 4 | (type $t2 (func (param i32) (result i32))) 5 | (type $t3 (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) 6 | (import "wasi_snapshot_preview1" "environ_sizes_get" (func $wasi_snapshot_preview1.environ_sizes_get (type $t0))) 7 | (import "wasi_snapshot_preview1" "environ_get" (func $wasi_snapshot_preview1.environ_get (type $t0))) 8 | (import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi_snapshot_preview1.args_sizes_get (type $t0))) 9 | (import "wasi_snapshot_preview1" "args_get" (func $wasi_snapshot_preview1.args_get (type $t0))) 10 | (import "wasi_snapshot_preview1" "fd_write" (func $wasi_snapshot_preview1.fd_write (type $t1))) 11 | (import "wasi_snapshot_preview1" "fd_read" (func $wasi_snapshot_preview1.fd_read (type $t1))) 12 | (import "wasi_snapshot_preview1" "fd_close" (func $wasi_snapshot_preview1.fd_close (type $t2))) 13 | (import "wasi_snapshot_preview1" "path_open" (func $wasi_snapshot_preview1.path_open (type $t3))) 14 | (memory $memory 1) 15 | (export "memory" (memory 0)) 16 | (func $call_environ_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) 17 | local.get $p0 18 | local.get $p1 19 | call $wasi_snapshot_preview1.environ_sizes_get) 20 | (func $call_environ_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) 21 | local.get $p0 22 | local.get $p1 23 | call $wasi_snapshot_preview1.environ_get) 24 | (func $call_args_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) 25 | local.get $p0 26 | local.get $p1 27 | call $wasi_snapshot_preview1.args_sizes_get) 28 | (func $call_args_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) 29 | local.get $p0 30 | local.get $p1 31 | call $wasi_snapshot_preview1.args_get) 32 | (func $call_fd_write (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) 33 | local.get $p0 34 | local.get $p1 35 | local.get $p2 36 | local.get $p3 37 | call $wasi_snapshot_preview1.fd_write) 38 | (func $call_fd_read (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) 39 | local.get $p0 40 | local.get $p1 41 | local.get $p2 42 | local.get $p3 43 | call $wasi_snapshot_preview1.fd_read) 44 | (func $call_fd_close (type $t2) (param $p0 i32) (result i32) 45 | local.get $p0 46 | call $wasi_snapshot_preview1.fd_close) 47 | (func $call_path_open (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i64) (param $p6 i64) (param $p7 i32) (param $p8 i32) (result i32) 48 | local.get $p0 49 | local.get $p1 50 | local.get $p2 51 | local.get $p3 52 | local.get $p4 53 | local.get $p5 54 | local.get $p6 55 | local.get $p7 56 | local.get $p8 57 | call $wasi_snapshot_preview1.path_open) 58 | (export "call_environ_sizes_get" (func $call_environ_sizes_get)) 59 | (export "call_environ_get" (func $call_environ_get)) 60 | (export "call_args_sizes_get" (func $call_args_sizes_get)) 61 | (export "call_args_get" (func $call_args_get)) 62 | (export "call_fd_write" (func $call_fd_write)) 63 | (export "call_fd_read" (func $call_fd_read)) 64 | (export "call_fd_close" (func $call_fd_close)) 65 | (export "call_path_open" (func $call_path_open)) 66 | ) 67 | -------------------------------------------------------------------------------- /tests/Modules/hello.wasm: -------------------------------------------------------------------------------- 1 | asm` 2 | hellorun 3 |  -------------------------------------------------------------------------------- /tests/Modules/hello.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func)) 3 | (import "" "hello" (func $.hello (type $t0))) 4 | (func $run 5 | call $.hello 6 | ) 7 | (export "run" (func $run)) 8 | ) 9 | -------------------------------------------------------------------------------- /tests/MultiMemoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class MultiMemoryTests 8 | { 9 | [Fact] 10 | public void ItFailsWithMultiMemoryDisabled() 11 | { 12 | using var engine = new Engine(new Config().WithMultiMemory(false)); 13 | Action action = () => 14 | { 15 | using var module = Module.FromText(engine, "test", @"(module (memory 0 1) (memory 0 1))"); 16 | }; 17 | 18 | action 19 | .Should() 20 | .Throw() 21 | .WithMessage("WebAssembly module 'test' is not valid: failed to parse WebAssembly module*"); 22 | } 23 | 24 | [Fact] 25 | public void ItSucceedsWithoutMultiMemoryDisabled() 26 | { 27 | using var engine = new Engine(); 28 | using var module = Module.FromText(engine, "test", @"(module (memory 0 1) (memory 0 1))"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/StoreDataTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Metrics; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Newtonsoft.Json.Linq; 6 | using Xunit; 7 | 8 | namespace Wasmtime.Tests 9 | { 10 | public class StoreDataFixture : ModuleFixture 11 | { 12 | protected override string ModuleFileName => "hello.wat"; 13 | } 14 | 15 | public class StoreDataTests : IClassFixture, IDisposable 16 | { 17 | private StoreDataFixture Fixture { get; } 18 | 19 | private Linker Linker { get; set; } 20 | 21 | public StoreDataTests(StoreDataFixture fixture) 22 | { 23 | Fixture = fixture; 24 | Linker = new Linker(Fixture.Engine); 25 | } 26 | 27 | unsafe class RefCounter 28 | { 29 | public int Counter { get => *counter; } 30 | 31 | internal RefCounter(int* counter) 32 | { 33 | this.counter = counter; 34 | System.Threading.Interlocked.Increment(ref *counter); 35 | } 36 | 37 | ~RefCounter() 38 | { 39 | System.Threading.Interlocked.Decrement(ref *counter); 40 | } 41 | 42 | int* counter; 43 | } 44 | 45 | 46 | [Fact] 47 | public void ItAddsDataToTheStore() 48 | { 49 | var msg = "Hello!"; 50 | using var store = new Store(Fixture.Engine, msg); 51 | 52 | Linker.DefineFunction("", "hello", ((Caller caller) => 53 | { 54 | var data = caller.GetData() as string; 55 | data.Should().NotBeNull(); 56 | data.Should().Be(msg); 57 | })); 58 | 59 | var instance = Linker.Instantiate(store, Fixture.Module); 60 | var func = instance.GetFunction("run"); 61 | func.Should().NotBeNull(); 62 | 63 | func.Invoke(); 64 | } 65 | 66 | [Fact] 67 | public void ItReturnsNullWhenNoDataWasInitialized() 68 | { 69 | using var store = new Store(Fixture.Engine); 70 | 71 | Linker.DefineFunction("", "hello", ((Caller caller) => 72 | { 73 | var data = caller.GetData(); 74 | data.Should().BeNull(); 75 | })); 76 | 77 | var instance = Linker.Instantiate(store, Fixture.Module); 78 | var func = instance.GetFunction("run"); 79 | func.Should().NotBeNull(); 80 | 81 | func.Invoke(); 82 | } 83 | 84 | [Fact] 85 | public void ItShouldReplaceStoreData() 86 | { 87 | var msg = "Hello!"; 88 | using var store = new Store(Fixture.Engine, msg); 89 | 90 | Linker.DefineFunction("", "hello", ((Caller caller) => 91 | { 92 | caller.SetData(new int[] { 1, 2, 3 }); 93 | })); 94 | 95 | var instance = Linker.Instantiate(store, Fixture.Module); 96 | var func = instance.GetFunction("run"); 97 | func.Should().NotBeNull(); 98 | 99 | func.Invoke(); 100 | 101 | var data = store.GetData() as int[]; 102 | data.Should().NotBeNull(); 103 | data.Should().BeOfType(); 104 | } 105 | 106 | [Fact] 107 | unsafe public void ItCollectsExistingData() 108 | { 109 | var counter = 0; 110 | 111 | RunTest(&counter); 112 | 113 | GC.Collect(); 114 | GC.WaitForPendingFinalizers(); 115 | 116 | counter.Should().Be(0); 117 | 118 | void RunTest(int* counter) 119 | { 120 | var storeData = new RefCounter(counter); 121 | var store = new Store(Fixture.Engine, storeData); 122 | 123 | Linker.DefineFunction("", "hello", ((Caller caller) => 124 | { 125 | var cnt = caller.GetData() as RefCounter; 126 | cnt.Counter.Should().Be(1); 127 | })); 128 | 129 | var instance = Linker.Instantiate(store, Fixture.Module); 130 | var run = instance.GetFunction("run"); 131 | 132 | run.Should().NotBeNull(); 133 | run.Invoke(); 134 | store.Dispose(); 135 | } 136 | } 137 | 138 | [Fact] 139 | unsafe public void ItCollectsExistingDataAfterSetData() 140 | { 141 | var counter = 0; 142 | 143 | RunTest(&counter); 144 | 145 | GC.Collect(); 146 | GC.WaitForPendingFinalizers(); 147 | 148 | counter.Should().Be(0); 149 | 150 | void RunTest(int* counter) 151 | { 152 | var storeData = new RefCounter(counter); 153 | var store = new Store(Fixture.Engine, storeData); 154 | 155 | Linker.DefineFunction("", "hello", ((Caller caller) => 156 | { 157 | var cnt = caller.GetData() as RefCounter; 158 | cnt.Counter.Should().Be(1); 159 | 160 | caller.SetData(null); 161 | })); 162 | 163 | var instance = Linker.Instantiate(store, Fixture.Module); 164 | var run = instance.GetFunction("run"); 165 | run.Should().NotBeNull(); 166 | run.Invoke(); 167 | 168 | store.GetData().Should().BeNull(); 169 | } 170 | } 171 | 172 | public void Dispose() 173 | { 174 | Linker.Dispose(); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /tests/StoreFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Wasmtime; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public abstract class StoreFixture : IDisposable 8 | { 9 | protected StoreFixture() 10 | { 11 | Engine = new Engine(); 12 | Store = new Store(Engine); 13 | } 14 | 15 | public void Dispose() 16 | { 17 | Store?.Dispose(); 18 | Store = null; 19 | 20 | Engine?.Dispose(); 21 | Engine = null; 22 | } 23 | 24 | public Engine Engine { get; set; } 25 | public Store Store { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/StoreTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace Wasmtime.Tests 7 | { 8 | public class StoreTests 9 | : StoreFixture 10 | { 11 | [Fact] 12 | public void ItSetsLimits() 13 | { 14 | Store.SetLimits(1, 2, 3, 4, 5); 15 | } 16 | 17 | [Fact] 18 | public void ItSetsDefaultLimits() 19 | { 20 | Store.SetLimits(null, null, null, null, null); 21 | } 22 | 23 | [Fact] 24 | public void ItLimitsMemorySize() 25 | { 26 | Store.SetLimits(memorySize: Memory.PageSize); 27 | 28 | var memory = new Memory(Store, 0, 1024); 29 | memory.GetSize().Should().Be(0); 30 | 31 | 32 | memory.Grow(1).Should().Be(0); 33 | 34 | var act = () => { memory.Grow(1); }; 35 | act.Should().Throw(); 36 | } 37 | 38 | [Fact] 39 | public void ItLimitsTableElements() 40 | { 41 | Store.SetLimits(tableElements: 5); 42 | 43 | var table = new Table(Store, TableKind.ExternRef, null, 0); 44 | table.GetSize().Should().Be(0); 45 | 46 | table.Grow(5, null); 47 | 48 | var act = () => { table.Grow(1, null); }; 49 | act.Should().Throw(); 50 | } 51 | 52 | [Fact] 53 | public void ItLimitsInstances() 54 | { 55 | Store.SetLimits(instances: 3); 56 | 57 | using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "Trap.wat")); 58 | 59 | var inst1 = new Instance(Store, module); 60 | var inst2 = new Instance(Store, module); 61 | var inst3 = new Instance(Store, module); 62 | 63 | var act = () => { new Instance(Store, module); }; 64 | act.Should().Throw(); 65 | } 66 | 67 | [Fact] 68 | public void ItLimitsTables() 69 | { 70 | Store.SetLimits(tables: 3); 71 | 72 | // This module exports exactly 3 tables 73 | using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "TableExports.wat")); 74 | 75 | var inst1 = new Instance(Store, module); 76 | 77 | var act = () => { new Instance(Store, module); }; 78 | act.Should().Throw(); 79 | } 80 | 81 | [Fact] 82 | public void ItLimitsMemories() 83 | { 84 | Store.SetLimits(memories: 2); 85 | 86 | // This module exports 1 memory 87 | using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "MemoryExports.wat")); 88 | 89 | var inst1 = new Instance(Store, module); 90 | var inst2 = new Instance(Store, module); 91 | 92 | var act = () => { new Instance(Store, module); }; 93 | act.Should().Throw(); 94 | } 95 | 96 | [Fact] 97 | public void ItCannotBeAccessedOnceDisposed() 98 | { 99 | var ctx = Store.Context; 100 | Assert.Equal(Store, ctx.Store); 101 | 102 | Store.Dispose(); 103 | 104 | Assert.Throws(() => { var x = Store.Context; }); 105 | Assert.Throws(() => Store.NativeHandle); 106 | Assert.Throws(() => Store.Fuel); 107 | Assert.Throws(() => Store.GC()); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/TableExportsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace Wasmtime.Tests 8 | { 9 | public class TableExportsFixture : ModuleFixture 10 | { 11 | protected override string ModuleFileName => "TableExports.wat"; 12 | } 13 | 14 | public class TableExportsTests : IClassFixture, IDisposable 15 | { 16 | private TableExportsFixture Fixture { get; set; } 17 | 18 | private Store Store { get; set; } 19 | 20 | private Linker Linker { get; set; } 21 | 22 | public TableExportsTests(TableExportsFixture fixture) 23 | { 24 | Fixture = fixture; 25 | Store = new Store(Fixture.Engine); 26 | Linker = new Linker(Fixture.Engine); 27 | } 28 | 29 | [Theory] 30 | [MemberData(nameof(GetTableExports))] 31 | public void ItHasTheExpectedTableExports(string exportName, ValueKind expectedKind, uint expectedMinimum, uint expectedMaximum) 32 | { 33 | var export = Fixture.Module.Exports.Where(f => f.Name == exportName).FirstOrDefault() as TableExport; 34 | export.Should().NotBeNull(); 35 | export.Kind.Should().Be(expectedKind); 36 | export.Minimum.Should().Be(expectedMinimum); 37 | export.Maximum.Should().Be(expectedMaximum); 38 | } 39 | 40 | [Fact] 41 | public void ItHasTheExpectedNumberOfExportedTables() 42 | { 43 | GetTableExports().Count().Should().Be(Fixture.Module.Exports.Count(e => e is TableExport)); 44 | } 45 | 46 | [Fact] 47 | public void ItCreatesExternsForTheTables() 48 | { 49 | var instance = Linker.Instantiate(Store, Fixture.Module); 50 | 51 | var table1 = instance.GetTable("table1"); 52 | table1.Should().NotBeNull(); 53 | table1.Kind.Should().Be(TableKind.FuncRef); 54 | table1.Minimum.Should().Be(1); 55 | table1.Maximum.Should().Be(10); 56 | 57 | var table2 = instance.GetTable("table2"); 58 | table2.Should().NotBeNull(); 59 | table2.Kind.Should().Be(TableKind.FuncRef); 60 | table2.Minimum.Should().Be(10); 61 | table2.Maximum.Should().Be(uint.MaxValue); 62 | 63 | var table3 = instance.GetTable("table3"); 64 | table3.Should().NotBeNull(); 65 | table3.Kind.Should().Be(TableKind.FuncRef); 66 | table3.Minimum.Should().Be(100); 67 | table3.Maximum.Should().Be(1000); 68 | } 69 | 70 | [Fact] 71 | public void ItReturnsNullForNonExistantTable() 72 | { 73 | var instance = Linker.Instantiate(Store, Fixture.Module); 74 | 75 | var table1 = instance.GetTable("no_such_table"); 76 | table1.Should().BeNull(); 77 | } 78 | 79 | public static IEnumerable GetTableExports() 80 | { 81 | yield return new object[] { 82 | "table1", 83 | ValueKind.FuncRef, 84 | 1, 85 | 10 86 | }; 87 | 88 | yield return new object[] { 89 | "table2", 90 | ValueKind.FuncRef, 91 | 10, 92 | uint.MaxValue 93 | }; 94 | 95 | yield return new object[] { 96 | "table3", 97 | ValueKind.FuncRef, 98 | 100, 99 | 1000 100 | }; 101 | } 102 | 103 | public void Dispose() 104 | { 105 | Store.Dispose(); 106 | Linker.Dispose(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/TableImportBindingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace Wasmtime.Tests 6 | { 7 | public class TableImportBindingFixture : ModuleFixture 8 | { 9 | protected override string ModuleFileName => "TableImportBinding.wat"; 10 | } 11 | 12 | public class TableImportBindingTests : IClassFixture, IDisposable 13 | { 14 | private TableImportBindingFixture Fixture { get; set; } 15 | private Store Store { get; set; } 16 | private Linker Linker { get; set; } 17 | 18 | public TableImportBindingTests(TableImportBindingFixture fixture) 19 | { 20 | Fixture = fixture; 21 | Store = new Store(Fixture.Engine); 22 | Linker = new Linker(Fixture.Engine); 23 | 24 | Linker.Define("", "assert", Function.FromCallback(Store, (string s1, string s2) => { s1.Should().Be(s2); })); 25 | } 26 | 27 | [Fact] 28 | public void ItFailsToInstantiateWithMissingImport() 29 | { 30 | Action action = () => { Linker.Instantiate(Store, Fixture.Module); }; 31 | 32 | action 33 | .Should() 34 | .Throw() 35 | .WithMessage("unknown import: `::funcs` has not been defined"); 36 | } 37 | 38 | [Fact] 39 | public void ItFailsToInstantiateWithTableTypeMismatch() 40 | { 41 | var funcs = new Table(Store, TableKind.ExternRef, null, 10); 42 | var externs = new Table(Store, TableKind.ExternRef, null, 10); 43 | 44 | Linker.Define("", "funcs", funcs); 45 | Linker.Define("", "externs", externs); 46 | 47 | Action action = () => { Linker.Instantiate(Store, Fixture.Module); }; 48 | 49 | action 50 | .Should() 51 | .Throw() 52 | .WithMessage("incompatible import type for `::funcs`*"); 53 | } 54 | 55 | [Fact] 56 | public void ItFailsToInstantiateWithTableLimitsMismatch() 57 | { 58 | var funcs = new Table(Store, TableKind.FuncRef, null, 10); 59 | var externs = new Table(Store, TableKind.ExternRef, null, 1); 60 | 61 | Linker.Define("", "funcs", funcs); 62 | Linker.Define("", "externs", externs); 63 | 64 | Action action = () => { Linker.Instantiate(Store, Fixture.Module); }; 65 | 66 | action 67 | .Should() 68 | .Throw() 69 | .WithMessage("incompatible import type for `::externs`*"); 70 | } 71 | 72 | [Fact] 73 | public void ItBindsTheTableCorrectly() 74 | { 75 | var funcs = new Table(Store, TableKind.FuncRef, null, 10); 76 | var externs = new Table(Store, TableKind.ExternRef, null, 10); 77 | 78 | Linker.Define("", "funcs", funcs); 79 | Linker.Define("", "externs", externs); 80 | 81 | var instance = Linker.Instantiate(Store, Fixture.Module); 82 | var is_null_func = instance.GetFunction("is_null_extern"); 83 | var is_null_extern = instance.GetFunction("is_null_extern"); 84 | var call = instance.GetFunction("call"); 85 | var assert_extern = instance.GetFunction("assert_extern"); 86 | 87 | for (int i = 0; i < 10; ++i) 88 | { 89 | Convert.ToBoolean(is_null_func.Invoke(i)).Should().BeTrue(); 90 | Convert.ToBoolean(is_null_extern.Invoke(i)).Should().BeTrue(); 91 | } 92 | 93 | var called = new bool[10]; 94 | 95 | for (int i = 0; i < 10; ++i) 96 | { 97 | int index = i; 98 | funcs.SetElement((uint)i, Function.FromCallback(Store, () => { called[index] = true; })); 99 | externs.SetElement((uint)i, string.Format("string{0}", i)); 100 | } 101 | 102 | for (int i = 0; i < 10; ++i) 103 | { 104 | Convert.ToBoolean(is_null_func.Invoke(i)).Should().BeFalse(); 105 | Convert.ToBoolean(is_null_extern.Invoke(i)).Should().BeFalse(); 106 | call.Invoke(i); 107 | assert_extern.Invoke(i, string.Format("string{0}", i)); 108 | funcs.SetElement((uint)i, Function.Null); 109 | externs.SetElement((uint)i, null); 110 | } 111 | 112 | for (int i = 0; i < 10; ++i) 113 | { 114 | Convert.ToBoolean(is_null_func.Invoke(i)).Should().BeTrue(); 115 | Convert.ToBoolean(is_null_extern.Invoke(i)).Should().BeTrue(); 116 | called[i].Should().BeTrue(); 117 | } 118 | } 119 | 120 | [Fact] 121 | public void ItGrowsATable() 122 | { 123 | var funcs = new Table(Store, TableKind.FuncRef, null, 10, 20); 124 | var externs = new Table(Store, TableKind.ExternRef, null, 10, 20); 125 | 126 | Linker.Define("", "funcs", funcs); 127 | Linker.Define("", "externs", externs); 128 | 129 | var instance = Linker.Instantiate(Store, Fixture.Module); 130 | var grow_funcs = instance.GetFunction("grow_funcs"); 131 | var grow_externs = instance.GetFunction("grow_externs"); 132 | 133 | funcs.GetSize().Should().Be(10); 134 | externs.GetSize().Should().Be(10); 135 | 136 | grow_funcs.Invoke(5); 137 | grow_externs.Invoke(3); 138 | 139 | funcs.GetSize().Should().Be(15); 140 | externs.GetSize().Should().Be(13); 141 | 142 | funcs.Grow(5, null).Should().Be(15); 143 | externs.Grow(5, null).Should().Be(13); 144 | 145 | funcs.GetSize().Should().Be(20); 146 | externs.GetSize().Should().Be(18); 147 | 148 | Action action = () => { funcs.Grow(5, null); }; 149 | 150 | action 151 | .Should() 152 | .Throw() 153 | .WithMessage("failed to grow table by `5`"); 154 | 155 | action = () => { externs.Grow(3, null); }; 156 | 157 | action 158 | .Should() 159 | .Throw() 160 | .WithMessage("failed to grow table by `3`"); 161 | } 162 | 163 | public void Dispose() 164 | { 165 | Store.Dispose(); 166 | Linker.Dispose(); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/TableImportsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace Wasmtime.Tests 8 | { 9 | public class TableImportsFixture : ModuleFixture 10 | { 11 | protected override string ModuleFileName => "TableImports.wat"; 12 | } 13 | 14 | public class TableImportsTests : IClassFixture 15 | { 16 | public TableImportsTests(TableImportsFixture fixture) 17 | { 18 | Fixture = fixture; 19 | } 20 | 21 | private TableImportsFixture Fixture { get; set; } 22 | 23 | [Theory] 24 | [MemberData(nameof(GetTableImports))] 25 | public void ItHasTheExpectedTableImports(string importModule, string importName, ValueKind expectedKind, uint expectedMinimum, uint expectedMaximum) 26 | { 27 | var import = Fixture.Module.Imports.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault() as TableImport; 28 | import.Should().NotBeNull(); 29 | import.Kind.Should().Be(expectedKind); 30 | import.Minimum.Should().Be(expectedMinimum); 31 | import.Maximum.Should().Be(expectedMaximum); 32 | } 33 | 34 | [Fact] 35 | public void ItHasTheExpectedNumberOfExportedTables() 36 | { 37 | GetTableImports().Count().Should().Be(Fixture.Module.Imports.Count(i => i is TableImport)); 38 | } 39 | 40 | public static IEnumerable GetTableImports() 41 | { 42 | yield return new object[] { 43 | "", 44 | "table1", 45 | ValueKind.FuncRef, 46 | 10, 47 | uint.MaxValue 48 | }; 49 | 50 | yield return new object[] { 51 | "", 52 | "table2", 53 | ValueKind.FuncRef, 54 | 15, 55 | uint.MaxValue 56 | }; 57 | 58 | yield return new object[] { 59 | "other", 60 | "table3", 61 | ValueKind.FuncRef, 62 | 1, 63 | uint.MaxValue 64 | }; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/TempFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Wasmtime.Tests 5 | { 6 | internal class TempFile : IDisposable 7 | { 8 | public TempFile() 9 | { 10 | Path = System.IO.Path.GetTempFileName(); 11 | } 12 | 13 | public void Dispose() 14 | { 15 | if (Path != null) 16 | { 17 | File.Delete(Path); 18 | Path = null; 19 | } 20 | } 21 | 22 | public string Path { get; private set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Wasmtime.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------