Skip to content

Commit

Permalink
Runtime: Add WASI Preview 1 proc_exit function
Browse files Browse the repository at this point in the history
This function will normally call Environment.Exit(code), but an
integration point was added to WasmRuntime in order to facilitate unit
testing (or allow a user of the runtime to choose to do something else
upon this call).
  • Loading branch information
paulirwin committed Oct 28, 2023
1 parent ef13d60 commit 7bbc1b5
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 14 deletions.
12 changes: 3 additions & 9 deletions WasmNet.Core/HostFunctionInstance.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
namespace WasmNet.Core;

public class HostFunctionInstance : IFunctionInstance
public class HostFunctionInstance(WasmType type, Delegate hostCode) : IFunctionInstance
{
public HostFunctionInstance(WasmType type, Delegate hostCode)
{
Type = type;
HostCode = hostCode;
}
public WasmType Type { get; } = type;

public WasmType Type { get; }

public Delegate HostCode { get; }
public Delegate HostCode { get; } = hostCode;

public Type ReturnType => HostCode.Method.ReturnType;

Expand Down
6 changes: 6 additions & 0 deletions WasmNet.Core/Wasi/Preview1.ProcExit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace WasmNet.Core.Wasi;

public static partial class Preview1
{
public static void ProcExit(WasmRuntime runtime, int exitCode) => runtime.ExitHandler(exitCode);
}
20 changes: 20 additions & 0 deletions WasmNet.Core/Wasi/Preview1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace WasmNet.Core.Wasi;

public static partial class Preview1
{
public const string Namespace = "wasi_snapshot_preview1";

public static void RegisterWasiPreview1(this WasmRuntime runtime)
{
var actionIntType = new WasmType
{
Kind = WasmTypeKind.Function,
Parameters = new List<WasmValueType>
{
WasmNumberType.I32
}
};

runtime.RegisterImportable(Namespace, "proc_exit", (int exitCode) => ProcExit(runtime, exitCode));
}
}
12 changes: 10 additions & 2 deletions WasmNet.Core/WasmRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
using System.Reflection;
using System.Reflection.Emit;
using WasmNet.Core.Wasi;

namespace WasmNet.Core;

public class WasmRuntime
{
public Store Store { get; } = new();

private readonly IDictionary<string, IDictionary<string, object?>> _importables = new Dictionary<string, IDictionary<string, object?>>();

public WasmRuntime()
{
this.RegisterWasiPreview1();
}

public Action<int> ExitHandler { get; set; } = Environment.Exit;

public Store Store { get; } = new();

public void RegisterImportable(string module, string name, object? value)
{
if (!_importables.TryGetValue(module, out var importables))
Expand Down
51 changes: 49 additions & 2 deletions WasmNet.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ public async Task IntegrationTest(string file)
throw new Exception($"wat2wasm failed to produce {wasmFile}.");
}

WasmRuntime runtime = new();
WasmRuntime runtime = new()
{
ExitHandler = code => throw new ExitCodeException(code),
};

var externalCalls = new Dictionary<string, int>();

Expand Down Expand Up @@ -128,7 +131,7 @@ public async Task IntegrationTest(string file)
}
else if (op is ExpectTrapOperation expectTrap)
{
if (exception is TargetInvocationException tie)
while (exception is TargetInvocationException tie)
{
exception = tie.InnerException;
}
Expand All @@ -137,6 +140,21 @@ public async Task IntegrationTest(string file)
Assert.Equal(expectTrap.ExceptionType, exception.GetType().Name);
testOutputHelper.WriteLine($"Exception is of type {exception.GetType().Name}: {exception.Message}");
}
else if (op is ExitCodeOperation exitCode)
{
while (exception is TargetInvocationException tie)
{
exception = tie.InnerException;
}

if (exception is not ExitCodeException ece)
{
throw new Exception($"Expected exit code {exitCode.ExitCode} but the program did not exit");
}

Assert.Equal(exitCode.ExitCode, ece.ExitCode);
testOutputHelper.WriteLine($"Expected exit code {exitCode.ExitCode} and got {ece.ExitCode}");
}
else
{
throw new NotImplementedException($"Unknown operation: {op.GetType().Name}");
Expand All @@ -150,9 +168,34 @@ public static IEnumerable<object[]> GetWatFiles()

return files.Select(i => new object[] { Path.GetFileName(i) });
}

private class ExitCodeException : Exception
{
public ExitCodeException(int exitCode)
{
ExitCode = exitCode;
}

public int ExitCode { get; }
}

private abstract class Operation;

private class ExitCodeOperation : Operation
{
public required int ExitCode { get; init; }

public static ExitCodeOperation Parse(string text)
{
var code = int.Parse(text);

return new ExitCodeOperation
{
ExitCode = code,
};
}
}

private class ExpectTrapOperation : Operation
{
public required string ExceptionType { get; init; }
Expand Down Expand Up @@ -348,6 +391,10 @@ public static Header Parse(string text)
{
ops.Add(ExpectTrapOperation.Parse(line[13..]));
}
else if (line.StartsWith("exit_code: "))
{
ops.Add(ExitCodeOperation.Parse(line[11..]));
}
else if (line.StartsWith("source: ") || line.StartsWith("TODO: ") || line.StartsWith("NOTE:"))
{
// ignore
Expand Down
12 changes: 12 additions & 0 deletions WasmNet.Tests/IntegrationTests/0150-WasiPreview1ProcExit.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
;; invoke: proc-exit
;; exit_code: 1

(module
(type $t0 (func (param i32)))
(import "wasi_snapshot_preview1" "proc_exit" (func $exit (type $t0)))
(func (export "proc-exit")
(call $exit (i32.const 1))
)
(memory (;0;) 256 256)
(export "memory" (memory 0))
)
3 changes: 2 additions & 1 deletion WasmNet.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=importables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=logmem/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=logmem/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Wasi/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 comments on commit 7bbc1b5

Please sign in to comment.