From 0d5d63fdb1b7d34eca0fa1d7d7daadeef5333698 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 20 Mar 2020 16:47:25 -0700 Subject: [PATCH 1/9] Support WASI snapshot0 in the C API. This commit adds support for snapshot0 in the WASI C API. A name parameter was added to `wasi_instance_new` to accept which WASI module is being instantiated. Additionally, the C# API now supports constructing a WASI instance based on the WASI module name. Fixes #1221. --- crates/c-api/include/wasi.h | 1 + crates/c-api/src/ext.rs | 2 +- crates/c-api/src/wasi.rs | 207 ++++++++++++--- crates/misc/dotnet/src/Interop.cs | 1 + crates/misc/dotnet/src/Wasi.cs | 16 +- crates/misc/dotnet/src/WasiBuilder.cs | 16 +- .../dotnet/tests/Modules/WasiSnapshot0.wat | 66 +++++ .../misc/dotnet/tests/WasiSnapshot0Tests.cs | 246 ++++++++++++++++++ crates/misc/dotnet/tests/WasiTests.cs | 18 +- crates/wasi-common/src/old/snapshot_0/ctx.rs | 134 ++++++---- crates/wasi-common/wig/src/wasi.rs | 17 +- src/commands/run.rs | 9 +- 12 files changed, 613 insertions(+), 120 deletions(-) create mode 100644 crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat create mode 100644 crates/misc/dotnet/tests/WasiSnapshot0Tests.cs diff --git a/crates/c-api/include/wasi.h b/crates/c-api/include/wasi.h index e1a7ffc5dc..3a5176925c 100644 --- a/crates/c-api/include/wasi.h +++ b/crates/c-api/include/wasi.h @@ -52,6 +52,7 @@ WASI_DECLARE_OWN(instance) WASI_API_EXTERN own wasi_instance_t* wasi_instance_new( wasm_store_t* store, + const char* name, own wasi_config_t* config, own wasm_trap_t** trap ); diff --git a/crates/c-api/src/ext.rs b/crates/c-api/src/ext.rs index 88688c8af7..6d0fe082cd 100644 --- a/crates/c-api/src/ext.rs +++ b/crates/c-api/src/ext.rs @@ -185,7 +185,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_wasi( instance: *const wasi_instance_t, ) -> bool { let linker = &mut (*linker).linker; - (*instance).wasi.add_to_linker(linker).is_ok() + (*instance).add_to_linker(linker).is_ok() } #[no_mangle] diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index 8905d023fb..c05ff90a6c 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -1,14 +1,18 @@ //! The WASI embedding API definitions for Wasmtime. use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType}; +use anyhow::Result; use std::collections::HashMap; use std::ffi::CStr; use std::fs::File; use std::os::raw::{c_char, c_int}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::slice; -use wasi_common::{preopen_dir, WasiCtxBuilder}; -use wasmtime::{HostRef, Trap}; -use wasmtime_wasi::Wasi; +use wasi_common::{ + old::snapshot_0::WasiCtxBuilder as WasiSnapshot0CtxBuilder, preopen_dir, + WasiCtxBuilder as WasiPreview1CtxBuilder, +}; +use wasmtime::{HostRef, Linker, Store, Trap}; +use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi as WasiPreview1}; unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> { CStr::from_ptr(path).to_str().map(Path::new).ok() @@ -22,18 +26,32 @@ unsafe fn create_file(path: *const c_char) -> Option { File::create(cstr_to_path(path)?).ok() } -#[repr(C)] -pub struct wasi_config_t { - builder: WasiCtxBuilder, +pub enum WasiModule { + Snapshot0(WasiSnapshot0), + Preview1(WasiPreview1), } -impl wasi_config_t {} +impl WasiModule {} + +#[repr(C)] +#[derive(Default)] +pub struct wasi_config_t { + args: Vec>, + env: Vec<(Vec, Vec)>, + stdin: Option, + stdout: Option, + stderr: Option, + preopens: Vec<(File, PathBuf)>, + inherit_args: bool, + inherit_env: bool, + inherit_stdin: bool, + inherit_stdout: bool, + inherit_stderr: bool, +} #[no_mangle] pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t { - Box::into_raw(Box::new(wasi_config_t { - builder: WasiCtxBuilder::new(), - })) + Box::into_raw(Box::new(wasi_config_t::default())) } #[no_mangle] @@ -47,16 +65,17 @@ pub unsafe extern "C" fn wasi_config_set_argv( argc: c_int, argv: *const *const c_char, ) { - (*config).builder.args( - slice::from_raw_parts(argv, argc as usize) - .iter() - .map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())), - ); + (*config).args = slice::from_raw_parts(argv, argc as usize) + .iter() + .map(|p| CStr::from_ptr(*p).to_bytes().to_owned()) + .collect(); + (*config).inherit_args = false; } #[no_mangle] pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) { - (*config).builder.inherit_args(); + (*config).args.clear(); + (*config).inherit_args = true; } #[no_mangle] @@ -69,17 +88,22 @@ pub unsafe extern "C" fn wasi_config_set_env( let names = slice::from_raw_parts(names, envc as usize); let values = slice::from_raw_parts(values, envc as usize); - (*config).builder.envs( - names - .iter() - .map(|p| CStr::from_ptr(*p).to_bytes()) - .zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())), - ); + (*config).env = names + .iter() + .map(|p| CStr::from_ptr(*p).to_bytes().to_owned()) + .zip( + values + .iter() + .map(|p| CStr::from_ptr(*p).to_bytes().to_owned()), + ) + .collect(); + (*config).inherit_env = false; } #[no_mangle] pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) { - (*config).builder.inherit_env(); + (*config).env.clear(); + (*config).inherit_env = true; } #[no_mangle] @@ -92,14 +116,16 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file( None => return false, }; - (*config).builder.stdin(file); + (*config).stdin = Some(file); + (*config).inherit_stdin = false; true } #[no_mangle] pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) { - (*config).builder.inherit_stdin(); + (*config).stdin = None; + (*config).inherit_stdin = true; } #[no_mangle] @@ -112,14 +138,16 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file( None => return false, }; - (*config).builder.stdout(file); + (*config).stdout = Some(file); + (*config).inherit_stdout = false; true } #[no_mangle] pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) { - (*config).builder.inherit_stdout(); + (*config).stdout = None; + (*config).inherit_stdout = true; } #[no_mangle] @@ -132,14 +160,16 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file( None => return false, }; - (*config).builder.stderr(file); + (*config).stderr = Some(file); + (*config).inherit_stderr = false; true } #[no_mangle] pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) { - (*config).builder.inherit_stderr(); + (*config).stderr = None; + (*config).inherit_stderr = true; } #[no_mangle] @@ -161,29 +191,110 @@ pub unsafe extern "C" fn wasi_config_preopen_dir( None => return false, }; - (*config).builder.preopened_dir(dir, guest_path); + (*config).preopens.push((dir, guest_path.to_owned())); true } +enum WasiInstance { + Preview1(WasiPreview1), + Snapshot0(WasiSnapshot0), +} + +macro_rules! config_to_builder { + ($builder:ident, $config:ident) => {{ + let mut builder = $builder::new(); + + if $config.inherit_args { + builder.inherit_args(); + } else if !$config.args.is_empty() { + builder.args($config.args); + } + + if $config.inherit_env { + builder.inherit_env(); + } else if !$config.env.is_empty() { + builder.envs($config.env); + } + + if $config.inherit_stdin { + builder.inherit_stdin(); + } else if let Some(file) = $config.stdin { + builder.stdin(file); + } + + if $config.inherit_stdout { + builder.inherit_stdout(); + } else if let Some(file) = $config.stdout { + builder.stdout(file); + } + + if $config.inherit_stderr { + builder.inherit_stderr(); + } else if let Some(file) = $config.stderr { + builder.stderr(file); + } + + for preopen in $config.preopens { + builder.preopened_dir(preopen.0, preopen.1); + } + + builder + }}; +} + +fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result { + Ok(WasiInstance::Snapshot0(WasiSnapshot0::new( + store, + config_to_builder!(WasiSnapshot0CtxBuilder, config) + .build() + .map_err(|e| e.to_string())?, + ))) +} + +fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result { + Ok(WasiInstance::Preview1(WasiPreview1::new( + store, + config_to_builder!(WasiPreview1CtxBuilder, config) + .build() + .map_err(|e| e.to_string())?, + ))) +} + #[repr(C)] pub struct wasi_instance_t { - pub wasi: Wasi, + wasi: WasiInstance, export_cache: HashMap>, } +impl wasi_instance_t { + pub fn add_to_linker(&self, linker: &mut Linker) -> Result<()> { + match &self.wasi { + WasiInstance::Snapshot0(w) => w.add_to_linker(linker), + WasiInstance::Preview1(w) => w.add_to_linker(linker), + } + } +} + #[no_mangle] pub unsafe extern "C" fn wasi_instance_new( store: *mut wasm_store_t, + name: *const c_char, config: *mut wasi_config_t, trap: *mut *mut wasm_trap_t, ) -> *mut wasi_instance_t { let store = &(*store).store.borrow(); - let mut config = Box::from_raw(config); + let config = Box::from_raw(config); - match config.builder.build() { - Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t { - wasi: Wasi::new(store, ctx), + let result = match CStr::from_ptr(name).to_str().unwrap_or("") { + "wasi_snapshot_preview1" => create_preview1_instance(store, *config), + "wasi_unstable" => create_snapshot0_instance(store, *config), + _ => Err("unsupported WASI version".into()), + }; + + match result { + Ok(wasi) => Box::into_raw(Box::new(wasi_instance_t { + wasi, export_cache: HashMap::new(), })), Err(e) => { @@ -206,20 +317,32 @@ pub unsafe extern "C" fn wasi_instance_bind_import( instance: *mut wasi_instance_t, import: *const wasm_importtype_t, ) -> *const wasm_extern_t { - // TODO: support previous versions? - if (*import).ty.module() != "wasi_snapshot_preview1" { - return std::ptr::null_mut(); - } - // The import should be a function (WASI only exports functions) let func_type = match (*import).ty.ty() { ExternType::Func(f) => f, _ => return std::ptr::null_mut(), }; + let module = (*import).ty.module(); let name = (*import).ty.name(); - match (*instance).wasi.get_export(name) { + let import = match &(*instance).wasi { + WasiInstance::Preview1(wasi) => { + if module != "wasi_snapshot_preview1" { + return std::ptr::null(); + } + wasi.get_export(name) + } + WasiInstance::Snapshot0(wasi) => { + if module != "wasi_unstable" { + return std::ptr::null(); + } + + wasi.get_export(name) + } + }; + + match import { Some(export) => { if export.ty() != func_type { return std::ptr::null_mut(); diff --git a/crates/misc/dotnet/src/Interop.cs b/crates/misc/dotnet/src/Interop.cs index 877b5efd88..81f29887a4 100644 --- a/crates/misc/dotnet/src/Interop.cs +++ b/crates/misc/dotnet/src/Interop.cs @@ -936,6 +936,7 @@ namespace Wasmtime [DllImport(LibraryName)] public static extern WasiInstanceHandle wasi_instance_new( StoreHandle store, + [MarshalAs(UnmanagedType.LPUTF8Str)] string name, WasiConfigHandle config, out IntPtr trap ); diff --git a/crates/misc/dotnet/src/Wasi.cs b/crates/misc/dotnet/src/Wasi.cs index e16feaf58b..a0cd1ef367 100644 --- a/crates/misc/dotnet/src/Wasi.cs +++ b/crates/misc/dotnet/src/Wasi.cs @@ -9,18 +9,26 @@ namespace Wasmtime /// /// Creates a default instance. /// - public Wasi(Store store) : + /// The store to use for the new WASI instance. + /// The name of the WASI module to create. + public Wasi(Store store, string name) : this( (store ?? throw new ArgumentNullException(nameof(store))).Handle, - Interop.wasi_config_new() + Interop.wasi_config_new(), + name ) { } - internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config) + internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config, string name) { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("Name cannot be null or empty.", nameof(name)); + } + IntPtr trap; - Handle = Interop.wasi_instance_new(store, config, out trap); + Handle = Interop.wasi_instance_new(store, name, config, out trap); config.SetHandleAsInvalid(); if (trap != IntPtr.Zero) diff --git a/crates/misc/dotnet/src/WasiBuilder.cs b/crates/misc/dotnet/src/WasiBuilder.cs index d1a363f3ea..fd5b445874 100644 --- a/crates/misc/dotnet/src/WasiBuilder.cs +++ b/crates/misc/dotnet/src/WasiBuilder.cs @@ -12,10 +12,22 @@ namespace Wasmtime /// /// Constructs a new . /// - public WasiBuilder() + public WasiBuilder(string name) { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("Name cannot be null or empty.", nameof(name)); + } + + Name = name; } + /// + /// Gets the name of the WASI module being built. + /// + /// The name of the WASI module. + public string Name { get; private set; } + /// /// Adds a command line argument to the builder. /// @@ -271,7 +283,7 @@ namespace Wasmtime SetStandardError(config); SetPreopenDirectories(config); - return new Wasi(store.Handle, config); + return new Wasi(store.Handle, config, Name); } private unsafe void SetConfigArgs(Interop.WasiConfigHandle config) diff --git a/crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat b/crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat new file mode 100644 index 0000000000..2ebca6b9b3 --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat @@ -0,0 +1,66 @@ +(module + (type $t0 (func (param i32 i32) (result i32))) + (type $t1 (func (param i32 i32 i32 i32) (result i32))) + (type $t2 (func (param i32) (result i32))) + (type $t3 (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) + (import "wasi_unstable" "environ_sizes_get" (func $wasi_unstable.environ_sizes_get (type $t0))) + (import "wasi_unstable" "environ_get" (func $wasi_unstable.environ_get (type $t0))) + (import "wasi_unstable" "args_sizes_get" (func $wasi_unstable.args_sizes_get (type $t0))) + (import "wasi_unstable" "args_get" (func $wasi_unstable.args_get (type $t0))) + (import "wasi_unstable" "fd_write" (func $wasi_unstable.fd_write (type $t1))) + (import "wasi_unstable" "fd_read" (func $wasi_unstable.fd_read (type $t1))) + (import "wasi_unstable" "fd_close" (func $wasi_unstable.fd_close (type $t2))) + (import "wasi_unstable" "path_open" (func $wasi_unstable.path_open (type $t3))) + (memory $memory 1) + (export "memory" (memory 0)) + (func $call_environ_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_unstable.environ_sizes_get) + (func $call_environ_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_unstable.environ_get) + (func $call_args_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_unstable.args_sizes_get) + (func $call_args_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_unstable.args_get) + (func $call_fd_write (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + call $wasi_unstable.fd_write) + (func $call_fd_read (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + call $wasi_unstable.fd_read) + (func $call_fd_close (type $t2) (param $p0 i32) (result i32) + local.get $p0 + call $wasi_unstable.fd_close) + (func $call_path_open (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i64) (param $p6 i64) (param $p7 i32) (param $p8 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + local.get $p4 + local.get $p5 + local.get $p6 + local.get $p7 + local.get $p8 + call $wasi_unstable.path_open) + (export "call_environ_sizes_get" (func $call_environ_sizes_get)) + (export "call_environ_get" (func $call_environ_get)) + (export "call_args_sizes_get" (func $call_args_sizes_get)) + (export "call_args_get" (func $call_args_get)) + (export "call_fd_write" (func $call_fd_write)) + (export "call_fd_read" (func $call_fd_read)) + (export "call_fd_close" (func $call_fd_close)) + (export "call_path_open" (func $call_path_open)) +) diff --git a/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs b/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs new file mode 100644 index 0000000000..6fae2f6aa8 --- /dev/null +++ b/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class WasiSnapshot0Fixture : ModuleFixture + { + protected override string ModuleFileName => "WasiSnapshot0.wat"; + } + + public class WasiSnapshot0Tests : IClassFixture + { + public WasiSnapshot0Tests(WasiSnapshot0Fixture fixture) + { + Fixture = fixture; + } + + private WasiSnapshot0Fixture Fixture { get; set; } + + [Fact] + public void ItHasNoEnvironmentByDefault() + { + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable")); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(0, memory.ReadInt32(0)); + Assert.Equal(0, memory.ReadInt32(4)); + } + + [Fact] + public void ItHasSpecifiedEnvironment() + { + var env = new Dictionary() { + {"FOO", "BAR"}, + {"WASM", "IS"}, + {"VERY", "COOL"}, + }; + + var wasi = new WasiBuilder("wasi_unstable") + .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(env.Count, memory.ReadInt32(0)); + Assert.Equal(env.Sum(kvp => kvp.Key.Length + kvp.Value.Length + 2), memory.ReadInt32(4)); + Assert.Equal(0, inst.call_environ_get(0, 4 * env.Count)); + + for (int i = 0; i < env.Count; ++i) + { + var kvp = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)).Split("="); + Assert.Equal(env[kvp[0]], kvp[1]); + } + } + + [Fact] + public void ItInheritsEnvironment() + { + var wasi = new WasiBuilder("wasi_unstable") + .WithInheritedEnvironment() + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(Environment.GetEnvironmentVariables().Keys.Count, memory.ReadInt32(0)); + } + + [Fact] + public void ItHasNoArgumentsByDefault() + { + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable")); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(0, memory.ReadInt32(0)); + Assert.Equal(0, memory.ReadInt32(4)); + } + + [Fact] + public void ItHasSpecifiedArguments() + { + var args = new List() { + "WASM", + "IS", + "VERY", + "COOL" + }; + + var wasi = new WasiBuilder("wasi_unstable") + .WithArgs(args) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(args.Count, memory.ReadInt32(0)); + Assert.Equal(args.Sum(a => a.Length + 1), memory.ReadInt32(4)); + Assert.Equal(0, inst.call_args_get(0, 4 * args.Count)); + + for (int i = 0; i < args.Count; ++i) + { + var arg = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)); + Assert.Equal(args[i], arg); + } + } + + [Fact] + public void ItInheritsArguments() + { + var wasi = new WasiBuilder("wasi_unstable") + .WithInheritedArgs() + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(Environment.GetCommandLineArgs().Length, memory.ReadInt32(0)); + } + + [Fact] + public void ItSetsStdIn() + { + const string MESSAGE = "WASM IS VERY COOL"; + + using var file = new TempFile(); + File.WriteAllText(file.Path, MESSAGE); + + var wasi = new WasiBuilder("wasi_unstable") + .WithStandardInput(file.Path) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + + Assert.Equal(0, inst.call_fd_read(0, 0, 1, 32)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(32)); + Assert.Equal(MESSAGE, memory.ReadString(8, MESSAGE.Length)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void ItSetsStdOutAndStdErr(int fd) + { + const string MESSAGE = "WASM IS VERY COOL"; + + using var file = new TempFile(); + + var builder = new WasiBuilder("wasi_unstable"); + if (fd == 1) + { + builder.WithStandardOutput(file.Path); + } + else if (fd == 2) + { + builder.WithStandardError(file.Path); + } + + var wasi = builder.Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + memory.WriteString(8, MESSAGE); + + Assert.Equal(0, inst.call_fd_write(fd, 0, 1, 32)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(32)); + Assert.Equal(0, inst.call_fd_close(fd)); + Assert.Equal(MESSAGE, File.ReadAllText(file.Path)); + } + + [Fact] + public void ItSetsPreopenDirectories() + { + const string MESSAGE = "WASM IS VERY COOL"; + + using var file = new TempFile(); + + var wasi = new WasiBuilder("wasi_unstable") + .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + var fileName = Path.GetFileName(file.Path); + memory.WriteString(0, fileName); + + Assert.Equal(0, inst.call_path_open( + 3, + 0, + 0, + fileName.Length, + 0, + 0x40 /* RIGHTS_FD_WRITE */, + 0, + 0, + 64 + ) + ); + + var fileFd = (int) memory.ReadInt32(64); + Assert.True(fileFd > 3); + + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + memory.WriteString(8, MESSAGE); + + Assert.Equal(0, inst.call_fd_write(fileFd, 0, 1, 64)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(64)); + Assert.Equal(0, inst.call_fd_close(fileFd)); + Assert.Equal(MESSAGE, File.ReadAllText(file.Path)); + } + } +} diff --git a/crates/misc/dotnet/tests/WasiTests.cs b/crates/misc/dotnet/tests/WasiTests.cs index 44696ee9dc..e5f5c0117e 100644 --- a/crates/misc/dotnet/tests/WasiTests.cs +++ b/crates/misc/dotnet/tests/WasiTests.cs @@ -24,7 +24,7 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoEnvironmentByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1")); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -43,7 +43,7 @@ namespace Wasmtime.Tests {"VERY", "COOL"}, }; - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) .Build(Fixture.Module.Store); @@ -67,7 +67,7 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsEnvironment() { - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithInheritedEnvironment() .Build(Fixture.Module.Store); @@ -83,7 +83,7 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoArgumentsByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1")); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -103,7 +103,7 @@ namespace Wasmtime.Tests "COOL" }; - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithArgs(args) .Build(Fixture.Module.Store); @@ -127,7 +127,7 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsArguments() { - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithInheritedArgs() .Build(Fixture.Module.Store); @@ -148,7 +148,7 @@ namespace Wasmtime.Tests using var file = new TempFile(); File.WriteAllText(file.Path, MESSAGE); - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithStandardInput(file.Path) .Build(Fixture.Module.Store); @@ -173,7 +173,7 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var builder = new WasiBuilder(); + var builder = new WasiBuilder("wasi_snapshot_preview1"); if (fd == 1) { builder.WithStandardOutput(file.Path); @@ -206,7 +206,7 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var wasi = new WasiBuilder() + var wasi = new WasiBuilder("wasi_snapshot_preview1") .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") .Build(Fixture.Module.Store); diff --git a/crates/wasi-common/src/old/snapshot_0/ctx.rs b/crates/wasi-common/src/old/snapshot_0/ctx.rs index 5b74ede010..28c1cac673 100644 --- a/crates/wasi-common/src/old/snapshot_0/ctx.rs +++ b/crates/wasi-common/src/old/snapshot_0/ctx.rs @@ -101,46 +101,46 @@ impl PendingCString { /// A builder allowing customizable construction of `WasiCtx` instances. pub struct WasiCtxBuilder { - stdin: PendingEntry, - stdout: PendingEntry, - stderr: PendingEntry, - preopens: Vec<(PathBuf, File)>, - args: Vec, - env: HashMap, + stdin: Option, + stdout: Option, + stderr: Option, + preopens: Option>, + args: Option>, + env: Option>, } impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { - let stdin = PendingEntry::Thunk(Entry::null); - let stdout = PendingEntry::Thunk(Entry::null); - let stderr = PendingEntry::Thunk(Entry::null); Self { - stdin, - stdout, - stderr, - preopens: Vec::new(), - args: vec![], - env: HashMap::new(), + stdin: Some(PendingEntry::Thunk(Entry::null)), + stdout: Some(PendingEntry::Thunk(Entry::null)), + stderr: Some(PendingEntry::Thunk(Entry::null)), + preopens: Some(Vec::new()), + args: Some(Vec::new()), + env: Some(HashMap::new()), } } /// Add arguments to the command-line arguments list. /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. - pub fn args>(mut self, args: impl IntoIterator) -> Self { - self.args = args - .into_iter() - .map(|arg| arg.as_ref().to_vec().into()) - .collect(); + pub fn args>(&mut self, args: impl IntoIterator) -> &mut Self { + self.args + .as_mut() + .unwrap() + .extend(args.into_iter().map(|a| a.as_ref().to_vec().into())); self } /// Add an argument to the command-line arguments list. /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. - pub fn arg>(mut self, arg: S) -> Self { - self.args.push(arg.as_ref().to_vec().into()); + pub fn arg>(&mut self, arg: S) -> &mut Self { + self.args + .as_mut() + .unwrap() + .push(arg.as_ref().to_vec().into()); self } @@ -148,16 +148,36 @@ impl WasiCtxBuilder { /// /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will /// fail. - pub fn inherit_args(mut self) -> Self { - self.args = env::args_os().map(PendingCString::OsString).collect(); + pub fn inherit_args(&mut self) -> &mut Self { + let args = self.args.as_mut().unwrap(); + args.clear(); + args.extend(env::args_os().map(PendingCString::OsString)); + self + } + + /// Inherit stdin from the host process. + pub fn inherit_stdin(&mut self) -> &mut Self { + self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin)); + self + } + + /// Inherit stdout from the host process. + pub fn inherit_stdout(&mut self) -> &mut Self { + self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout)); + self + } + + /// Inherit stdout from the host process. + pub fn inherit_stderr(&mut self) -> &mut Self { + self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr)); self } /// Inherit the stdin, stdout, and stderr streams from the host process. - pub fn inherit_stdio(mut self) -> Self { - self.stdin = PendingEntry::Thunk(Entry::duplicate_stdin); - self.stdout = PendingEntry::Thunk(Entry::duplicate_stdout); - self.stderr = PendingEntry::Thunk(Entry::duplicate_stderr); + pub fn inherit_stdio(&mut self) -> &mut Self { + self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin)); + self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout)); + self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr)); self } @@ -165,10 +185,10 @@ impl WasiCtxBuilder { /// /// If any environment variables from the host process contain invalid Unicode (UTF-16 for /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail. - pub fn inherit_env(mut self) -> Self { - self.env = std::env::vars_os() - .map(|(k, v)| (k.into(), v.into())) - .collect(); + pub fn inherit_env(&mut self) -> &mut Self { + let env = self.env.as_mut().unwrap(); + env.clear(); + env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into()))); self } @@ -176,8 +196,10 @@ impl WasiCtxBuilder { /// /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail. - pub fn env>(mut self, k: S, v: S) -> Self { + pub fn env>(&mut self, k: S, v: S) -> &mut Self { self.env + .as_mut() + .unwrap() .insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into()); self } @@ -187,40 +209,40 @@ impl WasiCtxBuilder { /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail. pub fn envs, T: Borrow<(S, S)>>( - mut self, + &mut self, envs: impl IntoIterator, - ) -> Self { - self.env = envs - .into_iter() - .map(|t| { - let (k, v) = t.borrow(); - (k.as_ref().to_vec().into(), v.as_ref().to_vec().into()) - }) - .collect(); + ) -> &mut Self { + self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| { + let (k, v) = t.borrow(); + (k.as_ref().to_vec().into(), v.as_ref().to_vec().into()) + })); self } /// Provide a File to use as stdin - pub fn stdin(mut self, file: File) -> Self { - self.stdin = PendingEntry::File(file); + pub fn stdin(&mut self, file: File) -> &mut Self { + self.stdin = Some(PendingEntry::File(file)); self } /// Provide a File to use as stdout - pub fn stdout(mut self, file: File) -> Self { - self.stdout = PendingEntry::File(file); + pub fn stdout(&mut self, file: File) -> &mut Self { + self.stdout = Some(PendingEntry::File(file)); self } /// Provide a File to use as stderr - pub fn stderr(mut self, file: File) -> Self { - self.stderr = PendingEntry::File(file); + pub fn stderr(&mut self, file: File) -> &mut Self { + self.stderr = Some(PendingEntry::File(file)); self } /// Add a preopened directory. - pub fn preopened_dir>(mut self, dir: File, guest_path: P) -> Self { - self.preopens.push((guest_path.as_ref().to_owned(), dir)); + pub fn preopened_dir>(&mut self, dir: File, guest_path: P) -> &mut Self { + self.preopens + .as_mut() + .unwrap() + .push((guest_path.as_ref().to_owned(), dir)); self } @@ -228,17 +250,21 @@ impl WasiCtxBuilder { /// /// If any of the arguments or environment variables in this builder cannot be converted into /// `CString`s, either due to NUL bytes or Unicode conversions, this returns an error. - pub fn build(self) -> WasiCtxBuilderResult { + pub fn build(&mut self) -> WasiCtxBuilderResult { // Process arguments and environment variables into `CString`s, failing quickly if they // contain any NUL bytes, or if conversion from `OsString` fails. let args = self .args + .take() + .unwrap() .into_iter() .map(|arg| arg.into_utf8_cstring()) .collect::>>()?; let env = self .env + .take() + .unwrap() .into_iter() .map(|(k, v)| { k.into_string().and_then(|mut pair| { @@ -257,12 +283,12 @@ impl WasiCtxBuilder { let mut fd_pool = FdPool::new(); let mut entries: HashMap = HashMap::new(); // Populate the non-preopen fds. - for pending in vec![self.stdin, self.stdout, self.stderr] { + for pending in &mut [&mut self.stdin, &mut self.stdout, &mut self.stderr] { let fd = fd_pool .allocate() .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending); - match pending { + match pending.take().unwrap() { PendingEntry::Thunk(f) => { entries.insert(fd, f()?); } @@ -272,7 +298,7 @@ impl WasiCtxBuilder { } } // Then add the preopen fds. - for (guest_path, dir) in self.preopens { + for (guest_path, dir) in self.preopens.take().unwrap() { // We do the increment at the beginning of the loop body, so that we don't overflow // unnecessarily if we have exactly the maximum number of file descriptors. let preopen_fd = fd_pool diff --git a/crates/wasi-common/wig/src/wasi.rs b/crates/wasi-common/wig/src/wasi.rs index 2c1fcdcc1a..001bb708fb 100644 --- a/crates/wasi-common/wig/src/wasi.rs +++ b/crates/wasi-common/wig/src/wasi.rs @@ -33,14 +33,19 @@ pub fn define_struct(args: TokenStream) -> TokenStream { let mut get_exports = Vec::new(); let mut ctor_externs = Vec::new(); let mut ctor_fields = Vec::new(); + let mut linker_add = Vec::new(); for module in doc.modules() { + let module_name = module.name.as_str(); for func in module.funcs() { let name = func.name.as_str(); - let name_ident = Ident::new(func.name.as_str(), Span::call_site()); + let name_ident = Ident::new(name, Span::call_site()); fields.push(quote! { pub #name_ident: wasmtime::Func }); get_exports.push(quote! { #name => Some(&self.#name_ident) }); ctor_fields.push(name_ident.clone()); + linker_add.push(quote! { + linker.define(#module_name, #name, self.#name_ident.clone())?; + }); let mut shim_arg_decls = Vec::new(); let mut params = Vec::new(); @@ -249,6 +254,12 @@ pub fn define_struct(args: TokenStream) -> TokenStream { _ => None, } } + + /// Adds all wasi items to the specified `Linker`. + pub fn add_to_linker(&self, linker: &mut wasmtime::Linker) -> anyhow::Result<()> { + #(#linker_add)* + Ok(()) + } } } } @@ -270,10 +281,10 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream { for module in doc.modules() { let module_name = module.name.as_str(); - let module_id = Ident::new(module.name.as_str(), Span::call_site()); + let module_id = Ident::new(module_name, Span::call_site()); for func in module.funcs() { let name = func.name.as_str(); - let name_ident = Ident::new(func.name.as_str(), Span::call_site()); + let name_ident = Ident::new(name, Span::call_site()); fields.push(quote! { pub #name_ident: wasmtime::Func }); get_exports.push(quote! { #name => Some(&self.#name_ident) }); ctor_fields.push(name_ident.clone()); diff --git a/src/commands/run.rs b/src/commands/run.rs index 769491e6a6..5908215ed2 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -336,13 +336,12 @@ impl ModuleRegistry { let cx1 = cx1.build()?; - let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new() - .inherit_stdio() - .args(argv) - .envs(vars); + let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new(); + + cx2.inherit_stdio().args(argv).envs(vars); for (name, file) in preopen_dirs { - cx2 = cx2.preopened_dir(file.try_clone()?, name); + cx2.preopened_dir(file.try_clone()?, name); } let cx2 = cx2.build()?; From cf1d9ee8575998132f9978447a1eb7e4291d01f1 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 24 Mar 2020 18:20:22 -0700 Subject: [PATCH 2/9] Reimplement the C# API. This commit reimplements the C# API in terms of a Wasmtime linker. It removes the custom binding implementation that was based on reflection in favor of the linker's implementation. This should make the C# API a little closer to the Rust API. The `Engine` and `Store` types have been hidden behind the `Host` type which is responsible for hosting WebAssembly module instances. Documentation and tests have been updated. --- crates/c-api/include/wasmtime.h | 13 +- crates/c-api/src/ext.rs | 79 +- crates/misc/dotnet/docs/articles/intro.md | 38 +- crates/misc/dotnet/examples/global/Program.cs | 32 +- crates/misc/dotnet/examples/hello/Program.cs | 25 +- crates/misc/dotnet/examples/memory/Program.cs | 29 +- crates/misc/dotnet/src/Bindings/Binding.cs | 131 --- .../dotnet/src/Bindings/FunctionBinding.cs | 339 -------- .../misc/dotnet/src/Bindings/GlobalBinding.cs | 125 --- .../misc/dotnet/src/Bindings/MemoryBinding.cs | 97 --- .../misc/dotnet/src/Bindings/WasiBinding.cs | 23 - crates/misc/dotnet/src/Caller.cs | 82 ++ crates/misc/dotnet/src/Engine.cs | 85 -- .../misc/dotnet/src/Externs/ExternMemory.cs | 227 +---- crates/misc/dotnet/src/Function.cs | 355 ++++++++ crates/misc/dotnet/src/Global.cs | 62 +- crates/misc/dotnet/src/Host.cs | 802 ++++++++++++++++++ .../src/{EngineBuilder.cs => HostBuilder.cs} | 37 +- crates/misc/dotnet/src/IHost.cs | 18 - crates/misc/dotnet/src/ImportAttribute.cs | 31 - crates/misc/dotnet/src/Instance.cs | 91 +- crates/misc/dotnet/src/Interop.cs | 79 +- crates/misc/dotnet/src/Memory.cs | 264 +----- crates/misc/dotnet/src/MemoryBase.cs | 236 ++++++ crates/misc/dotnet/src/Module.cs | 93 +- crates/misc/dotnet/src/MutableGlobal.cs | 65 +- crates/misc/dotnet/src/Store.cs | 75 -- crates/misc/dotnet/src/Wasi.cs | 52 -- .../{WasiBuilder.cs => WasiConfiguration.cs} | 132 ++- .../dotnet/tests/Fixtures/ModuleFixture.cs | 23 +- .../dotnet/tests/FunctionThunkingTests.cs | 49 +- .../misc/dotnet/tests/GlobalExportsTests.cs | 7 +- .../dotnet/tests/GlobalImportBindingTests.cs | 210 +---- .../misc/dotnet/tests/MemoryExportsTests.cs | 8 +- .../dotnet/tests/MemoryImportBindingTests.cs | 167 +--- .../dotnet/tests/Modules/FunctionThunking.wat | 36 +- .../misc/dotnet/tests/WasiSnapshot0Tests.cs | 73 +- crates/misc/dotnet/tests/WasiTests.cs | 72 +- 38 files changed, 2149 insertions(+), 2213 deletions(-) delete mode 100644 crates/misc/dotnet/src/Bindings/Binding.cs delete mode 100644 crates/misc/dotnet/src/Bindings/FunctionBinding.cs delete mode 100644 crates/misc/dotnet/src/Bindings/GlobalBinding.cs delete mode 100644 crates/misc/dotnet/src/Bindings/MemoryBinding.cs delete mode 100644 crates/misc/dotnet/src/Bindings/WasiBinding.cs create mode 100644 crates/misc/dotnet/src/Caller.cs delete mode 100644 crates/misc/dotnet/src/Engine.cs create mode 100644 crates/misc/dotnet/src/Function.cs create mode 100644 crates/misc/dotnet/src/Host.cs rename crates/misc/dotnet/src/{EngineBuilder.cs => HostBuilder.cs} (88%) delete mode 100644 crates/misc/dotnet/src/IHost.cs delete mode 100644 crates/misc/dotnet/src/ImportAttribute.cs create mode 100644 crates/misc/dotnet/src/MemoryBase.cs delete mode 100644 crates/misc/dotnet/src/Store.cs delete mode 100644 crates/misc/dotnet/src/Wasi.cs rename crates/misc/dotnet/src/{WasiBuilder.cs => WasiConfiguration.cs} (75%) diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index f3e8d3aa0a..7f78a44c5e 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -50,8 +50,6 @@ WASMTIME_CONFIG_PROP(profiler, wasmtime_profiling_strategy_t) // Converts from the text format of WebAssembly to to the binary format. // -// * `engine` - a previously created engine which will drive allocations and -// such // * `wat` - this it the input buffer with the WebAssembly Text Format inside of // it. This will be parsed and converted to the binary format. // * `ret` - if the conversion is successful, this byte vector is filled in with @@ -62,7 +60,6 @@ WASMTIME_CONFIG_PROP(profiler, wasmtime_profiling_strategy_t) // // Returns `true` if conversion succeeded, or `false` if it failed. WASM_API_EXTERN bool wasmtime_wat2wasm( - wasm_engine_t *engine, const wasm_byte_vec_t *wat, own wasm_byte_vec_t *ret, own wasm_byte_vec_t *error_message @@ -75,7 +72,7 @@ WASM_API_EXTERN bool wasmtime_wat2wasm( WASMTIME_DECLARE_OWN(linker) -WASM_API_EXTERN own wasmtime_linker_t* wasmtime_linker_new(wasm_store_t* store); +WASM_API_EXTERN own wasmtime_linker_t* wasmtime_linker_new(wasm_store_t* store, bool allow_shadowing); WASM_API_EXTERN bool wasmtime_linker_define( wasmtime_linker_t *linker, @@ -101,6 +98,14 @@ WASM_API_EXTERN wasm_instance_t* wasmtime_linker_instantiate( own wasm_trap_t **trap ); +typedef struct wasmtime_caller_t wasmtime_caller_t; + +typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* caller, const wasm_val_t args[], wasm_val_t results[]); + +WASM_API_EXTERN own wasm_func_t* wasmtime_func_new(wasm_store_t*, const wasm_functype_t*, wasmtime_func_callback_t callback); + +WASM_API_EXTERN own wasm_extern_t* wasmtime_caller_export_get(const wasmtime_caller_t* caller, const wasm_name_t* name); + #undef own #ifdef __cplusplus diff --git a/crates/c-api/src/ext.rs b/crates/c-api/src/ext.rs index 6d0fe082cd..5bc4231501 100644 --- a/crates/c-api/src/ext.rs +++ b/crates/c-api/src/ext.rs @@ -109,7 +109,6 @@ pub unsafe extern "C" fn wasmtime_config_profiler_set( #[no_mangle] pub unsafe extern "C" fn wasmtime_wat2wasm( - _engine: *mut wasm_engine_t, wat: *const wasm_byte_vec_t, ret: *mut wasm_byte_vec_t, error: *mut wasm_byte_vec_t, @@ -143,10 +142,13 @@ pub struct wasmtime_linker_t { } #[no_mangle] -pub unsafe extern "C" fn wasmtime_linker_new(store: *mut wasm_store_t) -> *mut wasmtime_linker_t { - Box::into_raw(Box::new(wasmtime_linker_t { - linker: Linker::new(&(*store).store.borrow()), - })) +pub unsafe extern "C" fn wasmtime_linker_new( + store: *mut wasm_store_t, + allow_shadowing: bool, +) -> *mut wasmtime_linker_t { + let mut linker = Linker::new(&(*store).store.borrow()); + linker.allow_shadowing(allow_shadowing); + Box::into_raw(Box::new(wasmtime_linker_t { linker })) } #[no_mangle] @@ -213,3 +215,70 @@ pub unsafe extern "C" fn wasmtime_linker_instantiate( let linker = &(*linker).linker; handle_instantiate(linker.instantiate(&(*module).module.borrow()), trap) } + +pub type wasmtime_func_callback_t = std::option::Option< + unsafe extern "C" fn( + caller: *const wasmtime_caller_t, + args: *const wasm_val_t, + results: *mut wasm_val_t, + ) -> *mut wasm_trap_t, +>; + +#[repr(C)] +pub struct wasmtime_caller_t<'a> { + inner: wasmtime::Caller<'a>, +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_func_new( + store: *mut wasm_store_t, + ty: *const wasm_functype_t, + callback: wasmtime_func_callback_t, +) -> *mut wasm_func_t { + let store = &(*store).store.borrow(); + let ty = (*ty).functype.clone(); + let func = Func::new(store, ty, move |caller, params, results| { + let params = params + .iter() + .map(|p| wasm_val_t::from_val(p)) + .collect::>(); + let mut out_results = vec![wasm_val_t::default(); results.len()]; + let func = callback.expect("wasm_func_callback_t fn"); + let caller = wasmtime_caller_t { inner: caller }; + let out = func(&caller, params.as_ptr(), out_results.as_mut_ptr()); + if !out.is_null() { + let trap: Box = Box::from_raw(out); + return Err(trap.trap.borrow().clone()); + } + for i in 0..results.len() { + results[i] = out_results[i].val(); + } + Ok(()) + }); + let func = Box::new(wasm_func_t { + ext: wasm_extern_t { + which: ExternHost::Func(HostRef::new(func)), + }, + }); + Box::into_raw(func) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_caller_export_get( + caller: *const wasmtime_caller_t, + name: *const wasm_name_t, +) -> *mut wasm_extern_t { + let name = match str::from_utf8((*name).as_slice()) { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }; + match (*caller).inner.get_export(name).map(|e| match e { + Extern::Func(f) => ExternHost::Func(HostRef::new(f.clone())), + Extern::Global(g) => ExternHost::Global(HostRef::new(g.clone())), + Extern::Memory(m) => ExternHost::Memory(HostRef::new(m.clone())), + Extern::Table(t) => ExternHost::Table(HostRef::new(t.clone())), + }) { + Some(export) => Box::into_raw(Box::new(wasm_extern_t { which: export })), + None => std::ptr::null_mut(), + } +} diff --git a/crates/misc/dotnet/docs/articles/intro.md b/crates/misc/dotnet/docs/articles/intro.md index a5065e35fb..2e6268bc52 100644 --- a/crates/misc/dotnet/docs/articles/intro.md +++ b/crates/misc/dotnet/docs/articles/intro.md @@ -109,7 +109,7 @@ dotnet new console 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 +dotnet add package --version 0.14.0-preview1 wasmtime ``` _Note that the `--version` option is required because the package is currently prerelease._ @@ -126,38 +126,32 @@ 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()); + using var host = new Host(); + host.DefineFunction( + "env", + "print", + (Caller caller, int address, int length) => { + Console.WriteLine(caller.GetMemory("mem").ReadString(address, length)); + } + ); + + using var module = host.LoadModule("hello.wasm"); + + using dynamic instance = host.Instantiate(module); instance.run(); } } } ``` -The `Host` class is responsible for implementing the imported [functions](https://webassembly.github.io/spec/core/syntax/modules.html#functions), [globals](https://webassembly.github.io/spec/core/syntax/modules.html#globals), [memories](https://webassembly.github.io/spec/core/syntax/modules.html#memories), and [tables](https://webassembly.github.io/spec/core/syntax/modules.html#syntax-table) for the WebAssembly module. For Wasmtime for .NET, this is done via the [`Import`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.ImportAttribute.html) attribute applied to functions and fields of type [`Global`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Global-1.html), [`MutableGlobal`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.MutableGlobal-1.html), and [`Memory`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Memory.html) (support for WebAssembly tables is not yet implemented). The [`Instance`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.IHost.html#Wasmtime_IHost_Instance) property of the host is set during instantiation of the WebAssembly module. +The `Host` class is responsible for implementing an environment that WebAssembly modules can execute in. -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. +Here we are creating a host that is implementing a function named `print` in the `env` module, which is the default import module name for WebAssembly modules compiled using the Rust toolchain. 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. @@ -165,7 +159,7 @@ Alternatively, the `run` function could be invoked without using the runtime bin ```c# ... -using var instance = module.Instantiate(new Host()); +using var instance = host.Instantiate(module); instance.Externs.Functions[0].Invoke(); ... ``` diff --git a/crates/misc/dotnet/examples/global/Program.cs b/crates/misc/dotnet/examples/global/Program.cs index 3e3945dc95..70243ff86c 100644 --- a/crates/misc/dotnet/examples/global/Program.cs +++ b/crates/misc/dotnet/examples/global/Program.cs @@ -3,29 +3,25 @@ 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 Global = new MutableGlobal(1); - } - class Program { static void Main(string[] args) { - using var engine = new Engine(); - using var store = engine.CreateStore(); - using var module = store.CreateModule("global.wasm"); - using dynamic instance = module.Instantiate(new Host()); + using var host = new Host(); + var global = host.DefineMutableGlobal("", "global", 1); + + host.DefineFunction( + "", + "print_global", + () => { + Console.WriteLine($"The value of the global is: {global.Value}."); + } + ); + + using var module = host.LoadModule("global.wasm"); + + using dynamic instance = host.Instantiate(module); instance.run(20); } } diff --git a/crates/misc/dotnet/examples/hello/Program.cs b/crates/misc/dotnet/examples/hello/Program.cs index 0d8f08a359..c3fed700b8 100644 --- a/crates/misc/dotnet/examples/hello/Program.cs +++ b/crates/misc/dotnet/examples/hello/Program.cs @@ -3,26 +3,21 @@ 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()); + using var host = new Host(); + host.DefineFunction( + "", + "hello", + () => Console.WriteLine("Hello from C#, WebAssembly!") + ); + + using var module = host.LoadModule("hello.wasm"); + + using dynamic instance = host.Instantiate(module); instance.run(); } } diff --git a/crates/misc/dotnet/examples/memory/Program.cs b/crates/misc/dotnet/examples/memory/Program.cs index 00454fd3ea..d6cb3fbbce 100644 --- a/crates/misc/dotnet/examples/memory/Program.cs +++ b/crates/misc/dotnet/examples/memory/Program.cs @@ -3,27 +3,24 @@ 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()); + using var host = new Host(); + host.DefineFunction( + "", + "log", + (Caller caller, int address, int length) => { + var message = caller.GetMemory("mem").ReadString(address, length); + Console.WriteLine($"Message from WebAssembly: {message}"); + } + ); + + using var module = host.LoadModule("memory.wasm"); + + using dynamic instance = host.Instantiate(module); instance.run(); } } diff --git a/crates/misc/dotnet/src/Bindings/Binding.cs b/crates/misc/dotnet/src/Bindings/Binding.cs deleted file mode 100644 index 3ca964ec0c..0000000000 --- a/crates/misc/dotnet/src/Bindings/Binding.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using Wasmtime.Imports; - -namespace Wasmtime.Bindings -{ - /// - /// Represents an abstract host binding. - /// - internal abstract class Binding - { - public abstract SafeHandle Bind(Store store, IHost host); - - public static WasmtimeException CreateBindingException(Import import, MemberInfo member, string message) - { - return new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}."); - } - - public static List GetImportBindings(Module module, Wasi wasi = null, IHost host = null) - { - if (module is null) - { - throw new ArgumentNullException(nameof(module)); - } - - var bindings = new List(); - 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))); - - foreach (var import in module.Imports.All) - { - var wasiBinding = wasi?.Bind(import); - if (!(wasiBinding is null)) - { - bindings.Add(wasiBinding); - continue; - } - - switch (import) - { - case FunctionImport func: - bindings.Add(BindFunction(func, methods)); - break; - - case GlobalImport global: - bindings.Add(BindGlobal(global, fields)); - break; - - case MemoryImport memory: - bindings.Add(BindMemory(memory, fields)); - break; - - default: - throw new NotSupportedException("Unsupported import binding type."); - } - } - - return bindings; - } - - private static FunctionBinding BindFunction(FunctionImport import, IEnumerable methods) - { - var method = methods?.Where(m => - { - var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute)); - if (attribute is null) - { - return false; - } - - return attribute.Name == import.Name && - ((string.IsNullOrEmpty(attribute.Module) && - string.IsNullOrEmpty(import.ModuleName)) || - attribute.Module == import.ModuleName); - } - ).FirstOrDefault(); - - if (method is null) - { - throw new WasmtimeException($"Failed to bind function import '{import}': the host does not contain a method with a matching 'Import' attribute."); - } - - return new FunctionBinding(import, method); - } - - private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable fields) - { - var field = fields?.Where(f => - { - var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); - return attribute.Name == import.Name && - ((string.IsNullOrEmpty(attribute.Module) && - string.IsNullOrEmpty(import.ModuleName)) || - attribute.Module == import.ModuleName); - } - ).FirstOrDefault(); - - if (field is null) - { - throw new WasmtimeException($"Failed to bind global import '{import}': the host does not contain a global field with a matching 'Import' attribute."); - } - - return new GlobalBinding(import, field); - } - - private static MemoryBinding BindMemory(MemoryImport import, IEnumerable fields) - { - var field = fields?.Where(f => - { - var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); - return attribute.Name == import.Name && - ((string.IsNullOrEmpty(attribute.Module) && - string.IsNullOrEmpty(import.ModuleName)) || - attribute.Module == import.ModuleName); - } - ).FirstOrDefault(); - - if (field is null) - { - throw new WasmtimeException($"Failed to bind memory import '{import}': the host does not contain a memory field with a matching 'Import' attribute."); - } - - return new MemoryBinding(import, field); - } - } -} diff --git a/crates/misc/dotnet/src/Bindings/FunctionBinding.cs b/crates/misc/dotnet/src/Bindings/FunctionBinding.cs deleted file mode 100644 index 5d5d781750..0000000000 --- a/crates/misc/dotnet/src/Bindings/FunctionBinding.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using Wasmtime.Imports; - -namespace Wasmtime.Bindings -{ - /// - /// Represents a host function binding. - /// - internal class FunctionBinding : Binding - { - /// - /// Constructs a new function binding. - /// - /// The function import of the binding. - /// The method the import is bound to. - public FunctionBinding(FunctionImport import, MethodInfo method) - { - if (import is null) - { - throw new ArgumentNullException(nameof(import)); - } - - if (method is null) - { - throw new ArgumentNullException(nameof(method)); - } - - Import = import; - Method = method; - - Validate(); - } - - /// - /// The function import of the binding. - /// - public FunctionImport Import { get; private set; } - - /// - /// The method the import is bound to. - /// - public MethodInfo Method { get; private set; } - - public override SafeHandle Bind(Store store, IHost host) - { - unsafe - { - var parameters = Interop.ToValueTypeVec(Import.Parameters); - var results = Interop.ToValueTypeVec(Import.Results); - using var funcType = Interop.wasm_functype_new(ref parameters, ref results); - var callback = CreateCallback(store, host); - var func = Interop.wasm_func_new(store.Handle, funcType, callback); - // Store the callback with the safe handle to keep the delegate GC reachable - func.Callback = callback; - return func; - } - } - - private void Validate() - { - if (Method.IsStatic) - { - throw CreateBindingException(Import, Method, "method cannot be static"); - } - - if (Method.IsGenericMethod) - { - throw CreateBindingException(Import, Method, "method cannot be generic"); - } - - if (Method.IsConstructor) - { - throw CreateBindingException(Import, Method, "method cannot be a constructor"); - } - - ValidateParameters(); - - ValidateReturnType(); - } - - private void ValidateParameters() - { - var parameters = Method.GetParameters(); - if (parameters.Length != Import.Parameters.Count) - { - throw CreateBindingException( - 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) - { - throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter"); - } - else - { - throw CreateBindingException(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)) - { - throw CreateBindingException(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)) - { - throw CreateBindingException(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)) - { - throw CreateBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'"); - } - } - else - { - if (!IsTupleOfSize(Method.ReturnType, resultsCount)) - { - throw CreateBindingException(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)) - { - throw CreateBindingException(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, BindingFlags.DoNotWrapExceptions, null, args, null); - - if (hasReturn) - { - SetResults(result, results); - } - return IntPtr.Zero; - } - catch (Exception ex) - { - var bytes = Encoding.UTF8.GetBytes(ex.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."); - } - } - } -} diff --git a/crates/misc/dotnet/src/Bindings/GlobalBinding.cs b/crates/misc/dotnet/src/Bindings/GlobalBinding.cs deleted file mode 100644 index da75e8148b..0000000000 --- a/crates/misc/dotnet/src/Bindings/GlobalBinding.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using Wasmtime.Imports; - -namespace Wasmtime.Bindings -{ - /// - /// Represents a host global binding. - /// - internal class GlobalBinding : Binding - { - /// - /// Constructs a new global binding. - /// - /// The global import of the binding. - /// The field the import is bound to. - public GlobalBinding(GlobalImport import, FieldInfo field) - { - if (import is null) - { - throw new ArgumentNullException(nameof(import)); - } - - if (field is null) - { - throw new ArgumentNullException(nameof(field)); - } - - Import = import; - Field = field; - - Validate(); - } - - /// - /// The global import of the binding. - /// - public GlobalImport Import { get; private set; } - - /// - /// The field the import is bound to. - /// - public FieldInfo Field { get; private set; } - - public override SafeHandle Bind(Store store, IHost host) - { - unsafe - { - dynamic global = Field.GetValue(host); - if (!(global.Handle is 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) - { - throw CreateBindingException(Import, Field, "field cannot be static"); - } - - if (!Field.IsInitOnly) - { - throw CreateBindingException(Import, Field, "field must be readonly"); - } - - if (!Field.FieldType.IsGenericType) - { - throw CreateBindingException(Import, Field, "field is expected to be of type 'Global'"); - } - - var definition = Field.FieldType.GetGenericTypeDefinition(); - if (definition == typeof(Global<>)) - { - if (Import.IsMutable) - { - throw CreateBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)"); - } - } - else if (definition == typeof(MutableGlobal<>)) - { - if (!Import.IsMutable) - { - throw CreateBindingException(Import, Field, "the import is constant (use the 'Global' type)"); - } - } - else - { - throw CreateBindingException(Import, Field, "field is expected to be of type 'Global' or 'MutableGlobal'"); - } - - var arg = Field.FieldType.GetGenericArguments()[0]; - - if (Interop.TryGetValueKind(arg, out var kind)) - { - if (!Interop.IsMatchingKind(kind, Import.Kind)) - { - throw CreateBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'"); - } - } - else - { - throw CreateBindingException(Import, Field, $"'{arg}' is not a valid global type"); - } - } - } -} diff --git a/crates/misc/dotnet/src/Bindings/MemoryBinding.cs b/crates/misc/dotnet/src/Bindings/MemoryBinding.cs deleted file mode 100644 index 30e9f18cde..0000000000 --- a/crates/misc/dotnet/src/Bindings/MemoryBinding.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using Wasmtime.Imports; - -namespace Wasmtime.Bindings -{ - /// - /// Represents a host memory binding. - /// - internal class MemoryBinding : Binding - { - /// - /// Constructs a new memory binding. - /// - /// The memory import of the binding. - /// The field the import is bound to. - public MemoryBinding(MemoryImport import, FieldInfo field) - { - if (import is null) - { - throw new ArgumentNullException(nameof(import)); - } - - if (field is null) - { - throw new ArgumentNullException(nameof(field)); - } - - Import = import; - Field = field; - - Validate(); - } - - /// - /// The memory import of the binding. - /// - public MemoryImport Import { get; private set; } - - /// - /// The field the import is bound to. - /// - public FieldInfo Field { get; private set; } - - public override SafeHandle Bind(Store store, IHost host) - { - Memory memory = (Memory)Field.GetValue(host); - if (!(memory.Handle is null)) - { - throw new InvalidOperationException("Cannot bind more than once."); - } - - uint min = memory.Minimum; - uint max = memory.Maximum; - - if (min != Import.Minimum) - { - throw CreateBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)"); - } - if (max != Import.Maximum) - { - throw CreateBindingException(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) - { - throw CreateBindingException(Import, Field, "field cannot be static"); - } - - if (!Field.IsInitOnly) - { - throw CreateBindingException(Import, Field, "field must be readonly"); - } - - if (Field.FieldType != typeof(Memory)) - { - throw CreateBindingException(Import, Field, "field is expected to be of type 'Memory'"); - } - } - } -} diff --git a/crates/misc/dotnet/src/Bindings/WasiBinding.cs b/crates/misc/dotnet/src/Bindings/WasiBinding.cs deleted file mode 100644 index 4541f808c7..0000000000 --- a/crates/misc/dotnet/src/Bindings/WasiBinding.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Wasmtime.Bindings -{ - /// - /// Represents a binding to a WASI export. - /// - internal class WasiBinding : Binding - { - public WasiBinding(IntPtr handle) - { - _handle = handle; - } - - public override SafeHandle Bind(Store store, IHost host) - { - return new Interop.WasiExportHandle(_handle); - } - - private IntPtr _handle; - } -} diff --git a/crates/misc/dotnet/src/Caller.cs b/crates/misc/dotnet/src/Caller.cs new file mode 100644 index 0000000000..bbfe0b6a4c --- /dev/null +++ b/crates/misc/dotnet/src/Caller.cs @@ -0,0 +1,82 @@ +using System; +using System.Text; + +namespace Wasmtime +{ + /// + /// Represents an exported memory of a host function caller. + /// + public class CallerMemory : MemoryBase, IDisposable + { + /// + public void Dispose() + { + if (!_extern.IsInvalid) + { + _extern.Dispose(); + _extern.SetHandleAsInvalid(); + } + } + + internal CallerMemory(Interop.ExternHandle ext, IntPtr memory) + { + _extern = ext; + _memory = memory; + } + + /// + protected override IntPtr MemoryHandle => _memory; + + private Interop.ExternHandle _extern; + + private IntPtr _memory; + } + + /// + /// Represents information of the caller of a host function. + /// + public class Caller + { + /// + /// Gets an exported memory of the caller by the given name. + /// + /// The name of the exported memory. + /// Returns the exported memory if found or null if a memory of the requested name is not exported. + public CallerMemory GetMemory(string name) + { + if (Handle == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + unsafe + { + var bytes = Encoding.UTF8.GetBytes(name); + + fixed (byte* ptr = bytes) + { + Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t(); + nameVec.size = (UIntPtr)bytes.Length; + nameVec.data = ptr; + + var export = Interop.wasmtime_caller_export_get(Handle, ref nameVec); + if (export.IsInvalid) + { + return null; + } + + var memory = Interop.wasm_extern_as_memory(export.DangerousGetHandle()); + if (memory == IntPtr.Zero) + { + export.Dispose(); + return null; + } + + return new CallerMemory(export, memory); + } + } + } + + internal IntPtr Handle { get; set; } + } +} \ No newline at end of file diff --git a/crates/misc/dotnet/src/Engine.cs b/crates/misc/dotnet/src/Engine.cs deleted file mode 100644 index 27bc5ebe79..0000000000 --- a/crates/misc/dotnet/src/Engine.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Text; -using System.Runtime.InteropServices; - -namespace Wasmtime -{ - /// - /// Represents the Wasmtime engine. - /// - public class Engine : IDisposable - { - /// - /// Constructs a new . - /// - public Engine() - { - Handle = Interop.wasm_engine_new(); - - if (Handle.IsInvalid) - { - throw new WasmtimeException("Failed to create Wasmtime engine."); - } - } - - internal Engine(Interop.WasmConfigHandle config) - { - Handle = Interop.wasm_engine_new_with_config(config); - config.SetHandleAsInvalid(); - - if (Handle.IsInvalid) - { - throw new WasmtimeException("Failed to create Wasmtime engine."); - } - } - - /// - /// Creates a new Wasmtime . - /// - /// Returns the new . - public Store CreateStore() - { - return new Store(this); - } - - /// - /// Converts the WebAssembly text format to the binary format - /// - /// Returns the binary-encoded wasm module. - public byte[] WatToWasm(string wat) - { - var watBytes = Encoding.UTF8.GetBytes(wat); - unsafe - { - fixed (byte *ptr = watBytes) - { - Interop.wasm_byte_vec_t watByteVec; - watByteVec.size = (UIntPtr)watBytes.Length; - watByteVec.data = ptr; - if (!Interop.wasmtime_wat2wasm(Handle, ref watByteVec, out var bytes, out var error)) { - var errorSpan = new ReadOnlySpan(error.data, checked((int)error.size)); - var message = Encoding.UTF8.GetString(errorSpan); - Interop.wasm_byte_vec_delete(ref error); - throw new WasmtimeException("failed to parse input wat: " + message); - } - var byteSpan = new ReadOnlySpan(bytes.data, checked((int)bytes.size)); - var ret = byteSpan.ToArray(); - Interop.wasm_byte_vec_delete(ref bytes); - return ret; - } - } - } - - /// - public void Dispose() - { - if (!Handle.IsInvalid) - { - Handle.Dispose(); - Handle.SetHandleAsInvalid(); - } - } - - internal Interop.EngineHandle Handle { get; private set; } - } -} diff --git a/crates/misc/dotnet/src/Externs/ExternMemory.cs b/crates/misc/dotnet/src/Externs/ExternMemory.cs index e86be4f341..38ef33f1a3 100644 --- a/crates/misc/dotnet/src/Externs/ExternMemory.cs +++ b/crates/misc/dotnet/src/Externs/ExternMemory.cs @@ -8,7 +8,7 @@ namespace Wasmtime.Externs /// /// Represents an external (instantiated) WebAssembly memory. /// - public class ExternMemory + public class ExternMemory : MemoryBase { internal ExternMemory(MemoryExport export, IntPtr memory) { @@ -31,230 +31,7 @@ namespace Wasmtime.Externs /// public uint Maximum => _export.Maximum; - /// - /// The span of the memory. - /// - /// - /// The span may become invalid if the memory grows. - /// - /// This may happen if the memory is explicitly requested to grow or - /// grows as a result of WebAssembly execution. - /// - /// Therefore, the returned Span should not be stored. - /// - public unsafe Span Span - { - get - { - var data = Interop.wasm_memory_data(_memory); - var size = Convert.ToInt32(Interop.wasm_memory_data_size(_memory).ToUInt32()); - return new Span(data, size); - } - } - - /// - /// Reads a UTF-8 string from memory. - /// - /// The zero-based address to read from. - /// The length of bytes to read. - /// Returns the string read from memory. - public string ReadString(int address, int length) - { - return Encoding.UTF8.GetString(Span.Slice(address, length)); - } - - /// - /// Reads a null-terminated UTF-8 string from memory. - /// - /// The zero-based address to read from. - /// Returns the string read from memory. - public string ReadNullTerminatedString(int address) - { - var slice = Span.Slice(address); - var terminator = slice.IndexOf((byte)0); - if (terminator == -1) - { - throw new InvalidOperationException("string is not null terminated"); - } - - return Encoding.UTF8.GetString(slice.Slice(0, terminator)); - } - - /// - /// Writes a UTF-8 string at the given address. - /// - /// The zero-based address to write to. - /// The string to write. - /// Returns the number of bytes written. - public int WriteString(int address, string value) - { - return Encoding.UTF8.GetBytes(value, Span.Slice(address)); - } - - /// - /// Reads a byte from memory. - /// - /// The zero-based address to read from. - /// Returns the byte read from memory. - public byte ReadByte(int address) - { - return Span[address]; - } - - /// - /// Writes a byte to memory. - /// - /// The zero-based address to write to. - /// The byte to write. - public void WriteByte(int address, byte value) - { - Span[address] = value; - } - - /// - /// Reads a short from memory. - /// - /// The zero-based address to read from. - /// Returns the short read from memory. - public short ReadInt16(int address) - { - return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2)); - } - - /// - /// Writes a short to memory. - /// - /// The zero-based address to write to. - /// The short to write. - public void WriteInt16(int address, short value) - { - BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value); - } - - /// - /// Reads an int from memory. - /// - /// The zero-based address to read from. - /// Returns the int read from memory. - public int ReadInt32(int address) - { - return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4)); - } - - /// - /// Writes an int to memory. - /// - /// The zero-based address to write to. - /// The int to write. - public void WriteInt32(int address, int value) - { - BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value); - } - - /// - /// Reads a long from memory. - /// - /// The zero-based address to read from. - /// Returns the long read from memory. - public long ReadInt64(int address) - { - return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8)); - } - - /// - /// Writes a long to memory. - /// - /// The zero-based address to write to. - /// The long to write. - public void WriteInt64(int address, long value) - { - BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value); - } - - /// - /// Reads an IntPtr from memory. - /// - /// The zero-based address to read from. - /// Returns the IntPtr read from memory. - public IntPtr ReadIntPtr(int address) - { - if (IntPtr.Size == 4) - { - return (IntPtr)ReadInt32(address); - } - return (IntPtr)ReadInt64(address); - } - - /// - /// Writes an IntPtr to memory. - /// - /// The zero-based address to write to. - /// The IntPtr to write. - public void WriteIntPtr(int address, IntPtr value) - { - if (IntPtr.Size == 4) - { - WriteInt32(address, value.ToInt32()); - } - else - { - WriteInt64(address, value.ToInt64()); - } - } - - /// - /// Reads a long from memory. - /// - /// The zero-based address to read from. - /// Returns the long read from memory. - public float ReadSingle(int address) - { - unsafe - { - var i = ReadInt32(address); - return *((float*)&i); - } - } - - /// - /// Writes a single to memory. - /// - /// The zero-based address to write to. - /// The single to write. - public void WriteSingle(int address, float value) - { - unsafe - { - WriteInt32(address, *(int*)&value); - } - } - - /// - /// Reads a double from memory. - /// - /// The zero-based address to read from. - /// Returns the double read from memory. - public double ReadDouble(int address) - { - unsafe - { - var i = ReadInt64(address); - return *((double*)&i); - } - } - - /// - /// Writes a double to memory. - /// - /// The zero-based address to write to. - /// The double to write. - public void WriteDouble(int address, double value) - { - unsafe - { - WriteInt64(address, *(long*)&value); - } - } + protected override IntPtr MemoryHandle => _memory; private MemoryExport _export; private IntPtr _memory; diff --git a/crates/misc/dotnet/src/Function.cs b/crates/misc/dotnet/src/Function.cs new file mode 100644 index 0000000000..b11ea17887 --- /dev/null +++ b/crates/misc/dotnet/src/Function.cs @@ -0,0 +1,355 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Wasmtime +{ + /// + /// Represents a host function. + /// + public class Function : IDisposable + { + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Function(Interop.StoreHandle store, Delegate func, bool hasReturn) + { + if (func is null) + { + throw new ArgumentNullException(nameof(func)); + } + + var type = func.GetType(); + Span parameterTypes = null; + Type returnType = null; + + if (hasReturn) + { + parameterTypes = type.GenericTypeArguments[0..^1]; + returnType = type.GenericTypeArguments[^1]; + } + else + { + parameterTypes = type.GenericTypeArguments; + returnType = null; + } + + bool hasCaller = parameterTypes.Length > 0 && parameterTypes[0] == typeof(Caller); + + if (hasCaller) + { + parameterTypes = parameterTypes[1..]; + } + + ValidateParameterTypes(parameterTypes); + + ValidateReturnType(returnType); + + var parameters = CreateValueTypeVec(parameterTypes); + var results = CreateReturnValueTypeVec(returnType); + using var funcType = Interop.wasm_functype_new(ref parameters, ref results); + + if (hasCaller) + { + Callback = CreateCallbackWithCaller(store, func, parameterTypes.Length, hasReturn); + Handle = Interop.wasmtime_func_new(store, funcType, (Interop.WasmtimeFuncCallback)Callback); + } + else + { + Callback = CreateCallback(store, func, parameterTypes.Length, hasReturn); + Handle = Interop.wasm_func_new(store, funcType, (Interop.WasmFuncCallback)Callback); + } + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime function."); + } + } + + private static void ValidateParameterTypes(Span parameters) + { + foreach (var type in parameters) + { + if (type == typeof(Caller)) + { + throw new WasmtimeException($"A Caller parameter must be the first parameter of the function."); + } + + if (!Interop.TryGetValueKind(type, out var kind)) + { + throw new WasmtimeException($"Unable to create a function with parameter of type '{type.ToString()}'."); + } + } + } + + private static void ValidateReturnType(Type returnType) + { + if (returnType is null) + { + return; + } + + if (IsTuple(returnType)) + { + var types = returnType + .GetGenericArguments() + .SelectMany(type => + { + if (type.IsConstructedGenericType) + { + return type.GenericTypeArguments; + } + return Enumerable.Repeat(type, 1); + }); + + foreach (var type in types) + { + ValidateReturnType(type); + } + return; + } + + if (!Interop.TryGetValueKind(returnType, out var kind)) + { + throw new WasmtimeException($"Unable to create a function with a return type of type '{returnType.ToString()}'."); + } + } + + private static bool IsTuple(Type type) + { + if (!type.IsConstructedGenericType) + { + return false; + } + + var definition = type.GetGenericTypeDefinition(); + + return definition == typeof(ValueTuple) || + definition == typeof(ValueTuple<>) || + definition == typeof(ValueTuple<,>) || + definition == typeof(ValueTuple<,,>) || + definition == typeof(ValueTuple<,,,>) || + definition == typeof(ValueTuple<,,,,>) || + definition == typeof(ValueTuple<,,,,,>) || + definition == typeof(ValueTuple<,,,,,,>) || + definition == typeof(ValueTuple<,,,,,,,>); + } + + private static unsafe Interop.WasmFuncCallback CreateCallback(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn) + { + // NOTE: this capture is not thread-safe. + var args = new object[parameterCount]; + var method = func.Method; + var target = func.Target; + + return (arguments, results) => + { + try + { + SetArgs(arguments, args); + + var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null); + + if (hasReturn) + { + SetResults(result, results); + } + return IntPtr.Zero; + } + catch (Exception ex) + { + var bytes = Encoding.UTF8.GetBytes(ex.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(store, ref message); + } + } + }; + } + + private static unsafe Interop.WasmtimeFuncCallback CreateCallbackWithCaller(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn) + { + // NOTE: this capture is not thread-safe. + var args = new object[parameterCount + 1]; + var caller = new Caller(); + var method = func.Method; + var target = func.Target; + + args[0] = caller; + + return (callerHandle, arguments, results) => + { + try + { + caller.Handle = callerHandle; + + SetArgs(arguments, args, 1); + + var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null); + + caller.Handle = IntPtr.Zero; + + if (hasReturn) + { + SetResults(result, results); + } + return IntPtr.Zero; + } + catch (Exception ex) + { + var bytes = Encoding.UTF8.GetBytes(ex.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(store, ref message); + } + } + }; + } + + private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args, int offset = 0) + { + for (int i = 0; i < args.Length - offset; ++i) + { + var arg = arguments[i]; + + switch (arg.kind) + { + case Interop.wasm_valkind_t.WASM_I32: + args[i + offset] = arg.of.i32; + break; + + case Interop.wasm_valkind_t.WASM_I64: + args[i + offset] = arg.of.i64; + break; + + case Interop.wasm_valkind_t.WASM_F32: + args[i + offset] = arg.of.f32; + break; + + case Interop.wasm_valkind_t.WASM_F64: + args[i + offset] = 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 static Interop.wasm_valtype_vec_t CreateValueTypeVec(Span types) + { + Interop.wasm_valtype_vec_t vec; + Interop.wasm_valtype_vec_new_uninitialized(out vec, (UIntPtr)types.Length); + + int i = 0; + foreach (var type in types) + { + var valType = Interop.wasm_valtype_new((Interop.wasm_valkind_t)Interop.ToValueKind(type)); + unsafe + { + vec.data[i++] = valType.DangerousGetHandle(); + } + valType.SetHandleAsInvalid(); + } + + return vec; + } + + private static Interop.wasm_valtype_vec_t CreateReturnValueTypeVec(Type returnType) + { + if (returnType is null) + { + Interop.wasm_valtype_vec_t vec; + Interop.wasm_valtype_vec_new_empty(out vec); + return vec; + } + + if (IsTuple(returnType)) + { + return CreateValueTypeVec( + returnType + .GetGenericArguments() + .SelectMany(type => + { + if (type.IsConstructedGenericType) + { + return type.GenericTypeArguments; + } + return Enumerable.Repeat(type, 1); + }) + .ToArray() + ); + } + + return CreateValueTypeVec(new Type[] { returnType }); + } + + internal Interop.FunctionHandle Handle { get; private set; } + internal Delegate Callback { get; private set; } + } +} diff --git a/crates/misc/dotnet/src/Global.cs b/crates/misc/dotnet/src/Global.cs index 13ca0e285e..ce421fee49 100644 --- a/crates/misc/dotnet/src/Global.cs +++ b/crates/misc/dotnet/src/Global.cs @@ -5,18 +5,8 @@ namespace Wasmtime /// /// Represents a constant WebAssembly global value. /// - public class Global + public class Global : IDisposable { - /// - /// Creates a new with the given initial value. - /// - /// The initial value of the global. - public Global(T initialValue) - { - InitialValue = initialValue; - Kind = Interop.ToValueKind(typeof(T)); - } - /// /// The value of the global. /// @@ -35,16 +25,58 @@ namespace Wasmtime 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; } + /// + /// Gets the value kind of the global. + /// + /// + public ValueKind Kind { get; private set; } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Global(Interop.StoreHandle store, T initialValue) + { + if (!Interop.TryGetValueKind(typeof(T), out var kind)) + { + throw new WasmtimeException($"Global variables cannot be of type '{typeof(T).ToString()}'."); + } + + Kind = kind; + + var value = Interop.ToValue((object)initialValue, Kind); + + var valueType = Interop.wasm_valtype_new(value.kind); + var valueTypeHandle = valueType.DangerousGetHandle(); + valueType.SetHandleAsInvalid(); + + using var globalType = Interop.wasm_globaltype_new( + valueTypeHandle, + Interop.wasm_mutability_t.WASM_CONST + ); + + unsafe + { + Handle = Interop.wasm_global_new(store, globalType, &value); + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime global."); + } + } + } internal Interop.GlobalHandle Handle { get; set; } - - internal T InitialValue { get; private set; } } } diff --git a/crates/misc/dotnet/src/Host.cs b/crates/misc/dotnet/src/Host.cs new file mode 100644 index 0000000000..92b8c05a29 --- /dev/null +++ b/crates/misc/dotnet/src/Host.cs @@ -0,0 +1,802 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Wasmtime +{ + /// + /// Represents a WebAssembly host environment. + /// + /// + /// A host is used to configure the environment for WebAssembly modules to execute in. + /// + public class Host : IDisposable + { + /// + /// Constructs a new host. + /// + public Host() + { + Initialize(Interop.wasm_engine_new()); + } + + /// + /// Defines a WASI implementation in the host. + /// + /// The name of the WASI module to define. + /// The to configure the WASI implementation with. + public void DefineWasi(string name, WasiConfiguration config = null) + { + CheckDisposed(); + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("Name cannot be null or empty.", nameof(name)); + } + + if (config is null) + { + config = new WasiConfiguration(); + } + + using var wasi = config.CreateWasi(Store, name); + + if (!Interop.wasmtime_linker_define_wasi(Linker, wasi)) + { + throw new WasmtimeException($"Failed to define WASI module '{name}'."); + } + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Action func) + { + return DefineFunction(moduleName, name, func, false); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a host function. + /// + /// The module name of the host function. + /// The name of the host function. + /// The callback for when the host function is invoked. + /// Returns a representing the host function. + public Function DefineFunction(string moduleName, string name, Func func) + { + return DefineFunction(moduleName, name, func, true); + } + + /// + /// Defines a new host global variable. + /// + /// The module name of the host variable. + /// The name of the host variable. + /// The initial value of the host variable. + /// The type of the host variable. + /// Returns a new representing the defined global variable. + public Global DefineGlobal(string moduleName, string name, T initialValue) + { + CheckDisposed(); + + if (moduleName is null) + { + throw new ArgumentNullException(nameof(moduleName)); + } + + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + var global = new Global(Store, initialValue); + + if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle))) + { + global.Dispose(); + throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'."); + } + + return global; + } + + /// + /// Defines a new host mutable global variable. + /// + /// The module name of the host variable. + /// The name of the host variable. + /// The initial value of the host variable. + /// The type of the host variable. + /// Returns a new representing the defined mutable global variable. + public MutableGlobal DefineMutableGlobal(string moduleName, string name, T initialValue) + { + CheckDisposed(); + + if (moduleName is null) + { + throw new ArgumentNullException(nameof(moduleName)); + } + + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + var global = new MutableGlobal(Store, initialValue); + + if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle))) + { + global.Dispose(); + throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'."); + } + + return global; + } + + /// + /// Defines a new host memory. + /// + /// The module name of the host memory. + /// The name of the host memory. + /// The minimum number of pages for the host memory. + /// The maximum number of pages for the host memory. + /// Returns a new representing the defined memory. + public Memory DefineMemory(string moduleName, string name, uint minimum = 1, uint maximum = uint.MaxValue) + { + CheckDisposed(); + + if (moduleName is null) + { + throw new ArgumentNullException(nameof(moduleName)); + } + + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + var memory = new Memory(Store, minimum, maximum); + + if (!Define(moduleName, name, Interop.wasm_memory_as_extern(memory.Handle))) + { + memory.Dispose(); + throw new WasmtimeException($"Failed to define memory '{name}' in module '{moduleName}'."); + } + + return memory; + } + + /// + /// Loads a given the module name and bytes. + /// + /// The name of the module. + /// The bytes of the module. + /// Returns a new . + public Module LoadModule(string name, byte[] bytes) + { + CheckDisposed(); + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (bytes is null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + return new Module(Store, name, bytes); + } + + /// + /// Loads a given the path to the WebAssembly file. + /// + /// The path to the WebAssembly file. + /// Returns a new . + public Module LoadModule(string path) + { + return LoadModule(Path.GetFileNameWithoutExtension(path), File.ReadAllBytes(path)); + } + + /// + /// Loads a based on a WebAssembly text format representation. + /// + /// The name of the module. + /// The WebAssembly text format representation of the module. + /// Returns a new . + public Module LoadModuleText(string name, string text) + { + CheckDisposed(); + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + var textBytes = Encoding.UTF8.GetBytes(text); + unsafe + { + fixed (byte *ptr = textBytes) + { + Interop.wasm_byte_vec_t textVec; + textVec.size = (UIntPtr)textBytes.Length; + textVec.data = ptr; + + if (!Interop.wasmtime_wat2wasm(ref textVec, out var bytes, out var error)) + { + var errorSpan = new ReadOnlySpan(error.data, checked((int)error.size)); + var message = Encoding.UTF8.GetString(errorSpan); + Interop.wasm_byte_vec_delete(ref error); + throw new WasmtimeException($"Failed to parse module text: {message}"); + } + + var byteSpan = new ReadOnlySpan(bytes.data, checked((int)bytes.size)); + var moduleBytes = byteSpan.ToArray(); + Interop.wasm_byte_vec_delete(ref bytes); + return LoadModule(name, moduleBytes); + } + } + } + + /// + /// Loads a based on the path to a WebAssembly text format file. + /// + /// The path to the WebAssembly text format file. + /// Returns a new . + public Module LoadModuleText(string path) + { + return LoadModuleText(Path.GetFileNameWithoutExtension(path), File.ReadAllText(path)); + } + + /// + /// Instantiates a WebAssembly module. + /// + /// The module to instantiate. + /// Returns a new . + public Instance Instantiate(Module module) + { + CheckDisposed(); + + if (module is null) + { + throw new ArgumentNullException(nameof(module)); + } + + return new Instance(Linker, module); + } + + /// + /// Clears all existing definitions in the host. + /// + public void ClearDefinitions() + { + CheckDisposed(); + + var linker = Interop.wasmtime_linker_new(Store, allowShadowing: true); + if (linker.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime linker."); + } + + Linker.Dispose(); + Linker = linker; + } + + /// + public void Dispose() + { + if (!Linker.IsInvalid) + { + Linker.Dispose(); + Linker.SetHandleAsInvalid(); + } + + if (!Store.IsInvalid) + { + Store.Dispose(); + Store.SetHandleAsInvalid(); + } + + if (!Engine.IsInvalid) + { + Engine.Dispose(); + Engine.SetHandleAsInvalid(); + } + } + + internal Host(Interop.WasmConfigHandle config) + { + var engine = Interop.wasm_engine_new_with_config(config); + config.SetHandleAsInvalid(); + + Initialize(engine); + } + + private void Initialize(Interop.EngineHandle engine) + { + if (engine.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime engine."); + } + + var store = Interop.wasm_store_new(engine); + + if (store.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime store."); + } + + var linker = Interop.wasmtime_linker_new(store, allowShadowing: true); + if (linker.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime linker."); + } + + Engine = engine; + Store = store; + Linker = linker; + } + + private void CheckDisposed() + { + if (Engine.IsInvalid) + { + throw new ObjectDisposedException(typeof(Host).FullName); + } + } + + private Function DefineFunction(string moduleName, string name, Delegate func, bool hasReturn) + { + if (moduleName is null) + { + throw new ArgumentNullException(nameof(moduleName)); + } + + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (func is null) + { + throw new ArgumentNullException(nameof(func)); + } + + var function = new Function(Store, func, hasReturn); + + if (!Define(moduleName, name, Interop.wasm_func_as_extern(function.Handle))) + { + function.Dispose(); + throw new WasmtimeException($"Failed to define function '{name}' in module '{moduleName}'."); + } + + _callbacks.Add(function.Callback); + return function; + } + + private bool Define(string moduleName, string name, IntPtr ext) + { + var moduleNameBytes = Encoding.UTF8.GetBytes(moduleName); + var nameBytes = Encoding.UTF8.GetBytes(name); + + unsafe + { + fixed (byte* moduleNamePtr = moduleNameBytes) + fixed (byte* namePtr = nameBytes) + { + Interop.wasm_byte_vec_t moduleNameVec = new Interop.wasm_byte_vec_t(); + moduleNameVec.size = (UIntPtr)moduleNameBytes.Length; + moduleNameVec.data = moduleNamePtr; + + Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t(); + nameVec.size = (UIntPtr)nameBytes.Length; + nameVec.data = namePtr; + + return Interop.wasmtime_linker_define(Linker, ref moduleNameVec, ref nameVec, ext); + } + } + } + + internal Interop.EngineHandle Engine { get; private set; } + internal Interop.StoreHandle Store { get; private set; } + internal Interop.LinkerHandle Linker { get; private set; } + + private List _callbacks = new List(); + } +} diff --git a/crates/misc/dotnet/src/EngineBuilder.cs b/crates/misc/dotnet/src/HostBuilder.cs similarity index 88% rename from crates/misc/dotnet/src/EngineBuilder.cs rename to crates/misc/dotnet/src/HostBuilder.cs index c9983c9b21..90235e7b80 100644 --- a/crates/misc/dotnet/src/EngineBuilder.cs +++ b/crates/misc/dotnet/src/HostBuilder.cs @@ -43,23 +43,16 @@ namespace Wasmtime } /// - /// Represents a builder of instances. + /// Represents a builder of instances. /// - public class EngineBuilder + public class HostBuilder { - /// - /// Constructs a new . - /// - public EngineBuilder() - { - } - /// /// Sets whether or not to enable debug information. /// /// True to enable debug information or false to disable. /// Returns the current builder. - public EngineBuilder WithDebugInfo(bool enable) + public HostBuilder WithDebugInfo(bool enable) { _enableDebugInfo = enable; return this; @@ -70,7 +63,7 @@ namespace Wasmtime /// /// True to enable WebAssembly threads support or false to disable. /// Returns the current builder. - public EngineBuilder WithWasmThreads(bool enable) + public HostBuilder WithWasmThreads(bool enable) { _enableWasmThreads = enable; return this; @@ -81,7 +74,7 @@ namespace Wasmtime /// /// True to enable WebAssembly reference types support or false to disable. /// Returns the current builder. - public EngineBuilder WithReferenceTypes(bool enable) + public HostBuilder WithReferenceTypes(bool enable) { _enableReferenceTypes = enable; return this; @@ -92,7 +85,7 @@ namespace Wasmtime /// /// True to enable WebAssembly SIMD support or false to disable. /// Returns the current builder. - public EngineBuilder WithSIMD(bool enable) + public HostBuilder WithSIMD(bool enable) { _enableSIMD = enable; return this; @@ -103,7 +96,7 @@ namespace Wasmtime /// /// True to enable WebAssembly multi-value support or false to disable. /// Returns the current builder. - public EngineBuilder WithMultiValue(bool enable) + public HostBuilder WithMultiValue(bool enable) { _enableMultiValue = enable; return this; @@ -114,7 +107,7 @@ namespace Wasmtime /// /// True to enable WebAssembly bulk memory support or false to disable. /// Returns the current builder. - public EngineBuilder WithBulkMemory(bool enable) + public HostBuilder WithBulkMemory(bool enable) { _enableBulkMemory = enable; return this; @@ -125,7 +118,7 @@ namespace Wasmtime /// /// The compiler strategy to use. /// Returns the current builder. - public EngineBuilder WithCompilerStrategy(CompilerStrategy strategy) + public HostBuilder WithCompilerStrategy(CompilerStrategy strategy) { switch (strategy) { @@ -152,7 +145,7 @@ namespace Wasmtime /// /// True to enable the Cranelift debug verifier or false to disable. /// Returns the current builder. - public EngineBuilder WithCraneliftDebugVerifier(bool enable) + public HostBuilder WithCraneliftDebugVerifier(bool enable) { _enableCraneliftDebugVerifier = enable; return this; @@ -163,7 +156,7 @@ namespace Wasmtime /// /// The optimization level to use. /// Returns the current builder. - public EngineBuilder WithOptimizationLevel(OptimizationLevel level) + public HostBuilder WithOptimizationLevel(OptimizationLevel level) { switch (level) { @@ -186,10 +179,10 @@ namespace Wasmtime } /// - /// Builds the instance. + /// Builds the instance. /// - /// Returns the new instance. - public Engine Build() + /// Returns the new instance. + public Host Build() { var config = Interop.wasm_config_new(); @@ -238,7 +231,7 @@ namespace Wasmtime Interop.wasmtime_config_cranelift_opt_level_set(config, _optLevel.Value); } - return new Engine(config); + return new Host(config); } private bool? _enableDebugInfo; diff --git a/crates/misc/dotnet/src/IHost.cs b/crates/misc/dotnet/src/IHost.cs deleted file mode 100644 index 1734c2d9f1..0000000000 --- a/crates/misc/dotnet/src/IHost.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using Wasmtime.Bindings; - -namespace Wasmtime -{ - /// - /// The interface implemented by Wasmtime hosts. - /// - public interface IHost - { - /// - /// The that the host is bound to. - /// - /// A host can only bind to one module instance at a time. - Instance Instance { get; set; } - } -} diff --git a/crates/misc/dotnet/src/ImportAttribute.cs b/crates/misc/dotnet/src/ImportAttribute.cs deleted file mode 100644 index ff06fc4cb3..0000000000 --- a/crates/misc/dotnet/src/ImportAttribute.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Wasmtime -{ - /// - /// Used to mark .NET methods and fields as imports to a WebAssembly module. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] - public class ImportAttribute : Attribute - { - /// - /// Constructs a new . - /// - /// The name of the import. - public ImportAttribute(string name) - { - Name = name; - } - - /// - /// The name of the import. - /// - public string Name { get; set; } - - /// - /// The module name of the import. - /// - /// A null or empty module name implies that the import is not scoped to a module. - public string Module { get; set; } - } -} diff --git a/crates/misc/dotnet/src/Instance.cs b/crates/misc/dotnet/src/Instance.cs index f274101d08..92232d3c0f 100644 --- a/crates/misc/dotnet/src/Instance.cs +++ b/crates/misc/dotnet/src/Instance.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Dynamic; using Wasmtime.Externs; -using Wasmtime.Bindings; namespace Wasmtime { @@ -13,49 +12,6 @@ namespace Wasmtime /// public class Instance : DynamicObject, IDisposable { - internal Instance(Module module, Wasi wasi = null, IHost host = null) - { - Host = host; - Module = module; - - // Save the bindings to root the objects. - // Otherwise the GC may collect the callback delegates from FunctionHandles for example. - _bindings = Binding.GetImportBindings(module, wasi, host) - .Select(b => b.Bind(module.Store, host)) - .ToArray(); - - unsafe - { - Handle = Interop.wasm_instance_new( - Module.Store.Handle, - Module.Handle, - _bindings.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}'."); - } - - Interop.wasm_instance_exports(Handle, out _externs); - - Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs); - - _functions = Externs.Functions.ToDictionary(f => f.Name); - _globals = Externs.Globals.ToDictionary(g => g.Name); - } - - /// - /// The host associated with this instance. - /// - public IHost Host { get; private set; } - /// /// The WebAssembly module associated with the instantiation. /// @@ -75,15 +31,6 @@ namespace Wasmtime Handle.SetHandleAsInvalid(); } - if (!(_bindings is null)) - { - foreach (var binding in _bindings) - { - binding.Dispose(); - } - _bindings = null; - } - if (!(_externs.data is null)) { Interop.wasm_extern_vec_delete(ref _externs); @@ -127,29 +74,35 @@ namespace Wasmtime return true; } - private static unsafe IntPtr ToExtern(SafeHandle handle) + internal Instance(Interop.LinkerHandle linker, Module module) { - switch (handle) + Module = module; + + unsafe { - case Interop.FunctionHandle f: - return Interop.wasm_func_as_extern(f); + Handle = Interop.wasmtime_linker_instantiate(linker, module.Handle, out var trap); - case Interop.GlobalHandle g: - return Interop.wasm_global_as_extern(g); - - case Interop.MemoryHandle m: - return Interop.wasm_memory_as_extern(m); - - case Interop.WasiExportHandle w: - return w.DangerousGetHandle(); - - default: - throw new NotSupportedException("Unexpected handle type."); + if (trap != IntPtr.Zero) + { + throw TrapException.FromOwnedTrap(trap); + } } + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime instance."); + } + + 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); } + internal Interop.InstanceHandle Handle { get; private set; } - private SafeHandle[] _bindings; private Interop.wasm_extern_vec_t _externs; private Dictionary _functions; private Dictionary _globals; diff --git a/crates/misc/dotnet/src/Interop.cs b/crates/misc/dotnet/src/Interop.cs index 81f29887a4..7432c3cb55 100644 --- a/crates/misc/dotnet/src/Interop.cs +++ b/crates/misc/dotnet/src/Interop.cs @@ -64,8 +64,6 @@ namespace Wasmtime { } - public WasmFuncCallback Callback { get; set; } = null; - public override bool IsInvalid => handle == IntPtr.Zero; protected override bool ReleaseHandle() @@ -240,6 +238,36 @@ namespace Wasmtime } } + internal class LinkerHandle : SafeHandle + { + public LinkerHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasmtime_linker_delete(handle); + return true; + } + } + + internal class ExternHandle : SafeHandle + { + public ExternHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasm_extern_delete(handle); + return true; + } + } + [StructLayout(LayoutKind.Sequential)] internal unsafe struct wasm_byte_vec_t { @@ -474,6 +502,8 @@ namespace Wasmtime internal unsafe delegate IntPtr WasmFuncCallback(wasm_val_t* parameters, wasm_val_t* results); + internal unsafe delegate IntPtr WasmtimeFuncCallback(IntPtr caller, wasm_val_t* parameters, wasm_val_t* results); + internal enum wasm_externkind_t : byte { WASM_EXTERN_FUNC, @@ -706,6 +736,11 @@ namespace Wasmtime [DllImport(LibraryName)] public static extern IntPtr wasm_extern_as_memory(IntPtr ext); + // Externs + + [DllImport(LibraryName)] + public static extern void wasm_extern_delete(IntPtr ext); + // Extern type imports [DllImport(LibraryName)] @@ -976,13 +1011,41 @@ namespace Wasmtime [DllImport(LibraryName)] public static extern void wasmtime_config_cranelift_opt_level_set(WasmConfigHandle config, wasmtime_opt_level_t level); + // Utility functions + [DllImport(LibraryName, CharSet=CharSet.Ansi)] [return: MarshalAs(UnmanagedType.I1)] - public static extern bool wasmtime_wat2wasm( - EngineHandle engine, - ref wasm_byte_vec_t wat, - out wasm_byte_vec_t vec, - out wasm_byte_vec_t error_message - ); + public static extern bool wasmtime_wat2wasm(ref wasm_byte_vec_t text, out wasm_byte_vec_t bytes, out wasm_byte_vec_t error); + + // Linking functions + + [DllImport(LibraryName)] + public static extern LinkerHandle wasmtime_linker_new(StoreHandle store, [MarshalAs(UnmanagedType.I1)] bool allowShadowing); + + [DllImport(LibraryName)] + public static extern void wasmtime_linker_delete(IntPtr linker); + + [DllImport(LibraryName)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool wasmtime_linker_define(LinkerHandle linker, ref wasm_byte_vec_t module, ref wasm_byte_vec_t name, IntPtr externType); + + [DllImport(LibraryName)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool wasmtime_linker_define_wasi(LinkerHandle linker, WasiInstanceHandle wasi); + + [DllImport(LibraryName)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool wasmtime_linker_define_instance(LinkerHandle linker, ref wasm_byte_vec_t name, InstanceHandle instance); + + [DllImport(LibraryName)] + public static extern InstanceHandle wasmtime_linker_instantiate(LinkerHandle linker, ModuleHandle module, out IntPtr trap); + + // Caller functions + + [DllImport(LibraryName)] + public static extern FunctionHandle wasmtime_func_new(StoreHandle store, FuncTypeHandle type, WasmtimeFuncCallback callback); + + [DllImport(LibraryName)] + public static extern ExternHandle wasmtime_caller_export_get(IntPtr caller, ref wasm_byte_vec_t name); } } diff --git a/crates/misc/dotnet/src/Memory.cs b/crates/misc/dotnet/src/Memory.cs index 2cfe060f81..952f0dc0bf 100644 --- a/crates/misc/dotnet/src/Memory.cs +++ b/crates/misc/dotnet/src/Memory.cs @@ -7,7 +7,7 @@ namespace Wasmtime /// /// Represents a WebAssembly memory. /// - public class Memory + public class Memory : MemoryBase, IDisposable { /// /// The size, in bytes, of a WebAssembly memory page. @@ -15,11 +15,26 @@ namespace Wasmtime public const int PageSize = 65536; /// - /// Creates a new memory with the given minimum and maximum page counts. + /// The minimum memory size (in WebAssembly page units). /// - /// - /// - public Memory(uint minimum = 1, uint maximum = uint.MaxValue) + public uint Minimum { get; private set; } + + /// + /// The minimum memory size (in WebAssembly page units). + /// + public uint Maximum { get; private set; } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal Memory(Interop.StoreHandle store, uint minimum = 1, uint maximum = uint.MaxValue) { if (minimum == 0) { @@ -33,238 +48,25 @@ namespace Wasmtime Minimum = minimum; Maximum = maximum; - } - /// - /// The minimum memory size (in WebAssembly page units). - /// - public uint Minimum { get; private set; } - - /// - /// The minimum memory size (in WebAssembly page units). - /// - public uint Maximum { get; private set; } - - /// - /// The span of the memory. - /// - /// - /// The span may become invalid if the memory grows. - /// - /// This may happen if the memory is explicitly requested to grow or - /// grows as a result of WebAssembly execution. - /// - /// Therefore, the returned Span should not be stored. - /// - public unsafe Span Span - { - get - { - var data = Interop.wasm_memory_data(_handle.DangerousGetHandle()); - var size = Convert.ToInt32(Interop.wasm_memory_data_size(_handle.DangerousGetHandle()).ToUInt32()); - return new Span(data, size); - } - } - - /// - /// Reads a UTF-8 string from memory. - /// - /// The zero-based address to read from. - /// The length of bytes to read. - /// Returns the string read from memory. - public string ReadString(int address, int length) - { - return Encoding.UTF8.GetString(Span.Slice(address, length)); - } - - /// - /// Writes a UTF-8 string at the given address. - /// - /// The zero-based address to write to. - /// The string to write. - /// Returns the number of bytes written. - public int WriteString(int address, string value) - { - return Encoding.UTF8.GetBytes(value, Span.Slice(address)); - } - - /// - /// Reads a byte from memory. - /// - /// The zero-based address to read from. - /// Returns the byte read from memory. - public byte ReadByte(int address) - { - return Span[address]; - } - - /// - /// Writes a byte to memory. - /// - /// The zero-based address to write to. - /// The byte to write. - public void WriteByte(int address, byte value) - { - Span[address] = value; - } - - /// - /// Reads a short from memory. - /// - /// The zero-based address to read from. - /// Returns the short read from memory. - public short ReadInt16(int address) - { - return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2)); - } - - /// - /// Writes a short to memory. - /// - /// The zero-based address to write to. - /// The short to write. - public void WriteInt16(int address, short value) - { - BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value); - } - - /// - /// Reads an int from memory. - /// - /// The zero-based address to read from. - /// Returns the int read from memory. - public int ReadInt32(int address) - { - return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4)); - } - - /// - /// Writes an int to memory. - /// - /// The zero-based address to write to. - /// The int to write. - public void WriteInt32(int address, int value) - { - BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value); - } - - /// - /// Reads a long from memory. - /// - /// The zero-based address to read from. - /// Returns the long read from memory. - public long ReadInt64(int address) - { - return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8)); - } - - /// - /// Writes a long to memory. - /// - /// The zero-based address to write to. - /// The long to write. - public void WriteInt64(int address, long value) - { - BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value); - } - - /// - /// Reads an IntPtr from memory. - /// - /// The zero-based address to read from. - /// Returns the IntPtr read from memory. - public IntPtr ReadIntPtr(int address) - { - if (IntPtr.Size == 4) - { - return (IntPtr)ReadInt32(address); - } - return (IntPtr)ReadInt64(address); - } - - /// - /// Writes an IntPtr to memory. - /// - /// The zero-based address to write to. - /// The IntPtr to write. - public void WriteIntPtr(int address, IntPtr value) - { - if (IntPtr.Size == 4) - { - WriteInt32(address, value.ToInt32()); - } - else - { - WriteInt64(address, value.ToInt64()); - } - } - - /// - /// Reads a long from memory. - /// - /// The zero-based address to read from. - /// Returns the long read from memory. - public float ReadSingle(int address) - { unsafe { - var i = ReadInt32(address); - return *((float*)&i); + Interop.wasm_limits_t limits = new Interop.wasm_limits_t(); + limits.min = minimum; + limits.max = maximum; + + using var memoryType = Interop.wasm_memorytype_new(&limits); + Handle = Interop.wasm_memory_new(store, memoryType); + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create Wasmtime memory."); + } } } - /// - /// Writes a single to memory. - /// - /// The zero-based address to write to. - /// The single to write. - public void WriteSingle(int address, float value) - { - unsafe - { - WriteInt32(address, *(int*)&value); - } - } + protected override IntPtr MemoryHandle => Handle.DangerousGetHandle(); - /// - /// Reads a double from memory. - /// - /// The zero-based address to read from. - /// Returns the double read from memory. - public double ReadDouble(int address) - { - unsafe - { - var i = ReadInt64(address); - return *((double*)&i); - } - } - - /// - /// Writes a double to memory. - /// - /// The zero-based address to write to. - /// The double to write. - public void WriteDouble(int address, double value) - { - unsafe - { - WriteInt64(address, *(long*)&value); - } - } - - internal Interop.MemoryHandle Handle - { - get - { - return _handle; - } - set - { - _handle = value; - } - } - - private Interop.MemoryHandle _handle; + internal Interop.MemoryHandle Handle { get; private set; } } } diff --git a/crates/misc/dotnet/src/MemoryBase.cs b/crates/misc/dotnet/src/MemoryBase.cs new file mode 100644 index 0000000000..78754ff08a --- /dev/null +++ b/crates/misc/dotnet/src/MemoryBase.cs @@ -0,0 +1,236 @@ +using System; +using System.Text; +using System.Buffers.Binary; + +namespace Wasmtime +{ + public abstract class MemoryBase + { + /// + /// The span of the memory. + /// + /// + /// The span may become invalid if the memory grows. + /// + /// This may happen if the memory is explicitly requested to grow or + /// grows as a result of WebAssembly execution. + /// + /// Therefore, the returned Span should not be stored. + /// + public unsafe Span Span + { + get + { + var data = Interop.wasm_memory_data(MemoryHandle); + var size = Convert.ToInt32(Interop.wasm_memory_data_size(MemoryHandle).ToUInt32()); + return new Span(data, size); + } + } + + /// + /// Reads a UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// The length of bytes to read. + /// Returns the string read from memory. + public string ReadString(int address, int length) + { + return Encoding.UTF8.GetString(Span.Slice(address, length)); + } + + /// + /// Reads a null-terminated UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// Returns the string read from memory. + public string ReadNullTerminatedString(int address) + { + var slice = Span.Slice(address); + var terminator = slice.IndexOf((byte)0); + if (terminator == -1) + { + throw new InvalidOperationException("string is not null terminated"); + } + + return Encoding.UTF8.GetString(slice.Slice(0, terminator)); + } + + /// + /// Writes a UTF-8 string at the given address. + /// + /// The zero-based address to write to. + /// The string to write. + /// Returns the number of bytes written. + public int WriteString(int address, string value) + { + return Encoding.UTF8.GetBytes(value, Span.Slice(address)); + } + + /// + /// Reads a byte from memory. + /// + /// The zero-based address to read from. + /// Returns the byte read from memory. + public byte ReadByte(int address) + { + return Span[address]; + } + + /// + /// Writes a byte to memory. + /// + /// The zero-based address to write to. + /// The byte to write. + public void WriteByte(int address, byte value) + { + Span[address] = value; + } + + /// + /// Reads a short from memory. + /// + /// The zero-based address to read from. + /// Returns the short read from memory. + public short ReadInt16(int address) + { + return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2)); + } + + /// + /// Writes a short to memory. + /// + /// The zero-based address to write to. + /// The short to write. + public void WriteInt16(int address, short value) + { + BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value); + } + + /// + /// Reads an int from memory. + /// + /// The zero-based address to read from. + /// Returns the int read from memory. + public int ReadInt32(int address) + { + return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4)); + } + + /// + /// Writes an int to memory. + /// + /// The zero-based address to write to. + /// The int to write. + public void WriteInt32(int address, int value) + { + BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value); + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public long ReadInt64(int address) + { + return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8)); + } + + /// + /// Writes a long to memory. + /// + /// The zero-based address to write to. + /// The long to write. + public void WriteInt64(int address, long value) + { + BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value); + } + + /// + /// Reads an IntPtr from memory. + /// + /// The zero-based address to read from. + /// Returns the IntPtr read from memory. + public IntPtr ReadIntPtr(int address) + { + if (IntPtr.Size == 4) + { + return (IntPtr)ReadInt32(address); + } + return (IntPtr)ReadInt64(address); + } + + /// + /// Writes an IntPtr to memory. + /// + /// The zero-based address to write to. + /// The IntPtr to write. + public void WriteIntPtr(int address, IntPtr value) + { + if (IntPtr.Size == 4) + { + WriteInt32(address, value.ToInt32()); + } + else + { + WriteInt64(address, value.ToInt64()); + } + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public float ReadSingle(int address) + { + unsafe + { + var i = ReadInt32(address); + return *((float*)&i); + } + } + + /// + /// Writes a single to memory. + /// + /// The zero-based address to write to. + /// The single to write. + public void WriteSingle(int address, float value) + { + unsafe + { + WriteInt32(address, *(int*)&value); + } + } + + /// + /// Reads a double from memory. + /// + /// The zero-based address to read from. + /// Returns the double read from memory. + public double ReadDouble(int address) + { + unsafe + { + var i = ReadInt64(address); + return *((double*)&i); + } + } + + /// + /// Writes a double to memory. + /// + /// The zero-based address to write to. + /// The double to write. + public void WriteDouble(int address, double value) + { + unsafe + { + WriteInt64(address, *(long*)&value); + } + } + + protected abstract IntPtr MemoryHandle { get; } + } +} diff --git a/crates/misc/dotnet/src/Module.cs b/crates/misc/dotnet/src/Module.cs index ac520cebe0..2640f5f4a9 100644 --- a/crates/misc/dotnet/src/Module.cs +++ b/crates/misc/dotnet/src/Module.cs @@ -8,75 +8,6 @@ namespace Wasmtime /// public class Module : IDisposable { - internal Module(Store store, string name, byte[] bytes) - { - if (store.Handle.IsInvalid) - { - throw new ArgumentNullException(nameof(store)); - } - - unsafe - { - fixed (byte *ptr = bytes) - { - Interop.wasm_byte_vec_t vec; - vec.size = (UIntPtr)bytes.Length; - vec.data = ptr; - - Handle = Interop.wasm_module_new(store.Handle, ref vec); - } - - if (Handle.IsInvalid) - { - throw new WasmtimeException($"WebAssembly module '{name}' is not valid."); - } - } - - Store = store; - Name = name; - Imports = new Wasmtime.Imports.Imports(this); - Exports = new Wasmtime.Exports.Exports(this); - } - - /// - /// Instantiates a WebAssembly module for the given host. - /// - /// The host to use for the WebAssembly module's instance. - /// Returns a new . - public Instance Instantiate(IHost host = null) - { - return Instantiate(null, host); - } - - /// - /// Instantiates a WebAssembly module for the given host. - /// - /// The WASI instance to use for WASI imports. - /// The host to use for the WebAssembly module's instance. - /// Returns a new . - public Instance Instantiate(Wasi wasi, IHost host = null) - { - if (!(host?.Instance is null)) - { - throw new InvalidOperationException("The host has already been associated with an instantiated module."); - } - - var instance = new Instance(this, wasi, host); - - if (!(host is null)) - { - host.Instance = instance; - return instance; - } - - return instance; - } - - /// - /// The associated with the module. - /// - public Store Store { get; private set; } - /// /// The name of the module. /// @@ -108,6 +39,30 @@ namespace Wasmtime } } + internal Module(Interop.StoreHandle store, string name, byte[] bytes) + { + unsafe + { + fixed (byte *ptr = bytes) + { + Interop.wasm_byte_vec_t vec; + vec.size = (UIntPtr)bytes.Length; + vec.data = ptr; + + Handle = Interop.wasm_module_new(store, ref vec); + } + + if (Handle.IsInvalid) + { + throw new WasmtimeException($"WebAssembly module '{name}' is not valid."); + } + } + + Name = name; + Imports = new Wasmtime.Imports.Imports(this); + Exports = new Wasmtime.Exports.Exports(this); + } + internal Interop.ModuleHandle Handle { get; private set; } } } diff --git a/crates/misc/dotnet/src/MutableGlobal.cs b/crates/misc/dotnet/src/MutableGlobal.cs index 223c8c5f0c..cad800dc2f 100644 --- a/crates/misc/dotnet/src/MutableGlobal.cs +++ b/crates/misc/dotnet/src/MutableGlobal.cs @@ -5,18 +5,8 @@ namespace Wasmtime /// /// Represents a mutable WebAssembly global value. /// - public class MutableGlobal + public class MutableGlobal : IDisposable { - /// - /// Creates a new with the given initial value. - /// - /// The initial value of the global. - public MutableGlobal(T initialValue) - { - InitialValue = initialValue; - Kind = Interop.ToValueKind(typeof(T)); - } - /// /// The value of the global. /// @@ -32,10 +22,7 @@ namespace Wasmtime 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); } } @@ -46,7 +33,6 @@ namespace Wasmtime 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 @@ -56,10 +42,53 @@ namespace Wasmtime } } - internal ValueKind Kind { get; private set; } + /// + /// Gets the value kind of the global. + /// + /// + public ValueKind Kind { get; private set; } + + /// + public void Dispose() + { + if (!Handle.IsInvalid) + { + Handle.Dispose(); + Handle.SetHandleAsInvalid(); + } + } + + internal MutableGlobal(Interop.StoreHandle store, T initialValue) + { + if (!Interop.TryGetValueKind(typeof(T), out var kind)) + { + throw new WasmtimeException($"Mutable global variables cannot be of type '{typeof(T).ToString()}'."); + } + + Kind = kind; + + var value = Interop.ToValue((object)initialValue, Kind); + + var valueType = Interop.wasm_valtype_new(value.kind); + var valueTypeHandle = valueType.DangerousGetHandle(); + valueType.SetHandleAsInvalid(); + + using var globalType = Interop.wasm_globaltype_new( + valueTypeHandle, + Interop.wasm_mutability_t.WASM_VAR + ); + + unsafe + { + Handle = Interop.wasm_global_new(store, globalType, &value); + + if (Handle.IsInvalid) + { + throw new WasmtimeException("Failed to create mutable Wasmtime global."); + } + } + } internal Interop.GlobalHandle Handle { get; set; } - - internal T InitialValue { get; private set; } } } diff --git a/crates/misc/dotnet/src/Store.cs b/crates/misc/dotnet/src/Store.cs deleted file mode 100644 index 37be522201..0000000000 --- a/crates/misc/dotnet/src/Store.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.IO; - -namespace Wasmtime -{ - /// - /// Represents the Wasmtime store. - /// - public sealed class Store : IDisposable - { - internal Store(Engine engine) - { - Handle = Interop.wasm_store_new(engine.Handle); - - if (Handle.IsInvalid) - { - throw new WasmtimeException("Failed to create Wasmtime store."); - } - } - - /// - /// Create a given the module name and bytes. - /// - /// The name of the module. - /// The bytes of the module. - /// Retuw . - public Module CreateModule(string name, byte[] bytes) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (bytes is null) - { - throw new ArgumentNullException(nameof(bytes)); - } - - return new Module(this, name, bytes); - } - - /// - /// Create a given the module name and path to the WebAssembly file. - /// - /// The name of the module. - /// The path to the WebAssembly file. - /// Returns a new . - public Module CreateModule(string name, string path) - { - return CreateModule(name, File.ReadAllBytes(path)); - } - - /// - /// Create a given the path to the WebAssembly file. - /// - /// The path to the WebAssembly file. - /// Returns a new . - public Module CreateModule(string path) - { - return CreateModule(Path.GetFileNameWithoutExtension(path), path); - } - - /// - public void Dispose() - { - if (!Handle.IsInvalid) - { - Handle.Dispose(); - Handle.SetHandleAsInvalid(); - } - } - - internal Interop.StoreHandle Handle { get; private set; } - } -} diff --git a/crates/misc/dotnet/src/Wasi.cs b/crates/misc/dotnet/src/Wasi.cs deleted file mode 100644 index a0cd1ef367..0000000000 --- a/crates/misc/dotnet/src/Wasi.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Wasmtime.Bindings; -using Wasmtime.Imports; - -namespace Wasmtime -{ - public class Wasi - { - /// - /// Creates a default instance. - /// - /// The store to use for the new WASI instance. - /// The name of the WASI module to create. - public Wasi(Store store, string name) : - this( - (store ?? throw new ArgumentNullException(nameof(store))).Handle, - Interop.wasi_config_new(), - name - ) - { - } - - internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config, string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("Name cannot be null or empty.", nameof(name)); - } - - IntPtr trap; - Handle = Interop.wasi_instance_new(store, name, config, out trap); - config.SetHandleAsInvalid(); - - if (trap != IntPtr.Zero) - { - throw TrapException.FromOwnedTrap(trap); - } - } - - internal WasiBinding Bind(Import import) - { - var export = Interop.wasi_instance_bind_import(Handle, import.Handle); - if (export == IntPtr.Zero) - { - return null; - } - return new WasiBinding(export); - } - - internal Interop.WasiInstanceHandle Handle { get; private set; } - } -} diff --git a/crates/misc/dotnet/src/WasiBuilder.cs b/crates/misc/dotnet/src/WasiConfiguration.cs similarity index 75% rename from crates/misc/dotnet/src/WasiBuilder.cs rename to crates/misc/dotnet/src/WasiConfiguration.cs index fd5b445874..53333f14ea 100644 --- a/crates/misc/dotnet/src/WasiBuilder.cs +++ b/crates/misc/dotnet/src/WasiConfiguration.cs @@ -5,35 +5,16 @@ using System.Linq; namespace Wasmtime { /// - /// Represents a builder of instances. + /// Represents a WASI configuration. /// - public class WasiBuilder + public class WasiConfiguration { /// - /// Constructs a new . - /// - public WasiBuilder(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("Name cannot be null or empty.", nameof(name)); - } - - Name = name; - } - - /// - /// Gets the name of the WASI module being built. - /// - /// The name of the WASI module. - public string Name { get; private set; } - - /// - /// Adds a command line argument to the builder. + /// Adds a command line argument to the configuration. /// /// The command line argument to add. - /// Returns the current builder. - public WasiBuilder WithArg(string arg) + /// Returns the current configuration. + public WasiConfiguration WithArg(string arg) { if (arg is null) { @@ -51,11 +32,11 @@ namespace Wasmtime } /// - /// Adds multiple command line arguments to the builder. + /// Adds multiple command line arguments to the configuration. /// /// The command line arguments to add. - /// Returns the current builder. - public WasiBuilder WithArgs(IEnumerable args) + /// Returns the current configuration. + public WasiConfiguration WithArgs(IEnumerable args) { if (args is null) { @@ -76,21 +57,21 @@ namespace Wasmtime } /// - /// Adds multiple command line arguments to the builder. + /// Adds multiple command line arguments to the configuration. /// /// The command line arguments to add. - /// Returns the current builder. - public WasiBuilder WithArgs(params string[] args) + /// Returns the current configuration. + public WasiConfiguration WithArgs(params string[] args) { return WithArgs((IEnumerable)args); } /// - /// Sets the builder to inherit command line arguments. + /// Sets the configuration to inherit command line arguments. /// /// Any explicitly specified command line arguments will be removed. - /// Returns the current builder. - public WasiBuilder WithInheritedArgs() + /// Returns the current configuration. + public WasiConfiguration WithInheritedArgs() { _inheritArgs = true; _args.Clear(); @@ -99,12 +80,12 @@ namespace Wasmtime } /// - /// Adds an environment variable to the builder. + /// Adds an environment variable to the configuration. /// /// The name of the environment variable. /// The value of the environment variable. - /// Returns the current builder. - public WasiBuilder WithEnvironmentVariable(string name, string value) + /// Returns the current configuration. + public WasiConfiguration WithEnvironmentVariable(string name, string value) { if (name is null) { @@ -126,11 +107,11 @@ namespace Wasmtime } /// - /// Adds multiple environment variables to the builder. + /// Adds multiple environment variables to the configuration. /// /// The name-value tuples of the environment variables to add. - /// Returns the current builder. - public WasiBuilder WithEnvironmentVariables(IEnumerable<(string,string)> vars) + /// Returns the current configuration. + public WasiConfiguration WithEnvironmentVariables(IEnumerable<(string,string)> vars) { if (vars is null) { @@ -148,11 +129,11 @@ namespace Wasmtime } /// - /// Sets the builder to inherit environment variables. + /// Sets the configuration to inherit environment variables. /// /// Any explicitly specified environment variables will be removed. - /// Returns the current builder. - public WasiBuilder WithInheritedEnvironment() + /// Returns the current configuration. + public WasiConfiguration WithInheritedEnvironment() { _inheritEnv = true; _vars.Clear(); @@ -160,11 +141,11 @@ namespace Wasmtime } /// - /// Sets the builder to use the given file path as stdin. + /// Sets the configuration to use the given file path as stdin. /// /// The file to use as stdin. - /// Returns the current builder. - public WasiBuilder WithStandardInput(string path) + /// Returns the current configuration. + public WasiConfiguration WithStandardInput(string path) { if (string.IsNullOrEmpty(path)) { @@ -177,11 +158,11 @@ namespace Wasmtime } /// - /// Sets the builder to inherit stdin. + /// Sets the configuration to inherit stdin. /// /// Any explicitly specified stdin file will be removed. - /// Returns the current builder. - public WasiBuilder WithInheritedStandardInput() + /// Returns the current configuration. + public WasiConfiguration WithInheritedStandardInput() { _inheritStandardInput = true; _standardInputPath = null; @@ -189,11 +170,11 @@ namespace Wasmtime } /// - /// Sets the builder to use the given file path as stdout. + /// Sets the configuration to use the given file path as stdout. /// /// The file to use as stdout. - /// Returns the current builder. - public WasiBuilder WithStandardOutput(string path) + /// Returns the current configuration. + public WasiConfiguration WithStandardOutput(string path) { if (string.IsNullOrEmpty(path)) { @@ -206,11 +187,11 @@ namespace Wasmtime } /// - /// Sets the builder to inherit stdout. + /// Sets the configuration to inherit stdout. /// /// Any explicitly specified stdout file will be removed. - /// Returns the current builder. - public WasiBuilder WithInheritedStandardOutput() + /// Returns the current configuration. + public WasiConfiguration WithInheritedStandardOutput() { _inheritStandardOutput = true; _standardOutputPath = null; @@ -218,11 +199,11 @@ namespace Wasmtime } /// - /// Sets the builder to use the given file path as stderr. + /// Sets the configuration to use the given file path as stderr. /// /// The file to use as stderr. - /// Returns the current builder. - public WasiBuilder WithStandardError(string path) + /// Returns the current configuration. + public WasiConfiguration WithStandardError(string path) { if (string.IsNullOrEmpty(path)) { @@ -235,11 +216,11 @@ namespace Wasmtime } /// - /// Sets the builder to inherit stderr. + /// Sets the configuration to inherit stderr. /// /// Any explicitly specified stderr file will be removed. - /// Returns the current builder. - public WasiBuilder WithInheritedStandardError() + /// Returns the current configuration. + public WasiConfiguration WithInheritedStandardError() { _inheritStandardError = true; _standardErrorPath = null; @@ -247,12 +228,12 @@ namespace Wasmtime } /// - /// Adds a preopen directory to the builder. + /// Adds a preopen directory to the configuration. /// /// The path to the directory to add. /// The path the guest will use to open the directory. - /// Returns the current builder. - public WasiBuilder WithPreopenedDirectory(string path, string guestPath) + /// Returns the current configuration. + public WasiConfiguration WithPreopenedDirectory(string path, string guestPath) { if (string.IsNullOrEmpty(path)) { @@ -267,12 +248,7 @@ namespace Wasmtime return this; } - /// - /// Builds the instance. - /// - /// The to use. - /// Returns the new instance. - public Wasi Build(Store store) + internal Interop.WasiInstanceHandle CreateWasi(Interop.StoreHandle store, string name) { var config = Interop.wasi_config_new(); @@ -282,8 +258,22 @@ namespace Wasmtime SetStandardOut(config); SetStandardError(config); SetPreopenDirectories(config); - - return new Wasi(store.Handle, config, Name); + + IntPtr trap; + var wasi = Interop.wasi_instance_new(store, name, config, out trap); + config.SetHandleAsInvalid(); + + if (trap != IntPtr.Zero) + { + throw TrapException.FromOwnedTrap(trap); + } + + if (wasi.IsInvalid) + { + throw new WasmtimeException($"Failed to create instance for WASI module '{name}'."); + } + + return wasi; } private unsafe void SetConfigArgs(Interop.WasiConfigHandle config) diff --git a/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs index 2eeb9f7482..7c2b840b7a 100644 --- a/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs +++ b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs @@ -8,14 +8,12 @@ namespace Wasmtime.Tests { public ModuleFixture() { - Engine = new EngineBuilder() + Host = new HostBuilder() .WithMultiValue(true) .WithReferenceTypes(true) .Build(); - Store = Engine.CreateStore(); - var wat = Path.Combine("Modules", ModuleFileName); - var wasm = Engine.WatToWasm(File.ReadAllText(wat)); - Module = Store.CreateModule(wat, wasm); + + Module = Host.LoadModuleText(Path.Combine("Modules", ModuleFileName)); } public void Dispose() @@ -26,21 +24,14 @@ namespace Wasmtime.Tests Module = null; } - if (!(Store is null)) + if (!(Host is null)) { - Store.Dispose(); - Store = null; - } - - if (!(Engine is null)) - { - Engine.Dispose(); - Engine = null; + Host.Dispose(); + Host = null; } } - public Engine Engine { get; set; } - public Store Store { get; set; } + public Host Host { get; set; } public Module Module { get; set; } protected abstract string ModuleFileName { get; } diff --git a/crates/misc/dotnet/tests/FunctionThunkingTests.cs b/crates/misc/dotnet/tests/FunctionThunkingTests.cs index 343b0889d8..cb5f1a5b91 100644 --- a/crates/misc/dotnet/tests/FunctionThunkingTests.cs +++ b/crates/misc/dotnet/tests/FunctionThunkingTests.cs @@ -14,20 +14,16 @@ namespace Wasmtime.Tests { const string THROW_MESSAGE = "Test error message for wasmtime dotnet unit tests."; - class MyHost : IHost - { - public Instance Instance { get; set; } - - [Import("add", Module = "env")] - public int Add(int x, int y) => x + y; - - [Import("do_throw", Module = "env")] - public void Throw() => throw new Exception(THROW_MESSAGE); - } - public FunctionThunkingTests(FunctionThunkingFixture fixture) { Fixture = fixture; + + Fixture.Host.DefineFunction("env", "add", (int x, int y) => x + y); + Fixture.Host.DefineFunction("env", "swap", (int x, int y) => (y, x)); + Fixture.Host.DefineFunction("env", "do_throw", () => throw new Exception(THROW_MESSAGE)); + Fixture.Host.DefineFunction("env", "check_string", (Caller caller, int address, int length) => { + caller.GetMemory("mem").ReadString(address, length).Should().Be("Hello World"); + }); } private FunctionThunkingFixture Fixture { get; } @@ -35,30 +31,37 @@ namespace Wasmtime.Tests [Fact] public void ItBindsImportMethodsAndCallsThemCorrectly() { - var host = new MyHost(); - using var instance = Fixture.Module.Instantiate(host); + using dynamic instance = Fixture.Host.Instantiate(Fixture.Module); - var add_func = instance.Externs.Functions.Where(f => f.Name == "add_wrapper").Single(); - int invoke_add(int x, int y) => (int)add_func.Invoke(new object[] { x, y }); + int x = instance.add(40, 2); + x.Should().Be(42); + x = instance.add(22, 5); + x.Should().Be(27); - invoke_add(40, 2).Should().Be(42); - invoke_add(22, 5).Should().Be(27); + object[] results = instance.swap(10, 100); + results.Should().Equal(new object[] { 100, 10 }); - //Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted. + instance.check_string(); + + // Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted. GC.Collect(); GC.WaitForPendingFinalizers(); - invoke_add(1970, 50).Should().Be(2020); + x = instance.add(1970, 50); + x.Should().Be(2020); + + results = instance.swap(2020, 1970); + results.Should().Equal(new object[] { 1970, 2020 }); + + instance.check_string(); } [Fact] public void ItPropagatesExceptionsToCallersViaTraps() { - var host = new MyHost(); - using var instance = Fixture.Module.Instantiate(host); + using dynamic instance = Fixture.Host.Instantiate(Fixture.Module); - var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single(); - Action action = () => throw_func.Invoke(); + Action action = () => instance.do_throw(); action .Should() diff --git a/crates/misc/dotnet/tests/GlobalExportsTests.cs b/crates/misc/dotnet/tests/GlobalExportsTests.cs index 8dc67f254c..a7b35557fc 100644 --- a/crates/misc/dotnet/tests/GlobalExportsTests.cs +++ b/crates/misc/dotnet/tests/GlobalExportsTests.cs @@ -15,11 +15,6 @@ namespace Wasmtime.Tests public class GlobalExportsTests : IClassFixture { - public class Host : IHost - { - public Instance Instance { get; set; } - } - public GlobalExportsTests(GlobalExportsFixture fixture) { Fixture = fixture; @@ -46,7 +41,7 @@ namespace Wasmtime.Tests [Fact] public void ItCreatesExternsForTheGlobals() { - using var instance = Fixture.Module.Instantiate(new Host()); + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic dyn = instance; var globals = instance.Externs.Globals; diff --git a/crates/misc/dotnet/tests/GlobalImportBindingTests.cs b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs index c87a9d3b51..d3c4cf71c8 100644 --- a/crates/misc/dotnet/tests/GlobalImportBindingTests.cs +++ b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs @@ -11,106 +11,11 @@ namespace Wasmtime.Tests public class GlobalImportBindingTests : IClassFixture { - class NoImportsHost : IHost - { - public Instance Instance { get; set; } - } - - class GlobalIsStaticHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public static int x = 0; - } - - class GlobalIsNotReadOnlyHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public int x = 0; - } - - class NotAGlobalHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly int x = 0; - } - - class NotAValidGlobalTypeHost : IHost - { - public struct NotAValue - { - } - - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly MutableGlobal x = new MutableGlobal(new NotAValue()); - } - - class TypeMismatchHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly MutableGlobal x = new MutableGlobal(0); - } - - class NotMutHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly Global Int32Mut = new Global(0); - } - - class MutHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly MutableGlobal Int32Mut = new MutableGlobal(0); - - [Import("global_i32")] - public readonly MutableGlobal Int32 = new MutableGlobal(0); - } - - class ValidHost : IHost - { - public Instance Instance { get; set; } - - [Import("global_i32_mut")] - public readonly MutableGlobal Int32Mut = new MutableGlobal(0); - - [Import("global_i32")] - public readonly Global Int32 = new Global(1); - - [Import("global_i64_mut")] - public readonly MutableGlobal Int64Mut = new MutableGlobal(2); - - [Import("global_i64")] - public readonly Global Int64 = new Global(3); - - [Import("global_f32_mut")] - public readonly MutableGlobal Float32Mut = new MutableGlobal(4); - - [Import("global_f32")] - public readonly Global Float32 = new Global(5); - - [Import("global_f64_mut")] - public readonly MutableGlobal Float64Mut = new MutableGlobal(6); - - [Import("global_f64")] - public readonly Global Float64 = new Global(7); - } - public GlobalImportBindingTests(GlobalImportBindingFixture fixture) { Fixture = fixture; + + Fixture.Host.ClearDefinitions(); } private GlobalImportBindingFixture Fixture { get; set; } @@ -118,140 +23,119 @@ namespace Wasmtime.Tests [Fact] public void ItFailsToInstantiateWithMissingImport() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new NoImportsHost()); }; + Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); }; action .Should() .Throw() - .WithMessage("Failed to bind global import 'global_i32_mut': the host does not contain a global field with a matching 'Import' attribute."); + .WithMessage("unknown import: `::global_i32_mut` has not been defined"); } [Fact] - public void ItFailsToInstantiateWithStaticField() + public void ItFailsToDefineAGlobalWithInvalidType() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost()); }; + Action action = () => { Fixture.Host.DefineGlobal("", "global_i32_mut", "invalid"); }; action .Should() .Throw() - .WithMessage("Unable to bind 'GlobalIsStaticHost.x' to WebAssembly import 'global_i32_mut': field cannot be static."); - } - - [Fact] - public void ItFailsToInstantiateWithNonReadOnlyField() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'GlobalIsNotReadOnlyHost.x' to WebAssembly import 'global_i32_mut': field must be readonly."); - } - - [Fact] - public void ItFailsToInstantiateWithInvalidType() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAGlobalHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'NotAGlobalHost.x' to WebAssembly import 'global_i32_mut': field is expected to be of type 'Global'."); - } - - [Fact] - public void ItFailsToInstantiateWithInvalidGlobalType() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost()); }; - - action - .Should() - .Throw() - .WithMessage("Type 'Wasmtime.Tests.GlobalImportBindingTests+NotAValidGlobalTypeHost+NotAValue' is not a supported WebAssembly value type."); + .WithMessage("Global variables cannot be of type 'System.String'."); } [Fact] public void ItFailsToInstantiateWithGlobalTypeMismatch() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new TypeMismatchHost()); }; + Fixture.Host.DefineGlobal("", "global_i32_mut", 0L); + Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); }; action .Should() .Throw() - .WithMessage("Unable to bind 'TypeMismatchHost.x' to WebAssembly import 'global_i32_mut': global type argument is expected to be of type 'int'."); + .WithMessage("incompatible import type for `::global_i32_mut` specified*"); } [Fact] public void ItFailsToInstantiateWhenGlobalIsNotMut() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new NotMutHost()); }; + Fixture.Host.DefineGlobal("", "global_i32_mut", 1); + Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); }; action .Should() .Throw() - .WithMessage("Unable to bind 'NotMutHost.Int32Mut' to WebAssembly import 'global_i32_mut': the import is mutable (use the 'MutableGlobal' type)."); + .WithMessage("incompatible import type for `::global_i32_mut` specified*"); } [Fact] public void ItFailsToInstantiateWhenGlobalIsMut() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new MutHost()); }; + Fixture.Host.DefineMutableGlobal("", "global_i32_mut", 0); + Fixture.Host.DefineMutableGlobal("", "global_i32", 0); + Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); }; action .Should() .Throw() - .WithMessage("Unable to bind 'MutHost.Int32' to WebAssembly import 'global_i32': the import is constant (use the 'Global' type)."); + .WithMessage("incompatible import type for `::global_i32` specified*"); } [Fact] public void ItBindsTheGlobalsCorrectly() { - var host = new ValidHost(); - using dynamic instance = Fixture.Module.Instantiate(host); + var global_i32_mut = Fixture.Host.DefineMutableGlobal("", "global_i32_mut", 0); + var global_i32 = Fixture.Host.DefineGlobal("", "global_i32", 1); + var global_i64_mut = Fixture.Host.DefineMutableGlobal("", "global_i64_mut", 2L); + var global_i64 = Fixture.Host.DefineGlobal("", "global_i64", 3L); + var global_f32_mut = Fixture.Host.DefineMutableGlobal("", "global_f32_mut", 4f); + var global_f32 = Fixture.Host.DefineGlobal("", "global_f32", 5f); + var global_f64_mut = Fixture.Host.DefineMutableGlobal("", "global_f64_mut", 6.0); + var global_f64 = Fixture.Host.DefineGlobal("", "global_f64", 7.0); - host.Int32Mut.Value.Should().Be(0); + using dynamic instance = Fixture.Host.Instantiate(Fixture.Module); + + global_i32_mut.Value.Should().Be(0); ((int)instance.get_global_i32_mut()).Should().Be(0); - host.Int32.Value.Should().Be(1); + global_i32.Value.Should().Be(1); ((int)instance.get_global_i32()).Should().Be(1); - host.Int64Mut.Value.Should().Be(2); + global_i64_mut.Value.Should().Be(2); ((long)instance.get_global_i64_mut()).Should().Be(2); - host.Int64.Value.Should().Be(3); + global_i64.Value.Should().Be(3); ((long)instance.get_global_i64()).Should().Be(3); - host.Float32Mut.Value.Should().Be(4); + global_f32_mut.Value.Should().Be(4); ((float)instance.get_global_f32_mut()).Should().Be(4); - host.Float32.Value.Should().Be(5); + global_f32.Value.Should().Be(5); ((float)instance.get_global_f32()).Should().Be(5); - host.Float64Mut.Value.Should().Be(6); + global_f64_mut.Value.Should().Be(6); ((double)instance.get_global_f64_mut()).Should().Be(6); - host.Float64.Value.Should().Be(7); + global_f64.Value.Should().Be(7); ((double)instance.get_global_f64()).Should().Be(7); - host.Int32Mut.Value = 10; - host.Int32Mut.Value.Should().Be(10); + global_i32_mut.Value = 10; + global_i32_mut.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); + global_i32_mut.Value.Should().Be(11); ((int)instance.get_global_i32_mut()).Should().Be(11); - host.Int64Mut.Value = 12; - host.Int64Mut.Value.Should().Be(12); + global_i64_mut.Value = 12; + global_i64_mut.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); + global_i64_mut.Value.Should().Be(13); ((long)instance.get_global_i64_mut()).Should().Be(13); - host.Float32Mut.Value = 14; - host.Float32Mut.Value.Should().Be(14); + global_f32_mut.Value = 14; + global_f32_mut.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); + global_f32_mut.Value.Should().Be(15); ((float)instance.get_global_f32_mut()).Should().Be(15); - host.Float64Mut.Value = 16; - host.Float64Mut.Value.Should().Be(16); + global_f64_mut.Value = 16; + global_f64_mut.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); + global_f64_mut.Value.Should().Be(17); ((double)instance.get_global_f64_mut()).Should().Be(17); } } diff --git a/crates/misc/dotnet/tests/MemoryExportsTests.cs b/crates/misc/dotnet/tests/MemoryExportsTests.cs index 30035e21e4..bd1e2d3823 100644 --- a/crates/misc/dotnet/tests/MemoryExportsTests.cs +++ b/crates/misc/dotnet/tests/MemoryExportsTests.cs @@ -13,11 +13,6 @@ namespace Wasmtime.Tests public class MemoryExportsTests : IClassFixture { - public class Host : IHost - { - public Instance Instance { get; set; } - } - public MemoryExportsTests(MemoryExportsFixture fixture) { Fixture = fixture; @@ -44,8 +39,7 @@ namespace Wasmtime.Tests [Fact] public void ItCreatesExternsForTheMemories() { - var host = new Host(); - using var instance = Fixture.Module.Instantiate(host); + using var instance = Fixture.Host.Instantiate(Fixture.Module); instance.Externs.Memories.Count.Should().Be(1); diff --git a/crates/misc/dotnet/tests/MemoryImportBindingTests.cs b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs index 6b59064c95..4cd87c4e90 100644 --- a/crates/misc/dotnet/tests/MemoryImportBindingTests.cs +++ b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs @@ -11,62 +11,11 @@ namespace Wasmtime.Tests public class MemoryImportBindingTests : IClassFixture { - class MissingImportsHost : IHost - { - public Instance Instance { get; set; } - } - - class MemoryIsStaticHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public static Memory x = new Memory(minimum: 1); - } - - class MemoryIsNotReadOnlyHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public Memory x = new Memory(minimum: 1); - } - - class NotAMemoryHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public readonly int x = 0; - } - - class InvalidMinimumHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public readonly Memory Mem = new Memory(minimum: 2); - } - - class InvalidMaximumHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public readonly Memory Mem = new Memory(maximum: 2); - } - - class ValidHost : IHost - { - public Instance Instance { get; set; } - - [Import("mem")] - public readonly Memory Mem = new Memory(minimum: 1); - } - public MemoryImportBindingTests(MemoryImportBindingFixture fixture) { Fixture = fixture; + + Fixture.Host.ClearDefinitions(); } private MemoryImportBindingFixture Fixture { get; set; } @@ -74,112 +23,58 @@ namespace Wasmtime.Tests [Fact] public void ItFailsToInstantiateWithMissingImport() { - Action action = () => { using var instance = Fixture.Module.Instantiate(new MissingImportsHost()); }; + Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); }; action .Should() .Throw() - .WithMessage("Failed to bind memory import 'mem': the host does not contain a memory field with a matching 'Import' attribute."); - } - - [Fact] - public void ItFailsToInstantiateWithStaticField() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'MemoryIsStaticHost.x' to WebAssembly import 'mem': field cannot be static."); - } - - [Fact] - public void ItFailsToInstantiateWithNonReadOnlyField() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'MemoryIsNotReadOnlyHost.x' to WebAssembly import 'mem': field must be readonly."); - } - - [Fact] - public void ItFailsToInstantiateWithInvalidType() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAMemoryHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'NotAMemoryHost.x' to WebAssembly import 'mem': field is expected to be of type 'Memory'."); - } - - [Fact] - public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMinimumHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'InvalidMinimumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected minimum of 1 page(s)."); - } - - [Fact] - public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum() - { - Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMaximumHost()); }; - - action - .Should() - .Throw() - .WithMessage("Unable to bind 'InvalidMaximumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected maximum of 4294967295 page(s)."); + .WithMessage("unknown import: `::mem` has not been defined"); } [Fact] public void ItBindsTheGlobalsCorrectly() { - var host = new ValidHost(); - using dynamic instance = Fixture.Module.Instantiate(host); + var mem = Fixture.Host.DefineMemory("", "mem"); - 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!"); + using dynamic instance = Fixture.Host.Instantiate(Fixture.Module); - host.Mem.ReadByte(20).Should().Be(1); - host.Mem.WriteByte(20, 11); - host.Mem.ReadByte(20).Should().Be(11); + mem.ReadString(0, 11).Should().Be("Hello World"); + int written = mem.WriteString(0, "WebAssembly Rocks!"); + mem.ReadString(0, written).Should().Be("WebAssembly Rocks!"); + + mem.ReadByte(20).Should().Be(1); + mem.WriteByte(20, 11); + 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); + mem.ReadInt16(21).Should().Be(2); + mem.WriteInt16(21, 12); + 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); + mem.ReadInt32(23).Should().Be(3); + mem.WriteInt32(23, 13); + 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); + mem.ReadInt64(27).Should().Be(4); + mem.WriteInt64(27, 14); + 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); + mem.ReadSingle(35).Should().Be(5); + mem.WriteSingle(35, 15); + 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); + mem.ReadDouble(39).Should().Be(6); + mem.WriteDouble(39, 16); + 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); + mem.ReadIntPtr(48).Should().Be((IntPtr)7); + mem.WriteIntPtr(48, (IntPtr)17); + mem.ReadIntPtr(48).Should().Be((IntPtr)17); ((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17); } } diff --git a/crates/misc/dotnet/tests/Modules/FunctionThunking.wat b/crates/misc/dotnet/tests/Modules/FunctionThunking.wat index fb487afaa8..d46f2dbbb7 100644 --- a/crates/misc/dotnet/tests/Modules/FunctionThunking.wat +++ b/crates/misc/dotnet/tests/Modules/FunctionThunking.wat @@ -1,20 +1,24 @@ (module - (type $FUNCSIG$iii (func (param i32 i32) (result i32))) - (type $FUNCSIG$v (func)) - (import "env" "add" (func $add (param i32 i32) (result i32))) - (import "env" "do_throw" (func $do_throw)) - (table 0 anyfunc) - (memory $0 1) - (export "memory" (memory $0)) - (export "add_wrapper" (func $add_wrapper)) - (export "do_throw_wrapper" (func $do_throw_wrapper)) - (func $add_wrapper (; 2 ;) (param $0 i32) (param $1 i32) (result i32) - (call $add - (get_local $0) - (get_local $1) - ) + (import "env" "add" (func $env.add (param i32 i32) (result i32))) + (import "env" "swap" (func $env.swap (param i32 i32) (result i32 i32))) + (import "env" "do_throw" (func $env.do_throw)) + (import "env" "check_string" (func $env.check_string (param i32 i32))) + (memory (export "mem") 1) + (export "add" (func $add)) + (export "swap" (func $swap)) + (export "do_throw" (func $do_throw)) + (export "check_string" (func $check_string)) + (func $add (param i32 i32) (result i32) + (call $env.add (local.get 0) (local.get 1)) ) - (func $do_throw_wrapper (; 3 ;) - (call $do_throw) + (func $swap (param i32 i32) (result i32 i32) + (call $env.swap (local.get 0) (local.get 1)) ) + (func $do_throw + (call $env.do_throw) + ) + (func $check_string + (call $env.check_string (i32.const 0) (i32.const 11)) + ) + (data (i32.const 0) "Hello World") ) diff --git a/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs b/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs index 6fae2f6aa8..de2f30efc6 100644 --- a/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs +++ b/crates/misc/dotnet/tests/WasiSnapshot0Tests.cs @@ -17,6 +17,8 @@ namespace Wasmtime.Tests public WasiSnapshot0Tests(WasiSnapshot0Fixture fixture) { Fixture = fixture; + + Fixture.Host.ClearDefinitions(); } private WasiSnapshot0Fixture Fixture { get; set; } @@ -24,7 +26,8 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoEnvironmentByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable")); + Fixture.Host.DefineWasi("wasi_unstable"); + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -43,11 +46,12 @@ namespace Wasmtime.Tests {"VERY", "COOL"}, }; - var wasi = new WasiBuilder("wasi_unstable") - .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -67,11 +71,12 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsEnvironment() { - var wasi = new WasiBuilder("wasi_unstable") - .WithInheritedEnvironment() - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithInheritedEnvironment(); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -83,7 +88,9 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoArgumentsByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable")); + Fixture.Host.DefineWasi("wasi_unstable"); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -103,11 +110,12 @@ namespace Wasmtime.Tests "COOL" }; - var wasi = new WasiBuilder("wasi_unstable") - .WithArgs(args) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithArgs(args); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -127,11 +135,12 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsArguments() { - var wasi = new WasiBuilder("wasi_unstable") - .WithInheritedArgs() - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithInheritedArgs(); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -148,11 +157,12 @@ namespace Wasmtime.Tests using var file = new TempFile(); File.WriteAllText(file.Path, MESSAGE); - var wasi = new WasiBuilder("wasi_unstable") - .WithStandardInput(file.Path) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithStandardInput(file.Path); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -173,19 +183,19 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var builder = new WasiBuilder("wasi_unstable"); + var config = new WasiConfiguration(); if (fd == 1) { - builder.WithStandardOutput(file.Path); + config.WithStandardOutput(file.Path); } else if (fd == 2) { - builder.WithStandardError(file.Path); + config.WithStandardError(file.Path); } - var wasi = builder.Build(Fixture.Module.Store); + Fixture.Host.DefineWasi("wasi_unstable", config); - using var instance = Fixture.Module.Instantiate(wasi); + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -206,11 +216,12 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var wasi = new WasiBuilder("wasi_unstable") - .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo"); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_unstable", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; diff --git a/crates/misc/dotnet/tests/WasiTests.cs b/crates/misc/dotnet/tests/WasiTests.cs index e5f5c0117e..f10e536c0a 100644 --- a/crates/misc/dotnet/tests/WasiTests.cs +++ b/crates/misc/dotnet/tests/WasiTests.cs @@ -24,7 +24,9 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoEnvironmentByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1")); + Fixture.Host.DefineWasi("wasi_snapshot_preview1"); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -43,11 +45,12 @@ namespace Wasmtime.Tests {"VERY", "COOL"}, }; - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -67,11 +70,12 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsEnvironment() { - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithInheritedEnvironment() - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithInheritedEnvironment(); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -83,7 +87,9 @@ namespace Wasmtime.Tests [Fact] public void ItHasNoArgumentsByDefault() { - using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1")); + Fixture.Host.DefineWasi("wasi_snapshot_preview1"); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -103,11 +109,12 @@ namespace Wasmtime.Tests "COOL" }; - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithArgs(args) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithArgs(args); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -127,11 +134,12 @@ namespace Wasmtime.Tests [Fact] public void ItInheritsArguments() { - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithInheritedArgs() - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithInheritedArgs(); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -148,11 +156,12 @@ namespace Wasmtime.Tests using var file = new TempFile(); File.WriteAllText(file.Path, MESSAGE); - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithStandardInput(file.Path) - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithStandardInput(file.Path); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -173,19 +182,19 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var builder = new WasiBuilder("wasi_snapshot_preview1"); + var config = new WasiConfiguration(); if (fd == 1) { - builder.WithStandardOutput(file.Path); + config.WithStandardOutput(file.Path); } else if (fd == 2) { - builder.WithStandardError(file.Path); + config.WithStandardError(file.Path); } - var wasi = builder.Build(Fixture.Module.Store); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); - using var instance = Fixture.Module.Instantiate(wasi); + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; @@ -206,11 +215,12 @@ namespace Wasmtime.Tests using var file = new TempFile(); - var wasi = new WasiBuilder("wasi_snapshot_preview1") - .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") - .Build(Fixture.Module.Store); + var config = new WasiConfiguration() + .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo"); - using var instance = Fixture.Module.Instantiate(wasi); + Fixture.Host.DefineWasi("wasi_snapshot_preview1", config); + + using var instance = Fixture.Host.Instantiate(Fixture.Module); dynamic inst = instance; var memory = instance.Externs.Memories[0]; From f40693d9e86f2802c82578ee4770a14fda499b86 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 25 Mar 2020 18:55:54 -0700 Subject: [PATCH 3/9] Fix calls to wasmtime_wat2wasm to remove engine argument. --- examples/gcd.c | 2 +- examples/hello.c | 2 +- examples/linking.c | 2 +- examples/memory.c | 2 +- examples/multi.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/gcd.c b/examples/gcd.c index 3338f9f182..b61fd75199 100644 --- a/examples/gcd.c +++ b/examples/gcd.c @@ -52,7 +52,7 @@ int main() { // Parse the wat into the binary wasm format wasm_byte_vec_t wasm, error; - if (wasmtime_wat2wasm(engine, &wat, &wasm, &error) == 0) { + if (wasmtime_wat2wasm(&wat, &wasm, &error) == 0) { fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data); goto free_store; } diff --git a/examples/hello.c b/examples/hello.c index b8ba6894c0..f596bee6a8 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -59,7 +59,7 @@ int main() { // Parse the wat into the binary wasm format wasm_byte_vec_t wasm, error; - if (wasmtime_wat2wasm(engine, &wat, &wasm, &error) == 0) { + if (wasmtime_wat2wasm(&wat, &wasm, &error) == 0) { fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data); goto free_store; } diff --git a/examples/linking.c b/examples/linking.c index 27cf612da3..95c28fd641 100644 --- a/examples/linking.c +++ b/examples/linking.c @@ -147,7 +147,7 @@ static void read_wat_file( // Parse the wat into the binary wasm format wasm_byte_vec_t error; - if (wasmtime_wat2wasm(engine, &wat, bytes, &error) == 0) { + if (wasmtime_wat2wasm(&wat, bytes, &error) == 0) { fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data); exit(1); } diff --git a/examples/memory.c b/examples/memory.c index 11361e0b69..7174d17aba 100644 --- a/examples/memory.c +++ b/examples/memory.c @@ -141,7 +141,7 @@ int main(int argc, const char* argv[]) { // Parse the wat into the binary wasm format wasm_byte_vec_t binary, error; - if (wasmtime_wat2wasm(engine, &wat, &binary, &error) == 0) { + if (wasmtime_wat2wasm(&wat, &binary, &error) == 0) { fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data); return 1; } diff --git a/examples/multi.c b/examples/multi.c index d3d2605d52..735964d561 100644 --- a/examples/multi.c +++ b/examples/multi.c @@ -84,7 +84,7 @@ int main(int argc, const char* argv[]) { // Parse the wat into the binary wasm format wasm_byte_vec_t binary, error; - if (wasmtime_wat2wasm(engine, &wat, &binary, &error) == 0) { + if (wasmtime_wat2wasm(&wat, &binary, &error) == 0) { fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data); return 1; } From 7e3710ad8648c3c31af2001f0a12f2507d89c746 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 25 Mar 2020 19:04:50 -0700 Subject: [PATCH 4/9] Fix calls to `wasi_instance_new` to pass in the WASI module name. --- examples/linking.c | 2 +- examples/wasi/main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/linking.c b/examples/linking.c index 95c28fd641..f3dc199429 100644 --- a/examples/linking.c +++ b/examples/linking.c @@ -58,7 +58,7 @@ int main() { wasi_config_inherit_stdout(wasi_config); wasi_config_inherit_stderr(wasi_config); wasm_trap_t *trap = NULL; - wasi_instance_t *wasi = wasi_instance_new(store, wasi_config, &trap); + wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap); if (wasi == NULL) { print_trap(trap); exit(1); diff --git a/examples/wasi/main.c b/examples/wasi/main.c index 513dc362ed..6e027025a3 100644 --- a/examples/wasi/main.c +++ b/examples/wasi/main.c @@ -67,7 +67,7 @@ int main() { wasi_config_inherit_stdout(wasi_config); wasi_config_inherit_stderr(wasi_config); wasm_trap_t *trap = NULL; - wasi_instance_t *wasi = wasi_instance_new(store, wasi_config, &trap); + wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap); if (wasi == NULL) { print_trap(trap); exit(1); From 64be4a30ae1297f88d33e5844aa14bf0f56c926d Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 25 Mar 2020 22:50:14 -0700 Subject: [PATCH 5/9] Fix wasmtime_linker_new call to pass in allow_shadowing argument. --- examples/linking.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/linking.c b/examples/linking.c index f3dc199429..2b31d1a368 100644 --- a/examples/linking.c +++ b/examples/linking.c @@ -66,7 +66,7 @@ int main() { // Create our linker which will be linking our modules together, and then add // our WASI instance to it. - wasmtime_linker_t *linker = wasmtime_linker_new(store); + wasmtime_linker_t *linker = wasmtime_linker_new(store, false); bool ok = wasmtime_linker_define_wasi(linker, wasi); assert(ok); From 183aa9af4cb00aa05ddaaac2040f46f4b755aff0 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 26 Mar 2020 01:10:45 -0700 Subject: [PATCH 6/9] Fix typo in method call. --- crates/misc/dotnet/src/Function.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/misc/dotnet/src/Function.cs b/crates/misc/dotnet/src/Function.cs index b11ea17887..2a8e84c5d4 100644 --- a/crates/misc/dotnet/src/Function.cs +++ b/crates/misc/dotnet/src/Function.cs @@ -267,7 +267,7 @@ namespace Wasmtime { for (int i = 0; i < tuple.Length; ++i) { - SetResults(tuple[i], &results[i]); + SetResult(tuple[i], &results[i]); } } } From 2d43d5a8fac27f58d5ad871a38a53d7680b7d9f4 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 26 Mar 2020 01:14:51 -0700 Subject: [PATCH 7/9] Fix typo in exception message. --- crates/misc/dotnet/src/Memory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/misc/dotnet/src/Memory.cs b/crates/misc/dotnet/src/Memory.cs index 952f0dc0bf..3138cf9e82 100644 --- a/crates/misc/dotnet/src/Memory.cs +++ b/crates/misc/dotnet/src/Memory.cs @@ -38,7 +38,7 @@ namespace Wasmtime { if (minimum == 0) { - throw new ArgumentException("The minimum cannot be zero..", nameof(minimum)); + throw new ArgumentException("The minimum cannot be zero.", nameof(minimum)); } if (maximum < minimum) From 382f68c620de9e27897ec7f5c36d1d0146a44466 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 26 Mar 2020 11:26:04 -0700 Subject: [PATCH 8/9] Add Wasmtime C API function to control linker shadowing. --- crates/c-api/include/wasmtime.h | 4 +++- crates/c-api/src/ext.rs | 17 +++++++++++------ crates/misc/dotnet/src/Host.cs | 8 ++++++-- crates/misc/dotnet/src/Interop.cs | 5 ++++- examples/linking.c | 2 +- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index 7f78a44c5e..a7ed8fcd55 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -72,7 +72,9 @@ WASM_API_EXTERN bool wasmtime_wat2wasm( WASMTIME_DECLARE_OWN(linker) -WASM_API_EXTERN own wasmtime_linker_t* wasmtime_linker_new(wasm_store_t* store, bool allow_shadowing); +WASM_API_EXTERN own wasmtime_linker_t* wasmtime_linker_new(wasm_store_t* store); + +WASM_API_EXTERN void wasmtime_linker_allow_shadowing(wasmtime_linker_t* linker, bool allow_shadowing); WASM_API_EXTERN bool wasmtime_linker_define( wasmtime_linker_t *linker, diff --git a/crates/c-api/src/ext.rs b/crates/c-api/src/ext.rs index 5bc4231501..159b6a1635 100644 --- a/crates/c-api/src/ext.rs +++ b/crates/c-api/src/ext.rs @@ -142,13 +142,18 @@ pub struct wasmtime_linker_t { } #[no_mangle] -pub unsafe extern "C" fn wasmtime_linker_new( - store: *mut wasm_store_t, +pub unsafe extern "C" fn wasmtime_linker_new(store: *mut wasm_store_t) -> *mut wasmtime_linker_t { + Box::into_raw(Box::new(wasmtime_linker_t { + linker: Linker::new(&(*store).store.borrow()), + })) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_linker_allow_shadowing( + linker: *mut wasmtime_linker_t, allow_shadowing: bool, -) -> *mut wasmtime_linker_t { - let mut linker = Linker::new(&(*store).store.borrow()); - linker.allow_shadowing(allow_shadowing); - Box::into_raw(Box::new(wasmtime_linker_t { linker })) +) { + (*linker).linker.allow_shadowing(allow_shadowing); } #[no_mangle] diff --git a/crates/misc/dotnet/src/Host.cs b/crates/misc/dotnet/src/Host.cs index 92b8c05a29..95d6e98042 100644 --- a/crates/misc/dotnet/src/Host.cs +++ b/crates/misc/dotnet/src/Host.cs @@ -668,12 +668,14 @@ namespace Wasmtime { CheckDisposed(); - var linker = Interop.wasmtime_linker_new(Store, allowShadowing: true); + var linker = Interop.wasmtime_linker_new(Store); if (linker.IsInvalid) { throw new WasmtimeException("Failed to create Wasmtime linker."); } + Interop.wasmtime_linker_allow_shadowing(linker, allowShadowing: true); + Linker.Dispose(); Linker = linker; } @@ -722,12 +724,14 @@ namespace Wasmtime throw new WasmtimeException("Failed to create Wasmtime store."); } - var linker = Interop.wasmtime_linker_new(store, allowShadowing: true); + var linker = Interop.wasmtime_linker_new(store); if (linker.IsInvalid) { throw new WasmtimeException("Failed to create Wasmtime linker."); } + Interop.wasmtime_linker_allow_shadowing(linker, allowShadowing: true); + Engine = engine; Store = store; Linker = linker; diff --git a/crates/misc/dotnet/src/Interop.cs b/crates/misc/dotnet/src/Interop.cs index 7432c3cb55..32f92585ed 100644 --- a/crates/misc/dotnet/src/Interop.cs +++ b/crates/misc/dotnet/src/Interop.cs @@ -1020,7 +1020,10 @@ namespace Wasmtime // Linking functions [DllImport(LibraryName)] - public static extern LinkerHandle wasmtime_linker_new(StoreHandle store, [MarshalAs(UnmanagedType.I1)] bool allowShadowing); + public static extern LinkerHandle wasmtime_linker_new(StoreHandle store); + + [DllImport(LibraryName)] + public static extern void wasmtime_linker_allow_shadowing(LinkerHandle linker, [MarshalAs(UnmanagedType.I1)] bool allowShadowing); [DllImport(LibraryName)] public static extern void wasmtime_linker_delete(IntPtr linker); diff --git a/examples/linking.c b/examples/linking.c index 2b31d1a368..f3dc199429 100644 --- a/examples/linking.c +++ b/examples/linking.c @@ -66,7 +66,7 @@ int main() { // Create our linker which will be linking our modules together, and then add // our WASI instance to it. - wasmtime_linker_t *linker = wasmtime_linker_new(store, false); + wasmtime_linker_t *linker = wasmtime_linker_new(store); bool ok = wasmtime_linker_define_wasi(linker, wasi); assert(ok); From fb0762bbd6ff54c80d9c5b2e40bffd325b9c98bc Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 26 Mar 2020 11:48:06 -0700 Subject: [PATCH 9/9] Add wasmtime_func_new_with_env. This commit adds the `wasmtime_func_new_with_env` C API function and refactors the implementation to share the implementation between the C API and Wasmtime extension variants. --- crates/c-api/include/wasmtime.h | 9 ++ crates/c-api/src/ext.rs | 65 +++++++------ crates/c-api/src/lib.rs | 156 ++++++++++++++++++++------------ 3 files changed, 137 insertions(+), 93 deletions(-) diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index a7ed8fcd55..1600b686fc 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -103,9 +103,18 @@ WASM_API_EXTERN wasm_instance_t* wasmtime_linker_instantiate( typedef struct wasmtime_caller_t wasmtime_caller_t; typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* caller, const wasm_val_t args[], wasm_val_t results[]); +typedef own wasm_trap_t* (*wasmtime_func_callback_with_env_t)(const wasmtime_caller_t* caller, void* env, const wasm_val_t args[], wasm_val_t results[]); WASM_API_EXTERN own wasm_func_t* wasmtime_func_new(wasm_store_t*, const wasm_functype_t*, wasmtime_func_callback_t callback); +WASM_API_EXTERN own wasm_func_t* wasmtime_func_new_with_env( + wasm_store_t* store, + const wasm_functype_t* type, + wasmtime_func_callback_with_env_t callback, + void* env, + void (*finalizer)(void*) +); + WASM_API_EXTERN own wasm_extern_t* wasmtime_caller_export_get(const wasmtime_caller_t* caller, const wasm_name_t* name); #undef own diff --git a/crates/c-api/src/ext.rs b/crates/c-api/src/ext.rs index 159b6a1635..dbccc6bc42 100644 --- a/crates/c-api/src/ext.rs +++ b/crates/c-api/src/ext.rs @@ -221,17 +221,22 @@ pub unsafe extern "C" fn wasmtime_linker_instantiate( handle_instantiate(linker.instantiate(&(*module).module.borrow()), trap) } -pub type wasmtime_func_callback_t = std::option::Option< - unsafe extern "C" fn( - caller: *const wasmtime_caller_t, - args: *const wasm_val_t, - results: *mut wasm_val_t, - ) -> *mut wasm_trap_t, ->; +pub type wasmtime_func_callback_t = unsafe extern "C" fn( + caller: *const wasmtime_caller_t, + args: *const wasm_val_t, + results: *mut wasm_val_t, +) -> *mut wasm_trap_t; + +pub type wasmtime_func_callback_with_env_t = unsafe extern "C" fn( + caller: *const wasmtime_caller_t, + env: *mut std::ffi::c_void, + args: *const wasm_val_t, + results: *mut wasm_val_t, +) -> *mut wasm_trap_t; #[repr(C)] pub struct wasmtime_caller_t<'a> { - inner: wasmtime::Caller<'a>, + pub inner: wasmtime::Caller<'a>, } #[no_mangle] @@ -240,32 +245,24 @@ pub unsafe extern "C" fn wasmtime_func_new( ty: *const wasm_functype_t, callback: wasmtime_func_callback_t, ) -> *mut wasm_func_t { - let store = &(*store).store.borrow(); - let ty = (*ty).functype.clone(); - let func = Func::new(store, ty, move |caller, params, results| { - let params = params - .iter() - .map(|p| wasm_val_t::from_val(p)) - .collect::>(); - let mut out_results = vec![wasm_val_t::default(); results.len()]; - let func = callback.expect("wasm_func_callback_t fn"); - let caller = wasmtime_caller_t { inner: caller }; - let out = func(&caller, params.as_ptr(), out_results.as_mut_ptr()); - if !out.is_null() { - let trap: Box = Box::from_raw(out); - return Err(trap.trap.borrow().clone()); - } - for i in 0..results.len() { - results[i] = out_results[i].val(); - } - Ok(()) - }); - let func = Box::new(wasm_func_t { - ext: wasm_extern_t { - which: ExternHost::Func(HostRef::new(func)), - }, - }); - Box::into_raw(func) + crate::create_function(store, ty, crate::Callback::Wasmtime(callback)) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_func_new_with_env( + store: *mut wasm_store_t, + ty: *const wasm_functype_t, + callback: wasmtime_func_callback_with_env_t, + env: *mut std::ffi::c_void, + finalizer: Option, +) -> *mut wasm_func_t { + crate::create_function_with_env( + store, + ty, + crate::CallbackWithEnv::Wasmtime(callback), + env, + finalizer, + ) } #[no_mangle] diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index e1af9ed205..47be831c4b 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -368,16 +368,14 @@ impl wasm_func_t { } } -pub type wasm_func_callback_t = std::option::Option< - unsafe extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> *mut wasm_trap_t, ->; -pub type wasm_func_callback_with_env_t = std::option::Option< - unsafe extern "C" fn( - env: *mut std::ffi::c_void, - args: *const wasm_val_t, - results: *mut wasm_val_t, - ) -> *mut wasm_trap_t, ->; +pub type wasm_func_callback_t = + unsafe extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> *mut wasm_trap_t; + +pub type wasm_func_callback_with_env_t = unsafe extern "C" fn( + env: *mut std::ffi::c_void, + args: *const wasm_val_t, + results: *mut wasm_val_t, +) -> *mut wasm_trap_t; #[derive(Clone)] #[repr(transparent)] @@ -622,22 +620,36 @@ impl wasm_val_t { } } -#[no_mangle] -pub unsafe extern "C" fn wasm_func_new( +enum Callback { + Wasm(wasm_func_callback_t), + Wasmtime(crate::ext::wasmtime_func_callback_t), +} + +enum CallbackWithEnv { + Wasm(wasm_func_callback_with_env_t), + Wasmtime(crate::ext::wasmtime_func_callback_with_env_t), +} + +unsafe fn create_function( store: *mut wasm_store_t, ty: *const wasm_functype_t, - callback: wasm_func_callback_t, + callback: Callback, ) -> *mut wasm_func_t { let store = &(*store).store.borrow(); let ty = (*ty).functype.clone(); - let func = Func::new(store, ty, move |_, params, results| { + let func = Func::new(store, ty, move |caller, params, results| { let params = params .iter() .map(|p| wasm_val_t::from_val(p)) .collect::>(); let mut out_results = vec![wasm_val_t::default(); results.len()]; - let func = callback.expect("wasm_func_callback_t fn"); - let out = func(params.as_ptr(), out_results.as_mut_ptr()); + let out = match callback { + Callback::Wasm(callback) => callback(params.as_ptr(), out_results.as_mut_ptr()), + Callback::Wasmtime(callback) => { + let caller = crate::ext::wasmtime_caller_t { inner: caller }; + callback(&caller, params.as_ptr(), out_results.as_mut_ptr()) + } + }; if !out.is_null() { let trap: Box = Box::from_raw(out); return Err(trap.trap.borrow().clone()); @@ -655,6 +667,74 @@ pub unsafe extern "C" fn wasm_func_new( Box::into_raw(func) } +unsafe fn create_function_with_env( + store: *mut wasm_store_t, + ty: *const wasm_functype_t, + callback: CallbackWithEnv, + env: *mut std::ffi::c_void, + finalizer: Option, +) -> *mut wasm_func_t { + let store = &(*store).store.borrow(); + let ty = (*ty).functype.clone(); + + // Create a small object which will run the finalizer when it's dropped, and + // then we move this `run_finalizer` object into the closure below (via the + // `drop(&run_finalizer)` statement so it's all dropped when the closure is + // dropped. + struct RunOnDrop(F); + impl Drop for RunOnDrop { + fn drop(&mut self) { + (self.0)(); + } + } + let run_finalizer = RunOnDrop(move || { + if let Some(finalizer) = finalizer { + finalizer(env); + } + }); + let func = Func::new(store, ty, move |caller, params, results| { + drop(&run_finalizer); + let params = params + .iter() + .map(|p| wasm_val_t::from_val(p)) + .collect::>(); + let mut out_results = vec![wasm_val_t::default(); results.len()]; + let out = match callback { + CallbackWithEnv::Wasm(callback) => { + callback(env, params.as_ptr(), out_results.as_mut_ptr()) + } + CallbackWithEnv::Wasmtime(callback) => { + let caller = crate::ext::wasmtime_caller_t { inner: caller }; + callback(&caller, env, params.as_ptr(), out_results.as_mut_ptr()) + } + }; + if !out.is_null() { + let trap: Box = Box::from_raw(out); + return Err(trap.trap.borrow().clone()); + } + for i in 0..results.len() { + results[i] = out_results[i].val(); + } + Ok(()) + }); + + let func = Box::new(wasm_func_t { + ext: wasm_extern_t { + which: ExternHost::Func(HostRef::new(func)), + }, + }); + Box::into_raw(func) +} + +#[no_mangle] +pub unsafe extern "C" fn wasm_func_new( + store: *mut wasm_store_t, + ty: *const wasm_functype_t, + callback: wasm_func_callback_t, +) -> *mut wasm_func_t { + create_function(store, ty, Callback::Wasm(callback)) +} + #[no_mangle] pub unsafe extern "C" fn wasm_func_delete(f: *mut wasm_func_t) { let _ = Box::from_raw(f); @@ -896,49 +976,7 @@ pub unsafe extern "C" fn wasm_func_new_with_env( env: *mut std::ffi::c_void, finalizer: Option, ) -> *mut wasm_func_t { - let store = &(*store).store.borrow(); - let ty = (*ty).functype.clone(); - - // Create a small object which will run the finalizer when it's dropped, and - // then we move this `run_finalizer` object into the closure below (via the - // `drop(&run_finalizer)` statement so it's all dropped when the closure is - // dropped. - struct RunOnDrop(F); - impl Drop for RunOnDrop { - fn drop(&mut self) { - (self.0)(); - } - } - let run_finalizer = RunOnDrop(move || { - if let Some(finalizer) = finalizer { - finalizer(env); - } - }); - let func = Func::new(store, ty, move |_, params, results| { - drop(&run_finalizer); - let params = params - .iter() - .map(|p| wasm_val_t::from_val(p)) - .collect::>(); - let mut out_results = vec![wasm_val_t::default(); results.len()]; - let func = callback.expect("wasm_func_callback_with_env_t fn"); - let out = func(env, params.as_ptr(), out_results.as_mut_ptr()); - if !out.is_null() { - let trap: Box = Box::from_raw(out); - return Err(trap.trap.borrow().clone()); - } - for i in 0..results.len() { - results[i] = out_results[i].val(); - } - Ok(()) - }); - - let func = Box::new(wasm_func_t { - ext: wasm_extern_t { - which: ExternHost::Func(HostRef::new(func)), - }, - }); - Box::into_raw(func) + create_function_with_env(store, ty, CallbackWithEnv::Wasm(callback), env, finalizer) } #[no_mangle]