Skip to content

Commit f38242b

Browse files
pavelsavaramaraf
andauthored
[browser] JSImport binding improvements (#95177)
Co-authored-by: Marek Fišera <[email protected]>
1 parent 3628544 commit f38242b

File tree

18 files changed

+290
-196
lines changed

18 files changed

+290
-196
lines changed

src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ internal static unsafe partial class Runtime
1414
[MethodImplAttribute(MethodImplOptions.InternalCall)]
1515
internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle);
1616
[MethodImpl(MethodImplOptions.InternalCall)]
17-
public static extern unsafe void BindJSFunction(in string function_name, in string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result);
17+
public static extern unsafe void BindJSImport(void* signature, out int is_exception, out object result);
1818
[MethodImpl(MethodImplOptions.InternalCall)]
19-
public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data);
19+
public static extern void InvokeJSFunction(int functionHandle, void* data);
2020
[MethodImpl(MethodImplOptions.InternalCall)]
21-
public static extern void InvokeImport(IntPtr fn_handle, void* data);
21+
public static extern void InvokeJSImport(int importHandle, void* data);
22+
2223
[MethodImpl(MethodImplOptions.InternalCall)]
2324
public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result);
2425
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -30,9 +31,9 @@ internal static unsafe partial class Runtime
3031

3132
#if FEATURE_WASM_THREADS
3233
[MethodImpl(MethodImplOptions.InternalCall)]
33-
public static extern void InstallWebWorkerInterop(bool installJSSynchronizationContext);
34+
public static extern void InstallWebWorkerInterop();
3435
[MethodImpl(MethodImplOptions.InternalCall)]
35-
public static extern void UninstallWebWorkerInterop(bool uninstallJSSynchronizationContext);
36+
public static extern void UninstallWebWorkerInterop();
3637
#endif
3738

3839
#region Legacy

src/libraries/System.Net.Http/src/System.Net.Http.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGET_BROWSER</DefineConstants>
2222
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'wasi'">$(DefineConstants);TARGET_WASI</DefineConstants>
2323
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
24+
<EmitCompilerGeneratedFiles Condition="'$(Configuration)' == 'Debug' and '$(TargetPlatformIdentifier)' == 'browser'">true</EmitCompilerGeneratedFiles>
2425
</PropertyGroup>
2526

2627
<ItemGroup>

src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<FeatureWasmThreads Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(MonoWasmBuildVariant)' == 'multithread'">true</FeatureWasmThreads>
1313
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGET_BROWSER</DefineConstants>
1414
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
15+
<EmitCompilerGeneratedFiles Condition="'$(Configuration)' == 'Debug' and '$(TargetPlatformIdentifier)' == 'browser'">true</EmitCompilerGeneratedFiles>
1516
</PropertyGroup>
1617

1718
<ItemGroup>

src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<WasmEnableLegacyJsInterop Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(WasmEnableLegacyJsInterop)' == ''">true</WasmEnableLegacyJsInterop>
1616
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
1717
<DefineConstants Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" >$(DefineConstants);DISABLE_LEGACY_JS_INTEROP</DefineConstants>
18+
<EmitCompilerGeneratedFiles Condition="'$(Configuration)' == 'Debug' and '$(TargetPlatformIdentifier)' == 'browser'">true</EmitCompilerGeneratedFiles>
1819
</PropertyGroup>
1920

2021
<ItemGroup>

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ public static void InstallSynchronizationContext (JSMarshalerArgument* arguments
253253
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
254254
try
255255
{
256-
InstallWebWorkerInterop(true, true);
256+
InstallWebWorkerInterop(true);
257257
}
258258
catch (Exception ex)
259259
{

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using System.Runtime.CompilerServices;
77
using System.Runtime.Versioning;
8+
using System.Threading;
89

910
namespace System.Runtime.InteropServices.JavaScript
1011
{
@@ -23,17 +24,29 @@ public sealed class JSFunctionBinding
2324
internal JSFunctionBinding() { }
2425

2526
#region intentionally opaque internal structure
27+
2628
internal unsafe JSBindingHeader* Header;
2729
internal unsafe JSBindingType* Sigs;// points to first arg, not exception, not result
28-
internal IntPtr FnHandle;
30+
internal static volatile uint nextImportHandle = 1;
31+
internal int ImportHandle;
32+
internal bool IsAsync;
33+
#if FEATURE_WASM_THREADS
34+
internal bool IsThreadCaptured;
35+
#endif
2936

3037
[StructLayout(LayoutKind.Sequential, Pack = 4)]
3138
internal struct JSBindingHeader
3239
{
33-
internal const int JSMarshalerSignatureHeaderSize = 4 + 4; // without Exception and Result
40+
internal const int JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result
3441

3542
public int Version;
3643
public int ArgumentCount;
44+
public int ImportHandle;
45+
public int _Reserved;
46+
public int FunctionNameOffset;
47+
public int FunctionNameLength;
48+
public int ModuleNameOffset;
49+
public int ModuleNameLength;
3750
public JSBindingType Exception;
3851
public JSBindingType Result;
3952
}
@@ -143,7 +156,7 @@ internal unsafe JSBindingType this[int position]
143156
[MethodImpl(MethodImplOptions.AggressiveInlining)]
144157
public static void InvokeJS(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
145158
{
146-
InvokeImportImpl(signature.FnHandle, arguments);
159+
InvokeJSImportImpl(signature, arguments);
147160
}
148161

149162
/// <summary>
@@ -192,10 +205,10 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerAr
192205
JSObject.AssertThreadAffinity(jsFunction);
193206
#endif
194207

195-
IntPtr functionJSHandle = jsFunction.JSHandle;
208+
var functionHandle = (int)jsFunction.JSHandle;
196209
fixed (JSMarshalerArgument* ptr = arguments)
197210
{
198-
Interop.Runtime.InvokeJSFunction(functionJSHandle, ptr);
211+
Interop.Runtime.InvokeJSFunction(functionHandle, ptr);
199212
ref JSMarshalerArgument exceptionArg = ref arguments[0];
200213
if (exceptionArg.slot.Type != MarshalerType.None)
201214
{
@@ -205,11 +218,11 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerAr
205218
}
206219

207220
[MethodImpl(MethodImplOptions.AggressiveInlining)]
208-
internal static unsafe void InvokeImportImpl(IntPtr fnHandle, Span<JSMarshalerArgument> arguments)
221+
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
209222
{
210223
fixed (JSMarshalerArgument* ptr = arguments)
211224
{
212-
Interop.Runtime.InvokeImport(fnHandle, ptr);
225+
Interop.Runtime.InvokeJSImport(signature.ImportHandle, ptr);
213226
ref JSMarshalerArgument exceptionArg = ref arguments[0];
214227
if (exceptionArg.slot.Type != MarshalerType.None)
215228
{
@@ -224,22 +237,20 @@ internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName,
224237
JSSynchronizationContext.AssertWebWorkerContext();
225238
#endif
226239

227-
var signature = JSHostImplementation.GetMethodSignature(signatures);
240+
var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName);
228241

229-
Interop.Runtime.BindJSFunction(functionName, moduleName, signature.Header, out IntPtr jsFunctionHandle, out int isException, out object exceptionMessage);
242+
Interop.Runtime.BindJSImport(signature.Header, out int isException, out object exceptionMessage);
230243
if (isException != 0)
231244
throw new JSException((string)exceptionMessage);
232245

233-
signature.FnHandle = jsFunctionHandle;
234-
235246
JSHostImplementation.FreeMethodSignatureBuffer(signature);
236247

237248
return signature;
238249
}
239250

240251
internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
241252
{
242-
var signature = JSHostImplementation.GetMethodSignature(signatures);
253+
var signature = JSHostImplementation.GetMethodSignature(signatures, null, null);
243254

244255
Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage);
245256
if (isException != 0)

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,30 @@ public static async Task<JSObject> CancelationHelper(Task<JSObject> jsTask, Canc
237237
}
238238

239239
// res type is first argument
240-
public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan<JSMarshalerType> types)
240+
public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan<JSMarshalerType> types, string? functionName, string? moduleName)
241241
{
242242
int argsCount = types.Length - 1;
243243
int size = JSFunctionBinding.JSBindingHeader.JSMarshalerSignatureHeaderSize + ((argsCount + 2) * sizeof(JSFunctionBinding.JSBindingType));
244+
245+
int functionNameBytes = 0;
246+
int functionNameOffset = 0;
247+
if (functionName != null)
248+
{
249+
functionNameOffset = size;
250+
size += 4;
251+
functionNameBytes = functionName.Length * 2;
252+
size += functionNameBytes;
253+
}
254+
int moduleNameBytes = 0;
255+
int moduleNameOffset = 0;
256+
if (moduleName != null)
257+
{
258+
moduleNameOffset = size;
259+
size += 4;
260+
moduleNameBytes = moduleName.Length * 2;
261+
size += moduleNameBytes;
262+
}
263+
244264
// this is never unallocated
245265
IntPtr buffer = Marshal.AllocHGlobal(size);
246266

@@ -254,9 +274,44 @@ public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan<JSMarshal
254274
signature.ArgumentCount = argsCount;
255275
signature.Exception = JSMarshalerType.Exception._signatureType;
256276
signature.Result = types[0]._signatureType;
277+
#if FEATURE_WASM_THREADS
278+
signature.ImportHandle = (int)Interlocked.Increment(ref JSFunctionBinding.nextImportHandle);
279+
signature.IsThreadCaptured = false;
280+
#else
281+
signature.ImportHandle = (int)JSFunctionBinding.nextImportHandle++;
282+
#endif
283+
257284
for (int i = 0; i < argsCount; i++)
258285
{
259-
signature.Sigs[i] = types[i + 1]._signatureType;
286+
var type = signature.Sigs[i] = types[i + 1]._signatureType;
287+
#if FEATURE_WASM_THREADS
288+
if (i > 0 && (type.Type == MarshalerType.JSObject || type.Type == MarshalerType.JSException))
289+
{
290+
signature.IsThreadCaptured = true;
291+
}
292+
#endif
293+
}
294+
signature.IsAsync = types[0]._signatureType.Type == MarshalerType.Task;
295+
296+
signature.Header[0].ImportHandle = signature.ImportHandle;
297+
signature.Header[0].FunctionNameLength = functionNameBytes;
298+
signature.Header[0].FunctionNameOffset = functionNameOffset;
299+
signature.Header[0].ModuleNameLength = moduleNameBytes;
300+
signature.Header[0].ModuleNameOffset = moduleNameOffset;
301+
if (functionNameBytes != 0)
302+
{
303+
fixed (void* fn = functionName)
304+
{
305+
Unsafe.CopyBlock((byte*)buffer + functionNameOffset, fn, (uint)functionNameBytes);
306+
}
307+
}
308+
if (moduleNameBytes != 0)
309+
{
310+
fixed (void* mn = moduleName)
311+
{
312+
Unsafe.CopyBlock((byte*)buffer + moduleNameOffset, mn, (uint)moduleNameBytes);
313+
}
314+
260315
}
261316

262317
return signature;
@@ -302,30 +357,27 @@ public static void LoadSatelliteAssembly(byte[] dllBytes)
302357
}
303358

304359
#if FEATURE_WASM_THREADS
305-
public static void InstallWebWorkerInterop(bool installJSSynchronizationContext, bool isMainThread)
360+
public static void InstallWebWorkerInterop(bool isMainThread)
306361
{
307-
Interop.Runtime.InstallWebWorkerInterop(installJSSynchronizationContext);
308-
if (installJSSynchronizationContext)
362+
Interop.Runtime.InstallWebWorkerInterop();
363+
var currentTID = GetNativeThreadId();
364+
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
365+
if (ctx == null)
309366
{
310-
var currentThreadId = GetNativeThreadId();
311-
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
312-
if (ctx == null)
367+
ctx = new JSSynchronizationContext(Thread.CurrentThread, currentTID);
368+
ctx.previousSynchronizationContext = SynchronizationContext.Current;
369+
JSSynchronizationContext.CurrentJSSynchronizationContext = ctx;
370+
SynchronizationContext.SetSynchronizationContext(ctx);
371+
if (isMainThread)
313372
{
314-
ctx = new JSSynchronizationContext(Thread.CurrentThread, currentThreadId);
315-
ctx.previousSynchronizationContext = SynchronizationContext.Current;
316-
JSSynchronizationContext.CurrentJSSynchronizationContext = ctx;
317-
SynchronizationContext.SetSynchronizationContext(ctx);
318-
if (isMainThread)
319-
{
320-
JSSynchronizationContext.MainJSSynchronizationContext = ctx;
321-
}
373+
JSSynchronizationContext.MainJSSynchronizationContext = ctx;
322374
}
323-
else if (ctx.TargetThreadId != currentThreadId)
324-
{
325-
Environment.FailFast($"JSSynchronizationContext.Install failed has wrong native thread id {ctx.TargetThreadId} != {currentThreadId}");
326-
}
327-
ctx.AwaitNewData();
328375
}
376+
else if (ctx.TargetTID != currentTID)
377+
{
378+
Environment.FailFast($"JSSynchronizationContext.Install has wrong native thread id {ctx.TargetTID} != {currentTID}");
379+
}
380+
ctx.AwaitNewData();
329381
}
330382

331383
public static void UninstallWebWorkerInterop()
@@ -364,7 +416,7 @@ public static void UninstallWebWorkerInterop()
364416
}
365417
}
366418

367-
Interop.Runtime.UninstallWebWorkerInterop(uninstallJSSynchronizationContext);
419+
Interop.Runtime.UninstallWebWorkerInterop();
368420

369421
if (uninstallJSSynchronizationContext)
370422
{

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Threading.Channels;
88
using System.Runtime.CompilerServices;
99
using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
10+
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
11+
using System.Collections.Generic;
1012

1113
namespace System.Runtime.InteropServices.JavaScript
1214
{
@@ -21,7 +23,7 @@ internal sealed class JSSynchronizationContext : SynchronizationContext
2123
{
2224
private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
2325
public readonly Thread TargetThread;
24-
public readonly IntPtr TargetThreadId;
26+
public readonly IntPtr TargetTID;
2527
private readonly WorkItemQueueType Queue;
2628

2729
internal static JSSynchronizationContext? MainJSSynchronizationContext;
@@ -65,17 +67,17 @@ internal static void AssertWebWorkerContext()
6567
#endif
6668
}
6769

68-
private JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId, WorkItemQueueType queue)
70+
private JSSynchronizationContext(Thread targetThread, IntPtr targetTID, WorkItemQueueType queue)
6971
{
7072
TargetThread = targetThread;
71-
TargetThreadId = targetThreadId;
73+
TargetTID = targetTID;
7274
Queue = queue;
7375
_DataIsAvailable = DataIsAvailable;
7476
}
7577

7678
public override SynchronizationContext CreateCopy()
7779
{
78-
return new JSSynchronizationContext(TargetThread, TargetThreadId, Queue);
80+
return new JSSynchronizationContext(TargetThread, TargetTID, Queue);
7981
}
8082

8183
internal void AwaitNewData()
@@ -101,7 +103,7 @@ private unsafe void DataIsAvailable()
101103
{
102104
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
103105
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
104-
TargetThreadScheduleBackgroundJob(TargetThreadId, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
106+
TargetThreadScheduleBackgroundJob(TargetTID, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
105107
}
106108

107109
public override void Post(SendOrPostCallback d, object? state)
@@ -110,7 +112,7 @@ public override void Post(SendOrPostCallback d, object? state)
110112

111113
var workItem = new WorkItem(d, state, null);
112114
if (!Queue.Writer.TryWrite(workItem))
113-
throw new Exception("Internal error");
115+
Environment.FailFast("JSSynchronizationContext.Post failed");
114116
}
115117

116118
// This path can only run when threading is enabled
@@ -130,14 +132,14 @@ public override void Send(SendOrPostCallback d, object? state)
130132
{
131133
var workItem = new WorkItem(d, state, signal);
132134
if (!Queue.Writer.TryWrite(workItem))
133-
throw new Exception("Internal error");
135+
Environment.FailFast("JSSynchronizationContext.Send failed");
134136

135137
signal.Wait();
136138
}
137139
}
138140

139141
[MethodImplAttribute(MethodImplOptions.InternalCall)]
140-
internal static extern unsafe void TargetThreadScheduleBackgroundJob(IntPtr targetThread, void* callback);
142+
internal static extern unsafe void TargetThreadScheduleBackgroundJob(IntPtr targetTID, void* callback);
141143

142144
#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
143145
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]

0 commit comments

Comments
 (0)