From 06aa4ee5d591c63cfcf1b675eed2817a51db9294 Mon Sep 17 00:00:00 2001 From: Martin Evans Date: Thu, 28 May 2026 21:03:45 +0100 Subject: [PATCH] - Added support for WASM tag types (import/export) - Added support for enabling wasm exceptions --- src/Config.cs | 16 ++++++++- src/Export.cs | 52 +++++++++++++++++++++++++--- src/Import.cs | 61 +++++++++++++++++++++++++-------- tests/ExportTagsTests.cs | 66 ++++++++++++++++++++++++++++++++++++ tests/Modules/ExportTags.wat | 10 ++++++ 5 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 tests/ExportTagsTests.cs create mode 100644 tests/Modules/ExportTags.wat diff --git a/src/Config.cs b/src/Config.cs index 273bb147..e22974d9 100644 --- a/src/Config.cs +++ b/src/Config.cs @@ -257,6 +257,17 @@ public Config WithWideArithmetic(bool enable) return this; } + /// + /// Configures whether the WebAssembly exceptions proposal is enabled. + /// + /// True to enable exceptions or false to disable. + /// Returns the current config. + public Config WithExceptions(bool enable) + { + Native.wasmtime_config_wasm_exceptions_set(handle, enable); + return this; + } + /// /// Sets whether the WebAssembly stack switching proposal is enabled. /// @@ -264,7 +275,7 @@ public Config WithWideArithmetic(bool enable) /// Returns the current config. private Config WithStackSwitching(bool enable) { - // todo: unlikely to be compatible with wasmtime-dotnet on Windows due to threads + // warning: **unlikely to be ever compatible with wasmtime-dotnet on Windows due to fibers** Native.wasmtime_config_wasm_stack_switching_set(handle, enable); return this; @@ -632,6 +643,9 @@ private static class Native [DllImport(Engine.LibraryName)] public static extern void wasmtime_config_wasm_component_model_set(Handle config, [MarshalAs(UnmanagedType.I1)] bool value); + [DllImport(Engine.LibraryName)] + public static extern void wasmtime_config_wasm_exceptions_set(Handle config, [MarshalAs(UnmanagedType.I1)] bool value); + // todo: void wasmtime_config_host_memory_creator_set(wasm_config_t *, wasmtime_memory_creator_t *) } diff --git a/src/Export.cs b/src/Export.cs index 06e4b6f3..56dc99bb 100644 --- a/src/Export.cs +++ b/src/Export.cs @@ -23,19 +23,20 @@ public Export[] ToExportArray() } var exports = new Export[(int)this.size]; - for (int i = 0; i < (int)this.size; ++i) + for (var i = 0; i < (int)this.size; ++i) { var exportType = this.data[i]; var externType = Native.wasm_exporttype_type(exportType); - exports[i] = (WasmExternKind)Native.wasm_externtype_kind(externType) switch + var kind = (WasmExternKind)Native.wasm_externtype_kind(externType); + exports[i] = kind switch { WasmExternKind.Func => new FunctionExport(exportType, externType), WasmExternKind.Global => new GlobalExport(exportType, externType), WasmExternKind.Table => new TableExport(exportType, externType), WasmExternKind.Memory => new MemoryExport(exportType, externType), - - _ => throw new NotSupportedException("Unsupported export extern type.") + WasmExternKind.Tag => new TagExport(exportType, externType), + _ => throw new NotSupportedException($"Unsupported export extern type: {kind}.") }; } @@ -254,4 +255,45 @@ internal static class Native public static extern IntPtr wasm_externtype_as_tabletype_const(IntPtr type); } } -} + + /// + /// Represents a tag exported from a WebAssembly module or instance. + /// + public class TagExport + : Export + { + /// + /// Parameter types of this tag + /// + public ValueKind[] Parameters { get; set; } + + internal TagExport(IntPtr exportType, IntPtr externType) : base(exportType) + { + var tagType = Native.wasm_externtype_as_tagtype_const(externType); + if (tagType == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + var funcType = Native.wasm_tagtype_functype(tagType); + if (funcType == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + unsafe + { + Parameters = (*Function.Native.wasm_functype_params(funcType)).ToArray(); + } + } + + internal static class Native + { + [DllImport(Engine.LibraryName)] + public static extern IntPtr wasm_externtype_as_tagtype_const(IntPtr type); + + [DllImport(Engine.LibraryName)] + public static extern IntPtr wasm_tagtype_functype(IntPtr tagType); + } + } +} \ No newline at end of file diff --git a/src/Import.cs b/src/Import.cs index f21b3649..b35ac440 100644 --- a/src/Import.cs +++ b/src/Import.cs @@ -8,10 +8,11 @@ namespace Wasmtime // in the Wasmtime API soon. The difference is the order of `Module` and `Instance`. internal enum WasmExternKind : byte { - Func, - Global, - Table, - Memory, + Func = 0, + Global = 1, + Table = 2, + Memory = 3, + Tag = 4, } [StructLayout(LayoutKind.Sequential)] @@ -36,18 +37,20 @@ public Import[] ToImportArray() } var imports = new Import[(int)this.size]; - for (int i = 0; i < (int)this.size; ++i) + for (var i = 0; i < (int)this.size; ++i) { var importType = this.data[i]; var externType = Native.wasm_importtype_type(importType); - imports[i] = (WasmExternKind)ExportTypeArray.Native.wasm_externtype_kind(externType) switch + var kind = (WasmExternKind)ExportTypeArray.Native.wasm_externtype_kind(externType); + imports[i] = kind switch { WasmExternKind.Func => new FunctionImport(importType, externType), WasmExternKind.Global => new GlobalImport(importType, externType), WasmExternKind.Table => new TableImport(importType, externType), WasmExternKind.Memory => new MemoryImport(importType, externType), - _ => throw new NotSupportedException("Unsupported import extern type.") + WasmExternKind.Tag => new TagImport(importType, externType), + _ => throw new NotSupportedException($"Unsupported import extern type: {kind}.") }; } return imports; @@ -77,14 +80,9 @@ internal Import(IntPtr importType) : Extensions.PtrToStringUTF8((IntPtr)moduleName->data, checked((int)moduleName->size)); var name = Native.wasm_importtype_name(importType); - if (name is null || name->size == 0) - { - Name = String.Empty; - } - else - { - Name = Extensions.PtrToStringUTF8((IntPtr)name->data, checked((int)name->size)); - } + Name = name is null || name->size == 0 + ? string.Empty + : Extensions.PtrToStringUTF8((IntPtr)name->data, checked((int)name->size)); } } @@ -260,4 +258,37 @@ internal TableImport(IntPtr importType, IntPtr externType) : base(importType) /// public uint Maximum { get; private set; } } + + /// + /// Represents a tag imported to a WebAssembly module or instance. + /// + public class TagImport + : Import + { + /// + /// Parameter types of this tag + /// + public ValueKind[] Parameters { get; set; } + + internal TagImport(IntPtr exportType, IntPtr externType) + : base(exportType) + { + var tagType = TagExport.Native.wasm_externtype_as_tagtype_const(externType); + if (tagType == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + var funcType = TagExport.Native.wasm_tagtype_functype(tagType); + if (funcType == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + unsafe + { + Parameters = (*Function.Native.wasm_functype_params(funcType)).ToArray(); + } + } + } } diff --git a/tests/ExportTagsTests.cs b/tests/ExportTagsTests.cs new file mode 100644 index 00000000..c07a351c --- /dev/null +++ b/tests/ExportTagsTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using Xunit; + +namespace Wasmtime.Tests; + +public class ExportTagsFixture + : ModuleFixture +{ + protected override string ModuleFileName => "ExportTags.wat"; + + public override Config GetEngineConfig() + { + return base.GetEngineConfig() + .WithExceptions(true); + } +} + +public sealed class ExportTagsTests + : IClassFixture, IDisposable +{ + private ExportTagsFixture Fixture { get; set; } + + private Store Store { get; set; } + + private Linker Linker { get; set; } + + public ExportTagsTests(ExportTagsFixture fixture) + { + Fixture = fixture; + Store = new Store(Fixture.Engine); + Linker = new Linker(Fixture.Engine); + } + + public void Dispose() + { + Store.Dispose(); + Linker.Dispose(); + } + + [Fact] + public void ItExportsTags() + { + Assert.Single(Fixture.Module.Exports); + + var export = (TagExport)Fixture.Module.Exports.Single(); + + Assert.Equal("$export_tag", export.Name); + + Assert.Single(export.Parameters); + Assert.Equal(ValueKind.Int32, export.Parameters.Single()); + } + + [Fact] + public void ItImportsTags() + { + Assert.Single(Fixture.Module.Imports); + + var export = (TagImport)Fixture.Module.Imports.Single(); + + Assert.Equal("$import_tag", export.Name); + + Assert.Single(export.Parameters); + Assert.Equal(ValueKind.Int32, export.Parameters.Single()); + } +} \ No newline at end of file diff --git a/tests/Modules/ExportTags.wat b/tests/Modules/ExportTags.wat new file mode 100644 index 00000000..0c790f8d --- /dev/null +++ b/tests/Modules/ExportTags.wat @@ -0,0 +1,10 @@ +(module + ;; import a tag from the host + (import "test" "$import_tag" (tag $import_tag (param i32))) + + ;; Define a tag + (tag $export_tag (param i32)) + + ;; export the tag + (export "$export_tag" (tag $export_tag)) +) \ No newline at end of file