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] Optimize out references to a bunch of BCL methods during JS interop startup #101217

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ internal static unsafe partial class Runtime
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void BindAssemblyExports(IntPtr assemblyNamePtr);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, IntPtr* monoMethodPtrPtr);
public static extern void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, int signatureHash, IntPtr* monoMethodPtrPtr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,15 +282,21 @@ public static unsafe Task BindAssemblyExports(string? assemblyName)
public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
var wrapper_name = $"__Wrapper_{methodName}_{signatureHash}";

var dllName = assemblyName + ".dll";

IntPtr monoMethod;
Interop.Runtime.GetAssemblyExport(
// FIXME: Pass UTF-16 through directly so C can work with it, doing the conversion
// in C# pulls in a bunch of dependencies we don't need this early in startup.
// I tested removing the UTF8 conversion from this specific call, but other parts
// of startup I can't identify still pull in UTF16->UTF8 conversion, so it's not
// worth it to do that yet.
Marshal.StringToCoTaskMemUTF8(dllName),
Marshal.StringToCoTaskMemUTF8(nameSpace),
Marshal.StringToCoTaskMemUTF8(shortClassName),
Marshal.StringToCoTaskMemUTF8(wrapper_name),
Marshal.StringToCoTaskMemUTF8(methodName),
signatureHash,
&monoMethod);

if (monoMethod == IntPtr.Zero)
Expand Down Expand Up @@ -327,16 +333,56 @@ public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
return temp.methodHandle;
}

// The BCL implementations of IndexOf/LastIndexOf/Trim are vectorized & fast,
// but they pull in a bunch of code that is otherwise not necessarily
// useful during early app startup, so we use simple scalar implementations
private static int SmallIndexOf (string s, char ch, int direction = 1) {
if (s.Length < 1)
return -1;
int start_index = (direction > 0) ? 0 : s.Length - 1,
end_index = (direction > 0) ? s.Length - 1 : 0;
for (int i = start_index; i != end_index; i += direction) {
if (s[i] == ch)
return i;
}
return -1;
}

private static string SmallTrim (string s) {
if (s.Length < 1)
return s;
int head = 0, tail = s.Length - 1;
while (head < s.Length) {
if (s[head] == ' ')
head++;
else
break;
}
while (tail >= 0) {
if (s[tail] == ' ')
tail--;
else
break;
}
if ((head > 0) || (tail < s.Length - 1))
return s.Substring(head, tail - head + 1);
else
return s;
}

public static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn)
{
var assembly = fqn.Substring(fqn.IndexOf('[') + 1, fqn.IndexOf(']') - 1).Trim();
fqn = fqn.Substring(fqn.IndexOf(']') + 1).Trim();
var methodName = fqn.Substring(fqn.IndexOf(':') + 1);
var className = fqn.Substring(0, fqn.IndexOf(':')).Trim();
var assembly = fqn.Substring(SmallIndexOf(fqn, '[') + 1, SmallIndexOf(fqn, ']') - 1);
fqn = SmallTrim(fqn);
fqn = fqn.Substring(SmallIndexOf(fqn, ']') + 1);
fqn = SmallTrim(fqn);
var methodName = fqn.Substring(SmallIndexOf(fqn, ':') + 1);
var className = fqn.Substring(0, SmallIndexOf(fqn, ':'));
className = SmallTrim(className);

var nameSpace = "";
var shortClassName = className;
var idx = fqn.LastIndexOf('.');
var idx = SmallIndexOf(fqn, '.', -1);
if (idx != -1)
{
nameSpace = fqn.Substring(0, idx);
Expand Down
12 changes: 9 additions & 3 deletions src/mono/browser/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ typedef void (*background_job_cb)(void);

void mono_wasm_bind_assembly_exports (char *assembly_name);
void mono_wasm_assembly_get_entry_point (char *assembly_name, int auto_insert_breakpoint, MonoMethod **method_out);
void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *classname, char *methodname, MonoMethod **method_out);
void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *classname, char *methodname, int signature_hash, MonoMethod **method_out);

#ifndef DISABLE_THREADS
void mono_wasm_release_cs_owned_object_post (pthread_t target_tid, int js_handle);
Expand Down Expand Up @@ -230,13 +230,14 @@ void mono_wasm_bind_assembly_exports (char *assembly_name)
}
}

void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *classname, char *methodname, MonoMethod **method_out)
void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *classname, char *methodname, int signature_hash, MonoMethod **method_out)
{
MonoError error;
MonoAssembly* assembly;
MonoImage *image;
MonoClass *klass;
MonoMethod *method=NULL;
char real_method_name_buffer[4096];
*method_out = NULL;

assert (assembly_name);
Expand All @@ -247,10 +248,15 @@ void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *

klass = mono_class_from_name (image, namespace, classname);
assert (klass);
method = mono_class_get_method_from_name (klass, methodname, -1);

snprintf(real_method_name_buffer, 4096, "__Wrapper_%s_%d", methodname, signature_hash);

method = mono_class_get_method_from_name (klass, real_method_name_buffer, -1);
assert (method);

*method_out = method;
// This is freed by _mono_wasm_assembly_load for some reason
// free (assembly_name);
free (namespace);
free (classname);
free (methodname);
Expand Down
Loading