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] optional legacy JS interop #79622

Closed
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
<TargetFrameworks>$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<FeatureWasmThreads Condition="'$(TargetOS)' == 'Browser' and ('$(WasmEnableThreads)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread')">true</FeatureWasmThreads>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' == 'true'">false</FeatureWasmLegacyJsInterop>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' != 'true'">true</FeatureWasmLegacyJsInterop>
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'" >$(DefineConstants);FEATURE_LEGACY_JS_INTEROP</DefineConstants>
</PropertyGroup>

<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
Expand All @@ -21,15 +24,6 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptImports.Generated.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptExports.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptImports.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Interop\LegacyExports.cs" />

<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Runtime.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Array.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\ArrayBuffer.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\DataView.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Function.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Uint8Array.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\LegacyHostImplementation.cs" />

<Compile Include="System\Runtime\InteropServices\JavaScript\JSHost.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSMarshalerType.cs" />
Expand Down Expand Up @@ -67,6 +61,16 @@

<Compile Include="System\Runtime\InteropServices\JavaScript\JSSynchronizationContext.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser' and '$(FeatureWasmLegacyJsInterop)' == 'true'">
<Compile Include="System\Runtime\InteropServices\JavaScript\Interop\LegacyExports.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Runtime.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Array.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\ArrayBuffer.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\DataView.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Function.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Uint8Array.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\LegacyHostImplementation.cs" />
</ItemGroup>

<ItemGroup>
<Reference Include="System.Collections" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static void MarshalPromise(Span<JSMarshalerArgument> arguments)
}

#region legacy
#if FEATURE_LEGACY_JS_INTEROP

public static object GetGlobalObject(string? str = null)
{
Expand All @@ -30,7 +31,7 @@ public static object GetGlobalObject(string? str = null)
if (exception != 0)
throw new JSException(SR.Format(SR.ErrorResolvingFromGlobalThis, str));

JSHostImplementation.ReleaseInFlight(jsObj);
LegacyHostImplementation.ReleaseInFlight(jsObj);
return jsObj;
}

Expand All @@ -43,6 +44,7 @@ public static IntPtr CreateCSOwnedObject(string typeName, object[] parms)
return (IntPtr)(int)res;
}

#endif
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ public static void InvokeJS(JSFunctionBinding signature, Span<JSMarshalerArgumen
/// <exception cref="PlatformNotSupportedException">The method is executed on an architecture other than WebAssembly.</exception>
// JavaScriptExports need to be protected from trimming because they are used from C/JS code which IL linker can't see
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JavaScriptExports", "System.Runtime.InteropServices.JavaScript")]
// TODO make this DynamicDependency conditional
#if FEATURE_LEGACY_JS_INTEROP
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.LegacyExports", "System.Runtime.InteropServices.JavaScript")]
#endif
Comment on lines +145 to +147
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not let us to remove LegacyExports because it's a library compile time setting. We could remove this DynamicDependency and use XML descriptor with System.Runtime.InteropServices.JavaScript.LegacyExports and pass it to linker only when legacy interop is enabled.

Copy link
Member Author

Choose a reason for hiding this comment

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

That XML would become embedded resource, right ? How do I tell the linker to ignore it or not in WasmApp.Native.targets ?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be additional file passed conditionally to the linker (we run it all the time to this should work).

Copy link
Member Author

Choose a reason for hiding this comment

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

For threaded runtime flavor everything would be already trimmed away.

For normal runtime flavor we would keep the code with DynamicDependency above.
User would be able to opt in for trimming on customer's dev machine.

I can follow example of WasmEnableSIMD and ILLink.Substitutions.WasmIntrinsics.xml.
Except I need ILLink.LinkAttributes.xml which could remove the attribute ?

Copy link
Contributor

Choose a reason for hiding this comment

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

That won't work DynamicDependency is always processed and it only removed later. The attribute cannot be visible to linker in this case to work as expected.

Copy link
Member

Choose a reason for hiding this comment

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

We should discuss the mechanisms we need in the regular build to pass additional linker files based on options as part of the bunder/sdk changes

public static JSFunctionBinding BindJSFunction(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
{
if (RuntimeInformation.OSArchitecture != Architecture.Wasm)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ public static void ReleaseCSOwnedObject(nint jsHandle)
throw new InvalidOperationException();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReleaseInFlight(object obj)
{
JSObject? jsObj = obj as JSObject;
jsObj?.ReleaseInFlight();
}

// A JSOwnedObject is a managed object with its lifetime controlled by javascript.
// The managed side maintains a strong reference to the object, while the JS side
// maintains a weak reference and notifies the managed side if the JS wrapper object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@ public partial class JSObject
{
internal nint JSHandle;

#if FEATURE_LEGACY_JS_INTEROP
internal GCHandle? InFlight;
internal int InFlightCounter;
#endif
private bool _isDisposed;

internal JSObject(IntPtr jsHandle)
{
JSHandle = jsHandle;
#if FEATURE_LEGACY_JS_INTEROP
InFlight = null;
InFlightCounter = 0;
#endif
}

#if FEATURE_LEGACY_JS_INTEROP
internal void AddInFlight()
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
Expand Down Expand Up @@ -53,6 +58,7 @@ internal void ReleaseInFlight()
}
}
}
#endif

/// <inheritdoc />
public override bool Equals([NotNullWhen(true)] object? obj) => obj is JSObject other && JSHandle == other.JSHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public object this[int i]

if (exception != 0)
throw new JSException((string)indexValue);
JSHostImplementation.ReleaseInFlight(indexValue);
LegacyHostImplementation.ReleaseInFlight(indexValue);
return indexValue;
}
set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ namespace System.Runtime.InteropServices.JavaScript
[SupportedOSPlatform("browser")]
internal static class LegacyHostImplementation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReleaseInFlight(object obj)
{
JSObject? jsObj = obj as JSObject;
jsObj?.ReleaseInFlight();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterCSOwnedObject(JSObject proxy)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static object Invoke(this JSObject self, string method, params object?[]
Interop.Runtime.InvokeJSWithArgsRef(self.JSHandle, method, args, out int exception, out object res);
if (exception != 0)
throw new JSException((string)res);
JSHostImplementation.ReleaseInFlight(res);
LegacyHostImplementation.ReleaseInFlight(res);
return res;
}

Expand Down Expand Up @@ -74,7 +74,7 @@ public static object GetObjectProperty(this JSObject self, string name)
Interop.Runtime.GetObjectPropertyRef(self.JSHandle, name, out int exception, out object propertyValue);
if (exception != 0)
throw new JSException((string)propertyValue);
JSHostImplementation.ReleaseInFlight(propertyValue);
LegacyHostImplementation.ReleaseInFlight(propertyValue);
return propertyValue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@
<TestRuntime>true</TestRuntime>
<WasmXHarnessArgs>$(WasmXHarnessArgs) --engine-arg=--expose-gc --web-server-use-cop</WasmXHarnessArgs>
<NoWarn>0612</NoWarn>
<FeatureWasmThreads Condition="'$(TargetOS)' == 'Browser' and ('$(WasmEnableThreads)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread')">true</FeatureWasmThreads>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' == 'true'">false</FeatureWasmLegacyJsInterop>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' != 'true'">true</FeatureWasmLegacyJsInterop>
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'" >$(DefineConstants);FEATURE_LEGACY_JS_INTEROP</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'" >$(DefineConstants);FEATURE_LEGACY_JS_INTEROP</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Runtime\InteropServices\JavaScript\ParallelTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'">
<Compile Include="System\Runtime\InteropServices\JavaScript\Http\HttpRequestMessageTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DataViewTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\MemoryTests.cs" />
Expand All @@ -15,9 +26,6 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DelegateTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\HelperMarshal.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Http\HttpRequestMessageTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\ParallelTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
<Compile Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\tests\System.Runtime.InteropServices.JavaScript.UnitTests\System\Runtime\InteropServices\JavaScript\Utils.cs" Link="System\Runtime\InteropServices\JavaScript\Utils.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
<WasmXHarnessArgs>$(WasmXHarnessArgs) --engine-arg=--expose-gc --web-server-use-cop</WasmXHarnessArgs>
<!-- Use following lines to write the generated files to disk. -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<FeatureWasmThreads Condition="'$(TargetOS)' == 'Browser' and ('$(WasmEnableThreads)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread')">true</FeatureWasmThreads>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' == 'true'">false</FeatureWasmLegacyJsInterop>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(FeatureWasmThreads)' != 'true'">true</FeatureWasmLegacyJsInterop>
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'" >$(DefineConstants);FEATURE_LEGACY_JS_INTEROP</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmLegacyJsInterop)' == 'true'" >$(DefineConstants);FEATURE_LEGACY_JS_INTEROP</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ public unsafe void GlobalThis()
[Fact]
public unsafe void DotnetInstance()
{
#if FEATURE_LEGACY_JS_INTEROP
Assert.True(JSHost.DotnetInstance.HasProperty("MONO"));
Assert.Equal("object", JSHost.DotnetInstance.GetTypeOfProperty("MONO"));
#endif

JSHost.DotnetInstance.SetProperty("testBool", true);
Assert.Equal("boolean", JSHost.DotnetInstance.GetTypeOfProperty("testBool"));
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono.proj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
<MonoWasmThreads Condition="'$(MonoWasmBuildVariant)' == 'singlethread'">false</MonoWasmThreads>
<MonoWasmThreads Condition="'$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread' or '$(MonoWasmBuildVariant)' == 'perftrace'">true</MonoWasmThreads>
<MonoWasmThreadsNoUser Condition="('$(WasmEnableThreads)' != 'true' and '$(WasmEnablePerfTracing)' == 'true') or '$(MonoWasmBuildVariant)' == 'perftrace'">true</MonoWasmThreadsNoUser>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(MonoWasmThreads)' == 'true'">false</FeatureWasmLegacyJsInterop>
<FeatureWasmLegacyJsInterop Condition="'$(FeatureWasmLegacyJsInterop)' == '' and '$(MonoWasmThreads)' != 'true'">true</FeatureWasmLegacyJsInterop>
</PropertyGroup>

<!-- default thread suspend for specific platforms -->
Expand Down
19 changes: 0 additions & 19 deletions src/mono/wasi/mono-wasi-driver/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -518,12 +518,6 @@ mono_wasm_assembly_load (const char *name)
return res;
}

MonoClass*
mono_wasm_find_corlib_class (const char *namespace, const char *name)
{
return mono_class_from_name (mono_get_corlib (), namespace, name);
}

MonoClass*
mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name)
{
Expand Down Expand Up @@ -700,12 +694,6 @@ mono_unbox_int (MonoObject *obj)
}
}

int
mono_wasm_array_length (MonoArray *array)
{
return mono_array_length (array);
}

MonoObject*
mono_wasm_array_get (MonoArray *array, int idx)
{
Expand Down Expand Up @@ -753,13 +741,6 @@ mono_wasm_string_get_data_ref (
return;
}

void
mono_wasm_string_get_data (
MonoString *string, mono_unichar2 **outChars, int *outLengthBytes, int *outIsInterned
) {
mono_wasm_string_get_data_ref(&string, outChars, outLengthBytes, outIsInterned);
}

void add_assembly(const char* base_dir, const char *name) {
FILE *fileptr;
unsigned char *buffer;
Expand Down
1 change: 0 additions & 1 deletion src/mono/wasi/mono-wasi-driver/driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ MonoArray* mono_wasm_obj_array_new (int size);
void mono_wasm_obj_array_set (MonoArray *array, int idx, MonoObject *obj);
MonoArray* mono_wasm_string_array_new (int size);
MonoString *mono_wasm_string_from_js (const char *str);
int mono_wasm_array_length(MonoArray* array);
char *mono_wasm_string_get_utf8 (MonoString *str);

MonoMethod* lookup_dotnet_method(const char* assembly_name, const char* namespace, const char* type_name, const char* method_name, int num_params);
2 changes: 2 additions & 0 deletions src/mono/wasm/build/WasmApp.Native.targets
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@
<EmscriptenEnvVars Include="PYTHONPATH=$(EmscriptenPythonToolsPath)" Condition="'$(OS)' == 'Windows_NT'" />
<EmscriptenEnvVars Include="PYTHONHOME=" Condition="'$(OS)' == 'Windows_NT'" />
<EmscriptenEnvVars Include="EM_CACHE=$(WasmCachePath)" Condition="'$(WasmCachePath)' != ''" />
<EmscriptenEnvVars Include="FeatureWasmLegacyJsInterop=$(FeatureWasmLegacyJsInterop)"/>
<EmscriptenEnvVars Include="MonoWasmThreads=$(MonoWasmThreads)"/>
</ItemGroup>

<Error Text="Could not find NativeFileReference %(NativeFileReference.Identity)" Condition="'%(NativeFileReference.Identity)' != '' and !Exists(%(NativeFileReference.Identity))" />
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ project(mono-wasm-runtime C)

option(DISABLE_THREADS "defined if the build does NOT support multithreading" ON)
option(DISABLE_WASM_USER_THREADS "defined if the build does not allow user threads to be created in a multithreaded build" OFF)
option(FEATURE_LEGACY_JS_INTEROP "defined if the build supports legacy JavaScript interop" ON)

set(CMAKE_EXECUTABLE_SUFFIX ".js")
add_executable(dotnet corebindings.c driver.c pinvoke.c)

target_include_directories(dotnet PUBLIC ${MONO_INCLUDES} ${MONO_OBJ_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}/include/wasm)
target_compile_options(dotnet PUBLIC @${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-compile.rsp -DCORE_BINDINGS -DGEN_PINVOKE=1)
target_compile_options(dotnet PUBLIC @${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-compile.rsp -DGEN_PINVOKE=1)

set_target_properties(dotnet PROPERTIES COMPILE_FLAGS ${CONFIGURATION_EMCC_FLAGS})

Expand Down
5 changes: 2 additions & 3 deletions src/mono/wasm/runtime/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import cwraps from "./cwraps";
import { mono_wasm_load_icu_data } from "./icu";
import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports";
import { mono_wasm_load_bytes_into_heap } from "./memory";
import { MONO } from "./net6-legacy/imports";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
import { createPromiseController, PromiseAndController } from "./promise-controller";
import { delay } from "./promise-utils";
Expand Down Expand Up @@ -522,12 +521,12 @@ export async function wait_for_all_assets() {
if (runtimeHelpers.config.assets) {
mono_assert(actual_downloaded_assets_count == expected_downloaded_assets_count, () => `Expected ${expected_downloaded_assets_count} assets to be downloaded, but only finished ${actual_downloaded_assets_count}`);
mono_assert(actual_instantiated_assets_count == expected_instantiated_assets_count, () => `Expected ${expected_instantiated_assets_count} assets to be in memory, but only instantiated ${actual_instantiated_assets_count}`);
loaded_files.forEach(value => MONO.loaded_files.push(value.url));
loaded_files.forEach(value => runtimeHelpers.loadedFiles.push(value.url));
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory");
}
}

// Used by the debugger to enumerate loaded dlls and pdbs
export function mono_wasm_get_loaded_files(): string[] {
return MONO.loaded_files;
return runtimeHelpers.loadedFiles;
}
Loading