Merge pull request #624 from peterhuene/wasmtime-for-dotnet

Move Wasmtime for .NET to the Wasmtime repo.
This commit is contained in:
Peter Huene
2019-11-26 12:47:04 -08:00
committed by GitHub
101 changed files with 6434 additions and 0 deletions

View File

@@ -275,6 +275,31 @@ jobs:
name: bins-${{ matrix.os }}
path: dist
# Build and test the .NET bindings
dotnet:
name: Test Wasmtime for .NET bindings
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@master
with:
submodules: true
- uses: ./.github/actions/install-rust
- uses: ./.github/actions/binary-compatible-builds
- run: rustup target add wasm32-wasi
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '3.0.101'
- run: |
cd crates/misc/dotnet/tests
dotnet test
- run: |
cd crates/misc/dotnet/src
dotnet pack
if: matrix.os == 'macos-latest' # Currently the pack target only supports macOS
# Consumes all published artifacts from all the previous build steps, creates
# a bunch of tarballs for all of them, and then publishes the tarballs
# themselves as an artifact (for inspection) and then optionally creates

8
crates/misc/dotnet/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*.swp
*.*~
.DS_Store
.vscode
bin/
obj/

View File

@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<WasmtimeVersion>0.8.0</WasmtimeVersion>
<WasmtimeLibraryName>wasmtime</WasmtimeLibraryName>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,12 @@
<Project>
<PropertyGroup>
<LibraryPrefix Condition="'$(LibraryPrefix)' == '' and !$(NETCoreSdkRuntimeIdentifier.StartsWith('win'))">lib</LibraryPrefix>
<LibraryExtension Condition="'$(LibraryExtension)' == '' and $(NETCoreSdkRuntimeIdentifier.StartsWith('win'))">.dll</LibraryExtension>
<LibraryExtension Condition="'$(LibraryExtension)' == '' and $(NETCoreSdkRuntimeIdentifier.StartsWith('osx'))">.dylib</LibraryExtension>
<LibraryExtension Condition="'$(LibraryExtension)' == ''">.so</LibraryExtension>
<WasmtimeLibraryFilename>$(LibraryPrefix)$(WasmtimeLibraryName)$(LibraryExtension)</WasmtimeLibraryFilename>
<WasmtimeOutputPath>$(MSBuildThisFileDirectory)../../../target/$(Configuration.ToLower())</WasmtimeOutputPath>
<BuildWasmtimeCommand Condition="'$(Configuration)' == 'Release'">cargo build --release -p $(WasmtimeLibraryName)</BuildWasmtimeCommand>
<BuildWasmtimeCommand Condition="'$(BuildWasmtimeCommand)' == ''">cargo build -p $(WasmtimeLibraryName)</BuildWasmtimeCommand>
</PropertyGroup>
</Project>

219
crates/misc/dotnet/LICENSE Normal file
View File

@@ -0,0 +1,219 @@
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.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -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.<version>.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 | ✅ |

9
crates/misc/dotnet/docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
###############
# folder #
###############
/**/DROP/
/**/TEMP/
/**/packages/
/**/bin/
/**/obj/
_site

View File

@@ -0,0 +1,5 @@
###############
# temp file #
###############
*.yml
.manifest

View File

@@ -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.

View File

@@ -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<T>`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Global-1.html), [`MutableGlobal<T>`](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_.

View File

@@ -0,0 +1,2 @@
- name: Introduction to Wasmtime for .NET
href: intro.md

View File

@@ -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"
}
}
}
}

View File

@@ -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.

View File

@@ -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.}}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>

View File

@@ -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;
}

View File

@@ -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

Binary file not shown.

View File

@@ -0,0 +1,33 @@
using System;
using Wasmtime;
namespace HelloExample
{
class Host : IHost
{
public Instance Instance { get; set; }
[Import("print_global")]
public void PrintGlobal()
{
Console.WriteLine($"The value of the global is: {Global.Value}.");
}
[Import("global")]
public readonly MutableGlobal<int> Global = new MutableGlobal<int>(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);
}
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Wasmtime.csproj" />
</ItemGroup>
<!-- This is needed as we're not referencing Wasmtime as a package. -->
<Target Name="BuildWasmtime" BeforeTargets="AssignTargetPaths">
<Message Text="Building Wasmtime from source." Importance="High" />
<Exec Command="$(BuildWasmtimeCommand)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
<ItemGroup>
<None Include="$(WasmtimeOutputPath)/$(WasmtimeLibraryFilename)" Link="$(WasmtimeLibraryFilename)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>

Binary file not shown.

View File

@@ -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))
)

View File

@@ -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();
}
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Wasmtime.csproj" />
</ItemGroup>
<!-- This is needed as we're not referencing Wasmtime as a package. -->
<Target Name="BuildWasmtime" BeforeTargets="AssignTargetPaths">
<Message Text="Building Wasmtime from source." Importance="High" />
<Exec Command="$(BuildWasmtimeCommand)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
<ItemGroup>
<None Include="$(WasmtimeOutputPath)/$(WasmtimeLibraryFilename)" Link="$(WasmtimeLibraryFilename)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>

Binary file not shown.

View File

@@ -0,0 +1,8 @@
(module
(type $t0 (func))
(import "" "hello" (func $.hello (type $t0)))
(func $run
call $.hello
)
(export "run" (func $run))
)

View File

@@ -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();
}
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Wasmtime.csproj" />
</ItemGroup>
<!-- This is needed as we're not referencing Wasmtime as a package. -->
<Target Name="BuildWasmtime" BeforeTargets="AssignTargetPaths">
<Message Text="Building Wasmtime from source." Importance="High" />
<Exec Command="$(BuildWasmtimeCommand)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
<ItemGroup>
<None Include="$(WasmtimeOutputPath)/$(WasmtimeLibraryFilename)" Link="$(WasmtimeLibraryFilename)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>

Binary file not shown.

View File

@@ -0,0 +1,12 @@
(module
(type $t0 (func (param i32 i32)))
(import "" "log" (func $.log (type $t0)))
(memory (export "mem") 1 2)
(data (i32.const 0) "Hello World")
(func $run
i32.const 0
i32.const 11
call $.log
)
(export "run" (func $run))
)

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents an abstract host binding.
/// </summary>
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<Binding> 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<Binding>();
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<MethodInfo> 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<FieldInfo> 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<FieldInfo> 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);
}
}
}

View File

@@ -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
{
/// <summary>
/// Represents a host function binding.
/// </summary>
public class FunctionBinding : Binding
{
/// <summary>
/// Constructs a new function binding.
/// </summary>
/// <param name="import">The function import of the binding.</param>
/// <param name="method">The method the import is bound to.</param>
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();
}
/// <summary>
/// The function import of the binding.
/// </summary>
public FunctionImport Import { get; private set; }
/// <summary>
/// The method the import is bound to.
/// </summary>
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;
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host global binding.
/// </summary>
public class GlobalBinding : Binding
{
/// <summary>
/// Constructs a new global binding.
/// </summary>
/// <param name="import">The global import of the binding.</param>
/// <param name="field">The field the import is bound to.</param>
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();
}
/// <summary>
/// The global import of the binding.
/// </summary>
public GlobalImport Import { get; private set; }
/// <summary>
/// The field the import is bound to.
/// </summary>
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<T>'");
}
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<T>' or 'MutableGlobal<T>'");
}
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");
}
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host memory binding.
/// </summary>
public class MemoryBinding : Binding
{
/// <summary>
/// Constructs a new memory binding.
/// </summary>
/// <param name="import">The memory import of the binding.</param>
/// <param name="field">The field the import is bound to.</param>
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();
}
/// <summary>
/// The memory import of the binding.
/// </summary>
public MemoryImport Import { get; private set; }
/// <summary>
/// The field the import is bound to.
/// </summary>
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'");
}
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Represents the Wasmtime engine.
/// </summary>
public class Engine : IDisposable
{
/// <summary>
/// Constructs a new <see cref="Engine" />.
/// </summary>
public Engine()
{
Handle = Interop.wasm_engine_new();
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime engine.");
}
}
/// <summary>
/// Creates a new Wasmtime <see cref="Store" />.
/// </summary>
/// <returns>Returns the new <see cref="Store" />.</returns>
public Store CreateStore()
{
return new Store(this);
}
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Interop.EngineHandle Handle { get; private set; }
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Runtime.InteropServices;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents an export of a WebAssembly module.
/// </summary>
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);
}
}
/// <summary>
/// The name of the export.
/// </summary>
public string Name { get; private set; }
/// <inheritdoc/>
public override string ToString()
{
return Name;
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents the exports of a WebAssembly module.
/// </summary>
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<Export>((int)exports.size);
var functions = new List<FunctionExport>();
var globals = new List<GlobalExport>();
var tables = new List<TableExport>();
var memories = new List<MemoryExport>();
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);
}
}
/// <summary>
/// The exported functions of a WebAssembly module.
/// </summary>
public IReadOnlyList<FunctionExport> Functions { get; private set; }
/// <summary>
/// The exported globals of a WebAssembly module.
/// </summary>
public IReadOnlyList<GlobalExport> Globals { get; private set; }
/// <summary>
/// The exported tables of a WebAssembly module.
/// </summary>
public IReadOnlyList<TableExport> Tables { get; private set; }
/// <summary>
/// The exported memories of a WebAssembly module.
/// </summary>
public IReadOnlyList<MemoryExport> Memories { get; private set; }
internal List<Export> All { get; private set; }
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents a function exported from a WebAssembly module.
/// </summary>
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));
}
}
/// <summary>
/// The parameter of the exported WebAssembly function.
/// </summary>
public IReadOnlyList<ValueKind> Parameters { get; private set; }
/// <summary>
/// The results of the exported WebAssembly function.
/// </summary>
public IReadOnlyList<ValueKind> Results { get; private set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents a global variable exported from a WebAssembly module.
/// </summary>
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;
}
/// <summary>
/// The kind of value for the global variable.
/// </summary>
public ValueKind Kind { get; private set; }
/// <summary>
/// Determines whether or not the global variable is mutable.
/// </summary>
public bool IsMutable { get; private set; }
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents a memory exported from a WebAssembly module.
/// </summary>
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;
}
}
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The maximum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum { get; private set; }
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Exports
{
/// <summary>
/// Represents a table exported from a WebAssembly module.
/// </summary>
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;
}
}
/// <summary>
/// The value kind of the table.
/// </summary>
public ValueKind Kind { get; private set; }
/// <summary>
/// The minimum number of elements in the table.
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The maximum number of elements in the table.
/// </summary>
public uint Maximum { get; private set; }
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using Wasmtime.Exports;
namespace Wasmtime.Externs
{
/// <summary>
/// Represents an external (instantiated) WebAssembly function.
/// </summary>
public class ExternFunction
{
internal ExternFunction(FunctionExport export, IntPtr func)
{
_export = export;
_func = func;
}
/// <summary>
/// The name of the WebAssembly function.
/// </summary>
public string Name => _export.Name;
/// <summary>
/// The parameters of the WebAssembly function.
/// </summary>
public IReadOnlyList<ValueKind> Parameters => _export.Parameters;
/// <summary>
/// The results of the WebAssembly function.
/// </summary>
public IReadOnlyList<ValueKind> Results => _export.Results;
/// <summary>
/// Invokes the WebAssembly function.
/// </summary>
/// <param name="arguments">The array of arguments to pass to the function.</param>
/// <returns>
/// 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.
/// </returns>
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;
}
}

View File

@@ -0,0 +1,62 @@
using System;
using Wasmtime.Exports;
namespace Wasmtime.Externs
{
/// <summary>
/// Represents an external (instantiated) WebAssembly global.
/// </summary>
public class ExternGlobal
{
internal ExternGlobal(GlobalExport export, IntPtr global)
{
_export = export;
_global = global;
}
/// <summary>
/// The name of the WebAssembly global.
/// </summary>
public string Name => _export.Name;
/// <summary>
/// The kind of value for the global variable.
/// </summary>
public ValueKind Kind => _export.Kind;
/// <summary>
/// Determines whether or not the global variable is mutable.
/// </summary>
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;
}
}

View File

@@ -0,0 +1,245 @@
using System;
using System.Buffers.Binary;
using System.Text;
using Wasmtime.Exports;
namespace Wasmtime.Externs
{
/// <summary>
/// Represents an external (instantiated) WebAssembly memory.
/// </summary>
public class ExternMemory
{
internal ExternMemory(MemoryExport export, IntPtr memory)
{
_export = export;
_memory = memory;
}
/// <summary>
/// The name of the WebAssembly memory.
/// </summary>
public string Name => _export.Name;
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Minimum => _export.Minimum;
/// <summary>
/// The maximum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum => _export.Maximum;
/// <summary>
/// The span of the memory.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public unsafe Span<byte> Span
{
get
{
var data = Interop.wasm_memory_data(_memory);
var size = Convert.ToInt32(Interop.wasm_memory_data_size(_memory).ToUInt32());
return new Span<byte>(data, size);
}
}
/// <summary>
/// Reads a UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <param name="length">The length of bytes to read.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadString(int address, int length)
{
return Encoding.UTF8.GetString(Span.Slice(address, length));
}
/// <summary>
/// Writes a UTF-8 string at the given address.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The string to write.</param>
/// <return>Returns the number of bytes written.</return>
public int WriteString(int address, string value)
{
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
}
/// <summary>
/// Reads a byte from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the byte read from memory.</returns>
public byte ReadByte(int address)
{
return Span[address];
}
/// <summary>
/// Writes a byte to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The byte to write.</param>
public void WriteByte(int address, byte value)
{
Span[address] = value;
}
/// <summary>
/// Reads a short from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the short read from memory.</returns>
public short ReadInt16(int address)
{
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
}
/// <summary>
/// Writes a short to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The short to write.</param>
public void WriteInt16(int address, short value)
{
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
}
/// <summary>
/// Reads an int from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the int read from memory.</returns>
public int ReadInt32(int address)
{
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
}
/// <summary>
/// Writes an int to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The int to write.</param>
public void WriteInt32(int address, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public long ReadInt64(int address)
{
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
}
/// <summary>
/// Writes a long to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The long to write.</param>
public void WriteInt64(int address, long value)
{
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
}
/// <summary>
/// Reads an IntPtr from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the IntPtr read from memory.</returns>
public IntPtr ReadIntPtr(int address)
{
if (IntPtr.Size == 4)
{
return (IntPtr)ReadInt32(address);
}
return (IntPtr)ReadInt64(address);
}
/// <summary>
/// Writes an IntPtr to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The IntPtr to write.</param>
public void WriteIntPtr(int address, IntPtr value)
{
if (IntPtr.Size == 4)
{
WriteInt32(address, value.ToInt32());
}
else
{
WriteInt64(address, value.ToInt64());
}
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public float ReadSingle(int address)
{
unsafe
{
var i = ReadInt32(address);
return *((float*)&i);
}
}
/// <summary>
/// Writes a single to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The single to write.</param>
public void WriteSingle(int address, float value)
{
unsafe
{
WriteInt32(address, *(int*)&value);
}
}
/// <summary>
/// Reads a double from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the double read from memory.</returns>
public double ReadDouble(int address)
{
unsafe
{
var i = ReadInt64(address);
return *((double*)&i);
}
}
/// <summary>
/// Writes a double to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The double to write.</param>
public void WriteDouble(int address, double value)
{
unsafe
{
WriteInt64(address, *(long*)&value);
}
}
private MemoryExport _export;
private IntPtr _memory;
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using Wasmtime.Exports;
namespace Wasmtime.Externs
{
/// <summary>
/// Represents external (instantiated) WebAssembly functions, globals, tables, and memories.
/// </summary>
public class Externs
{
internal Externs(Wasmtime.Exports.Exports exports, Interop.wasm_extern_vec_t externs)
{
var functions = new List<ExternFunction>();
var globals = new List<ExternGlobal>();
var memories = new List<ExternMemory>();
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;
}
/// <summary>
/// The extern functions from an instantiated WebAssembly module.
/// </summary>
public IReadOnlyList<ExternFunction> Functions { get; private set; }
/// <summary>
/// The extern globals from an instantiated WebAssembly module.
/// </summary>
public IReadOnlyList<ExternGlobal> Globals { get; private set; }
/// <summary>
/// The extern memories from an instantiated WebAssembly module.
/// </summary>
public IReadOnlyList<ExternMemory> Memories { get; private set; }
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Represents a constant WebAssembly global value.
/// </summary>
public class Global<T>
{
/// <summary>
/// Creates a new <see cref="Global&lt;T&gt;" /> with the given initial value.
/// </summary>
/// <param name="initialValue">The initial value of the global.</param>
public Global(T initialValue)
{
InitialValue = initialValue;
Kind = Interop.ToValueKind(typeof(T));
}
/// <summary>
/// The value of the global.
/// </summary>
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; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Wasmtime.Bindings;
namespace Wasmtime
{
/// <summary>
/// The interface implemented by Wasmtime hosts.
/// </summary>
public interface IHost
{
/// <summary>
/// The <see cref="Wasmtime.Instance" /> that the host is bound to.
/// </summary>
/// <remarks>A host can only bind to one module instance at a time.</remarks>
Instance Instance { get; set; }
/// <summary>
/// Gets the import bindings of the host given a WebAssembly module.
/// </summary>
/// <param name="module">The WebAssembly module to get the import bindings for.</param>
/// <returns>Returns the list of import bindings for the host.</returns>
List<Binding> GetImportBindings(Module module) => Binding.GetImportBindings(this, module);
}
}

View File

@@ -0,0 +1,31 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Used to mark .NET methods and fields as imports to a WebAssembly module.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
public class ImportAttribute : Attribute
{
/// <summary>
/// Constructs a new <see cref="ImportAttribute"/>.
/// </summary>
/// <param name="name">The name of the import.</param>
public ImportAttribute(string name)
{
Name = name;
}
/// <summary>
/// The name of the import.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The module name of the import.
/// </summary>
/// <remarks>A null or empty module name implies that the import is not scoped to a module.</remarks>
public string Module { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Wasmtime.Imports
{
/// <summary>
/// Represents a function imported to a WebAssembly module.
/// </summary>
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));
}
}
/// <summary>
/// The parameters of the imported function.
/// </summary>
public IReadOnlyList<ValueKind> Parameters { get; private set; }
/// <summary>
/// The results of the imported function.
/// </summary>
public IReadOnlyList<ValueKind> Results { get; private set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Imports
{
/// <summary>
/// Represents a global variable imported to a WebAssembly module.
/// </summary>
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;
}
/// <summary>
/// The kind of value for the global variable.
/// </summary>
public ValueKind Kind { get; private set; }
/// <summary>
/// Determines whether or not the global variable is mutable.
/// </summary>
public bool IsMutable { get; private set; }
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Runtime.InteropServices;
namespace Wasmtime.Imports
{
/// <summary>
/// The base class for import types.
/// </summary>
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);
}
}
/// <summary>
/// The module name of the import.
/// </summary>
public string ModuleName { get; private set; }
/// <summary>
/// The name of the import.
/// </summary>
public string Name { get; private set; }
/// <inheritdoc/>
public override string ToString()
{
return $"{ModuleName}{(string.IsNullOrEmpty(ModuleName) ? "" : ".")}{Name}";
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
namespace Wasmtime.Imports
{
/// <summary>
/// Represents imported functions, globals, tables, and memories to a WebAssembly module.
/// </summary>
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<Import>((int)imports.size);
var functions = new List<FunctionImport>();
var globals = new List<GlobalImport>();
var tables = new List<TableImport>();
var memories = new List<MemoryImport>();
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);
}
}
/// <summary>
/// The imported functions required by a WebAssembly module.
/// </summary>
public IReadOnlyList<FunctionImport> Functions { get; private set; }
/// <summary>
/// The imported globals required by a WebAssembly module.
/// </summary>
public IReadOnlyList<GlobalImport> Globals { get; private set; }
/// <summary>
/// The imported tables required by a WebAssembly module.
/// </summary>
public IReadOnlyList<TableImport> Tables { get; private set; }
/// <summary>
/// The imported memories required by a WebAssembly module.
/// </summary>
public IReadOnlyList<MemoryImport> Memories { get; private set; }
internal IReadOnlyList<Import> All { get; private set; }
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Imports
{
/// <summary>
/// Represents a memory imported to a WebAssembly module.
/// </summary>
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;
}
}
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The maximum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum { get; private set; }
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
namespace Wasmtime.Imports
{
/// <summary>
/// Represents a table imported to a WebAssembly module.
/// </summary>
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;
}
}
/// <summary>
/// The value kind of the table.
/// </summary>
public ValueKind Kind { get; private set; }
/// <summary>
/// The minimum number of elements in the table.
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The maximum number of elements in the table.
/// </summary>
public uint Maximum { get; private set; }
}
}

View File

@@ -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
{
/// <summary>
/// Represents an instantiated WebAssembly module.
/// </summary>
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);
}
/// <summary>
/// The host associated with this instance.
/// </summary>
public IHost Host { get; private set; }
/// <summary>
/// The WebAssembly module associated with the instantiation.
/// </summary>
public Module Module { get; private set; }
/// <summary>
/// The external (instantiated) collection of functions, globals, tables, and memories.
/// </summary>
public Wasmtime.Externs.Externs Externs { get; private set; }
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (_globals.TryGetValue(binder.Name, out var global))
{
global.Value = value;
return true;
}
return false;
}
/// <inheritdoc/>
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<string, ExternFunction> _functions;
private Dictionary<string, ExternGlobal> _globals;
}
}

View File

@@ -0,0 +1,762 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Wasmtime
{
/// <summary>
/// Implements the Wasmtime API bindings.
/// </summary>
/// <remarks>See https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.h for the C API reference.</remarks>
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<ValueKind> ToValueKindList(Interop.wasm_valtype_vec_t* vec)
{
var list = new List<ValueKind>((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<ValueKind> 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);
}
}

View File

@@ -0,0 +1,270 @@
using System;
using System.Text;
using System.Buffers.Binary;
namespace Wasmtime
{
/// <summary>
/// Represents a WebAssembly memory.
/// </summary>
public class Memory
{
/// <summary>
/// The size, in bytes, of a WebAssembly memory page.
/// </summary>
public const int PageSize = 65536;
/// <summary>
/// Creates a new memory with the given minimum and maximum page counts.
/// </summary>
/// <param name="minimum"></param>
/// <param name="maximum"></param>
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;
}
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum { get; private set; }
/// <summary>
/// The span of the memory.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public unsafe Span<byte> 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<byte>(data, size);
}
}
/// <summary>
/// Reads a UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <param name="length">The length of bytes to read.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadString(int address, int length)
{
return Encoding.UTF8.GetString(Span.Slice(address, length));
}
/// <summary>
/// Writes a UTF-8 string at the given address.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The string to write.</param>
/// <return>Returns the number of bytes written.</return>
public int WriteString(int address, string value)
{
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
}
/// <summary>
/// Reads a byte from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the byte read from memory.</returns>
public byte ReadByte(int address)
{
return Span[address];
}
/// <summary>
/// Writes a byte to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The byte to write.</param>
public void WriteByte(int address, byte value)
{
Span[address] = value;
}
/// <summary>
/// Reads a short from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the short read from memory.</returns>
public short ReadInt16(int address)
{
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
}
/// <summary>
/// Writes a short to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The short to write.</param>
public void WriteInt16(int address, short value)
{
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
}
/// <summary>
/// Reads an int from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the int read from memory.</returns>
public int ReadInt32(int address)
{
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
}
/// <summary>
/// Writes an int to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The int to write.</param>
public void WriteInt32(int address, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public long ReadInt64(int address)
{
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
}
/// <summary>
/// Writes a long to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The long to write.</param>
public void WriteInt64(int address, long value)
{
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
}
/// <summary>
/// Reads an IntPtr from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the IntPtr read from memory.</returns>
public IntPtr ReadIntPtr(int address)
{
if (IntPtr.Size == 4)
{
return (IntPtr)ReadInt32(address);
}
return (IntPtr)ReadInt64(address);
}
/// <summary>
/// Writes an IntPtr to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The IntPtr to write.</param>
public void WriteIntPtr(int address, IntPtr value)
{
if (IntPtr.Size == 4)
{
WriteInt32(address, value.ToInt32());
}
else
{
WriteInt64(address, value.ToInt64());
}
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public float ReadSingle(int address)
{
unsafe
{
var i = ReadInt32(address);
return *((float*)&i);
}
}
/// <summary>
/// Writes a single to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The single to write.</param>
public void WriteSingle(int address, float value)
{
unsafe
{
WriteInt32(address, *(int*)&value);
}
}
/// <summary>
/// Reads a double from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the double read from memory.</returns>
public double ReadDouble(int address)
{
unsafe
{
var i = ReadInt64(address);
return *((double*)&i);
}
}
/// <summary>
/// Writes a double to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The double to write.</param>
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;
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Runtime.InteropServices;
namespace Wasmtime
{
/// <summary>
/// Represents a WebAssembly module.
/// </summary>
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);
}
/// <summary>
/// Instantiates a WebAssembly module for the given host.
/// </summary>
/// <param name="host">The host to use for the WebAssembly module's instance.</param>
/// <returns>Returns a new <see cref="Instance" />.</returns>
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;
}
/// <summary>
/// The <see cref="Store"/> associated with the module.
/// </summary>
public Store Store { get; private set; }
/// <summary>
/// The name of the module.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The imports of the module.
/// </summary>
public Wasmtime.Imports.Imports Imports { get; private set; }
/// <summary>
/// The exports of the module.
/// </summary>
/// <value></value>
public Wasmtime.Exports.Exports Exports { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Interop.ModuleHandle Handle { get; private set; }
}
}

View File

@@ -0,0 +1,65 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Represents a mutable WebAssembly global value.
/// </summary>
public class MutableGlobal<T>
{
/// <summary>
/// Creates a new <see cref="MutableGlobal&lt;T&gt;" /> with the given initial value.
/// </summary>
/// <param name="initialValue">The initial value of the global.</param>
public MutableGlobal(T initialValue)
{
InitialValue = initialValue;
Kind = Interop.ToValueKind(typeof(T));
}
/// <summary>
/// The value of the global.
/// </summary>
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; }
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.IO;
namespace Wasmtime
{
/// <summary>
/// Represents the Wasmtime store.
/// </summary>
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.");
}
}
/// <summary>
/// Create a <see cref="Module"/> given the module name and bytes.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="bytes">The bytes of the module.</param>
/// <returns>Retuw <see cref="Module"/>.</returns>
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);
}
/// <summary>
/// Create a <see cref="Module"/> given the module name and path to the WebAssembly file.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="path">The path to the WebAssembly file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module CreateModule(string name, string path)
{
return CreateModule(name, File.ReadAllBytes(path));
}
/// <summary>
/// Create a <see cref="Module"/> given the path to the WebAssembly file.
/// </summary>
/// <param name="path">The path to the WebAssembly file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module CreateModule(string path)
{
return CreateModule(Path.GetFileNameWithoutExtension(path), path);
}
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Interop.StoreHandle Handle { get; private set; }
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
namespace Wasmtime
{
/// <summary>
/// The exception for WebAssembly traps.
/// </summary>
[Serializable]
public class TrapException : WasmtimeException
{
/// <inheritdoc/>
public TrapException() { }
/// <inheritdoc/>
public TrapException(string message) : base(message) { }
/// <inheritdoc/>
public TrapException(string message, Exception inner) : base(message, inner) { }
/// <inheritdoc/>
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
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Represents the possible kinds of WebAssembly values.
/// </summary>
public enum ValueKind
{
/// <summary>
/// The value is a 32-bit integer.
/// </summary>
Int32,
/// <summary>
/// The value is a 64-bit integer.
/// </summary>
Int64,
/// <summary>
/// The value is a 32-bit floating point number.
/// </summary>
Float32,
/// <summary>
/// The value is a 64-bit floating point number.
/// </summary>
Float64,
/// <summary>
/// The value is a reference.
/// </summary>
AnyRef = 128,
/// <summary>
/// The value is a function reference.
/// </summary>
FuncRef,
}
}

View File

@@ -0,0 +1,77 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.6.0" />
</ItemGroup>
<PropertyGroup>
<AssemblyName>Wasmtime.Dotnet</AssemblyName>
<PackageId>Wasmtime</PackageId>
<Version>$(WasmtimeVersion)-preview1</Version>
<Authors>Peter Huene</Authors>
<Owners>Peter Huene</Owners>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<RepositoryUrl>https://github.com/bytecodealliance/wasmtime</RepositoryUrl>
<PackageReleaseNotes>Initial release of Wasmtime for .NET.</PackageReleaseNotes>
<Summary>A .NET API for Wasmtime, a standalone WebAssembly runtime</Summary>
<PackageTags>webassembly, .net, wasm, wasmtime</PackageTags>
<Title>Wasmtime</Title>
<PackageDescription>
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.
</PackageDescription>
</PropertyGroup>
<Target Name="PackWasmtime" BeforeTargets="_GetPackageFiles">
<PropertyGroup>
<WasmtimeArchitecture>x86_64</WasmtimeArchitecture>
<ReleaseURLBase>https://github.com/bytecodealliance/wasmtime/releases/download/v$(WasmtimeVersion)/wasmtime-v$(WasmtimeVersion)-$(WasmtimeArchitecture)</ReleaseURLBase>
</PropertyGroup>
<ItemGroup>
<WasmtimeDownload Include="Linux">
<URL>$(ReleaseURLBase)-linux-c-api.tar.xz</URL>
<DownloadFolder>$(IntermediateOutputPath)wasmtime-linux</DownloadFolder>
<DownloadFilename>linux.tar.xz</DownloadFilename>
<WasmtimeLibraryFilename>libwasmtime.so</WasmtimeLibraryFilename>
<PackagePath>runtimes/linux-x64/native</PackagePath>
</WasmtimeDownload>
<WasmtimeDownload Include="macOS">
<URL>$(ReleaseURLBase)-macos-c-api.tar.xz</URL>
<DownloadFolder>$(IntermediateOutputPath)wasmtime-macos</DownloadFolder>
<DownloadFilename>macos.tar.xz</DownloadFilename>
<WasmtimeLibraryFilename>libwasmtime.dylib</WasmtimeLibraryFilename>
<PackagePath>runtimes/osx-x64/native</PackagePath>
</WasmtimeDownload>
<WasmtimeDownload Include="Windows">
<URL>$(ReleaseURLBase)-windows-c-api.zip</URL>
<DownloadFolder>$(IntermediateOutputPath)wasmtime-windows</DownloadFolder>
<DownloadFilename>windows.zip</DownloadFilename>
<WasmtimeLibraryFilename>wasmtime.dll</WasmtimeLibraryFilename>
<PackagePath>runtimes/win-x64/native</PackagePath>
</WasmtimeDownload>
</ItemGroup>
<Message Text="Downloading Wasmtime release." Importance="High" />
<DownloadFile SourceUrl="%(WasmtimeDownload.URL)" DestinationFolder="%(WasmtimeDownload.DownloadFolder)" DestinationFileName="%(WasmtimeDownload.DownloadFilename)" SkipUnchangedFiles="true" />
<Message Text="Decompressing Wasmtime release." Importance="High" />
<Exec Command="tar --strip-components 1 -xvzf %(WasmtimeDownload.DownloadFilename)" WorkingDirectory="%(WasmtimeDownload.DownloadFolder)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
<ItemGroup>
<Content Include="%(WasmtimeDownload.DownloadFolder)/lib/%(WasmtimeDownload.WasmtimeLibraryFilename)" Link="%(WasmtimeDownload.PackagePath)/%(WasmtimeDownload.WasmtimeLibraryFilename)">
<PackagePath>%(WasmtimeDownload.PackagePath)</PackagePath>
</Content>
</ItemGroup>
</Target>
</Project>

View File

@@ -0,0 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Wasmtime
{
/// <summary>
/// The base type for Wasmtime exceptions.
/// </summary>
[System.Serializable]
public class WasmtimeException : Exception
{
/// <inheritdoc/>
public WasmtimeException() { }
/// <inheritdoc/>
public WasmtimeException(string message) : base(message) { }
/// <inheritdoc/>
public WasmtimeException(string message, Exception inner) : base(message, inner) { }
/// <inheritdoc/>
protected WasmtimeException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}

View File

@@ -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; }
}
}

View File

@@ -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<FunctionExportsFixture>
{
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<object[]> GetFunctionExports()
{
yield return new object[] {
"no_params_no_results",
Array.Empty<ValueKind>(),
Array.Empty<ValueKind>()
};
yield return new object[] {
"one_i32_param_no_results",
new ValueKind[] {
ValueKind.Int32
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"one_i64_param_no_results",
new ValueKind[] {
ValueKind.Int64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"one_f32_param_no_results",
new ValueKind[] {
ValueKind.Float32
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"one_f64_param_no_results",
new ValueKind[] {
ValueKind.Float64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"one_param_of_each_type",
new ValueKind[] {
ValueKind.Int32,
ValueKind.Int64,
ValueKind.Float32,
ValueKind.Float64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"no_params_one_i32_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Int32,
}
};
yield return new object[] {
"no_params_one_i64_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Int64,
}
};
yield return new object[] {
"no_params_one_f32_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Float32,
}
};
yield return new object[] {
"no_params_one_f64_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Float64,
}
};
yield return new object[] {
"one_result_of_each_type",
Array.Empty<ValueKind>(),
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,
}
};
}
}
}

View File

@@ -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<FunctionImportsFixture>
{
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<object[]> GetFunctionImports()
{
yield return new object[] {
"",
"no_params_no_results",
Array.Empty<ValueKind>(),
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"one_i32_param_no_results",
new ValueKind[] {
ValueKind.Int32
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"one_i64_param_no_results",
new ValueKind[] {
ValueKind.Int64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"one_f32_param_no_results",
new ValueKind[] {
ValueKind.Float32
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"one_f64_param_no_results",
new ValueKind[] {
ValueKind.Float64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"one_param_of_each_type",
new ValueKind[] {
ValueKind.Int32,
ValueKind.Int64,
ValueKind.Float32,
ValueKind.Float64
},
Array.Empty<ValueKind>()
};
yield return new object[] {
"",
"no_params_one_i32_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Int32,
}
};
yield return new object[] {
"",
"no_params_one_i64_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Int64,
}
};
yield return new object[] {
"",
"no_params_one_f32_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Float32,
}
};
yield return new object[] {
"",
"no_params_one_f64_result",
Array.Empty<ValueKind>(),
new ValueKind[] {
ValueKind.Float64,
}
};
yield return new object[] {
"",
"one_result_of_each_type",
Array.Empty<ValueKind>(),
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<ValueKind>(),
Array.Empty<ValueKind>(),
};
}
}
}

View File

@@ -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<GlobalExportsFixture>
{
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<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
action = () => dyn.global_i32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
action = () => i64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
action = () => dyn.global_i64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
action = () => f32.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => dyn.global_f32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => f64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
action = () => dyn.global_f64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
}
}
public static IEnumerable<object[]> 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
};
}
}
}

View File

@@ -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<GlobalImportBindingFixture>
{
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<NotAValue> x = new MutableGlobal<NotAValue>(new NotAValue());
}
class TypeMismatchHost : IHost
{
public Instance Instance { get; set; }
[Import("global_i32_mut")]
public readonly MutableGlobal<long> x = new MutableGlobal<long>(0);
}
class NotMutHost : IHost
{
public Instance Instance { get; set; }
[Import("global_i32_mut")]
public readonly Global<int> Int32Mut = new Global<int>(0);
}
class MutHost : IHost
{
public Instance Instance { get; set; }
[Import("global_i32_mut")]
public readonly MutableGlobal<int> Int32Mut = new MutableGlobal<int>(0);
[Import("global_i32")]
public readonly MutableGlobal<int> Int32 = new MutableGlobal<int>(0);
}
class ValidHost : IHost
{
public Instance Instance { get; set; }
[Import("global_i32_mut")]
public readonly MutableGlobal<int> Int32Mut = new MutableGlobal<int>(0);
[Import("global_i32")]
public readonly Global<int> Int32 = new Global<int>(1);
[Import("global_i64_mut")]
public readonly MutableGlobal<long> Int64Mut = new MutableGlobal<long>(2);
[Import("global_i64")]
public readonly Global<long> Int64 = new Global<long>(3);
[Import("global_f32_mut")]
public readonly MutableGlobal<float> Float32Mut = new MutableGlobal<float>(4);
[Import("global_f32")]
public readonly Global<float> Float32 = new Global<float>(5);
[Import("global_f64_mut")]
public readonly MutableGlobal<double> Float64Mut = new MutableGlobal<double>(6);
[Import("global_f64")]
public readonly Global<double> Float64 = new Global<double>(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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.WithMessage("Unable to bind 'NotAGlobalHost.x' to WebAssembly import 'global_i32_mut': field is expected to be of type 'Global<T>'.");
}
[Fact]
public void ItFailsToInstantiateWithInvalidGlobalType()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost())) { } };
action
.Should()
.Throw<NotSupportedException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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);
}
}
}
}

View File

@@ -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<GlobalImportsFixture>
{
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<object[]> 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
};
}
}
}

View File

@@ -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<MemoryExportsFixture>
{
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<object[]> GetMemoryExports()
{
yield return new object[] {
"mem",
1,
2
};
}
}
}

View File

@@ -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<MemoryImportBindingFixture>
{
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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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<WasmtimeException>()
.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);
}
}
}
}

View File

@@ -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<MemoryImportFromModuleFixture>
{
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);
}
}
}

View File

@@ -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<MemoryImportNoUpperBoundFixture>
{
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);
}
}
}

View File

@@ -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<MemoryImportWithUpperBoundFixture>
{
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);
}
}
}

Binary file not shown.

View File

@@ -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))
)

Binary file not shown.

View File

@@ -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)))
)

Binary file not shown.

View File

@@ -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))
)

View File

@@ -0,0 +1,22 @@
(module
(import "" "global_i32_mut" (global $global_i32_mut (mut i32)))
(import "" "global_i32" (global $global_i32 i32))
(import "" "global_i64_mut" (global $global_i64_mut (mut i64)))
(import "" "global_i64" (global $global_i64 i64))
(import "" "global_f32_mut" (global $global_f32_mut (mut f32)))
(import "" "global_f32" (global $global_f32 f32))
(import "" "global_f64_mut" (global $global_f64_mut (mut f64)))
(import "" "global_f64" (global $global_f64 f64))
(func (export "get_global_i32_mut") (result i32) (global.get $global_i32_mut))
(func (export "get_global_i32") (result i32) (global.get $global_i32))
(func (export "set_global_i32_mut") (param i32) (global.set $global_i32_mut (local.get 0)))
(func (export "get_global_i64_mut") (result i64) (global.get $global_i64_mut))
(func (export "get_global_i64") (result i64) (global.get $global_i64))
(func (export "set_global_i64_mut") (param i64) (global.set $global_i64_mut (local.get 0)))
(func (export "get_global_f32_mut") (result f32) (global.get $global_f32_mut))
(func (export "get_global_f32") (result f32) (global.get $global_f32))
(func (export "set_global_f32_mut") (param f32) (global.set $global_f32_mut (local.get 0)))
(func (export "get_global_f64_mut") (result f64) (global.get $global_f64_mut))
(func (export "get_global_f64") (result f64) (global.get $global_f64))
(func (export "set_global_f64_mut") (param f64) (global.set $global_f64_mut (local.get 0)))
)

Binary file not shown.

View File

@@ -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)
)

Binary file not shown.

View File

@@ -0,0 +1,11 @@
(module
(memory (export "mem") 1 2)
(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")
)

View File

@@ -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
)
)

View File

@@ -0,0 +1,3 @@
(module
(import "js" "mem" (memory 1 2))
)

View File

@@ -0,0 +1,3 @@
(module
(import "" "mem" (memory 1))
)

View File

@@ -0,0 +1,3 @@
(module
(import "" "mem" (memory 10 100))
)

Binary file not shown.

View File

@@ -0,0 +1,8 @@
(module
(table $t0 1 10 funcref)
(table $t1 10 anyref)
(table $t2 100 1000 funcref)
(export "table1" (table $t0))
(export "table2" (table $t1))
(export "table3" (table $t2))
)

Binary file not shown.

View File

@@ -0,0 +1,5 @@
(module
(import "" "table1" (table $t1 10 funcref))
(import "" "table2" (table $t2 15 anyref))
(import "other" "table3" (table $t3 1 funcref))
)

View File

@@ -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<TableExportsFixture>
{
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<object[]> 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
};
}
}
}

View File

@@ -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<TableImportsFixture>
{
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<object[]> 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
};
}
}
}

Some files were not shown because too many files have changed in this diff Show More