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

Restructure JSImport/JSExport generators to share more code and utilize more Microsoft.Interop.SourceGeneration shared code #107769

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

jkoritzinsky
Copy link
Member

Split out the MarshalerType/Bind logic to not run through the generators (and instead run through the generator resolver only, which allows removing the IJSMarshallingGenerator type and many of the different generator types (as most of them end up using the same logic as the primitive generator).

Update JSFunctionBinding to not use Unsafe.AsPointer to allow the source generator to use collection expressions (fixing one of the rude-edit errors in #107577 by removing the stackalloc) without depending on Roslyn implementation details.

Also use more pattern matching instead of when expressions where possible in JSGeneratorResolver to make the giant table a little more readable in my opinion.

Replat the JS generators to use the same stub generators as the LibraryImport and GeneratedComInterface source generators by using local functions to shim from the JS generator signatures (which take a buffer) to the more standard "separate arguments" format used by those generators.

This PR also adds support in Microsoft.Interop.SourceGeneration for an exception marshaller that corresponds to a different native location than the return location (to support the separate exception return from the argument return).

…port and GeneratedComInterface by using local functions to shim from the JS generator signatures (which take a buffer) to the more standard "separate arguments" format used by those generators.

Split out the MarshalerType/Bind logic to not run through the generators, which allows removing the IJSMarshallingGenerator concept.
@jkoritzinsky jkoritzinsky force-pushed the js-generator-shared-code branch from 0bf3ad1 to 3cd828e Compare October 17, 2024 23:54
@@ -71,70 +71,116 @@ internal static partial void Annotated(object a1, long a2, long a3, global::Syst
{
if (__signature_Annotated_1583225186 == null)
{
__signature_Annotated_1583225186 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, new global::System.Runtime.InteropServices.JavaScript.JSMarshalerType[] { global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64) });
__signature_Annotated_1583225186 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64)]);
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could create separate PR, which would break this long line into multiple lines, so that it's easier to diff/review. Merge it first and into this PR.

Copy link
Member

Choose a reason for hiding this comment

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

I mean, can roslyn inject new line before each argument ?

Copy link
Member Author

Choose a reason for hiding this comment

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

With the way we're building up the syntax and manipulating it, it's difficult to do that well.

One of the eventual follow-ons from this PR is to move all of the interop generators to generate strings (at which point custom whitespace is easy), but we're not quite there yet.

…return arguments before we do any marshalling (instead of after).
@jkoritzinsky
Copy link
Member Author

For return values, here's the old codegen for return types (to compare against the ValidateGeneratedSourceOutput_Return test)

Old Code Gen
// JSImports.g.cs
// <auto-generated/>
unsafe partial class Foo
{
    [global::System.Diagnostics.DebuggerNonUserCode]
    public static partial global::System.Threading.Tasks.Task<int> Func()
    {
        if (__signature_Func_408569705 == null)
        {
            __signature_Func_408569705 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("Func", null, new global::System.Runtime.InteropServices.JavaScript.JSMarshalerType[] { global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32) });
        }

        global::System.Span<global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument> __arguments_buffer = stackalloc global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument[2];
        ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_exception = ref __arguments_buffer[0];
        __arg_exception.Initialize();
        ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_return = ref __arguments_buffer[1];
        __arg_return.Initialize();
        global::System.Threading.Tasks.Task<int> __retVal;
        global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.InvokeJS(__signature_Func_408569705, __arguments_buffer);
        // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception.
        __arg_return.ToManaged(out __retVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out int __task_result) =>
        {
            __task_result_arg.ToManaged(out __task_result);
        });
        return __retVal;
    }

    static global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding __signature_Func_408569705;
}
// JSExports.g.cs
// <auto-generated/>
namespace System.Runtime.InteropServices.JavaScript
{
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
    unsafe class __GeneratedInitializer
    {
        [global::System.ThreadStaticAttribute]
        static bool initialized;
        [global::System.Runtime.CompilerServices.ModuleInitializerAttribute, global::System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.__GeneratedInitializer", "Console")]
        static internal void __TrimmingPreserve_()
        {
        }

        [global::System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute("__Wrapper_NetFunc_408569705", "Foo", "Console")]
        static void __Register_()
        {
            if (initialized || global::System.Runtime.InteropServices.RuntimeInformation.OSArchitecture != global::System.Runtime.InteropServices.Architecture.Wasm)
                return;
            initialized = true;
            global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction("[Console]Foo:NetFunc", 408569705, new global::System.Runtime.InteropServices.JavaScript.JSMarshalerType[] { global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32) });
        }
    }
}
unsafe partial class Foo
{
    [global::System.Diagnostics.DebuggerNonUserCode]
    internal static unsafe void __Wrapper_NetFunc_408569705(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer)
    {
        ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_exception = ref __arguments_buffer[0];
        ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_return = ref __arguments_buffer[1];
        global::System.Threading.Tasks.Task<int> __retVal = default;
        try
        {
            __retVal = Foo.NetFunc();
            // Marshal - Convert managed data to native data.
            __arg_return.ToJS(__retVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, int __task_result) =>
            {
                __task_result_arg.ToJS(__task_result);
            });
        }
        catch (global::System.Exception ex)
        {
            __arg_exception.ToJS(ex);
        }
    }
}

@jkoritzinsky
Copy link
Member Author

I ran the JSImportManyArgs benchmark and I saw the following results:

main: average 71.2133ms
pr: average 68.3718ms

So, this PR is also a perf improvement for JSImport, about 3ms, which is 4% faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Runtime.InteropServices.JavaScript source-generator Indicates an issue with a source generator feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants