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.
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
@@ -9,18 +9,26 @@ namespace Wasmtime
|
||||
/// <summary>
|
||||
/// Creates a default <see cref="Wasi"/> instance.
|
||||
/// </summary>
|
||||
public Wasi(Store store) :
|
||||
/// <param name="store">The store to use for the new WASI instance.</param>
|
||||
/// <param name="name">The name of the WASI module to create.</param>
|
||||
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)
|
||||
|
||||
@@ -12,10 +12,22 @@ namespace Wasmtime
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="WasiBuilder" />.
|
||||
/// </summary>
|
||||
public WasiBuilder()
|
||||
public WasiBuilder(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the WASI module being built.
|
||||
/// </summary>
|
||||
/// <value>The name of the WASI module.</value>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command line argument to the builder.
|
||||
/// </summary>
|
||||
@@ -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)
|
||||
|
||||
66
crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat
Normal file
66
crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat
Normal file
@@ -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))
|
||||
)
|
||||
246
crates/misc/dotnet/tests/WasiSnapshot0Tests.cs
Normal file
246
crates/misc/dotnet/tests/WasiSnapshot0Tests.cs
Normal file
@@ -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<WasiSnapshot0Fixture>
|
||||
{
|
||||
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<string, string>() {
|
||||
{"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<string>() {
|
||||
"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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user