From 9fdf5bce8e9813c17be29b65eeeae4a95eca6326 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 22 Nov 2019 17:11:00 -0800 Subject: [PATCH] Move Wasmtime for .NET to the Wasmtime repo. This moves the Wasmtime for .NET implementation to the Wasmtime repo. Wasmtime for .NET is a binding of the Wasmtime API for use in .NET. --- crates/misc/dotnet/.gitignore | 8 + crates/misc/dotnet/Directory.Build.props | 6 + crates/misc/dotnet/Directory.Build.targets | 12 + crates/misc/dotnet/LICENSE | 201 +++++ crates/misc/dotnet/README.md | 108 +++ crates/misc/dotnet/docs/.gitignore | 9 + crates/misc/dotnet/docs/api/.gitignore | 5 + crates/misc/dotnet/docs/api/index.md | 5 + crates/misc/dotnet/docs/articles/intro.md | 214 +++++ crates/misc/dotnet/docs/articles/toc.yml | 2 + crates/misc/dotnet/docs/docfx.json | 72 ++ crates/misc/dotnet/docs/index.md | 7 + .../darkfx/partials/head.tmpl.partial | 21 + .../docs/templates/darkfx/styles/main.css | 394 +++++++++ crates/misc/dotnet/docs/toc.yml | 7 + crates/misc/dotnet/docs/wasm/intro/hello.wasm | Bin 0 -> 165 bytes crates/misc/dotnet/examples/global/Program.cs | 33 + .../misc/dotnet/examples/global/global.csproj | 21 + .../misc/dotnet/examples/global/global.wasm | Bin 0 -> 95 bytes crates/misc/dotnet/examples/global/global.wat | 22 + crates/misc/dotnet/examples/hello/Program.cs | 30 + .../misc/dotnet/examples/hello/hello.csproj | 21 + crates/misc/dotnet/examples/hello/hello.wasm | Bin 0 -> 47 bytes crates/misc/dotnet/examples/hello/hello.wat | 8 + crates/misc/dotnet/examples/memory/Program.cs | 31 + .../misc/dotnet/examples/memory/memory.csproj | 21 + .../misc/dotnet/examples/memory/memory.wasm | Bin 0 -> 85 bytes crates/misc/dotnet/examples/memory/memory.wat | 12 + crates/misc/dotnet/src/Bindings/Binding.cs | 129 +++ .../dotnet/src/Bindings/FunctionBinding.cs | 346 ++++++++ .../misc/dotnet/src/Bindings/GlobalBinding.cs | 125 +++ .../misc/dotnet/src/Bindings/MemoryBinding.cs | 98 +++ crates/misc/dotnet/src/Engine.cs | 44 + crates/misc/dotnet/src/Exports/Export.cs | 31 + crates/misc/dotnet/src/Exports/Exports.cs | 97 +++ .../misc/dotnet/src/Exports/FunctionExport.cs | 34 + .../misc/dotnet/src/Exports/GlobalExport.cs | 30 + .../misc/dotnet/src/Exports/MemoryExport.cs | 35 + crates/misc/dotnet/src/Exports/TableExport.cs | 42 + .../misc/dotnet/src/Externs/ExternFunction.cs | 87 ++ .../misc/dotnet/src/Externs/ExternGlobal.cs | 62 ++ .../misc/dotnet/src/Externs/ExternMemory.cs | 245 ++++++ crates/misc/dotnet/src/Externs/Externs.cs | 67 ++ crates/misc/dotnet/src/Global.cs | 50 ++ crates/misc/dotnet/src/IHost.cs | 25 + crates/misc/dotnet/src/ImportAttribute.cs | 31 + .../misc/dotnet/src/Imports/FunctionImport.cs | 34 + .../misc/dotnet/src/Imports/GlobalImport.cs | 30 + crates/misc/dotnet/src/Imports/Import.cs | 39 + crates/misc/dotnet/src/Imports/Imports.cs | 97 +++ .../misc/dotnet/src/Imports/MemoryImport.cs | 35 + crates/misc/dotnet/src/Imports/TableImport.cs | 42 + crates/misc/dotnet/src/Instance.cs | 145 ++++ crates/misc/dotnet/src/Interop.cs | 762 ++++++++++++++++++ crates/misc/dotnet/src/Memory.cs | 270 +++++++ crates/misc/dotnet/src/Module.cs | 101 +++ crates/misc/dotnet/src/MutableGlobal.cs | 65 ++ crates/misc/dotnet/src/Store.cs | 75 ++ crates/misc/dotnet/src/TrapException.cs | 41 + crates/misc/dotnet/src/ValueKind.cs | 35 + crates/misc/dotnet/src/Wasmtime.csproj | 77 ++ crates/misc/dotnet/src/WasmtimeException.cs | 24 + .../dotnet/tests/Fixtures/ModuleFixture.cs | 43 + .../misc/dotnet/tests/FunctionExportsTests.cs | 150 ++++ .../misc/dotnet/tests/FunctionImportsTests.cs | 169 ++++ .../misc/dotnet/tests/GlobalExportsTests.cs | 220 +++++ .../dotnet/tests/GlobalImportBindingTests.cs | 259 ++++++ .../misc/dotnet/tests/GlobalImportsTests.cs | 105 +++ .../misc/dotnet/tests/MemoryExportsTests.cs | 96 +++ .../dotnet/tests/MemoryImportBindingTests.cs | 187 +++++ .../tests/MemoryImportFromModuleTests.cs | 34 + .../tests/MemoryImportNoUpperBoundTests.cs | 34 + .../tests/MemoryImportWithUpperBoundTests.cs | 34 + .../dotnet/tests/Modules/FunctionExports.wasm | Bin 0 -> 509 bytes .../dotnet/tests/Modules/FunctionExports.wat | 26 + .../dotnet/tests/Modules/FunctionImports.wasm | Bin 0 -> 442 bytes .../dotnet/tests/Modules/FunctionImports.wat | 27 + .../dotnet/tests/Modules/GlobalExports.wasm | Bin 0 -> 194 bytes .../dotnet/tests/Modules/GlobalExports.wat | 18 + .../tests/Modules/GlobalImportBindings.wasm | Bin 0 -> 509 bytes .../tests/Modules/GlobalImportBindings.wat | 22 + .../dotnet/tests/Modules/GlobalImports.wasm | Bin 0 -> 176 bytes .../dotnet/tests/Modules/GlobalImports.wat | 11 + .../dotnet/tests/Modules/MemoryExports.wasm | Bin 0 -> 112 bytes .../dotnet/tests/Modules/MemoryExports.wat | 11 + .../tests/Modules/MemoryImportBinding.wasm | Bin 0 -> 287 bytes .../tests/Modules/MemoryImportBinding.wat | 39 + .../tests/Modules/MemoryImportFromModule.wasm | Bin 0 -> 22 bytes .../tests/Modules/MemoryImportFromModule.wat | 3 + .../Modules/MemoryImportNoUpperBound.wasm | Bin 0 -> 19 bytes .../Modules/MemoryImportNoUpperBound.wat | 3 + .../Modules/MemoryImportWithUpperBound.wasm | Bin 0 -> 20 bytes .../Modules/MemoryImportWithUpperBound.wat | 3 + .../dotnet/tests/Modules/TableExports.wasm | Bin 0 -> 53 bytes .../dotnet/tests/Modules/TableExports.wat | 8 + .../dotnet/tests/Modules/TableImports.wasm | Bin 0 -> 52 bytes .../dotnet/tests/Modules/TableImports.wat | 5 + crates/misc/dotnet/tests/TableExportsTests.cs | 64 ++ crates/misc/dotnet/tests/TableImportsTests.cs | 67 ++ .../misc/dotnet/tests/Wasmtime.Tests.csproj | 33 + 100 files changed, 6391 insertions(+) create mode 100644 crates/misc/dotnet/.gitignore create mode 100644 crates/misc/dotnet/Directory.Build.props create mode 100644 crates/misc/dotnet/Directory.Build.targets create mode 100644 crates/misc/dotnet/LICENSE create mode 100644 crates/misc/dotnet/README.md create mode 100644 crates/misc/dotnet/docs/.gitignore create mode 100644 crates/misc/dotnet/docs/api/.gitignore create mode 100644 crates/misc/dotnet/docs/api/index.md create mode 100644 crates/misc/dotnet/docs/articles/intro.md create mode 100644 crates/misc/dotnet/docs/articles/toc.yml create mode 100644 crates/misc/dotnet/docs/docfx.json create mode 100644 crates/misc/dotnet/docs/index.md create mode 100755 crates/misc/dotnet/docs/templates/darkfx/partials/head.tmpl.partial create mode 100755 crates/misc/dotnet/docs/templates/darkfx/styles/main.css create mode 100644 crates/misc/dotnet/docs/toc.yml create mode 100755 crates/misc/dotnet/docs/wasm/intro/hello.wasm create mode 100644 crates/misc/dotnet/examples/global/Program.cs create mode 100644 crates/misc/dotnet/examples/global/global.csproj create mode 100644 crates/misc/dotnet/examples/global/global.wasm create mode 100644 crates/misc/dotnet/examples/global/global.wat create mode 100644 crates/misc/dotnet/examples/hello/Program.cs create mode 100644 crates/misc/dotnet/examples/hello/hello.csproj create mode 100644 crates/misc/dotnet/examples/hello/hello.wasm create mode 100644 crates/misc/dotnet/examples/hello/hello.wat create mode 100644 crates/misc/dotnet/examples/memory/Program.cs create mode 100644 crates/misc/dotnet/examples/memory/memory.csproj create mode 100644 crates/misc/dotnet/examples/memory/memory.wasm create mode 100644 crates/misc/dotnet/examples/memory/memory.wat create mode 100644 crates/misc/dotnet/src/Bindings/Binding.cs create mode 100644 crates/misc/dotnet/src/Bindings/FunctionBinding.cs create mode 100644 crates/misc/dotnet/src/Bindings/GlobalBinding.cs create mode 100644 crates/misc/dotnet/src/Bindings/MemoryBinding.cs create mode 100644 crates/misc/dotnet/src/Engine.cs create mode 100644 crates/misc/dotnet/src/Exports/Export.cs create mode 100644 crates/misc/dotnet/src/Exports/Exports.cs create mode 100644 crates/misc/dotnet/src/Exports/FunctionExport.cs create mode 100644 crates/misc/dotnet/src/Exports/GlobalExport.cs create mode 100644 crates/misc/dotnet/src/Exports/MemoryExport.cs create mode 100644 crates/misc/dotnet/src/Exports/TableExport.cs create mode 100644 crates/misc/dotnet/src/Externs/ExternFunction.cs create mode 100644 crates/misc/dotnet/src/Externs/ExternGlobal.cs create mode 100644 crates/misc/dotnet/src/Externs/ExternMemory.cs create mode 100644 crates/misc/dotnet/src/Externs/Externs.cs create mode 100644 crates/misc/dotnet/src/Global.cs create mode 100644 crates/misc/dotnet/src/IHost.cs create mode 100644 crates/misc/dotnet/src/ImportAttribute.cs create mode 100644 crates/misc/dotnet/src/Imports/FunctionImport.cs create mode 100644 crates/misc/dotnet/src/Imports/GlobalImport.cs create mode 100644 crates/misc/dotnet/src/Imports/Import.cs create mode 100644 crates/misc/dotnet/src/Imports/Imports.cs create mode 100644 crates/misc/dotnet/src/Imports/MemoryImport.cs create mode 100644 crates/misc/dotnet/src/Imports/TableImport.cs create mode 100644 crates/misc/dotnet/src/Instance.cs create mode 100644 crates/misc/dotnet/src/Interop.cs create mode 100644 crates/misc/dotnet/src/Memory.cs create mode 100644 crates/misc/dotnet/src/Module.cs create mode 100644 crates/misc/dotnet/src/MutableGlobal.cs create mode 100644 crates/misc/dotnet/src/Store.cs create mode 100644 crates/misc/dotnet/src/TrapException.cs create mode 100644 crates/misc/dotnet/src/ValueKind.cs create mode 100644 crates/misc/dotnet/src/Wasmtime.csproj create mode 100644 crates/misc/dotnet/src/WasmtimeException.cs create mode 100644 crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs create mode 100644 crates/misc/dotnet/tests/FunctionExportsTests.cs create mode 100644 crates/misc/dotnet/tests/FunctionImportsTests.cs create mode 100644 crates/misc/dotnet/tests/GlobalExportsTests.cs create mode 100644 crates/misc/dotnet/tests/GlobalImportBindingTests.cs create mode 100644 crates/misc/dotnet/tests/GlobalImportsTests.cs create mode 100644 crates/misc/dotnet/tests/MemoryExportsTests.cs create mode 100644 crates/misc/dotnet/tests/MemoryImportBindingTests.cs create mode 100644 crates/misc/dotnet/tests/MemoryImportFromModuleTests.cs create mode 100644 crates/misc/dotnet/tests/MemoryImportNoUpperBoundTests.cs create mode 100644 crates/misc/dotnet/tests/MemoryImportWithUpperBoundTests.cs create mode 100644 crates/misc/dotnet/tests/Modules/FunctionExports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/FunctionExports.wat create mode 100644 crates/misc/dotnet/tests/Modules/FunctionImports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/FunctionImports.wat create mode 100644 crates/misc/dotnet/tests/Modules/GlobalExports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/GlobalExports.wat create mode 100644 crates/misc/dotnet/tests/Modules/GlobalImportBindings.wasm create mode 100644 crates/misc/dotnet/tests/Modules/GlobalImportBindings.wat create mode 100644 crates/misc/dotnet/tests/Modules/GlobalImports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/GlobalImports.wat create mode 100644 crates/misc/dotnet/tests/Modules/MemoryExports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/MemoryExports.wat create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportBinding.wasm create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportBinding.wat create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wasm create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wat create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wasm create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wat create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportWithUpperBound.wasm create mode 100644 crates/misc/dotnet/tests/Modules/MemoryImportWithUpperBound.wat create mode 100644 crates/misc/dotnet/tests/Modules/TableExports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/TableExports.wat create mode 100644 crates/misc/dotnet/tests/Modules/TableImports.wasm create mode 100644 crates/misc/dotnet/tests/Modules/TableImports.wat create mode 100644 crates/misc/dotnet/tests/TableExportsTests.cs create mode 100644 crates/misc/dotnet/tests/TableImportsTests.cs create mode 100644 crates/misc/dotnet/tests/Wasmtime.Tests.csproj diff --git a/crates/misc/dotnet/.gitignore b/crates/misc/dotnet/.gitignore new file mode 100644 index 0000000000..1944291481 --- /dev/null +++ b/crates/misc/dotnet/.gitignore @@ -0,0 +1,8 @@ +*.swp +*.*~ +.DS_Store + +.vscode + +bin/ +obj/ diff --git a/crates/misc/dotnet/Directory.Build.props b/crates/misc/dotnet/Directory.Build.props new file mode 100644 index 0000000000..b652414ca0 --- /dev/null +++ b/crates/misc/dotnet/Directory.Build.props @@ -0,0 +1,6 @@ + + + 0.8.0 + wasmtime + + diff --git a/crates/misc/dotnet/Directory.Build.targets b/crates/misc/dotnet/Directory.Build.targets new file mode 100644 index 0000000000..fd577ba0c8 --- /dev/null +++ b/crates/misc/dotnet/Directory.Build.targets @@ -0,0 +1,12 @@ + + + lib + .dll + .dylib + .so + $(LibraryPrefix)$(WasmtimeLibraryName)$(LibraryExtension) + $(MSBuildThisFileDirectory)../../../target/$(Configuration.ToLower()) + cargo build --release -p $(WasmtimeLibraryName) + cargo build -p $(WasmtimeLibraryName) + + diff --git a/crates/misc/dotnet/LICENSE b/crates/misc/dotnet/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/crates/misc/dotnet/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/misc/dotnet/README.md b/crates/misc/dotnet/README.md new file mode 100644 index 0000000000..5806934641 --- /dev/null +++ b/crates/misc/dotnet/README.md @@ -0,0 +1,108 @@ +# Wasmtime for .NET + +A .NET API for [Wasmtime](https://github.com/bytecodealliance/wasmtime). + +Wasmtime for .NET enables .NET code to instantiate WebAssembly modules and to interact with them in-process. + +# Getting Started + +## Prerequisites + +### .NET Core 3.0 + +Install a [.NET Core 3.0+ SDK](https://dotnet.microsoft.com/download) for your operating system. + +## Introduction to Wasmtime for .NET + +See the [introduction to Wasmtime for .NET](https://peterhuene.github.io/wasmtime.net/articles/intro.html) for a complete walkthrough of how to use Wasmtime for .NET. + +# Wasmtime for .NET API documentation + +See the [Wasmtime for .NET API documentation](https://peterhuene.github.io/wasmtime.net/api/index.html) for documentation on using the Wasmtime for .NET types. + +# Running the "Hello World" Example + +The "hello world" example demonstrates a simple C# function being called from WebAssembly. + +To run the "hello world" example, follow these instructions: + +1. `cd examples/hello` +2. `dotnet run` + +You should see a `Hello from C#, WebAssembly!` message printed. + +# Building Wasmtime for .NET + +To build Wasmtime for .NET, follow these instructions: + +1. `cd src`. +2. `dotnet build`. + +This should produce a `Wasmtime.Dotnet.dll` assembly in the `bin/Debug/netstandard2.1` directory. + +To build a release version of Wasmtime for .NET, follow these instructions: + +1. `cd src`. +2. `dotnet build -c Release`. + +This should produce a `Wasmtime.Dotnet.dll` assembly in the `bin/Release/netstandard2.1` directory. + +# Running the tests + +To run the Wasmtime for .NET unit tests, follow these instructions: + +1. `cd tests`. +2. `dotnet test`. + +# Packing Wasmtime for .NET + +To create a NuGet package for Wasmtime for .NET, follow these instructions: + +1. `cd src`. +2. `dotnet pack -c Release`. + +This should produce a `Wasmtime..nupkg` file in the `bin/Release` directory. + +# Implementation Status + +## Status + +| Feature | Status | +|---------------------------------------|--------| +| Wasmtime engine class | ✅ | +| Wasmtime store class | ✅ | +| Wasmtime module class | ✅ | +| Wasmtime instance class | 🔄 | +| Module function imports | ✅ | +| Module global imports | ✅ | +| Module table imports | ✅ | +| Module memory imports | ✅ | +| Module function exports | ✅ | +| Module global exports | ✅ | +| Module table exports | ✅ | +| Module memory exports | ✅ | +| Extern instance functions | ✅ | +| Extern instance globals | ✅️ | +| Extern instance tables | ⬜️ | +| Extern instance memories | ✅️ | +| Host function import binding | ✅ | +| Host global import binding | ✅ ️️ | +| Host table import binding | ⬜️ ️️ | +| Host memory import binding | ✅️ ️️ | +| `i32` parameters and return values | ✅ | +| `i64` parameters and return values | ✅ | +| `f32` parameters and return values | ✅ | +| `f64` parameters and return values | ✅ | +| `AnyRef` parameters and return values | ⬜️ | +| Tuple return types for host functions | ✅ | +| Trap messages | ✅ | +| Trap frames | ⬜️ | +| Create a NuGet package | ✅ | + +## Legend + +| Status | Icon | +|-----------------|--------| +| Not implemented | ⬜️ | +| In progress | 🔄 | +| Completed | ✅ | diff --git a/crates/misc/dotnet/docs/.gitignore b/crates/misc/dotnet/docs/.gitignore new file mode 100644 index 0000000000..2781f6d586 --- /dev/null +++ b/crates/misc/dotnet/docs/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/crates/misc/dotnet/docs/api/.gitignore b/crates/misc/dotnet/docs/api/.gitignore new file mode 100644 index 0000000000..f798527e61 --- /dev/null +++ b/crates/misc/dotnet/docs/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/crates/misc/dotnet/docs/api/index.md b/crates/misc/dotnet/docs/api/index.md new file mode 100644 index 0000000000..74ae3a863a --- /dev/null +++ b/crates/misc/dotnet/docs/api/index.md @@ -0,0 +1,5 @@ +# Wasmtime for NET + +This is the .NET API for [Wasmtime](https://github.com/bytecodealliance/wasmtime). + +See the [documentation](Wasmtime.html#classes) for the various .NET classes. \ No newline at end of file diff --git a/crates/misc/dotnet/docs/articles/intro.md b/crates/misc/dotnet/docs/articles/intro.md new file mode 100644 index 0000000000..6b1d2b9552 --- /dev/null +++ b/crates/misc/dotnet/docs/articles/intro.md @@ -0,0 +1,214 @@ +# Introduction to Wasmtime for .NET + +[Wasmtime](https://github.com/bytecodealliance/wasmtime) is a standalone runtime capable of executing [WebAssembly](https://webassembly.org/) outside of a web browser. + +Wasmtime for .NET is a .NET API for Wasmtime. It enables .NET developers to easily instantiate and execute WebAssembly modules. + +For this tutorial, we will create a WebAssembly module from a program written in Rust and use that WebAssembly module from a .NET Core 3.0 application. + +# Creating a simple WebAssembly module + +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. + +Here I've decided to use [Rust](https://www.rust-lang.org/) for the implementation of the WebAssembly module. Rust is a modern systems programming language that can easily target WebAssembly. + +If you wish to skip creating the WebAssembly module, download the [prebuilt WebAssembly module](https://raw.githubusercontent.com/bytecodealliance/wasmtime/master/crates/misc/dotnet/docs/wasm/intro/hello.wasm) from this tutorial, copy it to your .NET project directory, and continue from the _[Using the WebAssembly module from .NET](#using-the-webassembly-module-from-net)_ section. + +## Installing a Rust toolchain + +To get started with Rust, install [rustup](https://rustup.rs/), the manager for Rust toolchains. + +This will install both a `rustup` command and a `cargo` command (for the active Rust toolchain) to your PATH. + +## Installing the WebAssembly target + +To target WebAssembly with the active Rust toolchain, install the WebAssembly [target triple](https://forge.rust-lang.org/release/platform-support.html): + +```text +rustup target add wasm32-unknown-unknown +``` + +## Creating the Rust project + +Create a new Rust library project named `hello`: + +```text +cargo new --lib hello +cd hello +``` + +To target WebAssembly, the library needs to be built as a `cdylib` (dynamic library) rather than the default of a static Rust library. Add the following to the `Cargo.toml` file in the project root: + +```toml +[lib] +crate-type = ["cdylib"] +``` + +## Implementing the WebAssembly code + +The WebAssembly implementation will import a `print` function from the host environment and pass it a string to print. It will export a `run` function that will invoke the imported `print` function. + +Replace the code in `src/lib.rs` with the following Rust code: + +```rust +extern "C" { + fn print(address: i32, length: i32); +} + +#[no_mangle] +pub unsafe extern fn run() { + let message = "Hello world!"; + print(message.as_ptr() as i32, message.len() as i32); +} +``` + +Note that this example passes the string as a pair of _address and length_. This is because WebAssembly only supports a few core types (such as integers and floats) and a "string" has no native representation in WebAssembly. + +In the future, WebAssembly will support [interface types](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) that will enable higher-level abstractions of types like strings so they can be represented in a natural way. + +Also note that the _address_ is not actually a physical memory address within the address space of a process but an address within the _[WebAssembly memory](https://hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/)_ of the module. Thus the WebAssembly module has no direct access to the memory of the host environment. + +## Building the WebAssembly module + +Use `cargo build` to build the WebAssembly module: + +```text +cargo build --target wasm32-unknown-unknown --release +``` + +This should create a `hello.wasm` file in the `target/wasm32-unknown-unknown/release` directory. We will use `hello.wasm` in the next section of the tutorial. + +As this example is very simple and does not require any of the data from the custom sections of the WebAssembly module, you may use `wasm-strip` if you have the [WebAssembly Binary Toolkit](https://github.com/WebAssembly/wabt) installed: + +```text +wasm-strip target/wasm32-unknown-unknown/release/hello.wasm +``` + +The resulting file should be less than 200 bytes. + +# Using the WebAssembly module from .NET + +## Installing a .NET Core 3.0 SDK + +Install a [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0) for your platform if you haven't already. + +This will add a `dotnet` command to your PATH. + +## Creating the .NET Core project + +The .NET program will be a simple console application, so create a new console project with `dotnet new`: + +```text +mkdir tutorial +cd tutorial +dotnet new console +``` + +## Referencing the Wasmtime for .NET package + +To use Wasmtime for .NET from the project, we need to add a reference to the [Wasmtime NuGet package](https://www.nuget.org/packages/Wasmtime): + +```text +dotnet add package --version 0.0.1-alpha1 wasmtime +``` + +_Note that the `--version` option is required because the package is currently prerelease._ + +This will add a `PackageReference` to the project file so that Wasmtime for .NET can be used. + +## Implementing the .NET code + +Replace the contents of `Program.cs` with the following: + +```c# +using System; +using Wasmtime; + +namespace Tutorial +{ + class Host : IHost + { + public Instance Instance { get; set; } + + [Import("print", Module="env")] + public void Print(int address, int length) + { + var message = Instance.Externs.Memories[0].ReadString(address, length); + Console.WriteLine(message); + } + } + + class Program + { + static void Main(string[] args) + { + using (var engine = new Engine()) + using (var store = engine.CreateStore()) + using (var module = store.CreateModule("hello.wasm")) + using (dynamic instance = module.Instantiate(new Host())) + { + instance.run(); + } + } + } +} +``` + +The `Host` class is responsible for implementing the imported [functions](https://webassembly.github.io/spec/core/syntax/modules.html#functions), [globals](https://webassembly.github.io/spec/core/syntax/modules.html#globals), [memories](https://webassembly.github.io/spec/core/syntax/modules.html#memories), and [tables](https://webassembly.github.io/spec/core/syntax/modules.html#syntax-table) for the WebAssembly module. For Wasmtime for .NET, this is done via the [`Import`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.ImportAttribute.html) attribute applied to functions and fields of type [`Global`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Global-1.html), [`MutableGlobal`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.MutableGlobal-1.html), and [`Memory`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Memory.html) (support for WebAssembly tables is not yet implemented). The [`Instance`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.IHost.html#Wasmtime_IHost_Instance) property of the host is set during instantiation of the WebAssembly module. + +Here the host is implementing an import of `print` in the `env` module, which is the default import module name for WebAssembly modules compiled using the Rust toolchain. + +The [`Engine`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Engine.html) is used to create a [`Store`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Store.html) that will store all Wasmtime runtime objects, such as WebAssembly modules and their instantiations. + +A WebAssembly module _instantiation_ is the stateful representation of a module that can be executed. Here, the code is casting the [`Instance`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Instance.html) to [`dynamic`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic) which allows us to easily invoke the `run` function that was exported by the WebAssembly module. + +Alternatively, the `run` function could be invoked without using the runtime binding of the `dynamic` feature like this: + +```c# +... +using (var instance = module.Instantiate(new Host())) +{ + instance.Externs.Functions[0].Invoke(); +} +... +``` + +## Building the .NET application + +Use `dotnet build` to build the .NET application: + +```text +dotnet build +``` + +This will create a `tutorial.dll` in the `bin/Debug/netcoreapp3.0` directory that implements the .NET Core application. An executable `tutorial` (or `tutorial.exe` on Windows) should also be present in the same directory to run the application. + +## Running the .NET application + +Before running the application, we need to copy the `hello.wasm` file to the project directory. + +Once the WebAssembly module is present in project directory, we can run the application: + +```text +dotnet run +``` + +Alternatively, we can execute the program directly without building the application again: + +```text +bin/Debug/netcoreapp3.0/tutorial +``` + +This should result in the following output: + +```text +Hello world! +``` + +# Wrapping up + +We did it! We executed a function written in Rust from .NET and a function implemented in .NET from Rust without much trouble at all. And, thanks to the design of WebAssembly, the Rust code was effectively sandboxed from accessing the memory of the .NET application. + +Hopefully this introduction to Wasmtime for .NET has offered a small glipse of the potential of using WebAssembly from .NET. + +One last note: _Wasmtime for .NET is currently in a very early stage of development and the API might change dramatically in the future_. \ No newline at end of file diff --git a/crates/misc/dotnet/docs/articles/toc.yml b/crates/misc/dotnet/docs/articles/toc.yml new file mode 100644 index 0000000000..cc503c4cc2 --- /dev/null +++ b/crates/misc/dotnet/docs/articles/toc.yml @@ -0,0 +1,2 @@ +- name: Introduction to Wasmtime for .NET + href: intro.md diff --git a/crates/misc/dotnet/docs/docfx.json b/crates/misc/dotnet/docs/docfx.json new file mode 100644 index 0000000000..baabba0940 --- /dev/null +++ b/crates/misc/dotnet/docs/docfx.json @@ -0,0 +1,72 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "..", + "files": [ + "src/**.csproj" + ] + } + ], + "dest": "api", + "disableGitFeatures": false, + "disableDefaultFilter": false + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "overwrite": [ + { + "files": [ + "apidoc/**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "dest": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "templates/darkfx" + ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false, + "globalMetadata": { + "_gitContribute": { + "repo": "https://github.com/bytecodealliance/wasmtime", + "branch": "master" + } + } + } +} \ No newline at end of file diff --git a/crates/misc/dotnet/docs/index.md b/crates/misc/dotnet/docs/index.md new file mode 100644 index 0000000000..fcb2408085 --- /dev/null +++ b/crates/misc/dotnet/docs/index.md @@ -0,0 +1,7 @@ +# Wasmtime for .NET + +A .NET API for [Wasmtime](https://github.com/bytecodealliance/wasmtime). + +Wasmtime is a standalone runtime for [WebAssembly](https://webassembly.org/), using the Cranelift JIT compiler. + +Wasmtime for .NET enables .NET code to instantiate WebAssembly modules and to interact with them in-process. diff --git a/crates/misc/dotnet/docs/templates/darkfx/partials/head.tmpl.partial b/crates/misc/dotnet/docs/templates/darkfx/partials/head.tmpl.partial new file mode 100755 index 0000000000..0e43511ace --- /dev/null +++ b/crates/misc/dotnet/docs/templates/darkfx/partials/head.tmpl.partial @@ -0,0 +1,21 @@ +{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/crates/misc/dotnet/docs/templates/darkfx/styles/main.css b/crates/misc/dotnet/docs/templates/darkfx/styles/main.css new file mode 100755 index 0000000000..173e6ab304 --- /dev/null +++ b/crates/misc/dotnet/docs/templates/darkfx/styles/main.css @@ -0,0 +1,394 @@ +body { + color: #ccd5dc; + font-family: "Open Sans",sans-serif; + line-height: 1.5; + font-size: 14px; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + word-wrap: break-word; + background-color: #2d2d30; +} + +h1 { + font-weight: 600; + font-size: 32px; +} + +h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.8; +} + +h3 { + font-weight: 600; + font-size: 20px; + line-height: 1.8; +} + +h5 { + font-size: 14px; + padding: 10px 0px; +} + +article h1, +article h2, +article h3, +article h4 { + margin-top: 35px; + margin-bottom: 15px; +} + +article h4 { + padding-bottom: 8px; + border-bottom: 2px solid #ddd; +} + +.navbar-brand>img { + color: #2d2d30; +} + +.navbar { + border: none; +} + +.subnav { + border-top: 1px solid #ddd; + background-color: #333337; +} + +.navbar-inverse { + background-color: #1e1e1e; + z-index: 100; +} + +.navbar-inverse .navbar-nav>li>a, +.navbar-inverse .navbar-text { + color: #66666d; + background-color: #1e1e1e; + border-bottom: 3px solid transparent; + padding-bottom: 12px; +} + +.navbar-inverse .navbar-nav>li>a:focus, +.navbar-inverse .navbar-nav>li>a:hover { + color: #c5c5de; + background-color: #1e1e1e; + border-bottom: 3px solid #333337; + transition: all ease 0.25s; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:focus, +.navbar-inverse .navbar-nav>.active>a:hover { + color: #c5c5de; + background-color: #1e1e1e; + border-bottom: 3px solid #333337; + transition: all ease 0.25s; +} + +.navbar-form .form-control { + border: none; + border-radius: 0; +} + +.toc .level1>li { + font-weight: 400; +} + +.toc .nav>li>a { + color: #ccd5dc; +} + +.sidefilter { + background-color: #2d2d30; + border-left: none; + border-right: none; +} + +.sidefilter { + background-color: #2d2d30; + border-left: none; + border-right: none; +} + +.toc-filter { + padding: 10px; + margin: 0; + background-color: #2d2d30; +} + +.toc-filter>input { + border: none; + border-radius: unset; + background-color: #333337; + padding: 5px 0 5px 20px; + font-size: 90% +} + +.toc-filter>input:focus { + color: #ccd5dc; + transition: all ease 0.25s; +} + +.toc-filter>.filter-icon { + display: none; +} + +.sidetoc>.toc { + background-color: #2d2d30; + overflow-x: hidden; +} + +.sidetoc { + background-color: #2d2d30; + border: none; +} + +.alert { + background-color: inherit; + border: none; + padding: 10px 0; + border-radius: 0; +} + +.alert>p { + margin-bottom: 0; + padding: 5px 10px; + border-bottom: 1px solid; + background-color: #212123; +} + +.alert>h5 { + padding: 10px 15px; + margin-top: 0; + margin-bottom: 0; + text-transform: uppercase; + font-weight: bold; + border-top: 2px solid; + background-color: #212123; + border-radius: none; +} + +.alert>ul { + margin-bottom: 0; + padding: 5px 40px; +} + +.alert-info{ + color: #1976d2; +} + +.alert-warning{ + color: #f57f17; +} + +.alert-danger{ + color: #d32f2f; +} + +pre { + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + word-break: break-all; + word-wrap: break-word; + background-color: #1e1e1e;; + border-radius: 0; + border: none; +} + +code{ + background: #1e1e1e !important; + border-radius: 2px; +} + +.hljs{ + color: #bbb; +} + +.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before { + content: "▾"; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before { + content: "▸"; +} + +.affix ul ul > li > a:before { + content: "|"; +} + +.breadcrumb .label.label-primary { + background: #444; + border-radius: 0; + font-weight: normal; + font-size: 100%; +} + +#breadcrumb .breadcrumb>li a { + border-radius: 0; + font-weight: normal; + font-size: 85%; + display: inline; + padding: 0 .6em 0; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + color: #999; +} +#breadcrumb .breadcrumb>li a:hover{ + color: #c5c5de; + transition: all ease 0.25s; +} + +.breadcrumb > li + li:before { + content: "⯈"; + font-size: 75%; + color: #1e1e1e; + padding: 0; +} + +.toc .level1>li { + font-weight: 600; + font-size: 130%; + padding-left: 5px; +} + +.footer { + border-top: none; + background-color: #1e1e1e; + padding: 15px 0; + font-size: 90%; +} + +.toc .nav > li > a:hover, .toc .nav > li > a:focus { + color: #fff; + transition: all ease 0.1s; +} + +.form-control { + background-color: #333337; + border: none; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; +} + +input#search-query:focus { + color: #c5c5de; +} + +.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th { + border: 1px solid #1E1E1E; +} + +.table-striped>tbody>tr:nth-of-type(odd) { + background-color: #212123; +} + +blockquote { + padding: 10px 20px; + margin: 0 0 10px; + font-size: 110%; + border-left: 5px solid #69696e; + color: #69696e; +} + +.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover { + background-color: #333337; + border-color: #333337; +} + +.breadcrumb>li, .pagination { + display: inline; +} + +@media (min-width: 1600px){ + .container { + width: 100%; + } + .sidefilter { + width: 20%; + } + .sidetoc{ + width: 20%; + } + .article.grid-right { + margin-left: 21%; + } +} + +code { + color:#8ec4f5; +} + +.lang-text { + color:#ccd5dc; +} + +button, a { + color: #8ec4f5; +} + +.affix > ul > li.active > a, .affix > ul > li.active > a:before { + color: #8ec4f5; +} + +.affix ul > li.active > a, .affix ul > li.active > a:before { + color: #8ec4f5; +} + +.affix ul > li > a { + color: #d3cfcf; +} + +button:hover, button:focus, a:hover, a:focus { + color: #339eff; +} + +.toc .nav > li.active > a { + color: #8ec4f5; +} + +.toc .nav > li.active > a:hover, .toc .nav > li.active > a:focus { + color: #339eff; +} + +.navbar-inverse .navbar-nav > li > a, .navbar-inverse .navbar-text { + color: #9898a6; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:focus, +.navbar-inverse .navbar-nav>.active>a:hover { + color: #fff; +} + +.hljs-attr,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-title { + color:#b685ff; +} + +.hljs-keyword,.hljs-selector-tag,.hljs-type { + color:#cc74a6; +} + +.hljs-emphasis,.hljs-quote,.hljs-string,.hljs-strong,.hljs-template-variable,.hljs-variable { + color:#cc74a6 +} + +#breadcrumb .breadcrumb >li a { + color: #999; +} + +.toc-filter > input { + color: #d3cfcf; +} \ No newline at end of file diff --git a/crates/misc/dotnet/docs/toc.yml b/crates/misc/dotnet/docs/toc.yml new file mode 100644 index 0000000000..067dee7d4d --- /dev/null +++ b/crates/misc/dotnet/docs/toc.yml @@ -0,0 +1,7 @@ +- name: Articles + href: articles/ +- name: API Documentation + href: api/ + homepage: api/index.md +- name: GitHub Repo + href: https://github.com/bytecodealliance/wasmtime diff --git a/crates/misc/dotnet/docs/wasm/intro/hello.wasm b/crates/misc/dotnet/docs/wasm/intro/hello.wasm new file mode 100755 index 0000000000000000000000000000000000000000..9262fdacaa4689f29dcb997e2fe4627ed81f00aa GIT binary patch literal 165 zcmYj}OAdlC00rM`0VBl(NnE(owWr`DrG_S$Kp_P+uG#~5D39i6 Global = new MutableGlobal(1); + } + + class Program + { + static void Main(string[] args) + { + using (var engine = new Engine()) + using (var store = engine.CreateStore()) + using (var module = store.CreateModule("global.wasm")) + using (dynamic instance = module.Instantiate(new Host())) + { + instance.run(20); + } + } + } +} diff --git a/crates/misc/dotnet/examples/global/global.csproj b/crates/misc/dotnet/examples/global/global.csproj new file mode 100644 index 0000000000..0ac0a26c74 --- /dev/null +++ b/crates/misc/dotnet/examples/global/global.csproj @@ -0,0 +1,21 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + + + + + + diff --git a/crates/misc/dotnet/examples/global/global.wasm b/crates/misc/dotnet/examples/global/global.wasm new file mode 100644 index 0000000000000000000000000000000000000000..eb76274f5bb26b0cbe9e5562278928a1ab3bee09 GIT binary patch literal 95 zcmXBJTM9rx7zWVq`=^jgS%5TRAuPZe%yW=plt5J9ZRl&2Ch45`j%17ppS^s3EKqZ8Lav*S`xQ=`w2Bri^u57Phu literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/examples/global/global.wat b/crates/misc/dotnet/examples/global/global.wat new file mode 100644 index 0000000000..22e93a9c9c --- /dev/null +++ b/crates/misc/dotnet/examples/global/global.wat @@ -0,0 +1,22 @@ +(module + (import "" "print_global" (func $.print_global)) + (import "" "global" (global $.global (mut i32))) + (func $run (param i32) (local $i i32) + loop $l1 + call $.print_global + global.get $.global + i32.const 2 + i32.mul + global.set $.global + local.get $i + i32.const 1 + i32.add + local.set $i + local.get $i + local.get 0 + i32.le_u + br_if $l1 + end + ) + (export "run" (func $run)) +) diff --git a/crates/misc/dotnet/examples/hello/Program.cs b/crates/misc/dotnet/examples/hello/Program.cs new file mode 100644 index 0000000000..c4d853386b --- /dev/null +++ b/crates/misc/dotnet/examples/hello/Program.cs @@ -0,0 +1,30 @@ +using System; +using Wasmtime; + +namespace HelloExample +{ + class Host : IHost + { + public Instance Instance { get; set; } + + [Import("hello")] + public void SayHello() + { + Console.WriteLine("Hello from C#, WebAssembly!"); + } + } + + class Program + { + static void Main(string[] args) + { + using (var engine = new Engine()) + using (var store = engine.CreateStore()) + using (var module = store.CreateModule("hello.wasm")) + using (dynamic instance = module.Instantiate(new Host())) + { + instance.run(); + } + } + } +} diff --git a/crates/misc/dotnet/examples/hello/hello.csproj b/crates/misc/dotnet/examples/hello/hello.csproj new file mode 100644 index 0000000000..0ac0a26c74 --- /dev/null +++ b/crates/misc/dotnet/examples/hello/hello.csproj @@ -0,0 +1,21 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + + + + + + diff --git a/crates/misc/dotnet/examples/hello/hello.wasm b/crates/misc/dotnet/examples/hello/hello.wasm new file mode 100644 index 0000000000000000000000000000000000000000..dbc6d58cb17eb2fc852b806ba726a718d6573089 GIT binary patch literal 47 zcmZQbEY4+QU|?WmVN76PVB%tAV9iL)$;oG6U}j=uU}tA!E-KAqVB}(BWML3s;06GZ Cwgqhf literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/examples/hello/hello.wat b/crates/misc/dotnet/examples/hello/hello.wat new file mode 100644 index 0000000000..f7fb8df1aa --- /dev/null +++ b/crates/misc/dotnet/examples/hello/hello.wat @@ -0,0 +1,8 @@ +(module + (type $t0 (func)) + (import "" "hello" (func $.hello (type $t0))) + (func $run + call $.hello + ) + (export "run" (func $run)) +) diff --git a/crates/misc/dotnet/examples/memory/Program.cs b/crates/misc/dotnet/examples/memory/Program.cs new file mode 100644 index 0000000000..f42b806fec --- /dev/null +++ b/crates/misc/dotnet/examples/memory/Program.cs @@ -0,0 +1,31 @@ +using System; +using Wasmtime; + +namespace HelloExample +{ + class Host : IHost + { + public Instance Instance { get; set; } + + [Import("log")] + public void Log(int address, int length) + { + var message = Instance.Externs.Memories[0].ReadString(address, length); + Console.WriteLine($"Message from WebAssembly: {message}"); + } + } + + class Program + { + static void Main(string[] args) + { + using (var engine = new Engine()) + using (var store = engine.CreateStore()) + using (var module = store.CreateModule("memory.wasm")) + using (dynamic instance = module.Instantiate(new Host())) + { + instance.run(); + } + } + } +} diff --git a/crates/misc/dotnet/examples/memory/memory.csproj b/crates/misc/dotnet/examples/memory/memory.csproj new file mode 100644 index 0000000000..0ac0a26c74 --- /dev/null +++ b/crates/misc/dotnet/examples/memory/memory.csproj @@ -0,0 +1,21 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + + + + + + diff --git a/crates/misc/dotnet/examples/memory/memory.wasm b/crates/misc/dotnet/examples/memory/memory.wasm new file mode 100644 index 0000000000000000000000000000000000000000..700ec7a856c8a6e75fa69090dfe44291d3609c6e GIT binary patch literal 85 zcmWN@K?;B%5Czcx$0BnG>IPlKT{mcJkhIYRqqSGBw|Y39OaL4uNNWgySi+=o0Y=Ea hBPqiqCdVu=^}RvoXyM^?hU&t + /// Represents an abstract host binding. + /// + public abstract class Binding + { + internal abstract SafeHandle Bind(Store store, IHost host); + + internal static void ThrowBindingException(Import import, MemberInfo member, string message) + { + throw new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}."); + } + + internal static List GetImportBindings(IHost host, Module module) + { + if (host is null) + { + throw new ArgumentNullException(nameof(host)); + } + + if (module is null) + { + throw new ArgumentNullException(nameof(module)); + } + + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + var type = host.GetType(); + var methods = type.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute))); + var fields = type.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute))); + + var bindings = new List(); + foreach (var import in module.Imports.All) + { + switch (import) + { + case FunctionImport func: + bindings.Add(BindFunction(func, methods)); + break; + + case GlobalImport global: + bindings.Add(BindGlobal(global, fields)); + break; + + case MemoryImport memory: + bindings.Add(BindMemory(memory, fields)); + break; + + default: + throw new NotSupportedException("Unsupported import binding type."); + } + } + + return bindings; + } + + private static FunctionBinding BindFunction(FunctionImport import, IEnumerable methods) + { + var method = methods.Where(m => + { + var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute)); + if (attribute is null) + { + return false; + } + + return attribute.Name == import.Name && + ((string.IsNullOrEmpty(attribute.Module) && + string.IsNullOrEmpty(import.ModuleName)) || + attribute.Module == import.ModuleName); + } + ).FirstOrDefault(); + + if (method is null) + { + throw new WasmtimeException($"Failed to bind function import '{import}': the host does not contain a method with a matching 'Import' attribute."); + } + + return new FunctionBinding(import, method); + } + + private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable fields) + { + var field = fields.Where(f => + { + var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); + return attribute.Name == import.Name && + ((string.IsNullOrEmpty(attribute.Module) && + string.IsNullOrEmpty(import.ModuleName)) || + attribute.Module == import.ModuleName); + } + ).FirstOrDefault(); + + if (field is null) + { + throw new WasmtimeException($"Failed to bind global import '{import}': the host does not contain a global field with a matching 'Import' attribute."); + } + + return new GlobalBinding(import, field); + } + + private static MemoryBinding BindMemory(MemoryImport import, IEnumerable fields) + { + var field = fields.Where(f => + { + var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); + return attribute.Name == import.Name && + ((string.IsNullOrEmpty(attribute.Module) && + string.IsNullOrEmpty(import.ModuleName)) || + attribute.Module == import.ModuleName); + } + ).FirstOrDefault(); + + if (field is null) + { + throw new WasmtimeException($"Failed to bind memory import '{import}': the host does not contain a memory field with a matching 'Import' attribute."); + } + + return new MemoryBinding(import, field); + } + } +} diff --git a/crates/misc/dotnet/src/Bindings/FunctionBinding.cs b/crates/misc/dotnet/src/Bindings/FunctionBinding.cs new file mode 100644 index 0000000000..1941b20cf8 --- /dev/null +++ b/crates/misc/dotnet/src/Bindings/FunctionBinding.cs @@ -0,0 +1,346 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Wasmtime.Imports; + +namespace Wasmtime.Bindings +{ + /// + /// Represents a host function binding. + /// + public class FunctionBinding : Binding + { + /// + /// Constructs a new function binding. + /// + /// The function import of the binding. + /// The method the import is bound to. + public FunctionBinding(FunctionImport import, MethodInfo method) + { + if (import is null) + { + throw new ArgumentNullException(nameof(import)); + } + + if (method is null) + { + throw new ArgumentNullException(nameof(method)); + } + + Import = import; + Method = method; + + Validate(); + } + + /// + /// The function import of the binding. + /// + public FunctionImport Import { get; private set; } + + /// + /// The method the import is bound to. + /// + public MethodInfo Method { get; private set; } + + internal override SafeHandle Bind(Store store, IHost host) + { + unsafe + { + if (_callback != null) + { + throw new InvalidOperationException("Cannot bind more than once."); + } + + _callback = CreateCallback(store, host); + + var parameters = Interop.ToValueTypeVec(Import.Parameters); + var results = Interop.ToValueTypeVec(Import.Results); + using (var funcType = Interop.wasm_functype_new(ref parameters, ref results)) + { + return Interop.wasm_func_new(store.Handle, funcType, _callback); + } + } + } + + private void Validate() + { + if (Method.IsStatic) + { + ThrowBindingException(Import, Method, "method cannot be static"); + } + + if (Method.IsGenericMethod) + { + ThrowBindingException(Import, Method, "method cannot be generic"); + } + + if (Method.IsConstructor) + { + ThrowBindingException(Import, Method, "method cannot be a constructor"); + } + + ValidateParameters(); + + ValidateReturnType(); + } + + private void ValidateParameters() + { + var parameters = Method.GetParameters(); + if (parameters.Length != Import.Parameters.Count) + { + ThrowBindingException( + Import, + Method, + $"parameter mismatch: import requires {Import.Parameters.Count} but the method has {parameters.Length}"); + } + + for (int i = 0; i < parameters.Length; ++i) + { + var parameter = parameters[i]; + if (parameter.ParameterType.IsByRef) + { + if (parameter.IsOut) + { + ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter"); + } + else + { + ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter"); + } + } + + var expected = Import.Parameters[i]; + if (!Interop.TryGetValueKind(parameter.ParameterType, out var kind) || !Interop.IsMatchingKind(kind, expected)) + { + ThrowBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'"); + } + } + } + + private void ValidateReturnType() + { + int resultsCount = Import.Results.Count(); + if (resultsCount == 0) + { + if (Method.ReturnType != typeof(void)) + { + ThrowBindingException(Import, Method, "method must return void"); + } + } + else if (resultsCount == 1) + { + var expected = Import.Results[0]; + if (!Interop.TryGetValueKind(Method.ReturnType, out var kind) || !Interop.IsMatchingKind(kind, expected)) + { + ThrowBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'"); + } + } + else + { + if (!IsTupleOfSize(Method.ReturnType, resultsCount)) + { + ThrowBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}"); + } + + var typeArguments = + Method.ReturnType.GetGenericArguments().SelectMany(type => + { + if (type.IsConstructedGenericType) + { + return type.GenericTypeArguments; + } + return Enumerable.Repeat(type, 1); + }); + + int i = 0; + foreach (var typeArgument in typeArguments) + { + var expected = Import.Results[i]; + if (!Interop.TryGetValueKind(typeArgument, out var kind) || !Interop.IsMatchingKind(kind, expected)) + { + ThrowBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'"); + } + + ++i; + } + } + } + + private static bool IsTupleOfSize(Type type, int size) + { + if (!type.IsConstructedGenericType) + { + return false; + } + + var definition = type.GetGenericTypeDefinition(); + + if (size == 0) + { + return definition == typeof(ValueTuple); + } + + if (size == 1) + { + return definition == typeof(ValueTuple<>); + } + + if (size == 2) + { + return definition == typeof(ValueTuple<,>); + } + + if (size == 3) + { + return definition == typeof(ValueTuple<,,>); + } + + if (size == 4) + { + return definition == typeof(ValueTuple<,,,>); + } + + if (size == 5) + { + return definition == typeof(ValueTuple<,,,,>); + } + + if (size == 6) + { + return definition == typeof(ValueTuple<,,,,,>); + } + + if (size == 7) + { + return definition == typeof(ValueTuple<,,,,,,>); + } + + if (definition != typeof(ValueTuple<,,,,,,,>)) + { + return false; + } + + return IsTupleOfSize(type.GetGenericArguments().Last(), size - 7); + } + + private unsafe Interop.WasmFuncCallback CreateCallback(Store store, IHost host) + { + var args = new object[Import.Parameters.Count]; + bool hasReturn = Method.ReturnType != typeof(void); + var storeHandle = store.Handle; + + Interop.WasmFuncCallback callback = (arguments, results) => + { + try + { + SetArgs(arguments, args); + + var result = Method.Invoke(host, args); + + if (hasReturn) + { + SetResults(result, results); + } + return IntPtr.Zero; + } + catch (TargetInvocationException ex) + { + var bytes = Encoding.UTF8.GetBytes(ex.InnerException.Message + "\0" /* exception messages need a null */); + + fixed (byte* ptr = bytes) + { + Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t(); + message.size = (UIntPtr)bytes.Length; + message.data = ptr; + + return Interop.wasm_trap_new(storeHandle, ref message); + } + } + }; + + return callback; + } + + private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args) + { + for (int i = 0; i < args.Length; ++i) + { + var arg = arguments[i]; + + switch (arg.kind) + { + case Interop.wasm_valkind_t.WASM_I32: + args[i] = arg.of.i32; + break; + + case Interop.wasm_valkind_t.WASM_I64: + args[i] = arg.of.i64; + break; + + case Interop.wasm_valkind_t.WASM_F32: + args[i] = arg.of.f32; + break; + + case Interop.wasm_valkind_t.WASM_F64: + args[i] = arg.of.f64; + break; + + default: + throw new NotSupportedException("Unsupported value type."); + } + } + } + + private static unsafe void SetResults(object value, Interop.wasm_val_t* results) + { + var tuple = value as ITuple; + if (tuple is null) + { + SetResult(value, &results[0]); + } + else + { + for (int i = 0; i < tuple.Length; ++i) + { + SetResults(tuple[i], &results[i]); + } + } + } + + private static unsafe void SetResult(object value, Interop.wasm_val_t* result) + { + switch (value) + { + case int i: + result->kind = Interop.wasm_valkind_t.WASM_I32; + result->of.i32 = i; + break; + + case long l: + result->kind = Interop.wasm_valkind_t.WASM_I64; + result->of.i64 = l; + break; + + case float f: + result->kind = Interop.wasm_valkind_t.WASM_F32; + result->of.f32 = f; + break; + + case double d: + result->kind = Interop.wasm_valkind_t.WASM_F64; + result->of.f64 = d; + break; + + default: + throw new NotSupportedException("Unsupported return value type."); + } + } + + private Interop.WasmFuncCallback _callback; + } +} diff --git a/crates/misc/dotnet/src/Bindings/GlobalBinding.cs b/crates/misc/dotnet/src/Bindings/GlobalBinding.cs new file mode 100644 index 0000000000..33ebeb71c0 --- /dev/null +++ b/crates/misc/dotnet/src/Bindings/GlobalBinding.cs @@ -0,0 +1,125 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Wasmtime.Imports; + +namespace Wasmtime.Bindings +{ + /// + /// Represents a host global binding. + /// + public class GlobalBinding : Binding + { + /// + /// Constructs a new global binding. + /// + /// The global import of the binding. + /// The field the import is bound to. + public GlobalBinding(GlobalImport import, FieldInfo field) + { + if (import is null) + { + throw new ArgumentNullException(nameof(import)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + Import = import; + Field = field; + + Validate(); + } + + /// + /// The global import of the binding. + /// + public GlobalImport Import { get; private set; } + + /// + /// The field the import is bound to. + /// + public FieldInfo Field { get; private set; } + + internal override SafeHandle Bind(Store store, IHost host) + { + unsafe + { + dynamic global = Field.GetValue(host); + if (global.Handle != null) + { + throw new InvalidOperationException("Cannot bind more than once."); + } + + var v = Interop.ToValue((object)global.InitialValue, Import.Kind); + + var valueType = Interop.wasm_valtype_new(v.kind); + var valueTypeHandle = valueType.DangerousGetHandle(); + valueType.SetHandleAsInvalid(); + + using (var globalType = Interop.wasm_globaltype_new( + valueTypeHandle, + Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST)) + { + var handle = Interop.wasm_global_new(store.Handle, globalType, &v); + global.Handle = handle; + return handle; + } + } + } + + private void Validate() + { + if (Field.IsStatic) + { + ThrowBindingException(Import, Field, "field cannot be static"); + } + + if (!Field.IsInitOnly) + { + ThrowBindingException(Import, Field, "field must be readonly"); + } + + if (!Field.FieldType.IsGenericType) + { + ThrowBindingException(Import, Field, "field is expected to be of type 'Global'"); + } + + var definition = Field.FieldType.GetGenericTypeDefinition(); + if (definition == typeof(Global<>)) + { + if (Import.IsMutable) + { + ThrowBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)"); + } + } + else if (definition == typeof(MutableGlobal<>)) + { + if (!Import.IsMutable) + { + ThrowBindingException(Import, Field, "the import is constant (use the 'Global' type)"); + } + } + else + { + ThrowBindingException(Import, Field, "field is expected to be of type 'Global' or 'MutableGlobal'"); + } + + var arg = Field.FieldType.GetGenericArguments()[0]; + + if (Interop.TryGetValueKind(arg, out var kind)) + { + if (!Interop.IsMatchingKind(kind, Import.Kind)) + { + ThrowBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'"); + } + } + else + { + ThrowBindingException(Import, Field, $"'{arg}' is not a valid global type"); + } + } + } +} diff --git a/crates/misc/dotnet/src/Bindings/MemoryBinding.cs b/crates/misc/dotnet/src/Bindings/MemoryBinding.cs new file mode 100644 index 0000000000..ea6c860f96 --- /dev/null +++ b/crates/misc/dotnet/src/Bindings/MemoryBinding.cs @@ -0,0 +1,98 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Wasmtime.Imports; + +namespace Wasmtime.Bindings +{ + /// + /// Represents a host memory binding. + /// + public class MemoryBinding : Binding + { + /// + /// Constructs a new memory binding. + /// + /// The memory import of the binding. + /// The field the import is bound to. + public MemoryBinding(MemoryImport import, FieldInfo field) + { + if (import is null) + { + throw new ArgumentNullException(nameof(import)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + Import = import; + Field = field; + + Validate(); + } + + /// + /// The memory import of the binding. + /// + public MemoryImport Import { get; private set; } + + /// + /// The field the import is bound to. + /// + public FieldInfo Field { get; private set; } + + internal override SafeHandle Bind(Store store, IHost host) + { + dynamic memory = Field.GetValue(host); + if (memory.Handle != null) + { + throw new InvalidOperationException("Cannot bind more than once."); + } + + uint min = memory.Minimum; + uint max = memory.Maximum; + + if (min != Import.Minimum) + { + ThrowBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)"); + } + if (max != Import.Maximum) + { + ThrowBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)"); + } + + unsafe + { + Interop.wasm_limits_t limits = new Interop.wasm_limits_t(); + limits.min = min; + limits.max = max; + using (var memoryType = Interop.wasm_memorytype_new(&limits)) + { + var handle = Interop.wasm_memory_new(store.Handle, memoryType); + memory.Handle = handle; + return handle; + } + } + } + + private void Validate() + { + if (Field.IsStatic) + { + ThrowBindingException(Import, Field, "field cannot be static"); + } + + if (!Field.IsInitOnly) + { + ThrowBindingException(Import, Field, "field must be readonly"); + } + + if (Field.FieldType != typeof(Memory)) + { + ThrowBindingException(Import, Field, "field is expected to be of type 'Memory'"); + } + } + } +} diff --git a/crates/misc/dotnet/src/Engine.cs b/crates/misc/dotnet/src/Engine.cs new file mode 100644 index 0000000000..20b727a4ed --- /dev/null +++ b/crates/misc/dotnet/src/Engine.cs @@ -0,0 +1,44 @@ +using System; + +namespace Wasmtime +{ + /// + /// Represents the Wasmtime engine. + /// + public class Engine : IDisposable + { + /// + /// Constructs a new . + /// + public Engine() + { + Handle = Interop.wasm_engine_new(); + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime engine."); + } + } + + /// + /// Creates a new Wasmtime . + /// + /// Returns the new . + public Store CreateStore() + { + return new Store(this); + } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Interop.EngineHandle Handle { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Exports/Export.cs b/crates/misc/dotnet/src/Exports/Export.cs new file mode 100644 index 0000000000..047ad7025e --- /dev/null +++ b/crates/misc/dotnet/src/Exports/Export.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wasmtime.Exports +{ + /// + /// Represents an export of a WebAssembly module. + /// + public abstract class Export + { + internal Export(IntPtr exportType) + { + unsafe + { + var name = Interop.wasm_exporttype_name(exportType); + Name = Marshal.PtrToStringUTF8((IntPtr)name->data, (int)name->size); + } + } + + /// + /// The name of the export. + /// + public string Name { get; private set; } + + /// + public override string ToString() + { + return Name; + } + } +} diff --git a/crates/misc/dotnet/src/Exports/Exports.cs b/crates/misc/dotnet/src/Exports/Exports.cs new file mode 100644 index 0000000000..52d50c3c48 --- /dev/null +++ b/crates/misc/dotnet/src/Exports/Exports.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace Wasmtime.Exports +{ + /// + /// Represents the exports of a WebAssembly module. + /// + public class Exports + { + internal Exports(Module module) + { + Interop.wasm_exporttype_vec_t exports; + Interop.wasm_module_exports(module.Handle, out exports); + + try + { + var all = new List((int)exports.size); + var functions = new List(); + var globals = new List(); + var tables = new List(); + var memories = new List(); + + for (int i = 0; i < (int)exports.size; ++i) + { + unsafe + { + var exportType = exports.data[i]; + var externType = Interop.wasm_exporttype_type(exportType); + + switch (Interop.wasm_externtype_kind(externType)) + { + case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: + var function = new FunctionExport(exportType, externType); + functions.Add(function); + all.Add(function); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: + var global = new GlobalExport(exportType, externType); + globals.Add(global); + all.Add(global); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_TABLE: + var table = new TableExport(exportType, externType); + tables.Add(table); + all.Add(table); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: + var memory = new MemoryExport(exportType, externType); + memories.Add(memory); + all.Add(memory); + break; + + default: + throw new NotSupportedException("Unsupported export extern type."); + } + } + } + + Functions = functions; + Globals = globals; + Tables = tables; + Memories = memories; + All = all; + } + finally + { + Interop.wasm_exporttype_vec_delete(ref exports); + } + } + + /// + /// The exported functions of a WebAssembly module. + /// + public IReadOnlyList Functions { get; private set; } + + /// + /// The exported globals of a WebAssembly module. + /// + public IReadOnlyList Globals { get; private set; } + + /// + /// The exported tables of a WebAssembly module. + /// + public IReadOnlyList Tables { get; private set; } + + /// + /// The exported memories of a WebAssembly module. + /// + public IReadOnlyList Memories { get; private set; } + + internal List All { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Exports/FunctionExport.cs b/crates/misc/dotnet/src/Exports/FunctionExport.cs new file mode 100644 index 0000000000..1b57c65cb3 --- /dev/null +++ b/crates/misc/dotnet/src/Exports/FunctionExport.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Wasmtime.Exports +{ + /// + /// Represents a function exported from a WebAssembly module. + /// + public class FunctionExport : Export + { + internal FunctionExport(IntPtr exportType, IntPtr externType) : base(exportType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_FUNC); + + unsafe + { + var funcType = Interop.wasm_externtype_as_functype_const(externType); + Parameters = Interop.ToValueKindList(Interop.wasm_functype_params(funcType)); + Results = Interop.ToValueKindList(Interop.wasm_functype_results(funcType)); + } + } + + /// + /// The parameter of the exported WebAssembly function. + /// + public IReadOnlyList Parameters { get; private set; } + + /// + /// The results of the exported WebAssembly function. + /// + public IReadOnlyList Results { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Exports/GlobalExport.cs b/crates/misc/dotnet/src/Exports/GlobalExport.cs new file mode 100644 index 0000000000..3dc76be560 --- /dev/null +++ b/crates/misc/dotnet/src/Exports/GlobalExport.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Exports +{ + /// + /// Represents a global variable exported from a WebAssembly module. + /// + public class GlobalExport : Export + { + internal GlobalExport(IntPtr exportType, IntPtr externType) : base(exportType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL); + + var globalType = Interop.wasm_externtype_as_globaltype_const(externType); + Kind = Interop.wasm_valtype_kind(Interop.wasm_globaltype_content(globalType)); + IsMutable = Interop.wasm_globaltype_mutability(globalType) == Interop.wasm_mutability_t.WASM_VAR; + } + + /// + /// The kind of value for the global variable. + /// + public ValueKind Kind { get; private set; } + + /// + /// Determines whether or not the global variable is mutable. + /// + public bool IsMutable { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Exports/MemoryExport.cs b/crates/misc/dotnet/src/Exports/MemoryExport.cs new file mode 100644 index 0000000000..f5f200e39a --- /dev/null +++ b/crates/misc/dotnet/src/Exports/MemoryExport.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Exports +{ + /// + /// Represents a memory exported from a WebAssembly module. + /// + public class MemoryExport : Export + { + internal MemoryExport(IntPtr exportType, IntPtr externType) : base(exportType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_MEMORY); + + var memoryType = Interop.wasm_externtype_as_memorytype_const(externType); + + unsafe + { + var limits = Interop.wasm_memorytype_limits(memoryType); + Minimum = limits->min; + Maximum = limits->max; + } + } + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Minimum { get; private set; } + + /// + /// The maximum memory size (in WebAssembly page units). + /// + public uint Maximum { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Exports/TableExport.cs b/crates/misc/dotnet/src/Exports/TableExport.cs new file mode 100644 index 0000000000..1643aee68d --- /dev/null +++ b/crates/misc/dotnet/src/Exports/TableExport.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Exports +{ + /// + /// Represents a table exported from a WebAssembly module. + /// + public class TableExport : Export + { + internal TableExport(IntPtr exportType, IntPtr externType) : base(exportType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_TABLE); + + var tableType = Interop.wasm_externtype_as_tabletype_const(externType); + + Kind = Interop.wasm_valtype_kind(Interop.wasm_tabletype_element(tableType)); + + unsafe + { + var limits = Interop.wasm_tabletype_limits(tableType); + Minimum = limits->min; + Maximum = limits->max; + } + } + + /// + /// The value kind of the table. + /// + public ValueKind Kind { get; private set; } + + /// + /// The minimum number of elements in the table. + /// + public uint Minimum { get; private set; } + + /// + /// The maximum number of elements in the table. + /// + public uint Maximum { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Externs/ExternFunction.cs b/crates/misc/dotnet/src/Externs/ExternFunction.cs new file mode 100644 index 0000000000..462f3b9ffe --- /dev/null +++ b/crates/misc/dotnet/src/Externs/ExternFunction.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using Wasmtime.Exports; + +namespace Wasmtime.Externs +{ + /// + /// Represents an external (instantiated) WebAssembly function. + /// + public class ExternFunction + { + internal ExternFunction(FunctionExport export, IntPtr func) + { + _export = export; + _func = func; + } + + /// + /// The name of the WebAssembly function. + /// + public string Name => _export.Name; + + /// + /// The parameters of the WebAssembly function. + /// + public IReadOnlyList Parameters => _export.Parameters; + + /// + /// The results of the WebAssembly function. + /// + public IReadOnlyList Results => _export.Results; + + /// + /// Invokes the WebAssembly function. + /// + /// The array of arguments to pass to the function. + /// + /// Returns null if the function has no return value. + /// Returns the value if the function returns a single value. + /// Returns an array of values if the function returns more than one value. + /// + public object Invoke(params object[] arguments) + { + if (arguments.Length != Parameters.Count) + { + throw new WasmtimeException($"Argument mismatch when invoking function '{Name}': requires {Parameters.Count} but was given {arguments.Length}."); + } + + unsafe + { + Interop.wasm_val_t* args = stackalloc Interop.wasm_val_t[Parameters.Count]; + Interop.wasm_val_t* results = stackalloc Interop.wasm_val_t[Results.Count]; + + for (int i = 0; i < arguments.Length; ++i) + { + args[i] = Interop.ToValue(arguments[i], Parameters[i]); + } + + var trap = Interop.wasm_func_call(_func, args, results); + if (trap != IntPtr.Zero) + { + throw TrapException.FromOwnedTrap(trap); + } + + if (Results.Count == 0) + { + return null; + } + + if (Results.Count == 1) + { + return Interop.ToObject(&results[0]); + } + + var ret = new object[Results.Count]; + for (int i = 0; i < Results.Count; ++i) + { + ret[i] = Interop.ToObject(&results[i]); + } + return ret; + } + } + + private FunctionExport _export; + private IntPtr _func; + } +} diff --git a/crates/misc/dotnet/src/Externs/ExternGlobal.cs b/crates/misc/dotnet/src/Externs/ExternGlobal.cs new file mode 100644 index 0000000000..087260058c --- /dev/null +++ b/crates/misc/dotnet/src/Externs/ExternGlobal.cs @@ -0,0 +1,62 @@ +using System; +using Wasmtime.Exports; + +namespace Wasmtime.Externs +{ + /// + /// Represents an external (instantiated) WebAssembly global. + /// + public class ExternGlobal + { + internal ExternGlobal(GlobalExport export, IntPtr global) + { + _export = export; + _global = global; + } + + /// + /// The name of the WebAssembly global. + /// + public string Name => _export.Name; + + /// + /// The kind of value for the global variable. + /// + public ValueKind Kind => _export.Kind; + + /// + /// Determines whether or not the global variable is mutable. + /// + public bool IsMutable => _export.IsMutable; + + public object Value + { + get + { + unsafe + { + var v = stackalloc Interop.wasm_val_t[1]; + Interop.wasm_global_get(_global, v); + return Interop.ToObject(v); + } + } + set + { + if (!IsMutable) + { + throw new InvalidOperationException($"The value of global '{Name}' cannot be modified."); + } + + var v = Interop.ToValue(value, Kind); + + unsafe + { + Interop.wasm_global_set(_global, &v); + } + } + } + + private GlobalExport _export; + private IntPtr _global; + } +} diff --git a/crates/misc/dotnet/src/Externs/ExternMemory.cs b/crates/misc/dotnet/src/Externs/ExternMemory.cs new file mode 100644 index 0000000000..a9011aacb7 --- /dev/null +++ b/crates/misc/dotnet/src/Externs/ExternMemory.cs @@ -0,0 +1,245 @@ +using System; +using System.Buffers.Binary; +using System.Text; +using Wasmtime.Exports; + +namespace Wasmtime.Externs +{ + /// + /// Represents an external (instantiated) WebAssembly memory. + /// + public class ExternMemory + { + internal ExternMemory(MemoryExport export, IntPtr memory) + { + _export = export; + _memory = memory; + } + + /// + /// The name of the WebAssembly memory. + /// + public string Name => _export.Name; + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Minimum => _export.Minimum; + + /// + /// The maximum memory size (in WebAssembly page units). + /// + public uint Maximum => _export.Maximum; + + /// + /// The span of the memory. + /// + /// + /// The span may become invalid if the memory grows. + /// + /// This may happen if the memory is explicitly requested to grow or + /// grows as a result of WebAssembly execution. + /// + /// Therefore, the returned Span should not be stored. + /// + public unsafe Span Span + { + get + { + var data = Interop.wasm_memory_data(_memory); + var size = Convert.ToInt32(Interop.wasm_memory_data_size(_memory).ToUInt32()); + return new Span(data, size); + } + } + + /// + /// Reads a UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// The length of bytes to read. + /// Returns the string read from memory. + public string ReadString(int address, int length) + { + return Encoding.UTF8.GetString(Span.Slice(address, length)); + } + + /// + /// Writes a UTF-8 string at the given address. + /// + /// The zero-based address to write to. + /// The string to write. + /// Returns the number of bytes written. + public int WriteString(int address, string value) + { + return Encoding.UTF8.GetBytes(value, Span.Slice(address)); + } + + /// + /// Reads a byte from memory. + /// + /// The zero-based address to read from. + /// Returns the byte read from memory. + public byte ReadByte(int address) + { + return Span[address]; + } + + /// + /// Writes a byte to memory. + /// + /// The zero-based address to write to. + /// The byte to write. + public void WriteByte(int address, byte value) + { + Span[address] = value; + } + + /// + /// Reads a short from memory. + /// + /// The zero-based address to read from. + /// Returns the short read from memory. + public short ReadInt16(int address) + { + return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2)); + } + + /// + /// Writes a short to memory. + /// + /// The zero-based address to write to. + /// The short to write. + public void WriteInt16(int address, short value) + { + BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value); + } + + /// + /// Reads an int from memory. + /// + /// The zero-based address to read from. + /// Returns the int read from memory. + public int ReadInt32(int address) + { + return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4)); + } + + /// + /// Writes an int to memory. + /// + /// The zero-based address to write to. + /// The int to write. + public void WriteInt32(int address, int value) + { + BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value); + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public long ReadInt64(int address) + { + return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8)); + } + + /// + /// Writes a long to memory. + /// + /// The zero-based address to write to. + /// The long to write. + public void WriteInt64(int address, long value) + { + BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value); + } + + /// + /// Reads an IntPtr from memory. + /// + /// The zero-based address to read from. + /// Returns the IntPtr read from memory. + public IntPtr ReadIntPtr(int address) + { + if (IntPtr.Size == 4) + { + return (IntPtr)ReadInt32(address); + } + return (IntPtr)ReadInt64(address); + } + + /// + /// Writes an IntPtr to memory. + /// + /// The zero-based address to write to. + /// The IntPtr to write. + public void WriteIntPtr(int address, IntPtr value) + { + if (IntPtr.Size == 4) + { + WriteInt32(address, value.ToInt32()); + } + else + { + WriteInt64(address, value.ToInt64()); + } + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public float ReadSingle(int address) + { + unsafe + { + var i = ReadInt32(address); + return *((float*)&i); + } + } + + /// + /// Writes a single to memory. + /// + /// The zero-based address to write to. + /// The single to write. + public void WriteSingle(int address, float value) + { + unsafe + { + WriteInt32(address, *(int*)&value); + } + } + + /// + /// Reads a double from memory. + /// + /// The zero-based address to read from. + /// Returns the double read from memory. + public double ReadDouble(int address) + { + unsafe + { + var i = ReadInt64(address); + return *((double*)&i); + } + } + + /// + /// Writes a double to memory. + /// + /// The zero-based address to write to. + /// The double to write. + public void WriteDouble(int address, double value) + { + unsafe + { + WriteInt64(address, *(long*)&value); + } + } + + private MemoryExport _export; + private IntPtr _memory; + } +} diff --git a/crates/misc/dotnet/src/Externs/Externs.cs b/crates/misc/dotnet/src/Externs/Externs.cs new file mode 100644 index 0000000000..078f179180 --- /dev/null +++ b/crates/misc/dotnet/src/Externs/Externs.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Wasmtime.Exports; + +namespace Wasmtime.Externs +{ + /// + /// Represents external (instantiated) WebAssembly functions, globals, tables, and memories. + /// + public class Externs + { + internal Externs(Wasmtime.Exports.Exports exports, Interop.wasm_extern_vec_t externs) + { + var functions = new List(); + var globals = new List(); + var memories = new List(); + + for (int i = 0; i < (int)externs.size; ++i) + { + unsafe + { + var ext = externs.data[i]; + + switch (Interop.wasm_extern_kind(ext)) + { + case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: + var function = new ExternFunction((FunctionExport)exports.All[i], Interop.wasm_extern_as_func(ext)); + functions.Add(function); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: + var global = new ExternGlobal((GlobalExport)exports.All[i], Interop.wasm_extern_as_global(ext)); + globals.Add(global); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: + var memory = new ExternMemory((MemoryExport)exports.All[i], Interop.wasm_extern_as_memory(ext)); + memories.Add(memory); + break; + + default: + throw new NotSupportedException("Unsupported extern type."); + } + } + } + + Functions = functions; + Globals = globals; + Memories = memories; + } + + /// + /// The extern functions from an instantiated WebAssembly module. + /// + public IReadOnlyList Functions { get; private set; } + + /// + /// The extern globals from an instantiated WebAssembly module. + /// + public IReadOnlyList Globals { get; private set; } + + /// + /// The extern memories from an instantiated WebAssembly module. + /// + public IReadOnlyList Memories { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Global.cs b/crates/misc/dotnet/src/Global.cs new file mode 100644 index 0000000000..13ca0e285e --- /dev/null +++ b/crates/misc/dotnet/src/Global.cs @@ -0,0 +1,50 @@ +using System; + +namespace Wasmtime +{ + /// + /// Represents a constant WebAssembly global value. + /// + public class Global + { + /// + /// Creates a new with the given initial value. + /// + /// The initial value of the global. + public Global(T initialValue) + { + InitialValue = initialValue; + Kind = Interop.ToValueKind(typeof(T)); + } + + /// + /// The value of the global. + /// + public T Value + { + get + { + if (Handle is null) + { + throw new InvalidOperationException("The global cannot be used before it is bound to a module instance."); + } + + unsafe + { + var v = stackalloc Interop.wasm_val_t[1]; + + Interop.wasm_global_get(Handle.DangerousGetHandle(), v); + + // TODO: figure out a way that doesn't box the value + return (T)Interop.ToObject(v); + } + } + } + + internal ValueKind Kind { get; private set; } + + internal Interop.GlobalHandle Handle { get; set; } + + internal T InitialValue { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/IHost.cs b/crates/misc/dotnet/src/IHost.cs new file mode 100644 index 0000000000..f8a3f4175f --- /dev/null +++ b/crates/misc/dotnet/src/IHost.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Wasmtime.Bindings; + +namespace Wasmtime +{ + /// + /// The interface implemented by Wasmtime hosts. + /// + public interface IHost + { + /// + /// The that the host is bound to. + /// + /// A host can only bind to one module instance at a time. + Instance Instance { get; set; } + + /// + /// Gets the import bindings of the host given a WebAssembly module. + /// + /// The WebAssembly module to get the import bindings for. + /// Returns the list of import bindings for the host. + List GetImportBindings(Module module) => Binding.GetImportBindings(this, module); + } +} diff --git a/crates/misc/dotnet/src/ImportAttribute.cs b/crates/misc/dotnet/src/ImportAttribute.cs new file mode 100644 index 0000000000..ff06fc4cb3 --- /dev/null +++ b/crates/misc/dotnet/src/ImportAttribute.cs @@ -0,0 +1,31 @@ +using System; + +namespace Wasmtime +{ + /// + /// Used to mark .NET methods and fields as imports to a WebAssembly module. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class ImportAttribute : Attribute + { + /// + /// Constructs a new . + /// + /// The name of the import. + public ImportAttribute(string name) + { + Name = name; + } + + /// + /// The name of the import. + /// + public string Name { get; set; } + + /// + /// The module name of the import. + /// + /// A null or empty module name implies that the import is not scoped to a module. + public string Module { get; set; } + } +} diff --git a/crates/misc/dotnet/src/Imports/FunctionImport.cs b/crates/misc/dotnet/src/Imports/FunctionImport.cs new file mode 100644 index 0000000000..0d963a657e --- /dev/null +++ b/crates/misc/dotnet/src/Imports/FunctionImport.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Wasmtime.Imports +{ + /// + /// Represents a function imported to a WebAssembly module. + /// + public class FunctionImport : Import + { + internal FunctionImport(IntPtr importType, IntPtr externType) : base(importType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_FUNC); + + unsafe + { + var funcType = Interop.wasm_externtype_as_functype_const(externType); + Parameters = Interop.ToValueKindList(Interop.wasm_functype_params(funcType)); + Results = Interop.ToValueKindList(Interop.wasm_functype_results(funcType)); + } + } + + /// + /// The parameters of the imported function. + /// + public IReadOnlyList Parameters { get; private set; } + + /// + /// The results of the imported function. + /// + public IReadOnlyList Results { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Imports/GlobalImport.cs b/crates/misc/dotnet/src/Imports/GlobalImport.cs new file mode 100644 index 0000000000..0f92b40f19 --- /dev/null +++ b/crates/misc/dotnet/src/Imports/GlobalImport.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Imports +{ + /// + /// Represents a global variable imported to a WebAssembly module. + /// + public class GlobalImport : Import + { + internal GlobalImport(IntPtr importType, IntPtr externType) : base(importType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL); + + var globalType = Interop.wasm_externtype_as_globaltype_const(externType); + Kind = Interop.wasm_valtype_kind(Interop.wasm_globaltype_content(globalType)); + IsMutable = Interop.wasm_globaltype_mutability(globalType) == Interop.wasm_mutability_t.WASM_VAR; + } + + /// + /// The kind of value for the global variable. + /// + public ValueKind Kind { get; private set; } + + /// + /// Determines whether or not the global variable is mutable. + /// + public bool IsMutable { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Imports/Import.cs b/crates/misc/dotnet/src/Imports/Import.cs new file mode 100644 index 0000000000..f2e8115027 --- /dev/null +++ b/crates/misc/dotnet/src/Imports/Import.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wasmtime.Imports +{ + /// + /// The base class for import types. + /// + public abstract class Import + { + internal Import(IntPtr importType) + { + unsafe + { + var moduleName = Interop.wasm_importtype_module(importType); + ModuleName = Marshal.PtrToStringUTF8((IntPtr)moduleName->data, (int)moduleName->size); + + var name = Interop.wasm_importtype_name(importType); + Name = Marshal.PtrToStringUTF8((IntPtr)name->data, (int)name->size); + } + } + + /// + /// The module name of the import. + /// + public string ModuleName { get; private set; } + + /// + /// The name of the import. + /// + public string Name { get; private set; } + + /// + public override string ToString() + { + return $"{ModuleName}{(string.IsNullOrEmpty(ModuleName) ? "" : ".")}{Name}"; + } + } +} diff --git a/crates/misc/dotnet/src/Imports/Imports.cs b/crates/misc/dotnet/src/Imports/Imports.cs new file mode 100644 index 0000000000..153fee3363 --- /dev/null +++ b/crates/misc/dotnet/src/Imports/Imports.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace Wasmtime.Imports +{ + /// + /// Represents imported functions, globals, tables, and memories to a WebAssembly module. + /// + public class Imports + { + internal Imports(Module module) + { + Interop.wasm_importtype_vec_t imports; + Interop.wasm_module_imports(module.Handle, out imports); + + try + { + var all = new List((int)imports.size); + var functions = new List(); + var globals = new List(); + var tables = new List(); + var memories = new List(); + + for (int i = 0; i < (int)imports.size; ++i) + { + unsafe + { + var importType = imports.data[i]; + var externType = Interop.wasm_importtype_type(importType); + + switch (Interop.wasm_externtype_kind(externType)) + { + case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: + var function = new FunctionImport(importType, externType); + functions.Add(function); + all.Add(function); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: + var global = new GlobalImport(importType, externType); + globals.Add(global); + all.Add(global); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_TABLE: + var table = new TableImport(importType, externType); + tables.Add(table); + all.Add(table); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: + var memory = new MemoryImport(importType, externType); + memories.Add(memory); + all.Add(memory); + break; + + default: + throw new NotSupportedException("Unsupported import extern type."); + } + } + } + + Functions = functions; + Globals = globals; + Tables = tables; + Memories = memories; + All = all; + } + finally + { + Interop.wasm_importtype_vec_delete(ref imports); + } + } + + /// + /// The imported functions required by a WebAssembly module. + /// + public IReadOnlyList Functions { get; private set; } + + /// + /// The imported globals required by a WebAssembly module. + /// + public IReadOnlyList Globals { get; private set; } + + /// + /// The imported tables required by a WebAssembly module. + /// + public IReadOnlyList Tables { get; private set; } + + /// + /// The imported memories required by a WebAssembly module. + /// + public IReadOnlyList Memories { get; private set; } + + internal IReadOnlyList All { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Imports/MemoryImport.cs b/crates/misc/dotnet/src/Imports/MemoryImport.cs new file mode 100644 index 0000000000..3b6769c888 --- /dev/null +++ b/crates/misc/dotnet/src/Imports/MemoryImport.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Imports +{ + /// + /// Represents a memory imported to a WebAssembly module. + /// + public class MemoryImport : Import + { + internal MemoryImport(IntPtr importType, IntPtr externType) : base(importType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_MEMORY); + + var memoryType = Interop.wasm_externtype_as_memorytype_const(externType); + + unsafe + { + var limits = Interop.wasm_memorytype_limits(memoryType); + Minimum = limits->min; + Maximum = limits->max; + } + } + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Minimum { get; private set; } + + /// + /// The maximum memory size (in WebAssembly page units). + /// + public uint Maximum { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Imports/TableImport.cs b/crates/misc/dotnet/src/Imports/TableImport.cs new file mode 100644 index 0000000000..352a4c1047 --- /dev/null +++ b/crates/misc/dotnet/src/Imports/TableImport.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; + +namespace Wasmtime.Imports +{ + /// + /// Represents a table imported to a WebAssembly module. + /// + public class TableImport : Import + { + internal TableImport(IntPtr importType, IntPtr externType) : base(importType) + { + Debug.Assert(Interop.wasm_externtype_kind(externType) == Interop.wasm_externkind_t.WASM_EXTERN_TABLE); + + var tableType = Interop.wasm_externtype_as_tabletype_const(externType); + + Kind = Interop.wasm_valtype_kind(Interop.wasm_tabletype_element(tableType)); + + unsafe + { + var limits = Interop.wasm_tabletype_limits(tableType); + Minimum = limits->min; + Maximum = limits->max; + } + } + + /// + /// The value kind of the table. + /// + public ValueKind Kind { get; private set; } + + /// + /// The minimum number of elements in the table. + /// + public uint Minimum { get; private set; } + + /// + /// The maximum number of elements in the table. + /// + public uint Maximum { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Instance.cs b/crates/misc/dotnet/src/Instance.cs new file mode 100644 index 0000000000..c145d2be83 --- /dev/null +++ b/crates/misc/dotnet/src/Instance.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Dynamic; +using Wasmtime.Externs; + +namespace Wasmtime +{ + /// + /// Represents an instantiated WebAssembly module. + /// + public class Instance : DynamicObject, IDisposable + { + internal Instance(Module module, IHost host) + { + Host = host; + Module = module; + + var bindings = host.GetImportBindings(module); + var handles = bindings.Select(b => b.Bind(module.Store, host)).ToList(); + + unsafe + { + Handle = Interop.wasm_instance_new( + Module.Store.Handle, + Module.Handle, + handles.Select(h => ToExtern(h)).ToArray(), + out var trap); + + if (trap != IntPtr.Zero) + { + throw TrapException.FromOwnedTrap(trap); + } + } + + if (Handle.IsInvalid) + { + throw new WasmtimeException($"Failed to instantiate module '{module.Name}'."); + } + + // Dispose of all function handles (not needed at runtime) + foreach (var h in handles.Where(h => h is Interop.FunctionHandle)) + { + h.Dispose(); + } + + Interop.wasm_instance_exports(Handle, out _externs); + + Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs); + + _functions = Externs.Functions.ToDictionary(f => f.Name); + _globals = Externs.Globals.ToDictionary(g => g.Name); + } + + /// + /// The host associated with this instance. + /// + public IHost Host { get; private set; } + + /// + /// The WebAssembly module associated with the instantiation. + /// + public Module Module { get; private set; } + + /// + /// The external (instantiated) collection of functions, globals, tables, and memories. + /// + public Wasmtime.Externs.Externs Externs { get; private set; } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + if (_externs.size != UIntPtr.Zero) + { + Interop.wasm_extern_vec_delete(ref _externs); + _externs.size = UIntPtr.Zero; + } + } + + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (_globals.TryGetValue(binder.Name, out var global)) + { + result = global.Value; + return true; + } + result = null; + return false; + } + + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (_globals.TryGetValue(binder.Name, out var global)) + { + global.Value = value; + return true; + } + return false; + } + + /// + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + if (!_functions.TryGetValue(binder.Name, out var func)) + { + result = null; + return false; + } + + result = func.Invoke(args); + return true; + } + + private static unsafe IntPtr ToExtern(SafeHandle handle) + { + switch (handle) + { + case Interop.FunctionHandle f: + return Interop.wasm_func_as_extern(f); + + case Interop.GlobalHandle g: + return Interop.wasm_global_as_extern(g); + + case Interop.MemoryHandle m: + return Interop.wasm_memory_as_extern(m); + + default: + throw new NotSupportedException("Unexpected handle type."); + } + } + + internal Interop.InstanceHandle Handle { get; private set; } + private Interop.wasm_extern_vec_t _externs; + private Dictionary _functions; + private Dictionary _globals; + } +} diff --git a/crates/misc/dotnet/src/Interop.cs b/crates/misc/dotnet/src/Interop.cs new file mode 100644 index 0000000000..a7d9796175 --- /dev/null +++ b/crates/misc/dotnet/src/Interop.cs @@ -0,0 +1,762 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Wasmtime +{ + /// + /// Implements the Wasmtime API bindings. + /// + /// See https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.h for the C API reference. + internal static class Interop + { + internal class EngineHandle : SafeHandle + { + public EngineHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_engine_delete(handle); + return true; + } + } + + internal class StoreHandle : SafeHandle + { + public StoreHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_store_delete(handle); + return true; + } + } + + internal class ModuleHandle : SafeHandle + { + public ModuleHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_module_delete(handle); + return true; + } + } + + internal class FunctionHandle : SafeHandle + { + public FunctionHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_func_delete(handle); + return true; + } + } + + internal class GlobalHandle : SafeHandle + { + public GlobalHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_global_delete(handle); + return true; + } + } + + internal class MemoryHandle : SafeHandle + { + public MemoryHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_memory_delete(handle); + return true; + } + } + + internal class InstanceHandle : SafeHandle + { + public InstanceHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_instance_delete(handle); + return true; + } + } + + internal class FuncTypeHandle : SafeHandle + { + public FuncTypeHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_functype_delete(handle); + return true; + } + } + + internal class GlobalTypeHandle : SafeHandle + { + public GlobalTypeHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_globaltype_delete(handle); + return true; + } + } + + internal class MemoryTypeHandle : SafeHandle + { + public MemoryTypeHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_memorytype_delete(handle); + return true; + } + } + + internal class ValueTypeHandle : SafeHandle + { + public ValueTypeHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_valtype_delete(handle); + return true; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_byte_vec_t + { + public UIntPtr size; + public byte* data; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_valtype_vec_t + { + public UIntPtr size; + public IntPtr* data; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_export_vec_t + { + public UIntPtr size; + public IntPtr* data; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_extern_vec_t + { + public UIntPtr size; + public IntPtr* data; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_importtype_vec_t + { + public UIntPtr size; + public IntPtr* data; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct wasm_exporttype_vec_t + { + public UIntPtr size; + public IntPtr* data; + } + + internal enum wasm_valkind_t : byte + { + WASM_I32, + WASM_I64, + WASM_F32, + WASM_F64, + WASM_ANYREF = 128, + WASM_FUNCREF, + } + + [StructLayout(LayoutKind.Explicit)] + internal struct wasm_val_union_t + { + [FieldOffset(0)] + public int i32; + + [FieldOffset(0)] + public long i64; + + [FieldOffset(0)] + public float f32; + + [FieldOffset(0)] + public double f64; + + [FieldOffset(0)] + public IntPtr reference; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct wasm_val_t + { + public wasm_valkind_t kind; + public wasm_val_union_t of; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct wasm_limits_t + { + public uint min; + + public uint max; + } + + public static wasm_val_t ToValue(object o, ValueKind kind) + { + wasm_val_t value = new wasm_val_t(); + switch (kind) + { + case ValueKind.Int32: + value.kind = wasm_valkind_t.WASM_I32; + value.of.i32 = (int)Convert.ChangeType(o, TypeCode.Int32); + break; + + case ValueKind.Int64: + value.kind = wasm_valkind_t.WASM_I64; + value.of.i64 = (long)Convert.ChangeType(o, TypeCode.Int64); + break; + + case ValueKind.Float32: + value.kind = wasm_valkind_t.WASM_F32; + value.of.f32 = (float)Convert.ChangeType(o, TypeCode.Single); + break; + + case ValueKind.Float64: + value.kind = wasm_valkind_t.WASM_F64; + value.of.f64 = (double)Convert.ChangeType(o, TypeCode.Double); + break; + + // TODO: support AnyRef + + default: + throw new NotSupportedException("Unsupported value type."); + } + return value; + } + + public static unsafe object ToObject(wasm_val_t* v) + { + switch (v->kind) + { + case Interop.wasm_valkind_t.WASM_I32: + return v->of.i32; + + case Interop.wasm_valkind_t.WASM_I64: + return v->of.i64; + + case Interop.wasm_valkind_t.WASM_F32: + return v->of.f32; + + case Interop.wasm_valkind_t.WASM_F64: + return v->of.f64; + + // TODO: support AnyRef + + default: + throw new NotSupportedException("Unsupported value kind."); + } + } + + public static bool TryGetValueKind(Type type, out ValueKind kind) + { + if (type == typeof(int)) + { + kind = ValueKind.Int32; + return true; + } + + if (type == typeof(long)) + { + kind = ValueKind.Int64; + return true; + } + + if (type == typeof(float)) + { + kind = ValueKind.Float32; + return true; + } + + if (type == typeof(double)) + { + kind = ValueKind.Float64; + return true; + } + + kind = default(ValueKind); + return false; + } + + public static ValueKind ToValueKind(Type type) + { + if (TryGetValueKind(type, out var kind)) + { + return kind; + } + + throw new NotSupportedException($"Type '{type}' is not a supported WebAssembly value type."); + } + + public static string ToString(ValueKind kind) + { + switch (kind) + { + case ValueKind.Int32: + return "int"; + + case ValueKind.Int64: + return "long"; + + case ValueKind.Float32: + return "float"; + + case ValueKind.Float64: + return "double"; + + default: + throw new NotSupportedException("Unsupported value kind."); + } + } + + public static bool IsMatchingKind(ValueKind kind, ValueKind expected) + { + if (kind == expected) + { + return true; + } + + if (expected == ValueKind.AnyRef) + { + return kind == ValueKind.FuncRef; + } + + return false; + } + + internal unsafe delegate IntPtr WasmFuncCallback(wasm_val_t* parameters, wasm_val_t* results); + + internal enum wasm_externkind_t : byte + { + WASM_EXTERN_FUNC, + WASM_EXTERN_GLOBAL, + WASM_EXTERN_TABLE, + WASM_EXTERN_MEMORY, + } + + internal enum wasm_mutability_t : byte + { + WASM_CONST, + WASM_VAR, + } + + internal static unsafe List ToValueKindList(Interop.wasm_valtype_vec_t* vec) + { + var list = new List((int)vec->size); + + for (int i = 0; i < (int)vec->size; ++i) + { + list.Add(Interop.wasm_valtype_kind(vec->data[i])); + } + + return list; + } + + internal static Interop.wasm_valtype_vec_t ToValueTypeVec(IReadOnlyList collection) + { + Interop.wasm_valtype_vec_t vec; + Interop.wasm_valtype_vec_new_uninitialized(out vec, (UIntPtr)collection.Count); + + int i = 0; + foreach (var type in collection) + { + var valType = Interop.wasm_valtype_new((wasm_valkind_t)type); + unsafe + { + vec.data[i++] = valType.DangerousGetHandle(); + } + valType.SetHandleAsInvalid(); + } + + return vec; + } + + // Engine imports + + [DllImport("wasmtime")] + public static extern EngineHandle wasm_engine_new(); + + [DllImport("wasmtime")] + public static extern void wasm_engine_delete(IntPtr engine); + + // Store imports + + [DllImport("wasmtime")] + public static extern StoreHandle wasm_store_new(EngineHandle engine); + + [DllImport("wasmtime")] + public static extern void wasm_store_delete(IntPtr engine); + + // Byte vec imports + + [DllImport("wasmtime")] + public static extern void wasm_byte_vec_new_empty(out wasm_byte_vec_t vec); + + [DllImport("wasmtime")] + public static extern void wasm_byte_vec_new_uninitialized(out wasm_byte_vec_t vec, UIntPtr length); + + [DllImport("wasmtime")] + public static extern void wasm_byte_vec_new(out wasm_byte_vec_t vec, UIntPtr length, byte[] data); + + [DllImport("wasmtime")] + public static extern void wasm_byte_vec_copy(out wasm_byte_vec_t vec, ref wasm_byte_vec_t src); + + [DllImport("wasmtime")] + public static extern void wasm_byte_vec_delete(ref wasm_byte_vec_t vec); + + // Value type vec imports + + [DllImport("wasmtime")] + public static extern void wasm_valtype_vec_new_empty(out wasm_valtype_vec_t vec); + + [DllImport("wasmtime")] + public static extern void wasm_valtype_vec_new_uninitialized(out wasm_valtype_vec_t vec, UIntPtr length); + + [DllImport("wasmtime")] + public static extern void wasm_valtype_vec_new(out wasm_valtype_vec_t vec, UIntPtr length, IntPtr[] data); + + [DllImport("wasmtime")] + public static extern void wasm_valtype_vec_copy(out wasm_valtype_vec_t vec, ref wasm_valtype_vec_t src); + + [DllImport("wasmtime")] + public static extern void wasm_valtype_vec_delete(ref wasm_valtype_vec_t vec); + + // Extern vec imports + + [DllImport("wasmtime")] + public static extern void wasm_extern_vec_new_empty(out wasm_extern_vec_t vec); + + [DllImport("wasmtime")] + public static extern void wasm_extern_vec_new_uninitialized(out wasm_extern_vec_t vec, UIntPtr length); + + [DllImport("wasmtime")] + public static extern void wasm_extern_vec_new(out wasm_extern_vec_t vec, UIntPtr length, IntPtr[] data); + + [DllImport("wasmtime")] + public static extern void wasm_extern_vec_copy(out wasm_extern_vec_t vec, ref wasm_extern_vec_t src); + + [DllImport("wasmtime")] + public static extern void wasm_extern_vec_delete(ref wasm_extern_vec_t vec); + + // Import type vec imports + + [DllImport("wasmtime")] + public static extern void wasm_importtype_vec_new_empty(out wasm_importtype_vec_t vec); + + [DllImport("wasmtime")] + public static extern void wasm_importtype_vec_new_uninitialized(out wasm_importtype_vec_t vec, UIntPtr length); + + [DllImport("wasmtime")] + public static extern void wasm_importtype_vec_new(out wasm_importtype_vec_t vec, UIntPtr length, IntPtr[] data); + + [DllImport("wasmtime")] + public static extern void wasm_importtype_vec_copy(out wasm_importtype_vec_t vec, ref wasm_importtype_vec_t src); + + [DllImport("wasmtime")] + public static extern void wasm_importtype_vec_delete(ref wasm_importtype_vec_t vec); + + // Export type vec imports + + [DllImport("wasmtime")] + public static extern void wasm_exporttype_vec_new_empty(out wasm_exporttype_vec_t vec); + + [DllImport("wasmtime")] + public static extern void wasm_exporttype_vec_new_uninitialized(out wasm_exporttype_vec_t vec, UIntPtr length); + + [DllImport("wasmtime")] + public static extern void wasm_exporttype_vec_new(out wasm_exporttype_vec_t vec, UIntPtr length, IntPtr[] data); + + [DllImport("wasmtime")] + public static extern void wasm_exporttype_vec_copy(out wasm_exporttype_vec_t vec, ref wasm_exporttype_vec_t src); + + [DllImport("wasmtime")] + public static extern void wasm_exporttype_vec_delete(ref wasm_exporttype_vec_t vec); + + // Import type imports + + [DllImport("wasmtime")] + public static extern unsafe wasm_byte_vec_t* wasm_importtype_module(IntPtr importType); + + [DllImport("wasmtime")] + public static extern unsafe wasm_byte_vec_t* wasm_importtype_name(IntPtr importType); + + [DllImport("wasmtime")] + public static extern unsafe IntPtr wasm_importtype_type(IntPtr importType); + + // Export type imports + + [DllImport("wasmtime")] + public static extern unsafe wasm_byte_vec_t* wasm_exporttype_name(IntPtr exportType); + + [DllImport("wasmtime")] + public static extern unsafe IntPtr wasm_exporttype_type(IntPtr exportType); + + // Module imports + + [DllImport("wasmtime")] + public static extern ModuleHandle wasm_module_new(StoreHandle store, ref wasm_byte_vec_t bytes); + + [DllImport("wasmtime")] + public static extern void wasm_module_imports(ModuleHandle module, out wasm_importtype_vec_t imports); + + [DllImport("wasmtime")] + public static extern void wasm_module_exports(ModuleHandle module, out wasm_exporttype_vec_t exports); + + [DllImport("wasmtime")] + public static extern void wasm_module_delete(IntPtr module); + + // Value type imports + + [DllImport("wasmtime")] + public static extern ValueTypeHandle wasm_valtype_new(wasm_valkind_t kind); + + [DllImport("wasmtime")] + public static extern void wasm_valtype_delete(IntPtr valueType); + + [DllImport("wasmtime")] + public static extern ValueKind wasm_valtype_kind(IntPtr valueType); + + // Extern imports + + [DllImport("wasmtime")] + public static extern wasm_externkind_t wasm_extern_kind(IntPtr ext); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_extern_type(IntPtr ext); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_extern_as_func(IntPtr ext); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_extern_as_global(IntPtr ext); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_extern_as_table(IntPtr ext); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_extern_as_memory(IntPtr ext); + + // Extern type imports + + [DllImport("wasmtime")] + public static extern wasm_externkind_t wasm_externtype_kind(IntPtr externType); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_externtype_as_functype_const(IntPtr externType); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_externtype_as_globaltype_const(IntPtr externType); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_externtype_as_tabletype_const(IntPtr externType); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_externtype_as_memorytype_const(IntPtr externType); + + // Function imports + + [DllImport("wasmtime")] + public static extern FunctionHandle wasm_func_new(StoreHandle store, FuncTypeHandle type, WasmFuncCallback callback); + + [DllImport("wasmtime")] + public static extern void wasm_func_delete(IntPtr function); + + [DllImport("wasmtime")] + public static unsafe extern IntPtr wasm_func_call(IntPtr function, wasm_val_t* args, wasm_val_t* results); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_func_as_extern(FunctionHandle function); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_global_as_extern(GlobalHandle global); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_memory_as_extern(MemoryHandle memory); + + // Function type imports + + [DllImport("wasmtime")] + public static extern unsafe wasm_valtype_vec_t* wasm_functype_params(IntPtr funcType); + + [DllImport("wasmtime")] + public static extern unsafe wasm_valtype_vec_t* wasm_functype_results(IntPtr funcType); + + // Instance imports + + [DllImport("wasmtime")] + public static extern unsafe InstanceHandle wasm_instance_new(StoreHandle store, ModuleHandle module, IntPtr[] imports, out IntPtr trap); + + [DllImport("wasmtime")] + public static extern void wasm_instance_delete(IntPtr ext); + + [DllImport("wasmtime")] + public static extern void wasm_instance_exports(InstanceHandle instance, out wasm_extern_vec_t exports); + + // Function type imports + + [DllImport("wasmtime")] + public static extern FuncTypeHandle wasm_functype_new(ref wasm_valtype_vec_t parameters, ref wasm_valtype_vec_t results); + + [DllImport("wasmtime")] + public static extern void wasm_functype_delete(IntPtr functype); + + // Global type imports + + [DllImport("wasmtime")] + public static extern GlobalTypeHandle wasm_globaltype_new(IntPtr valueType, wasm_mutability_t mutability); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_globaltype_delete(IntPtr globalType); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_globaltype_content(IntPtr globalType); + + [DllImport("wasmtime")] + public static extern wasm_mutability_t wasm_globaltype_mutability(IntPtr globalType); + + // Memory type imports + + [DllImport("wasmtime")] + public static extern unsafe MemoryTypeHandle wasm_memorytype_new(wasm_limits_t* limits); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_memorytype_delete(IntPtr memoryType); + + + [DllImport("wasmtime")] + public static extern unsafe wasm_limits_t* wasm_memorytype_limits(MemoryTypeHandle memoryType); + + // Trap imports + + [DllImport("wasmtime")] + public static extern IntPtr wasm_trap_new(StoreHandle store, ref wasm_byte_vec_t message); + + [DllImport("wasmtime")] + public static extern void wasm_trap_delete(IntPtr trap); + + [DllImport("wasmtime")] + public static extern void wasm_trap_message(IntPtr trap, out wasm_byte_vec_t message); + + // Table type imports + + [DllImport("wasmtime")] + public static extern IntPtr wasm_tabletype_element(IntPtr tableType); + + [DllImport("wasmtime")] + public static unsafe extern wasm_limits_t* wasm_tabletype_limits(IntPtr tableType); + + // Memory type imports + + [DllImport("wasmtime")] + public static unsafe extern wasm_limits_t* wasm_memorytype_limits(IntPtr memoryType); + + // Global imports + + [DllImport("wasmtime")] + public static unsafe extern GlobalHandle wasm_global_new(StoreHandle handle, GlobalTypeHandle globalType, wasm_val_t* initialValue); + + [DllImport("wasmtime")] + public static extern void wasm_global_delete(IntPtr global); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_global_type(IntPtr global); + + [DllImport("wasmtime")] + public static unsafe extern void wasm_global_get(IntPtr global, wasm_val_t* value); + + [DllImport("wasmtime")] + public static unsafe extern void wasm_global_set(IntPtr global, wasm_val_t* value); + + // Memory imports + + [DllImport("wasmtime")] + public static extern MemoryHandle wasm_memory_new(StoreHandle handle, MemoryTypeHandle memoryType); + + [DllImport("wasmtime")] + public static extern void wasm_memory_delete(IntPtr memory); + + [DllImport("wasmtime")] + public static extern IntPtr wasm_memory_type(MemoryHandle memory); + + [DllImport("wasmtime")] + public static unsafe extern byte* wasm_memory_data(IntPtr memory); + + [DllImport("wasmtime")] + public static extern UIntPtr wasm_memory_data_size(IntPtr memory); + + [DllImport("wasmtime")] + public static extern uint wasm_memory_size(MemoryHandle memory); + + [DllImport("wasmtime")] + public static extern bool wasm_memory_grow(MemoryHandle memory, uint delta); + } +} diff --git a/crates/misc/dotnet/src/Memory.cs b/crates/misc/dotnet/src/Memory.cs new file mode 100644 index 0000000000..2cfe060f81 --- /dev/null +++ b/crates/misc/dotnet/src/Memory.cs @@ -0,0 +1,270 @@ +using System; +using System.Text; +using System.Buffers.Binary; + +namespace Wasmtime +{ + /// + /// Represents a WebAssembly memory. + /// + public class Memory + { + /// + /// The size, in bytes, of a WebAssembly memory page. + /// + public const int PageSize = 65536; + + /// + /// Creates a new memory with the given minimum and maximum page counts. + /// + /// + /// + public Memory(uint minimum = 1, uint maximum = uint.MaxValue) + { + if (minimum == 0) + { + throw new ArgumentException("The minimum cannot be zero..", nameof(minimum)); + } + + if (maximum < minimum) + { + throw new ArgumentException("The maximum cannot be less than the minimum.", nameof(maximum)); + } + + Minimum = minimum; + Maximum = maximum; + } + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Minimum { get; private set; } + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Maximum { get; private set; } + + /// + /// The span of the memory. + /// + /// + /// The span may become invalid if the memory grows. + /// + /// This may happen if the memory is explicitly requested to grow or + /// grows as a result of WebAssembly execution. + /// + /// Therefore, the returned Span should not be stored. + /// + public unsafe Span Span + { + get + { + var data = Interop.wasm_memory_data(_handle.DangerousGetHandle()); + var size = Convert.ToInt32(Interop.wasm_memory_data_size(_handle.DangerousGetHandle()).ToUInt32()); + return new Span(data, size); + } + } + + /// + /// Reads a UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// The length of bytes to read. + /// Returns the string read from memory. + public string ReadString(int address, int length) + { + return Encoding.UTF8.GetString(Span.Slice(address, length)); + } + + /// + /// Writes a UTF-8 string at the given address. + /// + /// The zero-based address to write to. + /// The string to write. + /// Returns the number of bytes written. + public int WriteString(int address, string value) + { + return Encoding.UTF8.GetBytes(value, Span.Slice(address)); + } + + /// + /// Reads a byte from memory. + /// + /// The zero-based address to read from. + /// Returns the byte read from memory. + public byte ReadByte(int address) + { + return Span[address]; + } + + /// + /// Writes a byte to memory. + /// + /// The zero-based address to write to. + /// The byte to write. + public void WriteByte(int address, byte value) + { + Span[address] = value; + } + + /// + /// Reads a short from memory. + /// + /// The zero-based address to read from. + /// Returns the short read from memory. + public short ReadInt16(int address) + { + return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2)); + } + + /// + /// Writes a short to memory. + /// + /// The zero-based address to write to. + /// The short to write. + public void WriteInt16(int address, short value) + { + BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value); + } + + /// + /// Reads an int from memory. + /// + /// The zero-based address to read from. + /// Returns the int read from memory. + public int ReadInt32(int address) + { + return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4)); + } + + /// + /// Writes an int to memory. + /// + /// The zero-based address to write to. + /// The int to write. + public void WriteInt32(int address, int value) + { + BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value); + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public long ReadInt64(int address) + { + return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8)); + } + + /// + /// Writes a long to memory. + /// + /// The zero-based address to write to. + /// The long to write. + public void WriteInt64(int address, long value) + { + BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value); + } + + /// + /// Reads an IntPtr from memory. + /// + /// The zero-based address to read from. + /// Returns the IntPtr read from memory. + public IntPtr ReadIntPtr(int address) + { + if (IntPtr.Size == 4) + { + return (IntPtr)ReadInt32(address); + } + return (IntPtr)ReadInt64(address); + } + + /// + /// Writes an IntPtr to memory. + /// + /// The zero-based address to write to. + /// The IntPtr to write. + public void WriteIntPtr(int address, IntPtr value) + { + if (IntPtr.Size == 4) + { + WriteInt32(address, value.ToInt32()); + } + else + { + WriteInt64(address, value.ToInt64()); + } + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public float ReadSingle(int address) + { + unsafe + { + var i = ReadInt32(address); + return *((float*)&i); + } + } + + /// + /// Writes a single to memory. + /// + /// The zero-based address to write to. + /// The single to write. + public void WriteSingle(int address, float value) + { + unsafe + { + WriteInt32(address, *(int*)&value); + } + } + + /// + /// Reads a double from memory. + /// + /// The zero-based address to read from. + /// Returns the double read from memory. + public double ReadDouble(int address) + { + unsafe + { + var i = ReadInt64(address); + return *((double*)&i); + } + } + + /// + /// Writes a double to memory. + /// + /// The zero-based address to write to. + /// The double to write. + public void WriteDouble(int address, double value) + { + unsafe + { + WriteInt64(address, *(long*)&value); + } + } + + internal Interop.MemoryHandle Handle + { + get + { + return _handle; + } + set + { + _handle = value; + } + } + + private Interop.MemoryHandle _handle; + } +} diff --git a/crates/misc/dotnet/src/Module.cs b/crates/misc/dotnet/src/Module.cs new file mode 100644 index 0000000000..c1f0dda6c3 --- /dev/null +++ b/crates/misc/dotnet/src/Module.cs @@ -0,0 +1,101 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wasmtime +{ + /// + /// Represents a WebAssembly module. + /// + public class Module : IDisposable + { + internal Module(Store store, string name, byte[] bytes) + { + if (store.Handle.IsInvalid) + { + throw new ArgumentNullException(nameof(store)); + } + + var bytesHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + + try + { + unsafe + { + Interop.wasm_byte_vec_t vec; + vec.size = (UIntPtr)bytes.Length; + vec.data = (byte*)bytesHandle.AddrOfPinnedObject(); + + Handle = Interop.wasm_module_new(store.Handle, ref vec); + } + + if (Handle.IsInvalid) + { + throw new WasmtimeException($"WebAssembly module '{name}' is not valid."); + } + } + finally + { + bytesHandle.Free(); + } + + Store = store; + Name = name; + Imports = new Wasmtime.Imports.Imports(this); + Exports = new Wasmtime.Exports.Exports(this); + } + + /// + /// Instantiates a WebAssembly module for the given host. + /// + /// The host to use for the WebAssembly module's instance. + /// Returns a new . + public Instance Instantiate(IHost host) + { + if (host is null) + { + throw new ArgumentNullException(nameof(host)); + } + + if (host.Instance != null) + { + throw new InvalidOperationException("The host has already been associated with an instantiated module."); + } + + host.Instance = new Instance(this, host); + return host.Instance; + } + + /// + /// The associated with the module. + /// + public Store Store { get; private set; } + + /// + /// The name of the module. + /// + public string Name { get; private set; } + + /// + /// The imports of the module. + /// + public Wasmtime.Imports.Imports Imports { get; private set; } + + /// + /// The exports of the module. + /// + /// + public Wasmtime.Exports.Exports Exports { get; private set; } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Interop.ModuleHandle Handle { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/MutableGlobal.cs b/crates/misc/dotnet/src/MutableGlobal.cs new file mode 100644 index 0000000000..223c8c5f0c --- /dev/null +++ b/crates/misc/dotnet/src/MutableGlobal.cs @@ -0,0 +1,65 @@ +using System; + +namespace Wasmtime +{ + /// + /// Represents a mutable WebAssembly global value. + /// + public class MutableGlobal + { + /// + /// Creates a new with the given initial value. + /// + /// The initial value of the global. + public MutableGlobal(T initialValue) + { + InitialValue = initialValue; + Kind = Interop.ToValueKind(typeof(T)); + } + + /// + /// The value of the global. + /// + public T Value + { + get + { + if (Handle is null) + { + throw new InvalidOperationException("The global cannot be used before it is instantiated."); + } + + unsafe + { + var v = stackalloc Interop.wasm_val_t[1]; + + Interop.wasm_global_get(Handle.DangerousGetHandle(), v); + + // TODO: figure out a way that doesn't box the value + return (T)Interop.ToObject(v); + } + } + set + { + if (Handle is null) + { + throw new InvalidOperationException("The global cannot be used before it is instantiated."); + } + + // TODO: figure out a way that doesn't box the value + var v = Interop.ToValue(value, Kind); + + unsafe + { + Interop.wasm_global_set(Handle.DangerousGetHandle(), &v); + } + } + } + + internal ValueKind Kind { get; private set; } + + internal Interop.GlobalHandle Handle { get; set; } + + internal T InitialValue { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Store.cs b/crates/misc/dotnet/src/Store.cs new file mode 100644 index 0000000000..37be522201 --- /dev/null +++ b/crates/misc/dotnet/src/Store.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; + +namespace Wasmtime +{ + /// + /// Represents the Wasmtime store. + /// + public sealed class Store : IDisposable + { + internal Store(Engine engine) + { + Handle = Interop.wasm_store_new(engine.Handle); + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime store."); + } + } + + /// + /// Create a given the module name and bytes. + /// + /// The name of the module. + /// The bytes of the module. + /// Retuw . + public Module CreateModule(string name, byte[] bytes) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (bytes is null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + return new Module(this, name, bytes); + } + + /// + /// Create a given the module name and path to the WebAssembly file. + /// + /// The name of the module. + /// The path to the WebAssembly file. + /// Returns a new . + public Module CreateModule(string name, string path) + { + return CreateModule(name, File.ReadAllBytes(path)); + } + + /// + /// Create a given the path to the WebAssembly file. + /// + /// The path to the WebAssembly file. + /// Returns a new . + public Module CreateModule(string path) + { + return CreateModule(Path.GetFileNameWithoutExtension(path), path); + } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Interop.StoreHandle Handle { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/TrapException.cs b/crates/misc/dotnet/src/TrapException.cs new file mode 100644 index 0000000000..795b4a397c --- /dev/null +++ b/crates/misc/dotnet/src/TrapException.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace Wasmtime +{ + /// + /// The exception for WebAssembly traps. + /// + [Serializable] + public class TrapException : WasmtimeException + { + /// + public TrapException() { } + + /// + public TrapException(string message) : base(message) { } + + /// + public TrapException(string message, Exception inner) : base(message, inner) { } + + /// + protected TrapException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + internal static TrapException FromOwnedTrap(IntPtr trap) + { + unsafe + { + Interop.wasm_trap_message(trap, out var bytes); + var message = Marshal.PtrToStringUTF8((IntPtr)bytes.data, (int)bytes.size - 1 /* remove null */); + Interop.wasm_byte_vec_delete(ref bytes); + + Interop.wasm_trap_delete(trap); + + return new TrapException(message); + } + } + + // TODO: expose trap frames + } +} diff --git a/crates/misc/dotnet/src/ValueKind.cs b/crates/misc/dotnet/src/ValueKind.cs new file mode 100644 index 0000000000..cce415aee4 --- /dev/null +++ b/crates/misc/dotnet/src/ValueKind.cs @@ -0,0 +1,35 @@ +using System; + +namespace Wasmtime +{ + /// + /// Represents the possible kinds of WebAssembly values. + /// + public enum ValueKind + { + /// + /// The value is a 32-bit integer. + /// + Int32, + /// + /// The value is a 64-bit integer. + /// + Int64, + /// + /// The value is a 32-bit floating point number. + /// + Float32, + /// + /// The value is a 64-bit floating point number. + /// + Float64, + /// + /// The value is a reference. + /// + AnyRef = 128, + /// + /// The value is a function reference. + /// + FuncRef, + } +} diff --git a/crates/misc/dotnet/src/Wasmtime.csproj b/crates/misc/dotnet/src/Wasmtime.csproj new file mode 100644 index 0000000000..ffb0054e9e --- /dev/null +++ b/crates/misc/dotnet/src/Wasmtime.csproj @@ -0,0 +1,77 @@ + + + + netstandard2.1 + true + + + + + + + + Wasmtime.Dotnet + Wasmtime + $(WasmtimeVersion)-preview1 + Peter Huene + Peter Huene + true + snupkg + https://github.com/bytecodealliance/wasmtime + Initial release of Wasmtime for .NET. + A .NET API for Wasmtime, a standalone WebAssembly runtime + webassembly, .net, wasm, wasmtime + Wasmtime + +A .NET API for Wasmtime. + +Wasmtime is a standalone runtime for WebAssembly, using the Cranelift JIT compiler. + +Wasmtime for .NET enables .NET code to instantiate WebAssembly modules and to interact with them in-process. + + + + + + x86_64 + https://github.com/bytecodealliance/wasmtime/releases/download/v$(WasmtimeVersion)/wasmtime-v$(WasmtimeVersion)-$(WasmtimeArchitecture) + + + + + $(ReleaseURLBase)-linux-c-api.tar.xz + $(IntermediateOutputPath)wasmtime-linux + linux.tar.xz + libwasmtime.so + runtimes/linux-x64/native + + + $(ReleaseURLBase)-macos-c-api.tar.xz + $(IntermediateOutputPath)wasmtime-macos + macos.tar.xz + libwasmtime.dylib + runtimes/osx-x64/native + + + $(ReleaseURLBase)-windows-c-api.zip + $(IntermediateOutputPath)wasmtime-windows + windows.zip + wasmtime.dll + runtimes/win-x64/native + + + + + + + + + + + + %(WasmtimeDownload.PackagePath) + + + + + diff --git a/crates/misc/dotnet/src/WasmtimeException.cs b/crates/misc/dotnet/src/WasmtimeException.cs new file mode 100644 index 0000000000..26f0e6c5e6 --- /dev/null +++ b/crates/misc/dotnet/src/WasmtimeException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace Wasmtime +{ + /// + /// The base type for Wasmtime exceptions. + /// + [System.Serializable] + public class WasmtimeException : Exception + { + /// + public WasmtimeException() { } + + /// + public WasmtimeException(string message) : base(message) { } + + /// + public WasmtimeException(string message, Exception inner) : base(message, inner) { } + + /// + protected WasmtimeException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs new file mode 100644 index 0000000000..91bd52bbad --- /dev/null +++ b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using Wasmtime; + +namespace Wasmtime.Tests +{ + public abstract class ModuleFixture : IDisposable + { + public ModuleFixture() + { + Engine = new Engine(); + Store = Engine.CreateStore(); + Module = Store.CreateModule(Path.Combine("Modules", ModuleFileName)); + } + + public void Dispose() + { + if (Module != null) + { + Module.Dispose(); + Module = null; + } + + if (Store != null) + { + Store.Dispose(); + Store = null; + } + + if (Engine != null) + { + Engine.Dispose(); + Engine = null; + } + } + + public Engine Engine { get; set; } + public Store Store { get; set; } + public Module Module { get; set; } + + protected abstract string ModuleFileName { get; } + } +} diff --git a/crates/misc/dotnet/tests/FunctionExportsTests.cs b/crates/misc/dotnet/tests/FunctionExportsTests.cs new file mode 100644 index 0000000000..01d0929216 --- /dev/null +++ b/crates/misc/dotnet/tests/FunctionExportsTests.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class FunctionExportsFixture : ModuleFixture + { + protected override string ModuleFileName => "FunctionExports.wasm"; + } + + public class FunctionExportsTests : IClassFixture + { + public FunctionExportsTests(FunctionExportsFixture fixture) + { + Fixture = fixture; + } + + private FunctionExportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetFunctionExports))] + public void ItHasTheExpectedFunctionExports(string exportName, ValueKind[] expectedParameters, ValueKind[] expectedResults) + { + var export = Fixture.Module.Exports.Functions.Where(f => f.Name == exportName).FirstOrDefault(); + export.Should().NotBeNull(); + export.Parameters.Should().Equal(expectedParameters); + export.Results.Should().Equal(expectedResults); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedFunctions() + { + GetFunctionExports().Count().Should().Be(Fixture.Module.Exports.Functions.Count); + } + + public static IEnumerable GetFunctionExports() + { + yield return new object[] { + "no_params_no_results", + Array.Empty(), + Array.Empty() + }; + + yield return new object[] { + "one_i32_param_no_results", + new ValueKind[] { + ValueKind.Int32 + }, + Array.Empty() + }; + + yield return new object[] { + "one_i64_param_no_results", + new ValueKind[] { + ValueKind.Int64 + }, + Array.Empty() + }; + + yield return new object[] { + "one_f32_param_no_results", + new ValueKind[] { + ValueKind.Float32 + }, + Array.Empty() + }; + + yield return new object[] { + "one_f64_param_no_results", + new ValueKind[] { + ValueKind.Float64 + }, + Array.Empty() + }; + + yield return new object[] { + "one_param_of_each_type", + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64 + }, + Array.Empty() + }; + + yield return new object[] { + "no_params_one_i32_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Int32, + } + }; + + yield return new object[] { + "no_params_one_i64_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Int64, + } + }; + + yield return new object[] { + "no_params_one_f32_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Float32, + } + }; + + yield return new object[] { + "no_params_one_f64_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Float64, + } + }; + + yield return new object[] { + "one_result_of_each_type", + Array.Empty(), + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + } + }; + + yield return new object[] { + "one_param_and_result_of_each_type", + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + }, + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + } + }; + } + } +} diff --git a/crates/misc/dotnet/tests/FunctionImportsTests.cs b/crates/misc/dotnet/tests/FunctionImportsTests.cs new file mode 100644 index 0000000000..bb2bdff302 --- /dev/null +++ b/crates/misc/dotnet/tests/FunctionImportsTests.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class FunctionImportsFixture : ModuleFixture + { + protected override string ModuleFileName => "FunctionImports.wasm"; + } + + public class FunctionImportsTests : IClassFixture + { + public FunctionImportsTests(FunctionImportsFixture fixture) + { + Fixture = fixture; + } + + private FunctionImportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetFunctionImports))] + public void ItHasTheExpectedFunctionImports(string importModule, string importName, ValueKind[] expectedParameters, ValueKind[] expectedResults) + { + var import = Fixture.Module.Imports.Functions.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault(); + import.Should().NotBeNull(); + import.Parameters.Should().Equal(expectedParameters); + import.Results.Should().Equal(expectedResults); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedFunctions() + { + GetFunctionImports().Count().Should().Be(Fixture.Module.Imports.Functions.Count); + } + + public static IEnumerable GetFunctionImports() + { + yield return new object[] { + "", + "no_params_no_results", + Array.Empty(), + Array.Empty() + }; + + yield return new object[] { + "", + "one_i32_param_no_results", + new ValueKind[] { + ValueKind.Int32 + }, + Array.Empty() + }; + + yield return new object[] { + "", + "one_i64_param_no_results", + new ValueKind[] { + ValueKind.Int64 + }, + Array.Empty() + }; + + yield return new object[] { + "", + "one_f32_param_no_results", + new ValueKind[] { + ValueKind.Float32 + }, + Array.Empty() + }; + + yield return new object[] { + "", + "one_f64_param_no_results", + new ValueKind[] { + ValueKind.Float64 + }, + Array.Empty() + }; + + yield return new object[] { + "", + "one_param_of_each_type", + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64 + }, + Array.Empty() + }; + + yield return new object[] { + "", + "no_params_one_i32_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Int32, + } + }; + + yield return new object[] { + "", + "no_params_one_i64_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Int64, + } + }; + + yield return new object[] { + "", + "no_params_one_f32_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Float32, + } + }; + + yield return new object[] { + "", + "no_params_one_f64_result", + Array.Empty(), + new ValueKind[] { + ValueKind.Float64, + } + }; + + yield return new object[] { + "", + "one_result_of_each_type", + Array.Empty(), + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + } + }; + + yield return new object[] { + "", + "one_param_and_result_of_each_type", + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + }, + new ValueKind[] { + ValueKind.Int32, + ValueKind.Int64, + ValueKind.Float32, + ValueKind.Float64, + } + }; + + yield return new object[] { + "other", + "function_from_module", + Array.Empty(), + Array.Empty(), + }; + } + } +} diff --git a/crates/misc/dotnet/tests/GlobalExportsTests.cs b/crates/misc/dotnet/tests/GlobalExportsTests.cs new file mode 100644 index 0000000000..63b6250679 --- /dev/null +++ b/crates/misc/dotnet/tests/GlobalExportsTests.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FluentAssertions; +using Wasmtime; +using Xunit; + +namespace Wasmtime.Tests +{ + public class GlobalExportsFixture : ModuleFixture + { + protected override string ModuleFileName => "GlobalExports.wasm"; + } + + public class GlobalExportsTests : IClassFixture + { + public class Host : IHost + { + public Instance Instance { get; set; } + } + + public GlobalExportsTests(GlobalExportsFixture fixture) + { + Fixture = fixture; + } + + private GlobalExportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetGlobalExports))] + public void ItHasTheExpectedGlobalExports(string exportName, ValueKind expectedKind, bool expectedMutable) + { + var export = Fixture.Module.Exports.Globals.Where(f => f.Name == exportName).FirstOrDefault(); + export.Should().NotBeNull(); + export.Kind.Should().Be(expectedKind); + export.IsMutable.Should().Be(expectedMutable); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedGlobals() + { + GetGlobalExports().Count().Should().Be(Fixture.Module.Exports.Globals.Count); + } + + [Fact] + public void ItCreatesExternsForTheGlobals() + { + using (var instance = Fixture.Module.Instantiate(new Host())) + { + dynamic dyn = instance; + var globals = instance.Externs.Globals; + globals.Count.Should().Be(8); + + var i32 = globals[0]; + i32.Name.Should().Be("global_i32"); + i32.Kind.Should().Be(ValueKind.Int32); + i32.IsMutable.Should().Be(false); + i32.Value.Should().Be(0); + + var i32Mut = globals[1]; + i32Mut.Name.Should().Be("global_i32_mut"); + i32Mut.Kind.Should().Be(ValueKind.Int32); + i32Mut.IsMutable.Should().Be(true); + i32Mut.Value.Should().Be(1); + i32Mut.Value = 11; + i32Mut.Value.Should().Be(11); + dyn.global_i32_mut = 12; + ((int)dyn.global_i32_mut).Should().Be(12); + i32Mut.Value.Should().Be(12); + + var i64 = globals[2]; + i64.Name.Should().Be("global_i64"); + i64.Kind.Should().Be(ValueKind.Int64); + i64.IsMutable.Should().Be(false); + i64.Value.Should().Be(2); + + var i64Mut = globals[3]; + i64Mut.Name.Should().Be("global_i64_mut"); + i64Mut.Kind.Should().Be(ValueKind.Int64); + i64Mut.IsMutable.Should().Be(true); + i64Mut.Value.Should().Be(3); + i64Mut.Value = 13; + i64Mut.Value.Should().Be(13); + dyn.global_i64_mut = 14; + ((long)dyn.global_i64_mut).Should().Be(14); + i64Mut.Value.Should().Be(14); + + var f32 = globals[4]; + f32.Name.Should().Be("global_f32"); + f32.Kind.Should().Be(ValueKind.Float32); + f32.IsMutable.Should().Be(false); + f32.Value.Should().Be(4); + + var f32Mut = globals[5]; + f32Mut.Name.Should().Be("global_f32_mut"); + f32Mut.Kind.Should().Be(ValueKind.Float32); + f32Mut.IsMutable.Should().Be(true); + f32Mut.Value.Should().Be(5); + f32Mut.Value = 15; + f32Mut.Value.Should().Be(15); + dyn.global_f32_mut = 16; + ((float)dyn.global_f32_mut).Should().Be(16); + f32Mut.Value.Should().Be(16); + + var f64 = globals[6]; + f64.Name.Should().Be("global_f64"); + f64.Kind.Should().Be(ValueKind.Float64); + f64.IsMutable.Should().Be(false); + f64.Value.Should().Be(6); + + var f64Mut = globals[7]; + f64Mut.Name.Should().Be("global_f64_mut"); + f64Mut.Kind.Should().Be(ValueKind.Float64); + f64Mut.IsMutable.Should().Be(true); + f64Mut.Value.Should().Be(7); + f64Mut.Value = 17; + f64Mut.Value.Should().Be(17); + dyn.global_f64_mut = 17; + ((double)dyn.global_f64_mut).Should().Be(17); + f64Mut.Value.Should().Be(17); + + Action action = () => i32.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i32' cannot be modified."); + action = () => dyn.global_i32 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i32' cannot be modified."); + + action = () => i64.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i64' cannot be modified."); + action = () => dyn.global_i64 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i64' cannot be modified."); + + action = () => f32.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f32' cannot be modified."); + action = () => dyn.global_f32 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f32' cannot be modified."); + + action = () => f64.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f64' cannot be modified."); + action = () => dyn.global_f64 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f64' cannot be modified."); + } + } + + public static IEnumerable GetGlobalExports() + { + yield return new object[] { + "global_i32", + ValueKind.Int32, + false + }; + + yield return new object[] { + "global_i32_mut", + ValueKind.Int32, + true + }; + + yield return new object[] { + "global_i64", + ValueKind.Int64, + false + }; + + yield return new object[] { + "global_i64_mut", + ValueKind.Int64, + true + }; + + yield return new object[] { + "global_f32", + ValueKind.Float32, + false + }; + + yield return new object[] { + "global_f32_mut", + ValueKind.Float32, + true + }; + + yield return new object[] { + "global_f64", + ValueKind.Float64, + false + }; + + yield return new object[] { + "global_f64_mut", + ValueKind.Float64, + true + }; + } + } +} diff --git a/crates/misc/dotnet/tests/GlobalImportBindingTests.cs b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs new file mode 100644 index 0000000000..f6b92d6e76 --- /dev/null +++ b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs @@ -0,0 +1,259 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class GlobalImportBindingFixture : ModuleFixture + { + protected override string ModuleFileName => "GlobalImportBindings.wasm"; + } + + public class GlobalImportBindingTests : IClassFixture + { + class NoImportsHost : IHost + { + public Instance Instance { get; set; } + } + + class GlobalIsStaticHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public static int x = 0; + } + + class GlobalIsNotReadOnlyHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public int x = 0; + } + + class NotAGlobalHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly int x = 0; + } + + class NotAValidGlobalTypeHost : IHost + { + public struct NotAValue + { + } + + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly MutableGlobal x = new MutableGlobal(new NotAValue()); + } + + class TypeMismatchHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly MutableGlobal x = new MutableGlobal(0); + } + + class NotMutHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly Global Int32Mut = new Global(0); + } + + class MutHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly MutableGlobal Int32Mut = new MutableGlobal(0); + + [Import("global_i32")] + public readonly MutableGlobal Int32 = new MutableGlobal(0); + } + + class ValidHost : IHost + { + public Instance Instance { get; set; } + + [Import("global_i32_mut")] + public readonly MutableGlobal Int32Mut = new MutableGlobal(0); + + [Import("global_i32")] + public readonly Global Int32 = new Global(1); + + [Import("global_i64_mut")] + public readonly MutableGlobal Int64Mut = new MutableGlobal(2); + + [Import("global_i64")] + public readonly Global Int64 = new Global(3); + + [Import("global_f32_mut")] + public readonly MutableGlobal Float32Mut = new MutableGlobal(4); + + [Import("global_f32")] + public readonly Global Float32 = new Global(5); + + [Import("global_f64_mut")] + public readonly MutableGlobal Float64Mut = new MutableGlobal(6); + + [Import("global_f64")] + public readonly Global Float64 = new Global(7); + } + + public GlobalImportBindingTests(GlobalImportBindingFixture fixture) + { + Fixture = fixture; + } + + private GlobalImportBindingFixture Fixture { get; set; } + + [Fact] + public void ItFailsToInstantiateWithMissingImport() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new NoImportsHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Failed to bind global import 'global_i32_mut': the host does not contain a global field with a matching 'Import' attribute."); + } + + [Fact] + public void ItFailsToInstantiateWithStaticField() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'GlobalIsStaticHost.x' to WebAssembly import 'global_i32_mut': field cannot be static."); + } + + [Fact] + public void ItFailsToInstantiateWithNonReadOnlyField() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'GlobalIsNotReadOnlyHost.x' to WebAssembly import 'global_i32_mut': field must be readonly."); + } + + [Fact] + public void ItFailsToInstantiateWithInvalidType() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAGlobalHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'NotAGlobalHost.x' to WebAssembly import 'global_i32_mut': field is expected to be of type 'Global'."); + } + + [Fact] + public void ItFailsToInstantiateWithInvalidGlobalType() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Type 'Wasmtime.Tests.GlobalImportBindingTests+NotAValidGlobalTypeHost+NotAValue' is not a supported WebAssembly value type."); + } + + [Fact] + public void ItFailsToInstantiateWithGlobalTypeMismatch() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new TypeMismatchHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'TypeMismatchHost.x' to WebAssembly import 'global_i32_mut': global type argument is expected to be of type 'int'."); + } + + [Fact] + public void ItFailsToInstantiateWhenGlobalIsNotMut() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotMutHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'NotMutHost.Int32Mut' to WebAssembly import 'global_i32_mut': the import is mutable (use the 'MutableGlobal' type)."); + } + + [Fact] + public void ItFailsToInstantiateWhenGlobalIsMut() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new MutHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'MutHost.Int32' to WebAssembly import 'global_i32': the import is constant (use the 'Global' type)."); + } + + [Fact] + public void ItBindsTheGlobalsCorrectly() + { + var host = new ValidHost(); + using (dynamic instance = Fixture.Module.Instantiate(host)) + { + host.Int32Mut.Value.Should().Be(0); + ((int)instance.get_global_i32_mut()).Should().Be(0); + host.Int32.Value.Should().Be(1); + ((int)instance.get_global_i32()).Should().Be(1); + host.Int64Mut.Value.Should().Be(2); + ((long)instance.get_global_i64_mut()).Should().Be(2); + host.Int64.Value.Should().Be(3); + ((long)instance.get_global_i64()).Should().Be(3); + host.Float32Mut.Value.Should().Be(4); + ((float)instance.get_global_f32_mut()).Should().Be(4); + host.Float32.Value.Should().Be(5); + ((float)instance.get_global_f32()).Should().Be(5); + host.Float64Mut.Value.Should().Be(6); + ((double)instance.get_global_f64_mut()).Should().Be(6); + host.Float64.Value.Should().Be(7); + ((double)instance.get_global_f64()).Should().Be(7); + + host.Int32Mut.Value = 10; + host.Int32Mut.Value.Should().Be(10); + ((int)instance.get_global_i32_mut()).Should().Be(10); + instance.set_global_i32_mut(11); + host.Int32Mut.Value.Should().Be(11); + ((int)instance.get_global_i32_mut()).Should().Be(11); + + host.Int64Mut.Value = 12; + host.Int64Mut.Value.Should().Be(12); + ((long)instance.get_global_i64_mut()).Should().Be(12); + instance.set_global_i64_mut(13); + host.Int64Mut.Value.Should().Be(13); + ((long)instance.get_global_i64_mut()).Should().Be(13); + + host.Float32Mut.Value = 14; + host.Float32Mut.Value.Should().Be(14); + ((float)instance.get_global_f32_mut()).Should().Be(14); + instance.set_global_f32_mut(15); + host.Float32Mut.Value.Should().Be(15); + ((float)instance.get_global_f32_mut()).Should().Be(15); + + host.Float64Mut.Value = 16; + host.Float64Mut.Value.Should().Be(16); + ((double)instance.get_global_f64_mut()).Should().Be(16); + instance.set_global_f64_mut(17); + host.Float64Mut.Value.Should().Be(17); + ((double)instance.get_global_f64_mut()).Should().Be(17); + } + } + } +} diff --git a/crates/misc/dotnet/tests/GlobalImportsTests.cs b/crates/misc/dotnet/tests/GlobalImportsTests.cs new file mode 100644 index 0000000000..2479baf132 --- /dev/null +++ b/crates/misc/dotnet/tests/GlobalImportsTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class GlobalImportsFixture : ModuleFixture + { + protected override string ModuleFileName => "GlobalImports.wasm"; + } + + public class GlobalImportsTests : IClassFixture + { + public GlobalImportsTests(GlobalImportsFixture fixture) + { + Fixture = fixture; + } + + private GlobalImportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetGlobalImports))] + public void ItHasTheExpectedGlobalImports(string importModule, string importName, ValueKind expectedKind, bool expectedMutable) + { + var import = Fixture.Module.Imports.Globals.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault(); + import.Should().NotBeNull(); + import.Kind.Should().Be(expectedKind); + import.IsMutable.Should().Be(expectedMutable); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedGlobals() + { + GetGlobalImports().Count().Should().Be(Fixture.Module.Imports.Globals.Count); + } + + public static IEnumerable GetGlobalImports() + { + yield return new object[] { + "", + "global_i32", + ValueKind.Int32, + false + }; + + yield return new object[] { + "", + "global_i32_mut", + ValueKind.Int32, + true + }; + + yield return new object[] { + "", + "global_i64", + ValueKind.Int64, + false + }; + + yield return new object[] { + "", + "global_i64_mut", + ValueKind.Int64, + true + }; + + yield return new object[] { + "", + "global_f32", + ValueKind.Float32, + false + }; + + yield return new object[] { + "", + "global_f32_mut", + ValueKind.Float32, + true + }; + + yield return new object[] { + "", + "global_f64", + ValueKind.Float64, + false + }; + + yield return new object[] { + "", + "global_f64_mut", + ValueKind.Float64, + true + }; + + yield return new object[] { + "other", + "global_from_module", + ValueKind.Int32, + false + }; + } + } +} diff --git a/crates/misc/dotnet/tests/MemoryExportsTests.cs b/crates/misc/dotnet/tests/MemoryExportsTests.cs new file mode 100644 index 0000000000..1a91ea18a3 --- /dev/null +++ b/crates/misc/dotnet/tests/MemoryExportsTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryExportsFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryExports.wasm"; + } + + public class MemoryExportsTests : IClassFixture + { + public class Host : IHost + { + public Instance Instance { get; set; } + } + + public MemoryExportsTests(MemoryExportsFixture fixture) + { + Fixture = fixture; + } + + private MemoryExportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetMemoryExports))] + public void ItHasTheExpectedMemoryExports(string exportName, uint expectedMinimum, uint expectedMaximum) + { + var export = Fixture.Module.Exports.Memories.Where(m => m.Name == exportName).FirstOrDefault(); + export.Should().NotBeNull(); + export.Minimum.Should().Be(expectedMinimum); + export.Maximum.Should().Be(expectedMaximum); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedTables() + { + GetMemoryExports().Count().Should().Be(Fixture.Module.Exports.Memories.Count); + } + + [Fact] + public void ItCreatesExternsForTheMemories() + { + var host = new Host(); + using (var instance = Fixture.Module.Instantiate(host)) + { + instance.Externs.Memories.Count.Should().Be(1); + + var memory = instance.Externs.Memories[0]; + memory.ReadString(0, 11).Should().Be("Hello World"); + int written = memory.WriteString(0, "WebAssembly Rocks!"); + memory.ReadString(0, written).Should().Be("WebAssembly Rocks!"); + + memory.ReadByte(20).Should().Be(1); + memory.WriteByte(20, 11); + memory.ReadByte(20).Should().Be(11); + + memory.ReadInt16(21).Should().Be(2); + memory.WriteInt16(21, 12); + memory.ReadInt16(21).Should().Be(12); + + memory.ReadInt32(23).Should().Be(3); + memory.WriteInt32(23, 13); + memory.ReadInt32(23).Should().Be(13); + + memory.ReadInt64(27).Should().Be(4); + memory.WriteInt64(27, 14); + memory.ReadInt64(27).Should().Be(14); + + memory.ReadSingle(35).Should().Be(5); + memory.WriteSingle(35, 15); + memory.ReadSingle(35).Should().Be(15); + + memory.ReadDouble(39).Should().Be(6); + memory.WriteDouble(39, 16); + memory.ReadDouble(39).Should().Be(16); + + memory.ReadIntPtr(48).Should().Be((IntPtr)7); + memory.WriteIntPtr(48, (IntPtr)17); + memory.ReadIntPtr(48).Should().Be((IntPtr)17); + } + } + + public static IEnumerable GetMemoryExports() + { + yield return new object[] { + "mem", + 1, + 2 + }; + } + } +} diff --git a/crates/misc/dotnet/tests/MemoryImportBindingTests.cs b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs new file mode 100644 index 0000000000..cbe9efaae9 --- /dev/null +++ b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs @@ -0,0 +1,187 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryImportBindingFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryImportBinding.wasm"; + } + + public class MemoryImportBindingTests : IClassFixture + { + class MissingImportsHost : IHost + { + public Instance Instance { get; set; } + } + + class MemoryIsStaticHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public static Memory x = new Memory(minimum: 1); + } + + class MemoryIsNotReadOnlyHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public Memory x = new Memory(minimum: 1); + } + + class NotAMemoryHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public readonly int x = 0; + } + + class InvalidMinimumHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public readonly Memory Mem = new Memory(minimum: 2); + } + + class InvalidMaximumHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public readonly Memory Mem = new Memory(maximum: 2); + } + + class ValidHost : IHost + { + public Instance Instance { get; set; } + + [Import("mem")] + public readonly Memory Mem = new Memory(minimum: 1); + } + + public MemoryImportBindingTests(MemoryImportBindingFixture fixture) + { + Fixture = fixture; + } + + private MemoryImportBindingFixture Fixture { get; set; } + + [Fact] + public void ItFailsToInstantiateWithMissingImport() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new MissingImportsHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Failed to bind memory import 'mem': the host does not contain a memory field with a matching 'Import' attribute."); + } + + [Fact] + public void ItFailsToInstantiateWithStaticField() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'MemoryIsStaticHost.x' to WebAssembly import 'mem': field cannot be static."); + } + + [Fact] + public void ItFailsToInstantiateWithNonReadOnlyField() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'MemoryIsNotReadOnlyHost.x' to WebAssembly import 'mem': field must be readonly."); + } + + [Fact] + public void ItFailsToInstantiateWithInvalidType() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAMemoryHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'NotAMemoryHost.x' to WebAssembly import 'mem': field is expected to be of type 'Memory'."); + } + + [Fact] + public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMinimumHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'InvalidMinimumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected minimum of 1 page(s)."); + } + + [Fact] + public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum() + { + Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMaximumHost())) { } }; + + action + .Should() + .Throw() + .WithMessage("Unable to bind 'InvalidMaximumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected maximum of 4294967295 page(s)."); + } + + [Fact] + public void ItBindsTheGlobalsCorrectly() + { + var host = new ValidHost(); + using (dynamic instance = Fixture.Module.Instantiate(host)) + { + host.Mem.ReadString(0, 11).Should().Be("Hello World"); + int written = host.Mem.WriteString(0, "WebAssembly Rocks!"); + host.Mem.ReadString(0, written).Should().Be("WebAssembly Rocks!"); + + host.Mem.ReadByte(20).Should().Be(1); + host.Mem.WriteByte(20, 11); + host.Mem.ReadByte(20).Should().Be(11); + ((byte)instance.ReadByte()).Should().Be(11); + + host.Mem.ReadInt16(21).Should().Be(2); + host.Mem.WriteInt16(21, 12); + host.Mem.ReadInt16(21).Should().Be(12); + ((short)instance.ReadInt16()).Should().Be(12); + + host.Mem.ReadInt32(23).Should().Be(3); + host.Mem.WriteInt32(23, 13); + host.Mem.ReadInt32(23).Should().Be(13); + ((int)instance.ReadInt32()).Should().Be(13); + + host.Mem.ReadInt64(27).Should().Be(4); + host.Mem.WriteInt64(27, 14); + host.Mem.ReadInt64(27).Should().Be(14); + ((long)instance.ReadInt64()).Should().Be(14); + + host.Mem.ReadSingle(35).Should().Be(5); + host.Mem.WriteSingle(35, 15); + host.Mem.ReadSingle(35).Should().Be(15); + ((float)instance.ReadFloat32()).Should().Be(15); + + host.Mem.ReadDouble(39).Should().Be(6); + host.Mem.WriteDouble(39, 16); + host.Mem.ReadDouble(39).Should().Be(16); + ((double)instance.ReadFloat64()).Should().Be(16); + + host.Mem.ReadIntPtr(48).Should().Be((IntPtr)7); + host.Mem.WriteIntPtr(48, (IntPtr)17); + host.Mem.ReadIntPtr(48).Should().Be((IntPtr)17); + ((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17); + } + } + } +} diff --git a/crates/misc/dotnet/tests/MemoryImportFromModuleTests.cs b/crates/misc/dotnet/tests/MemoryImportFromModuleTests.cs new file mode 100644 index 0000000000..b47da19374 --- /dev/null +++ b/crates/misc/dotnet/tests/MemoryImportFromModuleTests.cs @@ -0,0 +1,34 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryImportFromModuleFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryImportFromModule.wasm"; + } + + public class MemoryImportFromModuleTests : IClassFixture + { + public MemoryImportFromModuleTests(MemoryImportFromModuleFixture fixture) + { + Fixture = fixture; + } + + private MemoryImportFromModuleFixture Fixture { get; set; } + + [Fact] + public void ItHasTheExpectedImport() + { + Fixture.Module.Imports.Memories.Count.Should().Be(1); + + var memory = Fixture.Module.Imports.Memories[0]; + + memory.ModuleName.Should().Be("js"); + memory.Name.Should().Be("mem"); + memory.Minimum.Should().Be(1); + memory.Maximum.Should().Be(2); + } + } +} diff --git a/crates/misc/dotnet/tests/MemoryImportNoUpperBoundTests.cs b/crates/misc/dotnet/tests/MemoryImportNoUpperBoundTests.cs new file mode 100644 index 0000000000..8a9586bd28 --- /dev/null +++ b/crates/misc/dotnet/tests/MemoryImportNoUpperBoundTests.cs @@ -0,0 +1,34 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryImportNoUpperBoundFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryImportNoUpperBound.wasm"; + } + + public class MemoryImportNoUpperBoundTests : IClassFixture + { + public MemoryImportNoUpperBoundTests(MemoryImportNoUpperBoundFixture fixture) + { + Fixture = fixture; + } + + private MemoryImportNoUpperBoundFixture Fixture { get; set; } + + [Fact] + public void ItHasTheExpectedImport() + { + Fixture.Module.Imports.Memories.Count.Should().Be(1); + + var memory = Fixture.Module.Imports.Memories[0]; + + memory.ModuleName.Should().Be(""); + memory.Name.Should().Be("mem"); + memory.Minimum.Should().Be(1); + memory.Maximum.Should().Be(uint.MaxValue); + } + } +} diff --git a/crates/misc/dotnet/tests/MemoryImportWithUpperBoundTests.cs b/crates/misc/dotnet/tests/MemoryImportWithUpperBoundTests.cs new file mode 100644 index 0000000000..ec73d9da21 --- /dev/null +++ b/crates/misc/dotnet/tests/MemoryImportWithUpperBoundTests.cs @@ -0,0 +1,34 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryImportWithUpperBoundFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryImportWithUpperBound.wasm"; + } + + public class MemoryImportWithUpperBoundTests : IClassFixture + { + public MemoryImportWithUpperBoundTests(MemoryImportWithUpperBoundFixture fixture) + { + Fixture = fixture; + } + + private MemoryImportWithUpperBoundFixture Fixture { get; set; } + + [Fact] + public void ItHasTheExpectedImport() + { + Fixture.Module.Imports.Memories.Count.Should().Be(1); + + var memory = Fixture.Module.Imports.Memories[0]; + + memory.ModuleName.Should().Be(""); + memory.Name.Should().Be("mem"); + memory.Minimum.Should().Be(10); + memory.Maximum.Should().Be(100); + } + } +} diff --git a/crates/misc/dotnet/tests/Modules/FunctionExports.wasm b/crates/misc/dotnet/tests/Modules/FunctionExports.wasm new file mode 100644 index 0000000000000000000000000000000000000000..73e90b1387dd8bb2a092c3a1dc3bc31966c35957 GIT binary patch literal 509 zcmaixO%8%E5QSf%ic;Jeb?X(>xO3spEnJjFA(|){(S?b?@w}E(szH&&rY|#@Z>IB- zLM2BAfXzG~@L5jENGa(KpXXUh0W2qFq?B~`QPd4BQoG}USvGGpomSfw&cpJ$Q7ljD ztd5DK>>MQ5!zBR*aTLh?cx2coVg^kozsM?3ST|)GR5!(YdRO5=#-R+x z^!B!645C>@^zaKg6{PLFP^*f{sdis)Fn!-H++k@?McdjQ;jTRkqH;Or5F85#2TS@q R2!WN3`iL&kg{l83J^|Bujlci^ literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/FunctionExports.wat b/crates/misc/dotnet/tests/Modules/FunctionExports.wat new file mode 100644 index 0000000000..c939a1d23d --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/FunctionExports.wat @@ -0,0 +1,26 @@ +(module + (func $no_params_no_results) + (func $one_i32_param_no_results (param i32)) + (func $one_i64_param_no_results (param i64)) + (func $one_f32_param_no_results (param f32)) + (func $one_f64_param_no_results (param f64)) + (func $one_param_of_each_type (param i32 i64 f32 f64)) + (func $no_params_one_i32_result (result i32) i32.const 0) + (func $no_params_one_i64_result (result i64) i64.const 0) + (func $no_params_one_f32_result (result f32) f32.const 0) + (func $no_params_one_f64_result (result f64) f64.const 0) + (func $one_result_of_each_type (result i32 i64 f32 f64) i32.const 0 i64.const 0 f32.const 0 f64.const 0) + (func $one_param_and_result_of_each_type (param i32 i64 f32 f64) (result i32 i64 f32 f64) i32.const 0 i64.const 0 f32.const 0 f64.const 0) + (export "no_params_no_results" (func $no_params_no_results)) + (export "one_i32_param_no_results" (func $one_i32_param_no_results)) + (export "one_i64_param_no_results" (func $one_i64_param_no_results)) + (export "one_f32_param_no_results" (func $one_f32_param_no_results)) + (export "one_f64_param_no_results" (func $one_f64_param_no_results)) + (export "one_param_of_each_type" (func $one_param_of_each_type)) + (export "no_params_one_i32_result" (func $no_params_one_i32_result)) + (export "no_params_one_i64_result" (func $no_params_one_i64_result)) + (export "no_params_one_f32_result" (func $no_params_one_f32_result)) + (export "no_params_one_f64_result" (func $no_params_one_f64_result)) + (export "one_result_of_each_type" (func $one_result_of_each_type)) + (export "one_param_and_result_of_each_type" (func $one_param_and_result_of_each_type)) +) diff --git a/crates/misc/dotnet/tests/Modules/FunctionImports.wasm b/crates/misc/dotnet/tests/Modules/FunctionImports.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f52c32c22f68b1bbdf2dd5eab7872cae15b336e5 GIT binary patch literal 442 zcmZvW$qs@r6h&`wo_0jp`U`5@`7Nc95=fvW3=0#1U-lDxPz)+8Zu-*mh69ns3;?@r zUBG2ErJ|ISC){aWmGpqsl!{VPo-W#dL)+4NSv%ND^F*W~&Nx-6%!(+_Xt$%4?MWrMw>P>od+_t{X*UIP%G}II_9zVibw))K+TiC literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/FunctionImports.wat b/crates/misc/dotnet/tests/Modules/FunctionImports.wat new file mode 100644 index 0000000000..2f07daf63c --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/FunctionImports.wat @@ -0,0 +1,27 @@ +(module + (type $t0 (func)) + (type $t1 (func (param i32))) + (type $t2 (func (param i64))) + (type $t3 (func (param f32))) + (type $t4 (func (param f64))) + (type $t5 (func (param i32 i64 f32 f64))) + (type $t6 (func (result i32))) + (type $t7 (func (result i64))) + (type $t8 (func (result f32))) + (type $t9 (func (result f64))) + (type $t10 (func (result i32 i64 f32 f64))) + (type $t11 (func (param i32 i64 f32 f64) (result i32 i64 f32 f64))) + (import "" "no_params_no_results" (func $.f0 (type $t0))) + (import "" "one_i32_param_no_results" (func $.f1 (type $t1))) + (import "" "one_i64_param_no_results" (func $.f2 (type $t2))) + (import "" "one_f32_param_no_results" (func $.f3 (type $t3))) + (import "" "one_f64_param_no_results" (func $.f4 (type $t4))) + (import "" "one_param_of_each_type" (func $.f5 (type $t5))) + (import "" "no_params_one_i32_result" (func $.f6 (type $t6))) + (import "" "no_params_one_i64_result" (func $.f7 (type $t7))) + (import "" "no_params_one_f32_result" (func $.f8 (type $t8))) + (import "" "no_params_one_f64_result" (func $.f9 (type $t9))) + (import "" "one_result_of_each_type" (func $.f10 (type $t10))) + (import "" "one_param_and_result_of_each_type" (func $.f11 (type $t11))) + (import "other" "function_from_module" (func $.f12 (type $t0))) +) diff --git a/crates/misc/dotnet/tests/Modules/GlobalExports.wasm b/crates/misc/dotnet/tests/Modules/GlobalExports.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9e9f358fc5284af1e307505bd47cf1a41d50a535 GIT binary patch literal 194 zcmZQbEY4+QU|?Xg<)~+HWZ=B*bMliCbK*0NjhGqu5S;kj(h_DyxU888GZS3a%mggU43kX* X>Sck+0y$t=R=6xsFB@DIs+S!AYN0PI literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/GlobalExports.wat b/crates/misc/dotnet/tests/Modules/GlobalExports.wat new file mode 100644 index 0000000000..f8ab099c5d --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/GlobalExports.wat @@ -0,0 +1,18 @@ +(module + (global $g1 i32 (i32.const 0)) + (global $g2 (mut i32) (i32.const 1)) + (global $g3 i64 (i64.const 2)) + (global $g4 (mut i64) (i64.const 3)) + (global $g5 f32 (f32.const 4)) + (global $g6 (mut f32) (f32.const 5)) + (global $g7 f64 (f64.const 6)) + (global $g8 (mut f64) (f64.const 7)) + (export "global_i32" (global $g1)) + (export "global_i32_mut" (global $g2)) + (export "global_i64" (global $g3)) + (export "global_i64_mut" (global $g4)) + (export "global_f32" (global $g5)) + (export "global_f32_mut" (global $g6)) + (export "global_f64" (global $g7)) + (export "global_f64_mut" (global $g8)) +) diff --git a/crates/misc/dotnet/tests/Modules/GlobalImportBindings.wasm b/crates/misc/dotnet/tests/Modules/GlobalImportBindings.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8933739d655fbc5a6d59dec1479616b090307a42 GIT binary patch literal 509 zcmY+9O%8%E5QX2gr68aSPoU8gDB%X4gKDD0MB_r;@w4`@-oduiwB0b1FW-ApaD3hg z0If3x8WoMub5Jyx*(>VJ>=boiAIh+3Z;#XQ)?D|y=Kg;1k&t`DM|h*ItT6;eUGc#i z7uV5yLnGb?hU@48Lw9uG^AaG&xDbhyY5Jvd*`8mGe~P(niXsSFJ|kc)7DcC%;8C=$ zAfl+Vk}&H22QrLmh%}7;tt^VVzF8F2SMw<9tRg>@0vi7z?&%nTk;Kt{cmN3^dB2<`}FxMfeFf#$G zs6(ho1Da8bPyytERn#J>0Gd&QqylP24I^uQNk(ds5Y+axqWs+W-29Z%oK&Er0Q=Z9 AOaK4? literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/GlobalImports.wat b/crates/misc/dotnet/tests/Modules/GlobalImports.wat new file mode 100644 index 0000000000..1a0bbb704f --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/GlobalImports.wat @@ -0,0 +1,11 @@ +(module + (global $g1 (import "" "global_i32") i32) + (global $g2 (import "" "global_i32_mut") (mut i32)) + (global $g3 (import "" "global_i64") i64) + (global $g4 (import "" "global_i64_mut") (mut i64)) + (global $g5 (import "" "global_f32") f32) + (global $g6 (import "" "global_f32_mut") (mut f32)) + (global $g7 (import "" "global_f64") f64) + (global $g8 (import "" "global_f64_mut") (mut f64)) + (global $g9 (import "other" "global_from_module") i32) +) diff --git a/crates/misc/dotnet/tests/Modules/MemoryExports.wasm b/crates/misc/dotnet/tests/Modules/MemoryExports.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bc0fe01a33a9e38e33927684a06374ccac3e93c0 GIT binary patch literal 112 zcmXAgF$#b%5Cr#PB!O)e1slP_)+czKKS(W@E%DiLxic(5-@sv>xEJ(Tt)UZl4M%q?jk%|-C>SNy5YCW-?e z-(wyyNd9vIK)v7>bP^l|2fZWH?X!bLoS&>xI3GPmMt?qSyRh4LW=f=Q2ya6oSf2W$mCslLwtZN>=G=B8j_}J&X zv2a3PX_gg_o1)CNa*2uL4l4z?kOM6*z(s5WFMPQni#{*lMr@mCASUMeT_0QkMY$}L literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportBinding.wat b/crates/misc/dotnet/tests/Modules/MemoryImportBinding.wat new file mode 100644 index 0000000000..a0b519d3cc --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/MemoryImportBinding.wat @@ -0,0 +1,39 @@ +(module + (import "" "mem" (memory 1)) + (data (i32.const 0) "Hello World") + (data (i32.const 20) "\01") + (data (i32.const 21) "\02\00") + (data (i32.const 23) "\03\00\00\00") + (data (i32.const 27) "\04\00\00\00\00\00\00\00") + (data (i32.const 35) "\00\00\a0\40") + (data (i32.const 39) "\00\00\00\00\00\00\18\40") + (data (i32.const 48) "\07\00\00\00\00\00\00\00") + (func (export "ReadByte") (result i32) + i32.const 20 + i32.load8_s + ) + (func (export "ReadInt16") (result i32) + i32.const 21 + i32.load16_s + ) + (func (export "ReadInt32") (result i32) + i32.const 23 + i32.load + ) + (func (export "ReadInt64") (result i64) + i32.const 27 + i64.load + ) + (func (export "ReadFloat32") (result f32) + i32.const 35 + f32.load + ) + (func (export "ReadFloat64") (result f64) + i32.const 39 + f64.load + ) + (func (export "ReadIntPtr") (result i64) + i32.const 48 + i64.load + ) +) diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wasm b/crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wasm new file mode 100644 index 0000000000000000000000000000000000000000..73e12cb7cf1f6f05de3eb8662923d812e2808b77 GIT binary patch literal 22 dcmZQbEY4+QU|?Y4VPwiGX3kB`WnyGx0stx619<=d literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wat b/crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wat new file mode 100644 index 0000000000..23e4d4a5ea --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/MemoryImportFromModule.wat @@ -0,0 +1,3 @@ +(module + (import "js" "mem" (memory 1 2)) +) diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wasm b/crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wasm new file mode 100644 index 0000000000000000000000000000000000000000..813fefe6648bfd250032f5fa39e3af201e8f52a4 GIT binary patch literal 19 acmZQbEY4+QU|?Y4WMp8@P0eLuU<3dhHv*IZ literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wat b/crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wat new file mode 100644 index 0000000000..86dda0097b --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/MemoryImportNoUpperBound.wat @@ -0,0 +1,3 @@ +(module + (import "" "mem" (memory 1)) +) diff --git a/crates/misc/dotnet/tests/Modules/MemoryImportWithUpperBound.wasm b/crates/misc/dotnet/tests/Modules/MemoryImportWithUpperBound.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0776ce28e204650434c09960e68d2eb267a60cce GIT binary patch literal 20 bcmZQbEY4+QU|?Y4Vq{>>P0eLuR literal 0 HcmV?d00001 diff --git a/crates/misc/dotnet/tests/Modules/TableImports.wat b/crates/misc/dotnet/tests/Modules/TableImports.wat new file mode 100644 index 0000000000..a4c3b094c6 --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/TableImports.wat @@ -0,0 +1,5 @@ +(module + (import "" "table1" (table $t1 10 funcref)) + (import "" "table2" (table $t2 15 anyref)) + (import "other" "table3" (table $t3 1 funcref)) +) diff --git a/crates/misc/dotnet/tests/TableExportsTests.cs b/crates/misc/dotnet/tests/TableExportsTests.cs new file mode 100644 index 0000000000..ebd86658c6 --- /dev/null +++ b/crates/misc/dotnet/tests/TableExportsTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class TableExportsFixture : ModuleFixture + { + protected override string ModuleFileName => "TableExports.wasm"; + } + + public class TableExportsTests : IClassFixture + { + public TableExportsTests(TableExportsFixture fixture) + { + Fixture = fixture; + } + + private TableExportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetTableExports))] + public void ItHasTheExpectedTableExports(string exportName, ValueKind expectedKind, uint expectedMinimum, uint expectedMaximum) + { + var export = Fixture.Module.Exports.Tables.Where(f => f.Name == exportName).FirstOrDefault(); + export.Should().NotBeNull(); + export.Kind.Should().Be(expectedKind); + export.Minimum.Should().Be(expectedMinimum); + export.Maximum.Should().Be(expectedMaximum); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedTables() + { + GetTableExports().Count().Should().Be(Fixture.Module.Exports.Tables.Count); + } + + public static IEnumerable GetTableExports() + { + yield return new object[] { + "table1", + ValueKind.FuncRef, + 1, + 10 + }; + + yield return new object[] { + "table2", + ValueKind.AnyRef, + 10, + uint.MaxValue + }; + + yield return new object[] { + "table3", + ValueKind.FuncRef, + 100, + 1000 + }; + } + } +} diff --git a/crates/misc/dotnet/tests/TableImportsTests.cs b/crates/misc/dotnet/tests/TableImportsTests.cs new file mode 100644 index 0000000000..265205e0c1 --- /dev/null +++ b/crates/misc/dotnet/tests/TableImportsTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class TableImportsFixture : ModuleFixture + { + protected override string ModuleFileName => "TableImports.wasm"; + } + + public class TableImportsTests : IClassFixture + { + public TableImportsTests(TableImportsFixture fixture) + { + Fixture = fixture; + } + + private TableImportsFixture Fixture { get; set; } + + [Theory] + [MemberData(nameof(GetTableImports))] + public void ItHasTheExpectedTableImports(string importModule, string importName, ValueKind expectedKind, uint expectedMinimum, uint expectedMaximum) + { + var import = Fixture.Module.Imports.Tables.Where(f => f.ModuleName == importModule && f.Name == importName).FirstOrDefault(); + import.Should().NotBeNull(); + import.Kind.Should().Be(expectedKind); + import.Minimum.Should().Be(expectedMinimum); + import.Maximum.Should().Be(expectedMaximum); + } + + [Fact] + public void ItHasTheExpectedNumberOfExportedTables() + { + GetTableImports().Count().Should().Be(Fixture.Module.Imports.Tables.Count); + } + + public static IEnumerable GetTableImports() + { + yield return new object[] { + "", + "table1", + ValueKind.FuncRef, + 10, + uint.MaxValue + }; + + yield return new object[] { + "", + "table2", + ValueKind.AnyRef, + 15, + uint.MaxValue + }; + + yield return new object[] { + "other", + "table3", + ValueKind.FuncRef, + 1, + uint.MaxValue + }; + } + } +} diff --git a/crates/misc/dotnet/tests/Wasmtime.Tests.csproj b/crates/misc/dotnet/tests/Wasmtime.Tests.csproj new file mode 100644 index 0000000000..20f4181420 --- /dev/null +++ b/crates/misc/dotnet/tests/Wasmtime.Tests.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.0 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + +