From 7c199306bbb0e05af90047619fc965af7c947b9c Mon Sep 17 00:00:00 2001 From: Ujjwal Chadha <66971578+ujjwalchadha@users.noreply.github.com> Date: Tue, 8 Jun 2021 22:53:17 -0400 Subject: [PATCH] Ujjwalchadha/perf/eventsourcecodegen (#852) * initial implementation * Added benchmarks * Added special case for EventHandler and cleanup * Cleanup * Enabled all benchmarks * Cleanup * Added null check to unsubscribed event invoke * Refactor * Cleanup Benchmarks * Replaced tabs with spaces --- .gitignore | 1 + src/Benchmarks/Benchmarks.csproj | 12 +- src/Benchmarks/ReflectionPerf.cs | 17 +++ src/benchmark.cmd | 2 +- src/benchmark_winmd.cmd | 2 +- src/cswinrt/code_writers.h | 214 +++++++++++++++++++++++++++++-- src/cswinrt/main.cpp | 19 ++- src/cswinrt/strings/WinRT.cs | 62 ++++++--- src/get_testwinrt.cmd | 2 +- 9 files changed, 294 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 6cf0c7c25..a78688bb5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ nuget/Microsoft.Windows.CsWinRT.Prerelease.targets *.binlog vs_buildtools.exe .buildtools +src/BenchmarkDotNet.Artifacts/* diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj index f1a880ce1..4cd198a3b 100644 --- a/src/Benchmarks/Benchmarks.csproj +++ b/src/Benchmarks/Benchmarks.csproj @@ -3,12 +3,14 @@ Exe x64;x86 - netcoreapp2.0;net5.0;netcoreapp3.1 + netcoreapp2.0;netcoreapp3.1;net5.0; false false true Benchmarks.manifest + $(TargetFramework) + netstandard2.0 @@ -16,8 +18,11 @@ - + + + + @@ -32,6 +37,7 @@ $(MSBuildThisFileDirectory)..\_build\$(Platform)\$(Configuration)\BenchmarkComponent\bin\BenchmarkComponent\BenchmarkComponent.winmd true + True @@ -42,7 +48,7 @@ - + diff --git a/src/Benchmarks/ReflectionPerf.cs b/src/Benchmarks/ReflectionPerf.cs index 9794d6072..1d39bb009 100644 --- a/src/Benchmarks/ReflectionPerf.cs +++ b/src/Benchmarks/ReflectionPerf.cs @@ -1,5 +1,6 @@ using BenchmarkComponent; using BenchmarkDotNet.Attributes; +using System; namespace Benchmarks { @@ -61,5 +62,21 @@ public object ExecuteMarshalingForCustomObject() { return instance.NewWrappedClassObject; } + + [Benchmark] + public void IntEventSource() + { + ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines(); + + int x = 0; + int y = 1; + int z; + + instance.IntProperty = x; + instance.CallForInt(() => y); + + instance.IntPropertyChanged += (object sender, int value) => z = value; + instance.RaiseIntChanged(); + } } } diff --git a/src/benchmark.cmd b/src/benchmark.cmd index 12a855750..0f53fe734 100644 --- a/src/benchmark.cmd +++ b/src/benchmark.cmd @@ -1,2 +1,2 @@ -msbuild Benchmarks\Benchmarks.csproj -t:restore -t:build /p:platform=x64 /p:configuration=release +msbuild Benchmarks\Benchmarks.csproj -t:restore -t:build /p:platform=x64 /p:configuration=release /p:solutiondir=%~dp0 dotnet %~dp0Benchmarks\bin\x64\Release\netcoreapp2.0\Benchmarks.dll -filter * --runtimes netcoreapp2.0 netcoreapp3.1 netcoreapp5.0 \ No newline at end of file diff --git a/src/benchmark_winmd.cmd b/src/benchmark_winmd.cmd index 31b65db74..b00635228 100644 --- a/src/benchmark_winmd.cmd +++ b/src/benchmark_winmd.cmd @@ -1,4 +1,4 @@ -msbuild Benchmarks\Benchmarks.csproj -t:restore -t:clean;rebuild /p:BenchmarkWinmdSupport=true /p:platform=x64 /p:configuration=release /p:TargetFramework=netcoreapp3.1 +msbuild Benchmarks\Benchmarks.csproj -t:restore -t:clean;rebuild /p:BenchmarkWinmdSupport=true /p:platform=x64 /p:configuration=release /p:TargetFramework=netcoreapp3.1 /p:solutiondir=%~dp0 %~dp0Benchmarks\bin\x64\Release\netcoreapp3.1\Benchmarks.exe -filter * rem Clean project to prevent mismatch scenarios with the typical benchmark.cmd scenario. msbuild Benchmarks\Benchmarks.csproj -t:restore -t:clean /p:BenchmarkWinmdSupport=true /p:platform=x64 /p:configuration=release /p:TargetFramework=netcoreapp3.1 >nul \ No newline at end of file diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index d9d97c0a6..0117974ed 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace cswinrt { @@ -453,6 +455,60 @@ namespace cswinrt bind(param)); } + void write_projection_arg(writer& w, method_signature::param_t const& param) + { + w.write("%", + bind(param)); + } + + void write_event_source_type_name(writer& w, type_semantics const& eventTypeSemantics) + { + auto eventTypeCode = w.write_temp("%", bind(eventTypeSemantics, typedef_name_type::Projected, false)); + std::string eventTypeName = "_EventSource_" + eventTypeCode; + std::regex re(R"-((\ |:|<|>|,|\.))-"); + w.write("%", std::regex_replace(eventTypeName, re, "_")); + } + + method_signature get_event_invoke_method(TypeDef const& eventType) + { + for (auto&& method : eventType.MethodList()) + { + if (method.Name() == "Invoke") + { + return method_signature(method); + } + } + throw_invalid("Event type must have an Invoke method"); + } + + void write_event_invoke_params(writer& w, method_signature const& methodSig) + { + w.write("%", bind_list(", ", methodSig.params())); + } + + void write_event_invoke_return(writer& w, method_signature const& methodSig) + { + if (methodSig.return_signature()) + { + w.write("return "); + } + } + + void write_event_invoke_return_default(writer& w, method_signature const& methodSig) + { + if (!methodSig.return_signature()) + { + return; + } + auto&& semantics = get_type_semantics(methodSig.return_signature().Type()); + w.write("default(%)", bind(semantics)); + } + + void write_event_invoke_args(writer& w, method_signature const& methodSig) + { + w.write("%", bind_list(", ", methodSig.params())); + } + void write_abi_type(writer& w, type_semantics const& semantics) { call(semantics, @@ -2467,14 +2523,37 @@ db_path.stem().string()); ); } + void write_event_source_generic_args(writer& w, cswinrt::type_semantics eventTypeSemantics); + void write_event_source_ctor(writer& w, Event const& evt) { + if (for_typedef(w, get_type_semantics(evt.EventType()), [&](TypeDef const& eventType) + { + if (eventType.TypeNamespace() == "System" && eventType.TypeName() == "EventHandler`1") + { + auto [add, remove] = get_event_methods(evt); + w.write(R"( +new EventSource__EventHandler%(_obj, +%, +%))", +bind(eventType), +get_invoke_info(w, add).first, +get_invoke_info(w, remove).first); + return true; + } + return false; + })) + { + return; + } + auto [add, remove] = get_event_methods(evt); w.write(R"( - new EventSource<%>(_obj, + new %%(_obj, %, %))", - bind(get_type_semantics(evt.EventType()), typedef_name_type::Projected, false), + bind(get_type_semantics(evt.EventType())), + bind(get_type_semantics(evt.EventType())), get_invoke_info(w, add).first, get_invoke_info(w, remove).first); } @@ -6255,13 +6334,13 @@ bind_list(", ", signature.params()) { if (factory.activatable) { - w.write_each(factory.type.MethodList(), projected_type_name); + w.write_each(factory.type.MethodList(), projected_type_name); } else if (factory.statics) { - w.write_each(factory.type.MethodList(), projected_type_name, true, ""sv); - w.write_each(factory.type.PropertyList(), projected_type_name, true, ""sv); - w.write_each(factory.type.EventList(), projected_type_name, true, ""sv); + w.write_each(factory.type.MethodList(), projected_type_name, true, ""sv); + w.write_each(factory.type.PropertyList(), projected_type_name, true, ""sv); + w.write_each(factory.type.EventList(), projected_type_name, true, ""sv); } } } @@ -6272,7 +6351,7 @@ bind_list(", ", signature.params()) { auto factory_type_name = write_type_name_temp(w, type, "%ServerActivationFactory", typedef_name_type::CCW); auto base_class = (is_static(type) || !has_default_constructor(type)) ? - "ComponentActivationFactory" : write_type_name_temp(w, type, "ActivatableComponentActivationFactory<%>", typedef_name_type::Projected); + "ComponentActivationFactory" : write_type_name_temp(w, type, "ActivatableComponentActivationFactory<%>", typedef_name_type::Projected); auto type_name = write_type_name_temp(w, type, "%", typedef_name_type::Projected); w.write(R"( @@ -6328,8 +6407,8 @@ return IntPtr.Zero; } )", bind_each([](writer& w, TypeDef const& type) -{ - w.write(R"( + { + w.write(R"( if (runtimeClassId == "%.%") { @@ -6340,8 +6419,117 @@ type.TypeNamespace(), type.TypeName(), bind(type, typedef_name_type::CCW, true) ); -}, -types -)); + }, + types + )); } -} \ No newline at end of file + + void write_event_source_generic_args(writer& w, cswinrt::type_semantics eventTypeSemantics) + { + if (!std::holds_alternative(eventTypeSemantics)) + { + return; + } + for_typedef(w, eventTypeSemantics, [&](TypeDef const& eventType) + { + std::vector genericArgs; + int i = 0; + for (auto&& x: std::get(eventTypeSemantics).generic_args) + { + auto semantics = w.get_generic_arg_scope(i).first; + if (std::holds_alternative(semantics)) + { + genericArgs.push_back(w.write_temp("%", bind(i))); + } + i++; + } + if (genericArgs.size() == 0) + { + return; + } + w.write("<%>", bind_list([](writer& w, auto&& value) + { + w.write(value); + }, ", "sv, genericArgs)); + }); + } + + void write_event_source_subclass(writer& w, cswinrt::type_semantics eventTypeSemantics) + { + for_typedef(w, eventTypeSemantics, [&](TypeDef const& eventType) + { + if (eventType.TypeNamespace() == "System" && eventType.TypeName() == "EventHandler`1") + { + return; + } + auto eventTypeCode = w.write_temp("%", bind(eventType, typedef_name_type::Projected, false)); + auto invokeMethodSig = get_event_invoke_method(eventType); + w.write(R"( + internal unsafe class %% : EventSource<%> + { + private % handler; + + internal %(IObjectReference obj, + delegate* unmanaged[Stdcall] addHandler, + delegate* unmanaged[Stdcall] removeHandler) : base(obj, addHandler, removeHandler) + { + } + + override protected System.Delegate EventInvoke + { + get + { + if (handler == null) + { + handler = (%) => + { + if (_event == null) + { + return %; + } + %_event.Invoke(%); + }; + } + return handler; + } + } + } +)", + bind(eventTypeSemantics), + bind(eventTypeSemantics), + eventTypeCode, + eventTypeCode, + bind(eventTypeSemantics), + bind(invokeMethodSig), + bind(invokeMethodSig), + bind(invokeMethodSig), + bind(invokeMethodSig)); + }); + } + + void write_temp_class_event_source_subclass(writer& w, TypeDef const& classType, concurrency::concurrent_unordered_map& typeNameToDefinitionMap) + { + for (auto&& ii : classType.InterfaceImpl()) + { + for_typedef(w, get_type_semantics(ii.Interface()), [&](TypeDef const& interfaceType) + { + for (auto&& eventObj : interfaceType.EventList()) + { + auto&& eventTypeSemantics = get_type_semantics(eventObj.EventType()); + auto&& eventTypeCode = w.write_temp("%", bind(eventTypeSemantics, typedef_name_type::Projected, false)); + typeNameToDefinitionMap[eventTypeCode] = w.write_temp("%", bind(eventTypeSemantics)); + } + }); + } + } + + void write_temp_interface_event_source_subclass(writer& w, TypeDef const& interfaceType, concurrency::concurrent_unordered_map& typeNameToDefinitionMap) + { + for (auto&& eventObj : interfaceType.EventList()) + { + auto&& eventTypeSemantics = get_type_semantics(eventObj.EventType()); + auto&& eventTypeCode = w.write_temp("%", bind(eventTypeSemantics, typedef_name_type::Projected, false)); + typeNameToDefinitionMap[eventTypeCode] = w.write_temp("%", bind(eventTypeSemantics)); + } + } +} diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp index 75b31dfd2..1a4254304 100644 --- a/src/cswinrt/main.cpp +++ b/src/cswinrt/main.cpp @@ -4,6 +4,7 @@ #include "helpers.h" #include "type_writers.h" #include "code_writers.h" +#include namespace cswinrt { @@ -165,16 +166,19 @@ Where is one or more of: task_group group; + concurrency::concurrent_unordered_map typeNameToDefinitionMap; bool projectionFileWritten = false; for (auto&& ns_members : c.namespaces()) { - group.add([&ns_members, &componentActivatableClasses, &projectionFileWritten] + group.add([&ns_members, &componentActivatableClasses, &projectionFileWritten, &typeNameToDefinitionMap] { auto&& [ns, members] = ns_members; std::string_view currentType = ""; try { writer w(ns); + writer helperWriter("WinRT"); + w.write_begin(); bool written = false; bool requires_abi = false; @@ -188,6 +192,7 @@ Where is one or more of: continue; } auto guard{ w.push_generic_params(type.GenericParam()) }; + auto guard1{ helperWriter.push_generic_params(type.GenericParam()) }; bool type_requires_abi = true; switch (get_category(type)) @@ -212,6 +217,7 @@ Where is one or more of: write_factory_class(w, type); } } + write_temp_class_event_source_subclass(helperWriter, type, typeNameToDefinitionMap); break; case category::delegate_type: write_delegate(w, type); @@ -222,6 +228,7 @@ Where is one or more of: break; case category::interface_type: write_interface(w, type); + write_temp_interface_event_source_subclass(helperWriter, type, typeNameToDefinitionMap); break; case category::struct_type: if (is_api_contract_type(type)) @@ -308,7 +315,7 @@ Where is one or more of: } }); } - + if(settings.component) { group.add([&componentActivatableClasses, &projectionFileWritten] @@ -322,6 +329,14 @@ Where is one or more of: } group.get(); + writer eventHelperWriter("WinRT"); + eventHelperWriter.write("namespace WinRT\n{\n%\n}", bind([&](writer& w) { + for (auto&& [key, value] : typeNameToDefinitionMap) + { + w.write("%", value); + } + })); + eventHelperWriter.flush_to_file(settings.output_folder / "WinRTEventHelpers.cs"); if (projectionFileWritten) { diff --git a/src/cswinrt/strings/WinRT.cs b/src/cswinrt/strings/WinRT.cs index 8c05faaac..4142397aa 100644 --- a/src/cswinrt/strings/WinRT.cs +++ b/src/cswinrt/strings/WinRT.cs @@ -244,8 +244,8 @@ public static unsafe (ObjectReference obj, int hr) GetA { m.Dispose(); } - } - + } + ~WinrtModule() { Marshal.ThrowExceptionForHR(Platform.CoDecrementMTAUsage(_mtaCookie)); @@ -313,14 +313,14 @@ public ActivationFactory() : base(typeof(T).Namespace, typeof(T).FullName) { } public static ObjectReference As() => _factory.Value._As(); public static IObjectReference As(Guid iid) => _factory.Value._As(iid); public static ObjectReference ActivateInstance() => _factory.Value._ActivateInstance(); - } - + } + internal class ComponentActivationFactory : global::WinRT.Interop.IActivationFactory { public IntPtr ActivateInstance() { throw new NotImplementedException(); - } + } } internal class ActivatableComponentActivationFactory : ComponentActivationFactory, global::WinRT.Interop.IActivationFactory where T : class, new() @@ -329,10 +329,11 @@ public IntPtr ActivateInstance() { T comp = new T(); return MarshalInspectable.FromManaged(comp); - } + } } #pragma warning disable CA2002 + internal unsafe class EventSource where TDelegate : class, MulticastDelegate { @@ -341,7 +342,7 @@ internal unsafe class EventSource readonly delegate* unmanaged[Stdcall] _removeHandler; private EventRegistrationToken _token; - private TDelegate _event; + protected TDelegate _event; protected virtual IObjectReference CreateMarshaler(TDelegate del) { @@ -395,7 +396,7 @@ public void Unsubscribe(TDelegate del) } private System.Delegate _eventInvoke; - private System.Delegate EventInvoke + protected virtual System.Delegate EventInvoke { get { @@ -444,6 +445,35 @@ void _UnsubscribeFromNative() _token.Value = 0; } } + + internal unsafe class EventSource__EventHandler : EventSource> + { + private System.EventHandler handler; + + internal EventSource__EventHandler(IObjectReference obj, + delegate* unmanaged[Stdcall] addHandler, + delegate* unmanaged[Stdcall] removeHandler) : base(obj, addHandler, removeHandler) + { + } + + override protected System.Delegate EventInvoke + { + // This is synchronized from the base class + get + { + if (handler == null) + { + handler = (System.Object obj, T e) => + { + if (_event != null) + _event.Invoke(obj, e); + }; + } + return handler; + } + } + } + #pragma warning restore CA2002 // An event registration token table stores mappings from delegates to event tokens, in order to support @@ -573,20 +603,20 @@ private void RemoveEventHandlerNoLock(EventRegistrationToken token) namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method)] - internal class ModuleInitializerAttribute : Attribute {} + internal class ModuleInitializerAttribute : Attribute { } } namespace WinRT { using System.Runtime.CompilerServices; internal static class ProjectionInitializer - { + { #pragma warning disable 0436 - [ModuleInitializer] + [ModuleInitializer] #pragma warning restore 0436 - internal static void InitalizeProjection() - { - ComWrappersSupport.RegisterProjectionAssembly(typeof(ProjectionInitializer).Assembly); - } + internal static void InitalizeProjection() + { + ComWrappersSupport.RegisterProjectionAssembly(typeof(ProjectionInitializer).Assembly); + } } -} \ No newline at end of file +} diff --git a/src/get_testwinrt.cmd b/src/get_testwinrt.cmd index 4165607f5..ba2838c0c 100644 --- a/src/get_testwinrt.cmd +++ b/src/get_testwinrt.cmd @@ -14,7 +14,7 @@ git checkout -f master if ErrorLevel 1 popd & exit /b !ErrorLevel! git fetch -f if ErrorLevel 1 popd & exit /b !ErrorLevel! -git reset -q --hard a4ed612db03f8ef913ebe08c6d40109d14bda685 +git reset -q --hard 45c6a357c0293d202a1c090e18d24ce42833fd23 if ErrorLevel 1 popd & exit /b !ErrorLevel! echo Restoring Nuget %this_dir%.nuget\nuget.exe restore