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:
Peter Huene
2020-03-20 16:47:25 -07:00
parent 853d5f304d
commit 0d5d63fdb1
12 changed files with 613 additions and 120 deletions

View File

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

View File

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

View File

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

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

View 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));
}
}
}

View File

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