Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5f569e3
Refactor to allow fast-path
radical Sep 11, 2021
5b3b91f
MonoAOTCompiler: check for nothing-changed case
radical Sep 11, 2021
28fdcba
[wasm] Change optimization flag defaults for Debug config
radical Sep 17, 2021
6e52172
[wasm] EmccCompile: Fix incremental build, in case of only partial
radical Sep 21, 2021
08ae443
MonoAOTCompiler: Skip unmanaged assemblies, and emit a warning
radical Sep 22, 2021
7fe0e69
Apply suggestions from code review
radical Sep 22, 2021
1c0a340
MonoAOTCompiler: write the cache even when some files fail to compile
radical Sep 22, 2021
7230cd3
Don't set optimization defaults for Debug config, when publishing
radical Sep 22, 2021
c71f9c2
Wasm.Build.Tests: Disable net5.0 because they can't be tested right now
radical Sep 23, 2021
110ed85
[wasm] Error for undefined symbols only when publishing
radical Sep 24, 2021
d17bc64
[wasm] PInvokeTableGenerator: Add support for variadic functions
radical Sep 24, 2021
8ecfd1c
[wasm] Handle pinvokes with function pointers
radical Sep 24, 2021
f4cb745
[wasm] PInvokeTableGenerator: handle pinvokes with function pointers
radical Sep 25, 2021
aac1537
add missing variadic.{c,o}
radical Sep 25, 2021
f48d6b2
[wasm] Add test for issue dotnet#59255
radical Sep 25, 2021
9dd1976
Bump sdk for workload testing to 6.0.100-rc.2.21474.31
radical Sep 22, 2021
46ddc76
Merge remote-tracking branch 'origin/main' into wasm-improvements
radical Sep 27, 2021
712dbb3
MonoAOTCompiler: Check the hash for the file also, for "all up-to-dat…
radical Sep 27, 2021
b23e11e
Merge remote-tracking branch 'origin/main' into wasm-improvements
radical Sep 27, 2021
dc09bf6
Revert "MonoAOTCompiler: Check the hash for the file also, for "all u…
radical Sep 27, 2021
41c7476
PInvokeTableGenerator: don't generate any decl for variadic functions
radical Sep 27, 2021
dcbcc35
Add missing `using` for disposable objects.
radical Sep 29, 2021
1f57975
cleanup
radical Sep 29, 2021
4519646
Merge remote-tracking branch 'origin/main' into rf-wasm-improvements
radical Sep 29, 2021
20a67a1
address feedback from @lewing
radical Sep 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[wasm] Handle pinvokes with function pointers
- For pinvokes with function pointers, *no* declaration is added to
  `pinvoke-table.h`, and a warning is raised:

```
warning : DllImports with function pointers are not supported. Calling them will fail. Managed DllImports:  [/Users/radical/dev/r2/artifacts/bin/Wasm.Build.Tests/net6.0-Release/browser-wasm/g3acrk4b.a0o/variadic_g3acrk4b.a0o.csproj]
warning :    Type: Test, Method: System.Int32 using_sum_one(?) [/Users/radical/dev/r2/artifacts/bin/Wasm.Build.Tests/net6.0-Release/browser-wasm/g3acrk4b.a0o/variadic_g3acrk4b.a0o.csproj]
```

- Also, handle multiple pinvokes with the same number of params, but
  different types
  • Loading branch information
radical committed Sep 25, 2021
commit 8ecfd1cf2cc71cebc040abe551951cd15988035c
69 changes: 55 additions & 14 deletions src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,39 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules
w.WriteLine();

var decls = new HashSet<string>();
// FIXME: handle sigs with different first args
foreach (var group in pinvokes.OrderBy(l => l.EntryPoint).GroupBy(l => l.EntryPoint))
foreach (var group in pinvokes.GroupBy(l => l.EntryPoint))
{
IEnumerable<string?>? uniqueSigs = group.Select(l => l.Method.ToString()).Distinct();
bool treatAsVariadic = uniqueSigs.Count() > 1;
if (treatAsVariadic)
bool treatAsVariadic = false;
PInvoke first = group.First();

if (!ShouldApplyHackForMethodWithFunctionPointers(first.Method))
{
w.WriteLine($"// Variadic signature created for");
foreach (string? method in uniqueSigs)
w.WriteLine($"// {method}");
if (HasFunctionPointerParams(first.Method))
{
Log.LogWarning($"DllImports with function pointers are not supported. Calling them will fail. Managed DllImports: {Environment.NewLine}{GroupToString(group)}");
foreach (var pinvoke in group)
pinvoke.Skip = true;

continue;
}

int numArgs = first.Method.GetParameters().Length;
treatAsVariadic = group.Count() > 1 && group.Any(p => p.Method.GetParameters().Length != numArgs);
if (treatAsVariadic)
{
w.WriteLine($"// Variadic signature created for");
foreach (PInvoke pinvoke in group)
w.WriteLine($"// {pinvoke.Method}");

Log.LogWarning($"Found a native function ({first.EntryPoint}) with varargs, which is not supported. Calling it will fail at runtime. Module: {first.Module}." +
$" Managed DllImports: {Environment.NewLine}{GroupToString(group)}");
}
}

PInvoke pinvoke = group.First();
if (modules.ContainsKey(pinvoke.Module)) {
if (modules.ContainsKey(first.Module)) {
try
{
var decl = GenPInvokeDecl(pinvoke, treatAsVariadic);
var decl = GenPInvokeDecl(first, treatAsVariadic);
if (decls.Contains(decl))
continue;

Expand All @@ -148,8 +164,8 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules
catch (NotSupportedException)
{
// See the FIXME in GenPInvokeDecl
Log.LogWarning($"Cannot handle function pointer arguments/return value in pinvoke method '{pinvoke.Method}' in type '{pinvoke.Method.DeclaringType}'.");
pinvoke.Skip = true;
Log.LogWarning($"Cannot handle function pointer arguments/return value in pinvoke method '{first.Method}' in type '{first.Method.DeclaringType}'.");
first.Skip = true;
}
}
}
Expand Down Expand Up @@ -197,6 +213,27 @@ static string ModuleNameToId(string name)

return fixedName;
}

static bool HasFunctionPointerParams(MethodInfo method)
{
try
{
method.GetParameters();
}
catch (NotSupportedException nse) when (nse.Message.Contains("function pointer types in signatures is not supported"))
{
return true;
}
catch
{
// not concerned with other exceptions
}

return false;
}

static string GroupToString(IGrouping<string, PInvoke> group)
=> string.Join(Environment.NewLine, group.Select(p => $" Type: {p.Method.DeclaringType}, Method: {p.Method}"));
}

private string MapType (Type t)
Expand All @@ -216,11 +253,14 @@ private string MapType (Type t)
return "int";
}

private static bool ShouldApplyHackForMethodWithFunctionPointers(MethodInfo method) => method.Name == "EnumCalendarInfo";

private string GenPInvokeDecl(PInvoke pinvoke, bool treatAsVariadic=false)
{
var sb = new StringBuilder();
var method = pinvoke.Method;
if (method.Name == "EnumCalendarInfo") {
if (ShouldApplyHackForMethodWithFunctionPointers(method))
{
// FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types
// https://github.com/dotnet/runtime/issues/43791
sb.Append($"int {pinvoke.EntryPoint} (int, int, int, int, int);");
Expand All @@ -242,6 +282,7 @@ private string GenPInvokeDecl(PInvoke pinvoke, bool treatAsVariadic=false)
}
else
{
// FIXME: handle sigs with different first args
ParameterInfo firstParam = method.GetParameters()[0];
sb.Append(MapType(firstParam.ParameterType));
sb.Append(", ...");
Expand Down
89 changes: 89 additions & 0 deletions src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -91,5 +92,93 @@ public static int Main()

Assert.Contains("Size: 26462 Height: 599, Width: 499", output);
}

[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[BuildAndRun(host: RunHost.V8)]
public void NativeLibraryWithVariadicFunctions(BuildArgs buildArgs, RunHost host, string id)
{
string projectName = $"variadic_{id}";
string code = @"
using System;
using System.Runtime.InteropServices;
public class Test
{
public static int Main(string[] args)
{
Console.WriteLine($""Main running"");
if (args.Length > 0)
{
// We don't want to run this, because we can't call variadic functions
Console.WriteLine($""sum_three: {sum_three(7, 14, 21)}"");
Console.WriteLine($""sum_two: {sum_two(3, 6)}"");
Console.WriteLine($""sum_one: {sum_one(5)}"");
}
return 42;
}

[DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_one(int a);
[DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_two(int a, int b);
[DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_three(int a, int b, int c);
}";
string filename = "variadic.o";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs, extraItems: $"<NativeFileReference Include=\"{filename}\" />");

BuildProject(buildArgs,
initProject: () =>
{
File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code);
File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename),
Path.Combine(_projectDir!, filename));
},
publish: false,
id: id,
dotnetWasmFromRuntimePack: false);

string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id);
Assert.Contains("Main running", output);
}

[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[BuildAndRun(host: RunHost.V8)]
public void DllImportWithFunctionPointersCompilesWithWarning(BuildArgs buildArgs, RunHost host, string id)
{
string projectName = $"variadic_{id}";
string code = @"
using System;
using System.Runtime.InteropServices;
public class Test
{
public static int Main()
{
Console.WriteLine($""Main running"");
return 42;
}

[DllImport(""variadic"", EntryPoint=""sum"")] public unsafe static extern int using_sum_one(delegate* unmanaged<char*, IntPtr, void> callback);
}";
string filename = "variadic.o";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs, extraItems: $"<NativeFileReference Include=\"{filename}\" />", extraProperties: "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>");

Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}");

(_, string output) = BuildProject(buildArgs,
initProject: () =>
{
File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code);
File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename),
Path.Combine(_projectDir!, filename));
},
publish: false,
id: id,
dotnetWasmFromRuntimePack: false);

Assert.Matches("warning.*function pointers", output);
Assert.Matches("warning.*using_sum_one", output);

output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id);
Assert.Contains("Main running", output);
}
}
}