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