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 Bindings optimizations and fixes #41808

Merged
merged 92 commits into from
Nov 11, 2020
Merged

Conversation

kg
Copy link
Member

@kg kg commented Sep 3, 2020

This PR contains various improvements and optimizations to the bindings layer, along with related bug fixes (necessary to land it), including:

  • A new bind_method API that provides a more efficient alternative to call_method by preparing most of the information needed for an invoke ahead of time. The resulting method accepts bare arguments and can be called like a regular JS function. The resulting method also has a human-readable name and sourceURL so that it is clearly identified in stack traces and in browser devtools' sources tab
  • A JIT compiler for argument list marshaling to significantly boost its performance
  • A JIT compiler for bound methods to provide fast marshaling and (where possible) prevent return values from being boxed and causing JS GCs
  • Changes the syntax for method signatures so that a trailing ! indicates an unmarshaled return value, instead of the previous m (the old syntax was ambiguous and hinders efficient call dispatch)
  • Fixes multiple call_method call sites that had incorrect signatures
  • Changes various high-traffic call_method sites to use bind_method instead
  • Expands the browser sample to contain a simple benchmark that measures the overhead of bindings method calls.
  • Adds a new mono_wasm_try_unbox_primitive_and_get_type API to the driver that combines the type-check and unbox operations into one call, and writes the primitives to the heap instead of returning them through the wasm<->JS FFI. This enables faster unboxing and the values being written to the native heap ensures they don't get allocated on the JS GC heap.
  • Adds additional MARSHAL_TYPE_ enumeration values so that values of varying types can be marshaled more accurately.
  • Fixes some bugs in the existing JS root API and improves its performance.
  • Optimizes marshaling of JS strings and fixes an existing bug where JS strings were truncated at the first embedded null
  • Unifies the boxing and unboxing paths for primitives instead of going through managed/unmanaged code to manipulate boxes (with the exception of one API that runtime-test relies on)
  • Fixes passing and returning enums through the bindings layer (this was broken/half-implemented before)
  • Fixes issue where the managed wrapper for JS globals like Math would not be re-created after it had been GC'd
  • Fixes various problems with passing uint32 across the managed/js boundary
  • Fixes issue System.Runtime.InteropServices.JavaScript.Tests.MarshalTests.TestFunctionApply failed on CI (wasm mono subset) #40112

@kg kg added the arch-wasm WebAssembly architecture label Sep 3, 2020
@kg kg requested review from lewing, akoeplinger and vargaz September 3, 2020 16:39
@kg kg force-pushed the bindings-optimizations-1 branch 2 times, most recently from f09e11f to 6467165 Compare September 9, 2020 19:21
@kg kg changed the title [DRAFT] js->managed bindings optimizations WASM Bindings optimizations Sep 12, 2020
@kg kg added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Sep 12, 2020
@kg kg marked this pull request as ready for review September 12, 2020 05:05
@kg
Copy link
Member Author

kg commented Sep 12, 2020

An example of what the JIT-compiled functions look like in browser devtools (ignore the bug in this generated code's switch block, I fixed it)
image

@@ -19,5 +19,9 @@ public static int TestMeaning()
{
return 42;
}

public static float BenchmarkMethod (int i, float f, double d) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should go to benchmarks suite

/cc @SamMonoRT

Result from Sample.Test.TestMeaning: <span id="out"></span>
Result from Sample.Test.TestMeaning: <span id="out"></span><br>
Progress: <span id="progress">Starting...</span><br>
Avg. Speed: <span id="speed"></span>
Copy link
Contributor

Choose a reason for hiding this comment

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

This should go to benchmarks suite

Copy link
Member Author

Choose a reason for hiding this comment

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

Was planning to remove it entirely before merging, but I can move it to the benchmarks suite.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can't find the benchmarks suite you suggested moving it to, but I split it into its own folder for now. Where precisely did you want it to go?

Copy link
Contributor

Choose a reason for hiding this comment

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

src/mono/wasm/runtime/binding_support.js Outdated Show resolved Hide resolved
src/mono/wasm/runtime/binding_support.js Outdated Show resolved Hide resolved
var uriPrefix = "", escapedFunctionIdentifier = "";

if (name) {
uriPrefix = "//# sourceURL=https://mono-wasm.invalid/" + name + "\r\n";
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be more like .generated instead of .invalid?

Copy link
Member Author

Choose a reason for hiding this comment

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

.invalid is the only reserved TLD that is guaranteed not to ever resolve to anything.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it actually needs a scheme and a host so you could just use the name.

@kg kg changed the title WASM Bindings optimizations WASM Bindings optimizations and fixes Sep 19, 2020
@kg
Copy link
Member Author

kg commented Sep 19, 2020

The string changes in this PR appear to fix the root cause of #41604 and another issue that isn't tracked on github - essentially, large strings seem to cause intermittent crashes and/or memory corruption when crossing the JS<->WASM boundary. I'm working on figuring out whether that fix can easily be cherry-picked out.

@kg kg force-pushed the bindings-optimizations-1 branch from e29be9f to 649375b Compare September 24, 2020 01:33
@kg kg removed the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Oct 12, 2020
@kg kg force-pushed the bindings-optimizations-1 branch from 44d5fae to e1144dd Compare October 13, 2020 01:28
@lewing
Copy link
Member

lewing commented Oct 13, 2020

@kg the remaining failures appear genuine, have you taken a look at them?

@kg kg force-pushed the bindings-optimizations-1 branch from f83e6cf to 77bccfa Compare October 14, 2020 22:16
Copy link
Member

@lewing lewing left a comment

Choose a reason for hiding this comment

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

This is looking very good. More comments in a bit.

src/mono/wasm/runtime/library_mono.js Outdated Show resolved Hide resolved
if (!_boundObjects.TryGetValue(jsId, out reference))
if (_boundObjects.TryGetValue(jsId, out reference))
{
if ((reference.Target == null) || ((reference.Target as JSObject)?.IsDisposed == true))
Copy link
Member

Choose a reason for hiding this comment

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

I'm fairly sure this is just masking the problem, I wonder if the issue is that we need to be able to promote weak refs into strong ones.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's correct in some scenarios for it to have expired, if nothing is retaining a reference to globalThis.Math then the C# wrapper for it should be able to get collected. That's at least what was causing the test failures

@@ -105,10 +105,20 @@ mono_wasm_invoke_js (MonoString *str, int *is_exception)
res = res.toString ();
setValue ($2, 0, "i32");
} catch (e) {
res = e.toString ();
Copy link
Member

Choose a reason for hiding this comment

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

we need to fix our c and js formatting

var uriPrefix = "", escapedFunctionIdentifier = "";

if (name) {
uriPrefix = "//# sourceURL=https://mono-wasm.invalid/" + name + "\r\n";
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it actually needs a scheme and a host so you could just use the name.

src/mono/wasm/runtime/binding_support.js Outdated Show resolved Hide resolved
@lewing
Copy link
Member

lewing commented Nov 11, 2020

rebased on master to kick off CI again

@lewing lewing force-pushed the bindings-optimizations-1 branch from 0eb7a06 to 635aa03 Compare November 11, 2020 01:43
@lewing lewing merged commit 85cb1df into dotnet:master Nov 11, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly architecture area-Meta
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants