diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
index e6698486feec5e..e22b5edf99bf12 100644
--- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
+++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
@@ -24,10 +24,13 @@ internal static unsafe partial class Runtime
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 ResolveOrRejectPromise(void* data);
+
+#if !ENABLE_JS_INTEROP_BY_VALUE
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void DeregisterGCRoot(IntPtr handle);
+#endif
#if FEATURE_WASM_THREADS
[MethodImpl(MethodImplOptions.InternalCall)]
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
index 1ba8b10fc73a06..c8425dfe89f4a3 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
@@ -12,9 +12,12 @@
$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
SR.SystemRuntimeInteropServicesJavaScript_PlatformNotSupported
true
+ true
+ false
true
$(DefineConstants);FEATURE_WASM_THREADS
$(DefineConstants);DISABLE_LEGACY_JS_INTEROP
+ $(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE
true
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs
index fc40f481a098f5..515be14293d255 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs
@@ -344,7 +344,9 @@ public unsafe void ToManaged(out object?[]? value)
arg.ToManaged(out val);
value[i] = val;
}
+#if !ENABLE_JS_INTEROP_BY_VALUE
Interop.Runtime.DeregisterGCRoot(slot.IntPtrValue);
+#endif
Marshal.FreeHGlobal(slot.IntPtrValue);
}
@@ -366,7 +368,9 @@ public unsafe void ToJS(object?[] value)
slot.Type = MarshalerType.Array;
JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes);
Unsafe.InitBlock(payload, 0, (uint)bytes);
+#if !ENABLE_JS_INTEROP_BY_VALUE
Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero);
+#endif
for (int i = 0; i < slot.Length; i++)
{
ref JSMarshalerArgument arg = ref payload[i];
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs
index eef48c79c82dba..c101f2048e2457 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs
@@ -20,11 +20,15 @@ public unsafe void ToManaged(out string? value)
value = null;
return;
}
-
+#if ENABLE_JS_INTEROP_BY_VALUE
+ value = Marshal.PtrToStringUni(slot.IntPtrValue, slot.Length);
+ Marshal.FreeHGlobal(slot.IntPtrValue);
+#else
fixed (void* argAsRoot = &slot.IntPtrValue)
{
value = Unsafe.AsRef(argAsRoot);
}
+#endif
}
///
@@ -42,6 +46,10 @@ public unsafe void ToJS(string? value)
else
{
slot.Type = MarshalerType.String;
+#if ENABLE_JS_INTEROP_BY_VALUE
+ slot.IntPtrValue = Marshal.StringToHGlobalUni(value); // alloc, JS side will free
+ slot.Length = value.Length;
+#else
// here we treat JSMarshalerArgument.IntPtrValue as root, because it's allocated on stack
// or we register the buffer with JSFunctionBinding._RegisterGCRoot
// We assume that GC would keep updating on GC move
@@ -52,6 +60,7 @@ public unsafe void ToJS(string? value)
var currentRoot = (IntPtr*)Unsafe.AsPointer(ref cpy);
argAsRoot[0] = currentRoot[0];
}
+#endif
}
}
@@ -78,7 +87,9 @@ public unsafe void ToManaged(out string?[]? value)
arg.ToManaged(out val);
value[i] = val;
}
+#if !ENABLE_JS_INTEROP_BY_VALUE
Interop.Runtime.DeregisterGCRoot(slot.IntPtrValue);
+#endif
Marshal.FreeHGlobal(slot.IntPtrValue);
}
@@ -100,7 +111,9 @@ public unsafe void ToJS(string?[] value)
slot.Type = MarshalerType.Array;
JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes);
Unsafe.InitBlock(payload, 0, (uint)bytes);
+#if !ENABLE_JS_INTEROP_BY_VALUE
Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero);
+#endif
for (int i = 0; i < slot.Length; i++)
{
ref JSMarshalerArgument arg = ref payload[i];
diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets
index 438ea50f3fd176..9f3e488d2ac97c 100644
--- a/src/mono/wasm/build/WasmApp.Native.targets
+++ b/src/mono/wasm/build/WasmApp.Native.targets
@@ -245,6 +245,7 @@
<_EmccCFlags Include="-DENABLE_AOT_PROFILER=1" Condition="$(WasmProfilers.Contains('aot'))" />
<_EmccCFlags Include="-DENABLE_BROWSER_PROFILER=1" Condition="$(WasmProfilers.Contains('browser'))" />
<_EmccCFlags Include="-DDISABLE_LEGACY_JS_INTEROP=1" Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" />
+ <_EmccCFlags Include="-DENABLE_JS_INTEROP_BY_VALUE=1" Condition="'$(WasmEnableJsInteropByValue)' == 'true'" />
<_EmccCFlags Include="-DGEN_PINVOKE=1" />
<_EmccCFlags Include="-emit-llvm" />
@@ -288,6 +289,8 @@
+
+
diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets
index 469425b2c48570..4e7fdddd8816fa 100644
--- a/src/mono/wasm/build/WasmApp.targets
+++ b/src/mono/wasm/build/WasmApp.targets
@@ -73,6 +73,7 @@
Defaults to false.
- $(WasmAotProfilePath) - Path to an AOT profile file.
- $(WasmEnableLegacyJsInterop) - Include support for legacy JS interop. Defaults to true.
+ - $(WasmEnableJsInteropByValue) - Make JS interop to pass string by value instead of by reference. Defaults to false. Defaults to true for build with threads.
- $(WasmEnableExceptionHandling) - Enable support for the WASM post MVP Exception Handling runtime extension.
- $(WasmEnableSIMD) - Enable support for the WASM post MVP SIMD runtime extension.
- $(WasmEnableWebcil) - Enable conversion of assembly .dlls to Webcil wrapped in .wasm (default: true)
@@ -115,6 +116,8 @@
true
$(WasmEnableExceptionHandling)
true
+ true
+ false
diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt
index e0b19bb4a9b691..020529b23ef0aa 100644
--- a/src/mono/wasm/runtime/CMakeLists.txt
+++ b/src/mono/wasm/runtime/CMakeLists.txt
@@ -4,6 +4,7 @@ project(mono-wasm-runtime C)
option(DISABLE_THREADS "defined if the build does NOT support multithreading" ON)
option(DISABLE_LEGACY_JS_INTEROP "defined if the build does not support legacy JavaScript interop" OFF)
+option(ENABLE_JS_INTEROP_BY_VALUE "defined when JS interop without pointers to managed objects" OFF)
set(CMAKE_EXECUTABLE_SUFFIX ".js")
add_executable(dotnet.native corebindings.c driver.c pinvoke.c)
diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c
index 52c4371884df43..0496b65fd5c72c 100644
--- a/src/mono/wasm/runtime/corebindings.c
+++ b/src/mono/wasm/runtime/corebindings.c
@@ -66,8 +66,11 @@ void bindings_initialize_internals (void)
mono_add_internal_call ("Interop/Runtime::InvokeJSImport", mono_wasm_invoke_js_import);
mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function);
mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromise", mono_wasm_resolve_or_reject_promise);
+
+#ifndef ENABLE_JS_INTEROP_BY_VALUE
mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root);
mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root);
+#endif /* ENABLE_JS_INTEROP_BY_VALUE */
#ifndef DISABLE_THREADS
mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop);
diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts
index bde06aa55dc131..f741351be33f62 100644
--- a/src/mono/wasm/runtime/marshal-to-cs.ts
+++ b/src/mono/wasm/runtime/marshal-to-cs.ts
@@ -3,6 +3,7 @@
import MonoWasmThreads from "consts:monoWasmThreads";
import BuildConfiguration from "consts:configuration";
+import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue";
import { isThenable } from "./cancelable-promise";
import cwraps from "./cwraps";
@@ -18,7 +19,7 @@ import {
} from "./marshal";
import { get_marshaler_to_js_by_type } from "./marshal-to-js";
import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory";
-import { stringToMonoStringRoot } from "./strings";
+import { stringToMonoStringRoot, stringToUTF16 } from "./strings";
import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal";
import { TypedArray } from "./types/emscripten";
import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop";
@@ -229,12 +230,20 @@ function _marshal_string_to_cs(arg: JSMarshalerArgument, value: string) {
}
function _marshal_string_to_cs_impl(arg: JSMarshalerArgument, value: string) {
- const root = get_string_root(arg);
- try {
- stringToMonoStringRoot(value, root);
- }
- finally {
- root.release();
+ if (WasmEnableJsInteropByValue) {
+ const bufferLen = value.length * 2;
+ const buffer = Module._malloc(bufferLen);
+ stringToUTF16(buffer as any, buffer as any + bufferLen, value);
+ set_arg_intptr(arg, buffer);
+ set_arg_length(arg, value.length);
+ } else {
+ const root = get_string_root(arg);
+ try {
+ stringToMonoStringRoot(value, root);
+ }
+ finally {
+ root.release();
+ }
}
}
@@ -505,7 +514,9 @@ export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array<
if (element_type == MarshalerType.String) {
mono_check(Array.isArray(value), "Value is not an Array");
_zero_region(buffer_ptr, buffer_length);
- cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ if (!WasmEnableJsInteropByValue) {
+ cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ }
for (let index = 0; index < length; index++) {
const element_arg = get_arg(buffer_ptr, index);
_marshal_string_to_cs(element_arg, value[index]);
@@ -514,7 +525,9 @@ export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array<
else if (element_type == MarshalerType.Object) {
mono_check(Array.isArray(value), "Value is not an Array");
_zero_region(buffer_ptr, buffer_length);
- cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ if (!WasmEnableJsInteropByValue) {
+ cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ }
for (let index = 0; index < length; index++) {
const element_arg = get_arg(buffer_ptr, index);
_marshal_cs_object_to_cs(element_arg, value[index]);
diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts
index 21af5460110ee7..dba386715c2e14 100644
--- a/src/mono/wasm/runtime/marshal-to-js.ts
+++ b/src/mono/wasm/runtime/marshal-to-js.ts
@@ -3,6 +3,7 @@
import MonoWasmThreads from "consts:monoWasmThreads";
import BuildConfiguration from "consts:configuration";
+import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue";
import cwraps from "./cwraps";
import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_release_cs_owned_object, register_with_jsv_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles";
@@ -15,7 +16,7 @@ import {
get_signature_res_type, get_arg_u16, array_element_size, get_string_root,
ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize, proxy_debug_symbol
} from "./marshal";
-import { monoStringToString } from "./strings";
+import { monoStringToString, utf16ToString } from "./strings";
import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal";
import { TypedArray } from "./types/emscripten";
import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs";
@@ -310,12 +311,21 @@ export function marshal_string_to_js(arg: JSMarshalerArgument): string | null {
if (type == MarshalerType.None) {
return null;
}
- const root = get_string_root(arg);
- try {
- const value = monoStringToString(root);
+ if (WasmEnableJsInteropByValue) {
+ const buffer = get_arg_intptr(arg);
+ const len = get_arg_length(arg) * 2;
+ const value = utf16ToString(buffer, buffer + len);
+ Module._free(buffer as any);
return value;
- } finally {
- root.release();
+ }
+ else {
+ const root = get_string_root(arg);
+ try {
+ const value = monoStringToString(root);
+ return value;
+ } finally {
+ root.release();
+ }
}
}
@@ -421,7 +431,9 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh
const element_arg = get_arg(buffer_ptr, index);
result[index] = marshal_string_to_js(element_arg);
}
- cwraps.mono_wasm_deregister_root(buffer_ptr);
+ if (!WasmEnableJsInteropByValue) {
+ cwraps.mono_wasm_deregister_root(buffer_ptr);
+ }
}
else if (element_type == MarshalerType.Object) {
result = new Array(length);
@@ -429,7 +441,9 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh
const element_arg = get_arg(buffer_ptr, index);
result[index] = _marshal_cs_object_to_js(element_arg);
}
- cwraps.mono_wasm_deregister_root(buffer_ptr);
+ if (!WasmEnableJsInteropByValue) {
+ cwraps.mono_wasm_deregister_root(buffer_ptr);
+ }
}
else if (element_type == MarshalerType.JSObject) {
result = new Array(length);
diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js
index 50bcfc3110d933..4c1622e8f829fb 100644
--- a/src/mono/wasm/runtime/rollup.config.js
+++ b/src/mono/wasm/runtime/rollup.config.js
@@ -23,6 +23,7 @@ const monoWasmThreads = process.env.MonoWasmThreads === "true" ? true : false;
const wasmEnableSIMD = process.env.WASM_ENABLE_SIMD === "1" ? true : false;
const wasmEnableExceptionHandling = process.env.WASM_ENABLE_EH === "1" ? true : false;
const wasmEnableLegacyJsInterop = process.env.DISABLE_LEGACY_JS_INTEROP !== "1" ? true : false;
+const wasmEnableJsInteropByValue = process.env.ENABLE_JS_INTEROP_BY_VALUE == "1" ? true : false;
const monoDiagnosticsMock = process.env.MonoDiagnosticsMock === "true" ? true : false;
// because of stack walk at src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
// and unit test at src\libraries\System.Runtime.InteropServices.JavaScript\tests\System.Runtime.InteropServices.JavaScript.Legacy.UnitTests\timers.mjs
@@ -103,6 +104,7 @@ const envConstants = {
monoDiagnosticsMock,
gitHash,
wasmEnableLegacyJsInterop,
+ wasmEnableJsInteropByValue,
isContinuousIntegrationBuild,
};
diff --git a/src/mono/wasm/runtime/wasm-config.h.in b/src/mono/wasm/runtime/wasm-config.h.in
index ee9adc20600c7e..044bac49598083 100644
--- a/src/mono/wasm/runtime/wasm-config.h.in
+++ b/src/mono/wasm/runtime/wasm-config.h.in
@@ -10,4 +10,7 @@
/* Support for legacy JS interop is disabled */
#cmakedefine DISABLE_LEGACY_JS_INTEROP
+/* Support for JS interop without pointers to managed objects */
+#cmakedefine ENABLE_JS_INTEROP_BY_VALUE
+
#endif/*__MONO_WASM_CONFIG_H__*/
diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj
index 6ed392341e6b66..d741533a238250 100644
--- a/src/mono/wasm/wasm.proj
+++ b/src/mono/wasm/wasm.proj
@@ -27,6 +27,8 @@
true
true
true
+ true
+ false
false
emcc
$(ArtifactsObjDir)wasm
@@ -392,6 +394,7 @@
$(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd"
$(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd"
$(CMakeBuildRuntimeConfigureCmd) -DDISABLE_THREADS=0
+ $(CMakeBuildRuntimeConfigureCmd) -DENABLE_JS_INTEROP_BY_VALUE=1
$(CMakeBuildRuntimeConfigureCmd) -DDISABLE_LEGACY_JS_INTEROP=1
$(CMakeBuildRuntimeConfigureCmd) $(CMakeConfigurationEmsdkPath)
@@ -406,6 +409,8 @@
<_CmakeEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP=1" Condition="'$(WasmEnableLegacyJsInterop)' == 'false'"/>
<_CmakeEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP=0" Condition="'$(WasmEnableLegacyJsInterop)' != 'false'"/>
+ <_CmakeEnvironmentVariable Include="ENABLE_JS_INTEROP_BY_VALUE=1" Condition="'$(WasmEnableJsInteropByValue)' != 'false'"/>
+ <_CmakeEnvironmentVariable Include="ENABLE_JS_INTEROP_BY_VALUE=0" Condition="'$(WasmEnableJsInteropByValue)' == 'false'"/>
<_CmakeEnvironmentVariable Include="WASM_ENABLE_SIMD=1" Condition="'$(WasmEnableSIMD)' != 'false'" />
<_CmakeEnvironmentVariable Include="WASM_ENABLE_SIMD=0" Condition="'$(WasmEnableSIMD)' == 'false'" />
<_CmakeEnvironmentVariable Include="WASM_ENABLE_EH=1" Condition="'$(WasmEnableExceptionHandling)' != 'false'" />
@@ -546,6 +551,8 @@
<_MonoRollupEnvironmentVariable Include="WASM_ENABLE_EH:0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" />
<_MonoRollupEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP:1" Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" />
<_MonoRollupEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP:0" Condition="'$(WasmEnableLegacyJsInterop)' != 'false'" />
+ <_MonoRollupEnvironmentVariable Include="ENABLE_JS_INTEROP_BY_VALUE:1" Condition="'$(WasmEnableJsInteropByValue)' == 'true'" />
+ <_MonoRollupEnvironmentVariable Include="ENABLE_JS_INTEROP_BY_VALUE:0" Condition="'$(WasmEnableJsInteropByValue)' != 'true'" />
<_MonoRollupEnvironmentVariable Include="MonoDiagnosticsMock:$(MonoDiagnosticsMock)" />
<_MonoRollupEnvironmentVariable Include="ContinuousIntegrationBuild:$(ContinuousIntegrationBuild)" />