Skip to content

Commit 1f00cda

Browse files
mkhamoyanlewing
andauthored
[mono][wasm] Force interpreter to initialize the pointers (#100288)
Force interpreter to initialize the pointers --------- Co-authored-by: Larry Ewing <lewing@microsoft.com>
1 parent d80a09f commit 1f00cda

File tree

6 files changed

+131
-6
lines changed

6 files changed

+131
-6
lines changed

eng/testing/scenarios/BuildWasiAppsJobsList.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ Wasi.Build.Tests.ILStripTests
33
Wasi.Build.Tests.SdkMissingTests
44
Wasi.Build.Tests.RuntimeConfigTests
55
Wasi.Build.Tests.WasiTemplateTests
6+
Wasi.Build.Tests.PInvokeTableGeneratorTests

src/mono/browser/runtime/runtime.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ int monoeg_g_setenv(const char *variable, const char *value, int overwrite);
7676
int32_t mini_parse_debug_option (const char *option);
7777
char *mono_method_get_full_name (MonoMethod *method);
7878
void mono_trace_init (void);
79+
MonoMethod *mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, MonoError *error);
7980

8081
/* Not part of public headers */
8182
#define MONO_ICALL_TABLE_CALLBACKS_VERSION 3
@@ -356,3 +357,25 @@ mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int argument
356357
MONO_EXIT_GC_UNSAFE;
357358
return result;
358359
}
360+
361+
/*
362+
* mono_wasm_marshal_get_managed_wrapper:
363+
* Creates a wrapper for a function pointer to a method marked with
364+
* UnamangedCallersOnlyAttribute.
365+
* This wrapper ensures that the interpreter initializes the pointers.
366+
*/
367+
void
368+
mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* typeName, const char* methodName, int num_params)
369+
{
370+
MonoError error;
371+
mono_error_init (&error);
372+
MonoAssembly* assembly = mono_wasm_assembly_load (assemblyName);
373+
assert (assembly);
374+
MonoClass* class = mono_wasm_assembly_find_class (assembly, "", typeName);
375+
assert (class);
376+
MonoMethod* method = mono_wasm_assembly_find_method (class, methodName, num_params);
377+
assert (method);
378+
MonoMethod *managedWrapper = mono_marshal_get_managed_wrapper (method, NULL, 0, &error);
379+
assert (managedWrapper);
380+
mono_compile_method (managedWrapper);
381+
}

src/mono/browser/runtime/runtime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ extern int mono_wasm_enable_gc;
1818
MonoDomain *mono_wasm_load_runtime_common (int debug_level, MonoLogCallback log_callback, const char *interp_opts);
1919
MonoAssembly *mono_wasm_assembly_load (const char *name);
2020
MonoClass *mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name);
21+
MonoMethod *mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments);
22+
void mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* typeName, const char* methodName, int num_params);
2123

2224
#endif

src/mono/sample/wasi/native/Program.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ public static int MyExport(int number)
2020
public unsafe static int Main(string[] args)
2121
{
2222
Console.WriteLine($"main: {args.Length}");
23-
// workaround to force the interpreter to initialize wasm_native_to_interp_ftndesc for MyExport
24-
if (args.Length > 10000) {
25-
((IntPtr)(delegate* unmanaged<int,int>)&MyExport).ToString();
26-
}
27-
2823
MyImport();
2924
return 0;
3025
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.IO;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
using Wasm.Build.Tests;
8+
9+
#nullable enable
10+
11+
namespace Wasi.Build.Tests;
12+
13+
public class PInvokeTableGeneratorTests : BuildTestBase
14+
{
15+
public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
16+
: base(output, buildContext)
17+
{
18+
}
19+
20+
[Fact]
21+
public void InteropSupportForUnmanagedEntryPointWithoutDelegate()
22+
{
23+
string config = "Release";
24+
string id = $"{config}_{GetRandomId()}";
25+
string projectFile = CreateWasmTemplateProject(id, "wasiconsole");
26+
string code =
27+
"""
28+
using System;
29+
using System.Runtime.InteropServices;
30+
public unsafe class Test
31+
{
32+
[UnmanagedCallersOnly(EntryPoint = "ManagedFunc")]
33+
public static int MyExport(int number)
34+
{
35+
// called from MyImport aka UnmanagedFunc
36+
Console.WriteLine($"MyExport({number}) -> 42");
37+
return 42;
38+
}
39+
40+
[DllImport("*", EntryPoint = "UnmanagedFunc")]
41+
public static extern void MyImport(); // calls ManagedFunc aka MyExport
42+
43+
public unsafe static int Main(string[] args)
44+
{
45+
Console.WriteLine($"main: {args.Length}");
46+
MyImport();
47+
return 0;
48+
}
49+
}
50+
""";
51+
string cCode =
52+
"""
53+
#include <stdio.h>
54+
55+
int ManagedFunc(int number);
56+
57+
void UnmanagedFunc()
58+
{
59+
int ret = 0;
60+
printf("UnmanagedFunc calling ManagedFunc\n");
61+
ret = ManagedFunc(123);
62+
printf("ManagedFunc returned %d\n", ret);
63+
}
64+
""";
65+
File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code);
66+
File.WriteAllText(Path.Combine(_projectDir!, "local.c"), cCode);
67+
string extraProperties = @"<WasmNativeStrip>false</WasmNativeStrip>
68+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>";
69+
AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties, extraItems: @"<NativeFileReference Include=""local.c"" />");
70+
string projectName = Path.GetFileNameWithoutExtension(projectFile);
71+
var buildArgs = new BuildArgs(projectName, config, AOT: true, ProjectFileContents: id, ExtraBuildArgs: null);
72+
buildArgs = ExpandBuildArgs(buildArgs);
73+
BuildProject(buildArgs,
74+
id: id,
75+
new BuildProjectOptions(
76+
DotnetWasmFromRuntimePack: false,
77+
CreateProject: false,
78+
Publish: true
79+
));
80+
81+
CommandResult res = new RunCommand(s_buildEnv, _testOutput)
82+
.WithWorkingDirectory(_projectDir!)
83+
.ExecuteWithCapturedOutput($"run --no-silent --no-build -c {config}")
84+
.EnsureSuccessful();
85+
Assert.Contains("MyExport(123) -> 42", res.Output);
86+
}
87+
}

src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,14 @@ private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
323323
// Only blittable parameter/return types are supposed.
324324
int cb_index = 0;
325325

326+
w.Write(@"#include <mono/utils/details/mono-error-types.h>
327+
#include <mono/metadata/assembly.h>
328+
#include <mono/utils/mono-error.h>
329+
#include <mono/metadata/object.h>
330+
#include <mono/utils/details/mono-logger-types.h>
331+
#include ""runtime.h""
332+
");
333+
326334
// Arguments to interp entry functions in the runtime
327335
w.WriteLine($"InterpFtnDesc wasm_native_to_interp_ftndescs[{callbacks.Count}] = {{}};");
328336

@@ -371,7 +379,16 @@ private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
371379
if (!is_void)
372380
sb.Append($" {MapType(method.ReturnType)} res;\n");
373381

374-
//sb.Append($" printf(\"{entry_name} called\\n\");\n");
382+
// In case when null force interpreter to initialize the pointers
383+
sb.Append($" if (!(WasmInterpEntrySig_{cb_index})wasm_native_to_interp_ftndescs [{cb_index}].func) {{\n");
384+
var assemblyFullName = cb.Method.DeclaringType == null ? "" : cb.Method.DeclaringType.Assembly.FullName;
385+
var assemblyName = assemblyFullName != null && assemblyFullName.Split(',').Length > 0 ? assemblyFullName.Split(',')[0].Trim() : "";
386+
var typeName = cb.Method.DeclaringType == null || cb.Method.DeclaringType.FullName == null ? "" : cb.Method.DeclaringType.FullName;
387+
var methodName = cb.Method.Name;
388+
int numParams = method.GetParameters().Length;
389+
sb.Append($" mono_wasm_marshal_get_managed_wrapper (\"{assemblyName}\", \"{typeName}\", \"{methodName}\", {numParams});\n");
390+
sb.Append($" }}\n");
391+
375392
sb.Append($" ((WasmInterpEntrySig_{cb_index})wasm_native_to_interp_ftndescs [{cb_index}].func) (");
376393
if (!is_void)
377394
{

0 commit comments

Comments
 (0)