Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] Implement initial support for WasmImportLinkage in the AppBuilders #94615

Merged
merged 13 commits into from
Nov 21, 2023
Merged
15 changes: 15 additions & 0 deletions src/mono/sample/wasi/native/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
TOP=../../../../..

include ../wasi.mk

ifneq ($(AOT),)
override MSBUILD_ARGS+=/p:RunAOTCompilation=true
endif

ifneq ($(V),)
DOTNET_MONO_LOG_LEVEL=--setenv=MONO_LOG_LEVEL=debug
endif

PROJECT_NAME=Wasi.Console.Sample.csproj

run: run-console
31 changes: 31 additions & 0 deletions src/mono/sample/wasi/native/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.Runtime.InteropServices;

public unsafe class Test
{
[UnmanagedCallersOnly(EntryPoint = "ManagedFunc")]
public static int MyExport(int number)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with Pavel that it might be good for the entrypoint and managed name to match, unless we are explicitly testing that you can make them not match and it will still work

{
// called from MyImport aka UnmanagedFunc
Console.WriteLine($"MyExport({number}) -> 42");
return 42;
}

[DllImport("*", EntryPoint = "UnmanagedFunc")]
public static extern void MyImport(); // calls ManagedFunc aka MyExport

public unsafe static int Main(string[] args)
{
Console.WriteLine($"main: {args.Length}");
// workaround to force the interpreter to initialize wasm_native_to_interp_ftndesc for MyExport
if (args.Length > 10000) {
((IntPtr)(delegate* unmanaged<int,int>)&MyExport).ToString();
}

MyImport();
return 0;
}
}
17 changes: 17 additions & 0 deletions src/mono/sample/wasi/native/Wasi.Native.Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>

<TargetOs>wasi</TargetOs>
<WasmBuildNative>true</WasmBuildNative>
<WasmNativeStrip>false</WasmNativeStrip>
<IsBrowserWasmProject>false</IsBrowserWasmProject>
<WasmSingleFileBundle>true</WasmSingleFileBundle>
</PropertyGroup>

<ItemGroup>
<NativeFileReference Include="local.c" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that all symbols in this sample are statically linked inside dotnet.wasm ? If so, should we also have sample that makes them external ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably need to happen in the tests rather than the sample, I'm pretty sure the smoke tests run the samples and the import wouldn't resolve.

</ItemGroup>
<Target Name="RunSample" DependsOnTargets="RunSampleWithWasmtime" />
</Project>
11 changes: 11 additions & 0 deletions src/mono/sample/wasi/native/local.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <stdio.h>

int ManagedFunc(int number);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to MyExport ?


void UnmanagedFunc()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's rename this to MyImport so that it's not confusing the reader of C# side

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did the mismatched names on purpose to demonstrate what setting EntryPoint does in both cases

{
int ret = 0;
printf("UnmanagedFunc calling ManagedFunc\n");
ret = ManagedFunc(123);
printf("ManagedFunc returned %d\n", ret);
}
22 changes: 20 additions & 2 deletions src/tasks/WasmAppBuilder/PInvokeCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@
internal sealed class PInvoke : IEquatable<PInvoke>
#pragma warning restore CA1067
{
public PInvoke(string entryPoint, string module, MethodInfo method)
public PInvoke(string entryPoint, string module, MethodInfo method, bool wasmLinkage)
{
EntryPoint = entryPoint;
Module = module;
Method = method;
WasmLinkage = wasmLinkage;
}

public string EntryPoint;
public string Module;
public MethodInfo Method;
public bool Skip;
public bool WasmLinkage;

lewing marked this conversation as resolved.
Show resolved Hide resolved

public bool Equals(PInvoke? other)
=> other != null &&
Expand Down Expand Up @@ -100,9 +103,10 @@ void CollectPInvokesForMethod(MethodInfo method)
if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0)
{
var dllimport = method.CustomAttributes.First(attr => attr.AttributeType.Name == "DllImportAttribute");
var wasmLinkage = method.CustomAttributes.Any(attr => attr.AttributeType.Name == "WasmImportLinkageAttribute");
var module = (string)dllimport.ConstructorArguments[0].Value!;
var entrypoint = (string)dllimport.NamedArguments.First(arg => arg.MemberName == "EntryPoint").TypedValue.Value!;
pinvokes.Add(new PInvoke(entrypoint, module, method));
pinvokes.Add(new PInvoke(entrypoint, module, method, wasmLinkage));

string? signature = SignatureMapper.MethodToSignature(method);
if (signature == null)
Expand Down Expand Up @@ -241,8 +245,22 @@ internal sealed class PInvokeCallback
public PInvokeCallback(MethodInfo method)
{
Method = method;
foreach (var attr in method.CustomAttributes)
{
if (attr.AttributeType.Name == "UnmanagedCallersOnlyAttribute")
{
foreach(var arg in attr.NamedArguments)
{
if (arg.MemberName == "EntryPoint")
{
EntryPoint = arg.TypedValue.Value!.ToString();
lewing marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}

public string? EntryPoint;
public MethodInfo Method;
public string? EntryName;
}
Expand Down
Loading
Loading