Skip to content

Commit

Permalink
[wasm] JS interop without JS code gen - CSP friendly (#74441)
Browse files Browse the repository at this point in the history
- interpret signature data instead of generating the JS code
- tests
  • Loading branch information
pavelsavara authored Sep 1, 2022
1 parent d684343 commit a919d61
Show file tree
Hide file tree
Showing 16 changed files with 501 additions and 296 deletions.
2 changes: 2 additions & 0 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ internal static unsafe partial class Runtime
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeImport(IntPtr fn_handle, void* data);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void MarshalPromise(void* data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed partial class JSFunctionBinding
#region intentionally opaque internal structure
internal unsafe JSBindingHeader* Header;
internal unsafe JSBindingType* Sigs;// points to first arg, not exception, not result
internal JSObject? JSFunction;
internal IntPtr FnHandle;

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct JSBindingHeader
Expand Down Expand Up @@ -121,7 +121,7 @@ internal unsafe JSBindingType this[int position]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InvokeJS(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
{
InvokeJSImpl(signature.JSFunction!, arguments);
InvokeImportImpl(signature.FnHandle, arguments);
}

/// <summary>
Expand Down Expand Up @@ -167,6 +167,20 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerAr
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void InvokeImportImpl(IntPtr fnHandle, Span<JSMarshalerArgument> arguments)
{
fixed (JSMarshalerArgument* ptr = arguments)
{
Interop.Runtime.InvokeImport(fnHandle, ptr);
ref JSMarshalerArgument exceptionArg = ref arguments[0];
if (exceptionArg.slot.Type != MarshalerType.None)
{
JSHostImplementation.ThrowException(ref exceptionArg);
}
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
{
Expand All @@ -176,7 +190,7 @@ internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName,
if (isException != 0)
throw new JSException((string)exceptionMessage);

signature.JSFunction = JSHostImplementation.CreateCSOwnedProxy(jsFunctionHandle);
signature.FnHandle = jsFunctionHandle;

return signature;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ public unsafe void OutOfRange()
Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message);
}

[Fact]
public unsafe void OptimizedPaths()
{
JavaScriptTestHelper.optimizedReached = 0;
JavaScriptTestHelper.invoke0V();
Assert.Equal(1, JavaScriptTestHelper.optimizedReached);
JavaScriptTestHelper.invoke1V(42);
Assert.Equal(43, JavaScriptTestHelper.optimizedReached);
Assert.Equal(124, JavaScriptTestHelper.invoke1R(123));
Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached);
Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16));
Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached);
}


#region Get/Set Property

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,48 @@ public static DateTime Now()
return DateTime.Now;
}

// the methods in the region have signature for which we have optimized path in the call marshaler
// it's the combination of number of arguments and void vs result
// see mono_wasm_bind_js_function and mono_wasm_bind_cs_function
#region Optimized

public static int optimizedReached = 0;
[JSExport]
public static void Optimized0V()
{
optimizedReached++;
}
[JSImport("invoke0V", "JavaScriptTestHelper")]
public static partial void invoke0V();

[JSExport]
public static void Optimized1V(int a1)
{
optimizedReached+= a1;
}
[JSImport("invoke1V", "JavaScriptTestHelper")]
public static partial void invoke1V(int a1);

[JSExport]
public static int Optimized1R(int a1)
{
optimizedReached += a1;
return a1 + 1;
}
[JSImport("invoke1R", "JavaScriptTestHelper")]
public static partial int invoke1R(int a1);

[JSExport]
public static int Optimized2R(int a1, int a2)
{
optimizedReached += a1+ a2;
return a1 + a2 +1;
}
[JSImport("invoke2R", "JavaScriptTestHelper")]
public static partial int invoke2R(int a1, int a2);

#endregion

[JSImport("create_function", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>]
public static partial Func<int, int, int> createMath([JSMarshalAs<JSType.String>] string a, [JSMarshalAs<JSType.String>] string b, [JSMarshalAs<JSType.String>] string code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ export function getClass1() {
return cname;
}
let dllExports;

export function invoke0V() {
const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper;
const fn = JavaScriptTestHelper['Optimized0V'];
fn();
}

export function invoke1V(arg1) {
const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper;
const fn = JavaScriptTestHelper['Optimized1V'];
fn(arg1);
}

export function invoke1R(arg1) {
const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper;
const fn = JavaScriptTestHelper['Optimized1R'];
return fn(arg1);
}

export function invoke2R(arg1, arg2) {
const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper;
const fn = JavaScriptTestHelper['Optimized2R'];
return fn(arg1, arg2);
}

export function invoke1(arg1, name) {
if (globalThis.gc) {
// console.log('globalThis.gc');
Expand Down
7 changes: 6 additions & 1 deletion src/mono/sample/wasm/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
<Exec Command="dotnet tool install -g dotnet-serve" IgnoreExitCode="true" />
</Target>
<Target Name="RunSampleWithBrowser" DependsOnTargets="BuildSampleInTree;CheckServe">
<Exec Command="$(_Dotnet) serve -o -d:bin/$(Configuration)/AppBundle -p:8000 --mime .wasm=application/wasm --mime .mjs=text/javascript --mime .js=text/javascript --mime .cjs=text/javascript -h Cross-Origin-Opener-Policy:same-origin -h Cross-Origin-Embedder-Policy:require-corp " IgnoreExitCode="true" YieldDuringToolExecution="true" />
<!--
- we add MIME type for .wasm .mjs .js .cjs .json. Browsers require it for proper and fast execution. For example streaming instantiation of WASM module.
- we set `Cross-Origin-Opener-Policy` headers so that SharedArrayBuffer is enabled
- we set `Content-Security-Policy` headers to test that the app is able to run in environments with such restrictions.
-->
<Exec Command="$(_Dotnet) serve -o -d:bin/$(Configuration)/AppBundle -p:8000 --mime .wasm=application/wasm --mime .mjs=text/javascript --mime .js=text/javascript --mime .cjs=text/javascript --mime .json=application/json -h Cross-Origin-Opener-Policy:same-origin -h &quot;Cross-Origin-Embedder-Policy:require-corp&quot; -h &quot;Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'&quot; " IgnoreExitCode="true" YieldDuringToolExecution="true" />
</Target>
<Target Name="RunSampleWithBrowserAndSimpleServer" DependsOnTargets="BuildSampleInTree">
<Exec Command="$(_Dotnet) build -c $(Configuration) ..\simple-server\HttpServer.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions src/mono/wasm/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern void mono_wasm_typed_array_from_ref (int ptr, int begin, int end, int byt

extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString **module_name, void *signature, int* function_js_handle, int *is_exception, MonoObject **result);
extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data);
extern void mono_wasm_invoke_import(int fn_handle, void *data);
extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result);
extern void mono_wasm_marshal_promise(void *data);

Expand All @@ -49,6 +50,7 @@ void core_initialize_internals ()

mono_add_internal_call ("Interop/Runtime::BindJSFunction", mono_wasm_bind_js_function);
mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function);
mono_add_internal_call ("Interop/Runtime::InvokeImport", mono_wasm_invoke_import);
mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function);
mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise);
mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root);
Expand Down
1 change: 1 addition & 0 deletions src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const linked_functions = [
"mono_wasm_compile_function_ref",
"mono_wasm_bind_js_function",
"mono_wasm_invoke_bound_function",
"mono_wasm_invoke_import",
"mono_wasm_bind_cs_function",
"mono_wasm_marshal_promise",

Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/exports-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_was
import { mono_wasm_release_cs_owned_object } from "./gc-handles";
import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu";
import { mono_wasm_bind_cs_function } from "./invoke-cs";
import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function } from "./invoke-js";
import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_invoke_import } from "./invoke-js";
import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers";
import {
mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref,
Expand Down Expand Up @@ -70,6 +70,7 @@ export function export_linker(): any {
mono_wasm_typed_array_from_ref,
mono_wasm_bind_js_function,
mono_wasm_invoke_bound_function,
mono_wasm_invoke_import,
mono_wasm_bind_cs_function,
mono_wasm_marshal_promise,

Expand Down
Loading

0 comments on commit a919d61

Please sign in to comment.