Skip to content

Commit 7ae87de

Browse files
authored
[wasm] more cases when looking up unmanaged delegates (#107113)
Make the association between the wasm_native_to_interp_ftndescs generation and the lookup from unmanaged more robust so that we don't see problems like #107212 where the same slot was being reused for multiple methods with different signatures. To do this we change the Key(s) we use and fix the string escaping it relies on, and attempt to lookup by token first. Next , we rewrite the C code generation to make it easier to read and modify and mitigate some potentially negative memory side effects of that we introduce a gratuitous custom text writer that understands the idea of concatenated strings and use that where possible when building the output. Next, we change the import code generation to use binary rather than linear search for both the module and symbol. And finally, we update the ICall table generation to use the extensions. part of #104391 and #107212
1 parent 1808129 commit 7ae87de

File tree

11 files changed

+549
-227
lines changed

11 files changed

+549
-227
lines changed

src/mono/browser/runtime/pinvoke.c

+47-15
Original file line numberDiff line numberDiff line change
@@ -23,39 +23,71 @@ mono_wasm_pinvoke_vararg_stub (void)
2323
/* This is just a stub used to mark vararg pinvokes */
2424
}
2525

26+
int
27+
table_compare_name (const void *t1, const void *t2)
28+
{
29+
return strcmp (((PinvokeTable*)t1)->name, ((PinvokeTable*)t2)->name);
30+
}
31+
2632
void*
2733
wasm_dl_lookup_pinvoke_table (const char *name)
2834
{
29-
for (int i = 0; i < sizeof (pinvoke_tables) / sizeof (void*); ++i) {
30-
if (!strcmp (name, pinvoke_names [i]))
31-
return pinvoke_tables [i];
32-
}
33-
return NULL;
35+
PinvokeImport needle = { name, NULL };
36+
return bsearch (&needle, pinvoke_tables, (sizeof (pinvoke_tables) / sizeof (PinvokeTable)), sizeof (PinvokeTable), table_compare_name);
3437
}
3538

3639
int
3740
wasm_dl_is_pinvoke_table (void *handle)
3841
{
39-
for (int i = 0; i < sizeof (pinvoke_tables) / sizeof (void*); ++i) {
40-
if (pinvoke_tables [i] == handle) {
42+
for (int i = 0; i < sizeof (pinvoke_tables) / sizeof (PinvokeTable); ++i) {
43+
if (&pinvoke_tables[i] == handle) {
4144
return 1;
4245
}
4346
}
4447
return 0;
4548
}
4649

50+
static int
51+
export_compare_key (const void *k1, const void *k2)
52+
{
53+
return strcmp (((UnmanagedExport*)k1)->key, ((UnmanagedExport*)k2)->key);
54+
}
55+
56+
static int
57+
export_compare_key_and_token (const void *k1, const void *k2)
58+
{
59+
UnmanagedExport *e1 = (UnmanagedExport*)k1;
60+
UnmanagedExport *e2 = (UnmanagedExport*)k2;
61+
62+
// first compare by key
63+
int compare = strcmp (e1->key, e2->key);
64+
if (compare)
65+
return compare;
66+
67+
// then by token
68+
return (int)(e1->token - e2->token);
69+
}
70+
4771
void*
48-
wasm_dl_get_native_to_interp (const char *key, void *extra_arg)
72+
wasm_dl_get_native_to_interp (uint32_t token, const char *key, void *extra_arg)
4973
{
5074
#ifdef GEN_PINVOKE
51-
for (int i = 0; i < sizeof (wasm_native_to_interp_map) / sizeof (void*); ++i) {
52-
if (!strcmp (wasm_native_to_interp_map [i], key)) {
53-
void *addr = wasm_native_to_interp_funcs [i];
54-
wasm_native_to_interp_ftndescs [i] = *(InterpFtnDesc*)extra_arg;
55-
return addr;
56-
}
75+
UnmanagedExport needle = { key, token, NULL };
76+
int count = (sizeof (wasm_native_to_interp_table) / sizeof (UnmanagedExport));
77+
78+
// comparison must match the one used in the PInvokeTableGenerator to ensure the same order
79+
UnmanagedExport *result = bsearch (&needle, wasm_native_to_interp_table, count, sizeof (UnmanagedExport), export_compare_key_and_token);
80+
if (!result) {
81+
// assembly may have been trimmed / modified, try to find by key only
82+
result = bsearch (&needle, wasm_native_to_interp_table, count, sizeof (UnmanagedExport), export_compare_key);
5783
}
58-
return NULL;
84+
85+
if (!result)
86+
return NULL;
87+
88+
void *addr = result->func;
89+
wasm_native_to_interp_ftndescs [result - wasm_native_to_interp_table] = *(InterpFtnDesc*)extra_arg;
90+
return addr;
5991
#else
6092
return NULL;
6193
#endif

src/mono/browser/runtime/pinvoke.h

+14-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ typedef struct {
88
void *func;
99
} PinvokeImport;
1010

11+
typedef struct {
12+
const char *name;
13+
PinvokeImport *imports;
14+
int count;
15+
} PinvokeTable;
16+
17+
typedef struct {
18+
const char *key;
19+
uint32_t token;
20+
void *func;
21+
} UnmanagedExport;
22+
1123
typedef struct {
1224
void *func;
1325
void *arg;
@@ -20,7 +32,7 @@ int
2032
wasm_dl_is_pinvoke_table (void *handle);
2133

2234
void*
23-
wasm_dl_get_native_to_interp (const char *key, void *extra_arg);
35+
wasm_dl_get_native_to_interp (uint32_t token, const char *key, void *extra_arg);
2436

2537
void
2638
mono_wasm_pinvoke_vararg_stub (void);
@@ -45,6 +57,6 @@ double
4557
mono_wasm_interp_method_args_get_darg (MonoInterpMethodArguments *margs, int i);
4658

4759
void*
48-
mono_wasm_interp_method_args_get_retval (MonoInterpMethodArguments *margs);
60+
mono_wasm_interp_method_args_get_retval (MonoInterpMethodArguments *margs);
4961

5062
#endif

src/mono/browser/runtime/runtime.c

+72-23
Original file line numberDiff line numberDiff line change
@@ -199,29 +199,43 @@ init_icall_table (void)
199199
static void*
200200
get_native_to_interp (MonoMethod *method, void *extra_arg)
201201
{
202-
void *addr;
203-
202+
void *addr = NULL;
204203
MONO_ENTER_GC_UNSAFE;
205204
MonoClass *klass = mono_method_get_class (method);
206205
MonoImage *image = mono_class_get_image (klass);
207206
MonoAssembly *assembly = mono_image_get_assembly (image);
208207
MonoAssemblyName *aname = mono_assembly_get_name (assembly);
209208
const char *name = mono_assembly_name_get_name (aname);
209+
const char *namespace = mono_class_get_namespace (klass);
210210
const char *class_name = mono_class_get_name (klass);
211211
const char *method_name = mono_method_get_name (method);
212-
char key [128];
212+
MonoMethodSignature *sig = mono_method_signature (method);
213+
uint32_t param_count = mono_signature_get_param_count (sig);
214+
uint32_t token = mono_method_get_token (method);
215+
216+
char buf [128];
217+
char *key = buf;
213218
int len;
219+
if (name != NULL) {
220+
// the key must match the one used in PInvokeTableGenerator
221+
len = snprintf (key, sizeof(buf), "%s#%d:%s:%s:%s", method_name, param_count, name, namespace, class_name);
222+
223+
if (len >= sizeof (buf)) {
224+
// The key is too long, try again with a larger buffer
225+
key = g_new (char, len + 1);
226+
snprintf (key, len + 1, "%s#%d:%s:%s:%s", method_name, param_count, name, namespace, class_name);
227+
}
214228

215-
assert (strlen (name) < 100);
216-
snprintf (key, sizeof(key), "%s_%s_%s", name, class_name, method_name);
217-
char *fixedName = mono_fixup_symbol_name ("", key, "");
218-
addr = wasm_dl_get_native_to_interp (fixedName, extra_arg);
219-
free (fixedName);
229+
addr = wasm_dl_get_native_to_interp (token, key, extra_arg);
230+
231+
if (key != buf)
232+
free (key);
233+
}
220234
MONO_EXIT_GC_UNSAFE;
221235
return addr;
222236
}
223237

224-
static void *sysglobal_native_handle;
238+
static void *sysglobal_native_handle = (void *)0xDeadBeef;
225239

226240
static void*
227241
wasm_dl_load (const char *name, int flags, char **err, void *user_data)
@@ -248,24 +262,33 @@ wasm_dl_load (const char *name, int flags, char **err, void *user_data)
248262
return NULL;
249263
}
250264

265+
int
266+
import_compare_name (const void *k1, const void *k2)
267+
{
268+
const PinvokeImport *e1 = (const PinvokeImport*)k1;
269+
const PinvokeImport *e2 = (const PinvokeImport*)k2;
270+
271+
return strcmp (e1->name, e2->name);
272+
}
273+
251274
static void*
252275
wasm_dl_symbol (void *handle, const char *name, char **err, void *user_data)
253276
{
254-
if (handle == sysglobal_native_handle)
255-
assert (0);
277+
assert (handle != sysglobal_native_handle);
256278

257279
#if WASM_SUPPORTS_DLOPEN
258280
if (!wasm_dl_is_pinvoke_tables (handle)) {
259281
return dlsym (handle, name);
260282
}
261283
#endif
262-
263-
PinvokeImport *table = (PinvokeImport*)handle;
264-
for (int i = 0; table [i].name; ++i) {
265-
if (!strcmp (table [i].name, name))
266-
return table [i].func;
267-
}
268-
return NULL;
284+
PinvokeTable* index = (PinvokeTable*)handle;
285+
PinvokeImport key = { name, NULL };
286+
PinvokeImport* result = (PinvokeImport *)bsearch(&key, index->imports, index->count, sizeof(PinvokeImport), import_compare_name);
287+
if (!result) {
288+
// *err = g_strdup_printf ("Symbol not found: %s", name);
289+
return NULL;
290+
}
291+
return result->func;
269292
}
270293

271294
MonoDomain *
@@ -363,24 +386,50 @@ mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int argument
363386
return result;
364387
}
365388

389+
MonoMethod*
390+
mono_wasm_get_method_matching (MonoImage *image, uint32_t token, MonoClass *klass, const char* name, int param_count)
391+
{
392+
MonoMethod *result = NULL;
393+
MONO_ENTER_GC_UNSAFE;
394+
MonoMethod *method = mono_get_method (image, token, klass);
395+
MonoMethodSignature *sig = mono_method_signature (method);
396+
// Lookp by token but verify the name and param count in case assembly was trimmed
397+
if (mono_signature_get_param_count (sig) == param_count) {
398+
const char *method_name = mono_method_get_name (method);
399+
if (!strcmp (method_name, name)) {
400+
result = method;
401+
}
402+
}
403+
// If the token lookup failed, try to find the method by name and param count
404+
if (!result) {
405+
result = mono_class_get_method_from_name (klass, name, param_count);
406+
}
407+
MONO_EXIT_GC_UNSAFE;
408+
return result;
409+
}
410+
366411
/*
367412
* mono_wasm_marshal_get_managed_wrapper:
368413
* Creates a wrapper for a function pointer to a method marked with
369414
* UnamangedCallersOnlyAttribute.
370415
* This wrapper ensures that the interpreter initializes the pointers.
371416
*/
372417
void
373-
mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* namespaceName, const char* typeName, const char* methodName, int num_params)
418+
mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* namespaceName, const char* typeName, const char* methodName, uint32_t token, int param_count)
374419
{
375420
MonoError error;
376421
mono_error_init (&error);
422+
MONO_ENTER_GC_UNSAFE;
377423
MonoAssembly* assembly = mono_wasm_assembly_load (assemblyName);
378424
assert (assembly);
379-
MonoClass* class = mono_wasm_assembly_find_class (assembly, namespaceName, typeName);
380-
assert (class);
381-
MonoMethod* method = mono_wasm_assembly_find_method (class, methodName, num_params);
425+
MonoImage *image = mono_assembly_get_image (assembly);
426+
assert (image);
427+
MonoClass* klass = mono_class_from_name (image, namespaceName, typeName);
428+
assert (klass);
429+
MonoMethod *method = mono_wasm_get_method_matching (image, token, klass, methodName, param_count);
382430
assert (method);
383431
MonoMethod *managedWrapper = mono_marshal_get_managed_wrapper (method, NULL, 0, &error);
384432
assert (managedWrapper);
385433
mono_compile_method (managedWrapper);
386-
}
434+
MONO_EXIT_GC_UNSAFE;
435+
}

src/mono/browser/runtime/runtime.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ MonoDomain *mono_wasm_load_runtime_common (int debug_level, MonoLogCallback log_
1919
MonoAssembly *mono_wasm_assembly_load (const char *name);
2020
MonoClass *mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name);
2121
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* namespaceName, const char* typeName, const char* methodName, int num_params);
22+
void mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* namespaceName, const char* typeName, const char* methodName, uint32_t token, int param_count);
2323
int initialize_runtime ();
2424

2525
#endif

src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs

+66
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,71 @@ file class Foo
437437
Assert.Contains("Main running", output);
438438
}
439439

440+
[Theory]
441+
[BuildAndRun(host: RunHost.Chrome)]
442+
public void UnmanagedCallersOnly_Namespaced(BuildArgs buildArgs, RunHost host, string id)
443+
{
444+
string code =
445+
"""
446+
using System;
447+
using System.Runtime.InteropServices;
448+
449+
public class Test
450+
{
451+
public unsafe static int Main()
452+
{
453+
((delegate* unmanaged<void>)&A.Conflict.C)();
454+
((delegate* unmanaged<void>)&B.Conflict.C)();
455+
((delegate* unmanaged<void>)&A.Conflict.C\u733f)();
456+
((delegate* unmanaged<void>)&B.Conflict.C\u733f)();
457+
return 42;
458+
}
459+
}
460+
461+
namespace A {
462+
public class Conflict {
463+
[UnmanagedCallersOnly(EntryPoint = "A_Conflict_C")]
464+
public static void C() {
465+
Console.WriteLine("A.Conflict.C");
466+
}
467+
468+
[UnmanagedCallersOnly(EntryPoint = "A_Conflict_C\u733f")]
469+
public static void C\u733f() {
470+
Console.WriteLine("A.Conflict.C\U0001F412");
471+
}
472+
}
473+
}
474+
475+
namespace B {
476+
public class Conflict {
477+
[UnmanagedCallersOnly(EntryPoint = "B_Conflict_C")]
478+
public static void C() {
479+
Console.WriteLine("B.Conflict.C");
480+
}
481+
482+
[UnmanagedCallersOnly(EntryPoint = "B_Conflict_C\u733f")]
483+
public static void C\u733f() {
484+
Console.WriteLine("B.Conflict.C\U0001F412");
485+
}
486+
}
487+
}
488+
""";
489+
490+
(buildArgs, string output) = BuildForVariadicFunctionTests(
491+
code,
492+
buildArgs with { ProjectName = $"cb_namespace_{buildArgs.Config}" },
493+
id
494+
);
495+
496+
Assert.DoesNotMatch(".*(warning|error).*>[A-Z0-9]+__Foo", output);
497+
498+
output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id);
499+
Assert.Contains("A.Conflict.C", output);
500+
Assert.Contains("B.Conflict.C", output);
501+
Assert.Contains("A.Conflict.C\U0001F412", output);
502+
Assert.Contains("B.Conflict.C\U0001F412", output);
503+
}
504+
440505
[Theory]
441506
[BuildAndRun(host: RunHost.None)]
442507
public void IcallWithOverloadedParametersAndEnum(BuildArgs buildArgs, string id)
@@ -951,6 +1016,7 @@ public void UCOWithSpecialCharacters(BuildArgs buildArgs, RunHost host, string i
9511016
DotnetWasmFromRuntimePack: false));
9521017

9531018
var runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id);
1019+
Assert.DoesNotContain("Conflict.A.Managed8\u4F60Func(123) -> 123", runOutput);
9541020
Assert.Contains("ManagedFunc returned 42", runOutput);
9551021
}
9561022
}

src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ public unsafe partial class Test
66
public unsafe static int Main(string[] args)
77
{
88
((IntPtr)(delegate* unmanaged<int,int>)&Interop.Managed8\u4F60Func).ToString();
9-
109
Console.WriteLine($"main: {args.Length}");
1110
Interop.UnmanagedFunc();
11+
1212
return 42;
1313
}
1414
}
1515

16+
namespace Conflict.A {
17+
file class Interop {
18+
[UnmanagedCallersOnly(EntryPoint = "ConflictManagedFunc")]
19+
public static int Managed8\u4F60Func(int number)
20+
{
21+
Console.WriteLine($"Conflict.A.Managed8\u4F60Func({number}) -> {number}");
22+
return number;
23+
}
24+
}
25+
}
26+
1627
file partial class Interop
1728
{
1829
[UnmanagedCallersOnly(EntryPoint = "ManagedFunc")]

src/mono/wasm/testassets/native-libs/local.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ void UnmanagedFunc()
77
printf("UnmanagedFunc calling ManagedFunc\n");
88
ret = ManagedFunc(123);
99
printf("ManagedFunc returned %d\n", ret);
10-
}
10+
}

0 commit comments

Comments
 (0)