Skip to content

Commit

Permalink
Ujjwalchadha/perf/eventsourcecodegen (#852)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ujjwalchadha authored Jun 9, 2021
1 parent 2a6c82e commit 7c19930
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ nuget/Microsoft.Windows.CsWinRT.Prerelease.targets
*.binlog
vs_buildtools.exe
.buildtools
src/BenchmarkDotNet.Artifacts/*
12 changes: 9 additions & 3 deletions src/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<Platforms>x64;x86</Platforms>
<TargetFrameworks>netcoreapp2.0;net5.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp3.1;net5.0;</TargetFrameworks>
<UseWinmd>false</UseWinmd>
<BenchmarkWinmdSupport Condition="'$(BenchmarkWinmdSupport)' == ''">false</BenchmarkWinmdSupport>
<!-- Controls whether to build using WinMDs or the projection. -->
<UseWinmd Condition="'$(TargetFramework)' == 'netcoreapp3.1' And $(BenchmarkWinmdSupport) == true">true</UseWinmd>
<ApplicationManifest Condition="$(UseWinmd) == true">Benchmarks.manifest</ApplicationManifest>
<BenchmarkTargetFramework>$(TargetFramework)</BenchmarkTargetFramework>
<BenchmarkTargetFramework Condition="'$(BenchmarkTargetFramework)' == 'netcoreapp2.0'">netstandard2.0</BenchmarkTargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.18362.2005" Condition="$(UseWinmd) == true"/>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.3" Condition="$(UseWinmd) == false"/>
</ItemGroup>

<ItemGroup>
<Reference Include="$(MSBuildThisFileDirectory)..\Projections\Windows\bin\x64\Release\$(BenchmarkTargetFramework)\Windows.dll"></Reference>
<Reference Include="$(MSBuildThisFileDirectory)..\Projections\Benchmark\bin\x64\Release\$(BenchmarkTargetFramework)\Benchmark.dll"></Reference>

<ProjectReference Include="..\Projections\Windows\Windows.csproj" Condition="$(UseWinmd) == false" />
<ProjectReference Include="..\Projections\Benchmark\Benchmark.csproj" Condition="$(UseWinmd) == false" />
<ProjectReference Include="..\TestWinRT\BenchmarkComponent\BenchmarkComponent.vcxproj" Condition="$(UseWinmd) == true" />
Expand All @@ -32,6 +37,7 @@
<Reference Include="BenchmarkComponent.winmd" Condition="$(UseWinmd) == true">
<HintPath>$(MSBuildThisFileDirectory)..\_build\$(Platform)\$(Configuration)\BenchmarkComponent\bin\BenchmarkComponent\BenchmarkComponent.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Visible>True</Visible>
</Reference>

<!-- When building for NetCoreApp 3.1, we test the WinMD scenario via reg free winrt, so it needs to be in the bin folder for the generated project to reference it. -->
Expand All @@ -42,7 +48,7 @@
</None>

</ItemGroup>

<Target Name="FilterProjection" BeforeTargets="CoreCompile" Condition="$(UseWinmd) == false">
<ItemGroup>
<!--Remove references to projection source winmds to prevent compile conflict warnings-->
Expand Down
17 changes: 17 additions & 0 deletions src/Benchmarks/ReflectionPerf.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BenchmarkComponent;
using BenchmarkDotNet.Attributes;
using System;

namespace Benchmarks
{
Expand Down Expand Up @@ -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();
}
}
}
2 changes: 1 addition & 1 deletion src/benchmark.cmd
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/benchmark_winmd.cmd
Original file line number Diff line number Diff line change
@@ -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
214 changes: 201 additions & 13 deletions src/cswinrt/code_writers.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <set>
#include <filesystem>
#include <iostream>
#include <regex>
#include <concurrent_unordered_map.h>

namespace cswinrt
{
Expand Down Expand Up @@ -453,6 +455,60 @@ namespace cswinrt
bind<write_parameter_name>(param));
}

void write_projection_arg(writer& w, method_signature::param_t const& param)
{
w.write("%",
bind<write_parameter_name>(param));
}

void write_event_source_type_name(writer& w, type_semantics const& eventTypeSemantics)
{
auto eventTypeCode = w.write_temp("%", bind<write_type_name>(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<write_projection_parameter>(", ", 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<write_projection_type>(semantics));
}

void write_event_invoke_args(writer& w, method_signature const& methodSig)
{
w.write("%", bind_list<write_projection_arg>(", ", methodSig.params()));
}

void write_abi_type(writer& w, type_semantics const& semantics)
{
call(semantics,
Expand Down Expand Up @@ -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<write_type_params>(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<write_type_name>(get_type_semantics(evt.EventType()), typedef_name_type::Projected, false),
bind<write_event_source_type_name>(get_type_semantics(evt.EventType())),
bind<write_event_source_generic_args>(get_type_semantics(evt.EventType())),
get_invoke_info(w, add).first,
get_invoke_info(w, remove).first);
}
Expand Down Expand Up @@ -6255,13 +6334,13 @@ bind_list<write_parameter_name_with_modifier>(", ", signature.params())
{
if (factory.activatable)
{
w.write_each<write_factory_activatable_method>(factory.type.MethodList(), projected_type_name);
w.write_each<write_factory_activatable_method>(factory.type.MethodList(), projected_type_name);
}
else if (factory.statics)
{
w.write_each<write_static_method>(factory.type.MethodList(), projected_type_name, true, ""sv);
w.write_each<write_static_property>(factory.type.PropertyList(), projected_type_name, true, ""sv);
w.write_each<write_static_event>(factory.type.EventList(), projected_type_name, true, ""sv);
w.write_each<write_static_method>(factory.type.MethodList(), projected_type_name, true, ""sv);
w.write_each<write_static_property>(factory.type.PropertyList(), projected_type_name, true, ""sv);
w.write_each<write_static_event>(factory.type.EventList(), projected_type_name, true, ""sv);
}
}
}
Expand All @@ -6272,7 +6351,7 @@ bind_list<write_parameter_name_with_modifier>(", ", 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"(
Expand Down Expand Up @@ -6328,8 +6407,8 @@ return IntPtr.Zero;
}
)",
bind_each([](writer& w, TypeDef const& type)
{
w.write(R"(
{
w.write(R"(

if (runtimeClassId == "%.%")
{
Expand All @@ -6340,8 +6419,117 @@ type.TypeNamespace(),
type.TypeName(),
bind<write_type_name>(type, typedef_name_type::CCW, true)
);
},
types
));
},
types
));
}
}

void write_event_source_generic_args(writer& w, cswinrt::type_semantics eventTypeSemantics)
{
if (!std::holds_alternative<generic_type_instance>(eventTypeSemantics))
{
return;
}
for_typedef(w, eventTypeSemantics, [&](TypeDef const& eventType)
{
std::vector<std::string> genericArgs;
int i = 0;
for (auto&& x: std::get<generic_type_instance>(eventTypeSemantics).generic_args)
{
auto semantics = w.get_generic_arg_scope(i).first;
if (std::holds_alternative<generic_type_param>(semantics))
{
genericArgs.push_back(w.write_temp("%", bind<write_generic_type_name>(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<write_type_name>(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]<System.IntPtr, System.IntPtr, out WinRT.EventRegistrationToken, int> addHandler,
delegate* unmanaged[Stdcall]<System.IntPtr, WinRT.EventRegistrationToken, int> removeHandler) : base(obj, addHandler, removeHandler)
{
}

override protected System.Delegate EventInvoke
{
get
{
if (handler == null)
{
handler = (%) =>
{
if (_event == null)
{
return %;
}
%_event.Invoke(%);
};
}
return handler;
}
}
}
)",
bind<write_event_source_type_name>(eventTypeSemantics),
bind<write_event_source_generic_args>(eventTypeSemantics),
eventTypeCode,
eventTypeCode,
bind<write_event_source_type_name>(eventTypeSemantics),
bind<write_event_invoke_params>(invokeMethodSig),
bind<write_event_invoke_return_default>(invokeMethodSig),
bind<write_event_invoke_return>(invokeMethodSig),
bind<write_event_invoke_args>(invokeMethodSig));
});
}

void write_temp_class_event_source_subclass(writer& w, TypeDef const& classType, concurrency::concurrent_unordered_map<std::string, std::string>& 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<write_type_name>(eventTypeSemantics, typedef_name_type::Projected, false));
typeNameToDefinitionMap[eventTypeCode] = w.write_temp("%", bind<write_event_source_subclass>(eventTypeSemantics));
}
});
}
}

void write_temp_interface_event_source_subclass(writer& w, TypeDef const& interfaceType, concurrency::concurrent_unordered_map<std::string, std::string>& typeNameToDefinitionMap)
{
for (auto&& eventObj : interfaceType.EventList())
{
auto&& eventTypeSemantics = get_type_semantics(eventObj.EventType());
auto&& eventTypeCode = w.write_temp("%", bind<write_type_name>(eventTypeSemantics, typedef_name_type::Projected, false));
typeNameToDefinitionMap[eventTypeCode] = w.write_temp("%", bind<write_event_source_subclass>(eventTypeSemantics));
}
}
}
Loading

0 comments on commit 7c19930

Please sign in to comment.