├── .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 |
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