Skip to content

Commit

Permalink
Run IIDOptimizer on WinRT.Runtime (#931)
Browse files Browse the repository at this point in the history
  • Loading branch information
j0shuams authored Jul 29, 2021
1 parent c956f4c commit 3507d24
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 84 deletions.
79 changes: 79 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.IIDOptimizer.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!--
***********************************************************************************************
Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">


<!-- For the IIDOptimizer to work, it needs to be given all the (reference) assemblies used during compilation
This target consolidates them based on an item group output by the CoreCompile target,
so they may be passed as an option to the IIDOptimizer in the target that invokes the tool -->
<Target Name="CsWinRTGenerateIIDOptimizerResponseFile" AfterTargets="Compile" BeforeTargets="CsWinRTInvokeGuidPatcher"
Condition="'$(CsWinRTIIDOptimizerOptOut)' != 'true'">

<PropertyGroup>
<!-- CsWinRTIIDOptimizerPath: If building in the CsWinRT Repo, then this is set already via Directory.Build.props -->
<CsWinRTIIDOptimizerPath Condition="'$(CsWinRTIIDOptimizerPath)' == ''">$(CsWinRTPath)build\tools\IIDOptimizer\</CsWinRTIIDOptimizerPath>
<!-- CsWinRTIIDOptimizerTargetAssembly: First argument to the IIDOptimizer.
We are using the output's .dll from the *obj* folder.
After linking, the output's .dll gets copied to the bin folder. These targets run before linking. -->
<CsWinRTIIDOptimizerTargetAssembly>@(BuiltProjectOutputGroupKeyOutput->'%(Identity)')</CsWinRTIIDOptimizerTargetAssembly>
<!-- IIDOptimizerInterimDir: Second argument to the IIDOptimizer.
The folder used by the IIDOptimizer to save output files -->
<IIDOptimizerInterimDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)', 'IIDOptimizer'))</IIDOptimizerInterimDir>
<!-- GuidPatchTargetAssemblyReferences: third argument to the IIDOptimizer.
We use ReferencePathWithRefAssemblies as this is passed to Csc (C# Compile Task) for the TargetAssembly's References -->
<GuidPatchTargetAssemblyReferences>@(ReferencePathWithRefAssemblies->'--refs &#x0d;&#x0a; %(Identity)', '&#x0d;&#x0a;')</GuidPatchTargetAssemblyReferences>
<!-- GuidPatchParams: Used to write the response file (.rsp) when executing IIDOptimizer.
include the current dll, the folder to winrt.runtime, and the reference assemblies -->
<GuidPatchParams>
--target
$(CsWinRTIIDOptimizerTargetAssembly)
--outdir
$(IIDOptimizerInterimDir)
$(GuidPatchTargetAssemblyReferences)
</GuidPatchParams>
</PropertyGroup>

<MakeDir Directories="$(IIDOptimizerInterimDir)"/>

<PropertyGroup>
<CsWinRTIIDOptimizerResponseFile>$(IIDOptimizerInterimDir)cswinrt_iidoptimizer.rsp</CsWinRTIIDOptimizerResponseFile>
<CsWinRTIIDOptimizerCommand>"$(CsWinRTIIDOptimizerPath)IIDOptimizer.exe" %40"$(CsWinRTIIDOptimizerResponseFile)"</CsWinRTIIDOptimizerCommand>
</PropertyGroup>

<WriteLinesToFile File="$(CsWinRTIIDOptimizerResponseFile)" Lines="$(GuidPatchParams)" Overwrite="true" WriteOnlyWhenDifferent="true" />
</Target>

<!-- Run the IIDOptimizer on the projection .dll -->
<Target Name="CsWinRTInvokeGuidPatcher" AfterTargets="Compile" BeforeTargets="Link" DependsOnTargets="CsWinRTGenerateIIDOptimizerResponseFile"
Condition="'$(CsWinRTIIDOptimizerOptOut)' != 'true'">
<Message Text="$(CsWinRTIIDOptimizerCommand)" Importance="$(CsWinRTMessageImportance)" />
<Exec Command="$(CsWinRTIIDOptimizerCommand)" ConsoleToMsBuild="true" IgnoreExitCode="true">
<Output TaskParameter="ConsoleOutput" PropertyName="CsWinRTGuidPatchOutput" />
<Output TaskParameter="ExitCode" PropertyName="CsWinRTGuidPatchExitCode"/>
</Exec>
</Target>

<!-- When the IIDOptimizer succeeds, replace the input .dll with the patched version -->
<Target Name="CsWinRTReplaceForPatchedRuntime"
Condition="$(CsWinRTGuidPatchExitCode) == 0"
AfterTargets="CsWinRTInvokeGuidPatcher"
BeforeTargets="Link">

<ItemGroup>
<CsWinRTGuidPatchedFiles Include="$(IIDOptimizerInterimDir)$(AssemblyName).dll;$(IIDOptimizerInterimDir)$(AssemblyName).pdb" />
</ItemGroup>

<PropertyGroup>
<!-- Use the above .dll to find the directory we should copy to when we finish (usually "obj")-->
<CsWinRTIIDOptimizerOutputDir>$([System.IO.Directory]::GetParent($(CsWinRTIIDOptimizerTargetAssembly))) </CsWinRTIIDOptimizerOutputDir>
</PropertyGroup>

<!-- Replace the .dll in the output folder with the optimized version we just made -->
<Copy SourceFiles="@(CsWinRTGuidPatchedFiles)" DestinationFolder="$(CsWinRTIIDOptimizerOutputDir)" />
</Target>


</Project>
71 changes: 1 addition & 70 deletions nuget/Microsoft.Windows.CsWinRT.targets
Original file line number Diff line number Diff line change
Expand Up @@ -128,78 +128,9 @@ $(CsWinRTIncludeWinRTInterop)
<Compile Include="$(CsWinRTGeneratedFilesDir)*.cs" Exclude="@(Compile)" />
</ItemGroup>
</Target>

<!-- For the IIDOptimizer to work, it needs to be given all the (reference) assemblies used during compilation
This target consolidates them based on an item group output by the CoreCompile target,
so they may be passed as an option to the IIDOptimizer in the target that invokes the tool -->
<Target Name="CsWinRTGenerateIIDOptimizerResponseFile" AfterTargets="Compile" BeforeTargets="CsWinRTInvokeGuidPatcher"
Condition="'$(CsWinRTIIDOptimizerOptOut)' != 'true'">

<PropertyGroup>
<!-- CsWinRTIIDOptimizerPath: If building in the CsWinRT Repo, then this is set already via Directory.Build.props -->
<CsWinRTIIDOptimizerPath Condition="'$(CsWinRTIIDOptimizerPath)' == ''">$(CsWinRTPath)build\tools\IIDOptimizer\</CsWinRTIIDOptimizerPath>
<!-- CsWinRTIIDOptimizerTargetAssembly: First argument to the IIDOptimizer.
We are using the output's .dll from the *obj* folder.
After linking, the output's .dll gets copied to the bin folder. These targets run before linking. -->
<CsWinRTIIDOptimizerTargetAssembly>@(BuiltProjectOutputGroupKeyOutput->'%(Identity)')</CsWinRTIIDOptimizerTargetAssembly>
<!-- IIDOptimizerInterimDir: Second argument to the IIDOptimizer.
The folder used by the IIDOptimizer to save output files -->
<IIDOptimizerInterimDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)', 'IIDOptimizer'))</IIDOptimizerInterimDir>
<!-- GuidPatchTargetAssemblyReferences: third argument to the IIDOptimizer.
We use ReferencePathWithRefAssemblies as this is passed to Csc (C# Compile Task) for the TargetAssembly's References -->
<GuidPatchTargetAssemblyReferences>@(ReferencePathWithRefAssemblies->'--refs &#x0d;&#x0a; %(Identity)', '&#x0d;&#x0a;')</GuidPatchTargetAssemblyReferences>
<!-- GuidPatchParams: Used to write the response file (.rsp) when executing IIDOptimizer.
include the current dll, the folder to winrt.runtime, and the reference assemblies -->
<GuidPatchParams>
--target
$(CsWinRTIIDOptimizerTargetAssembly)
--outdir
$(IIDOptimizerInterimDir)
$(GuidPatchTargetAssemblyReferences)
</GuidPatchParams>
</PropertyGroup>

<MakeDir Directories="$(IIDOptimizerInterimDir)"/>

<PropertyGroup>
<CsWinRTIIDOptimizerResponseFile>$(IIDOptimizerInterimDir)cswinrt_iidoptimizer.rsp</CsWinRTIIDOptimizerResponseFile>
<CsWinRTIIDOptimizerCommand>"$(CsWinRTIIDOptimizerPath)IIDOptimizer.exe" %40"$(CsWinRTIIDOptimizerResponseFile)"</CsWinRTIIDOptimizerCommand>
</PropertyGroup>

<WriteLinesToFile File="$(CsWinRTIIDOptimizerResponseFile)" Lines="$(GuidPatchParams)" Overwrite="true" WriteOnlyWhenDifferent="true" />
</Target>

<!-- Run the IIDOptimizer on the projection .dll -->
<Target Name="CsWinRTInvokeGuidPatcher" AfterTargets="Compile" BeforeTargets="Link" DependsOnTargets="CsWinRTGenerateIIDOptimizerResponseFile"
Condition="'$(CsWinRTIIDOptimizerOptOut)' != 'true'">
<Message Text="$(CsWinRTIIDOptimizerCommand)" Importance="$(CsWinRTMessageImportance)" />
<Exec Command="$(CsWinRTIIDOptimizerCommand)" ConsoleToMsBuild="true" IgnoreExitCode="true">
<Output TaskParameter="ConsoleOutput" PropertyName="CsWinRTGuidPatchOutput" />
<Output TaskParameter="ExitCode" PropertyName="CsWinRTGuidPatchExitCode"/>
</Exec>
</Target>

<!-- When the IIDOptimizer succeeds, replace the input .dll with the patched version -->
<Target Name="CsWinRTReplaceForPatchedRuntime"
Condition="$(CsWinRTGuidPatchExitCode) == 0"
AfterTargets="CsWinRTInvokeGuidPatcher"
DependsOnTargets="CsWinRTInvokeGuidPatcher"
BeforeTargets="Link">

<ItemGroup>
<CsWinRTGuidPatchedFiles Include="$(IIDOptimizerInterimDir)$(AssemblyName).dll;$(IIDOptimizerInterimDir)$(AssemblyName).pdb" />
</ItemGroup>

<PropertyGroup>
<!-- Use the above .dll to find the directory we should copy to when we finish (usually "obj")-->
<CsWinRTIIDOptimizerOutputDir>$([System.IO.Directory]::GetParent($(CsWinRTIIDOptimizerTargetAssembly))) </CsWinRTIIDOptimizerOutputDir>
</PropertyGroup>

<!-- Replace the .dll in the output folder with the optimized version we just made -->
<Copy SourceFiles="@(CsWinRTGuidPatchedFiles)" DestinationFolder="$(CsWinRTIIDOptimizerOutputDir)" />
</Target>

<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Prerelease.targets" Condition="Exists('$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Prerelease.targets')"/>
<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Authoring.targets" Condition="'$(CsWinRTComponent)' == 'true'"/>
<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.IIDOptimizer.targets" />

</Project>
11 changes: 9 additions & 2 deletions src/Perf/IIDOptimizer/GuidPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,16 @@ public GuidPatcher(AssemblyDefinition winRTRuntime, AssemblyDefinition targetAss

getTypeFromHandleMethod = systemType.Methods.First(m => m.Name == "GetTypeFromHandle");

guidGeneratorType = null;
guidGeneratorType = null;

TypeDefinition? typeExtensionsType = null;
TypeDefinition? typeExtensionsType = null;

// Use the type definition if we are patching WinRT.Runtime, otherwise lookup the types as references
if (assembly.Name.Name == "WinRT.Runtime")
{
guidGeneratorType = winRTRuntimeAssembly.MainModule.Types.Where(typeDef => typeDef.Name == "GuidGenerator").First();
typeExtensionsType = winRTRuntimeAssembly.MainModule.Types.Where(typeDef => typeDef.Name == "TypeExtensions").First();
}

foreach (var asm in assembly.MainModule.AssemblyReferences)
{
Expand Down
51 changes: 43 additions & 8 deletions src/Perf/IIDOptimizer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,9 @@ static async Task Main(string[] args)
await rootCommand.InvokeAsync(args);
}

private static int GuidPatch(string targetAssembly, string outputDirectory, IEnumerable<FileInfo> references)
{
var resolver = new ReferenceAssemblyResolver(references);
try
{
AssemblyDefinition winRTRuntimeAssembly = resolver.Resolve(new AssemblyNameReference("WinRT.Runtime", default));

var readerParameters = new ReaderParameters(ReadingMode.Deferred)
private static ReaderParameters MakeReaderParams(ReferenceAssemblyResolver resolver)
{
return new ReaderParameters(ReadingMode.Deferred)
{
ReadWrite = true,
InMemory = true,
Expand All @@ -73,15 +68,55 @@ private static int GuidPatch(string targetAssembly, string outputDirectory, IEnu
ApplyWindowsRuntimeProjections = false,
ReadSymbols = true
};
}


// Set WinRT.Runtime.dll -- either we are patching it, or it is in the references
private static AssemblyDefinition ResolveWinRTRuntime(AssemblyDefinition targetAssemblyDefinition, ReferenceAssemblyResolver resolver)
{
AssemblyDefinition? winRTRuntimeAssembly = null;

if (targetAssemblyDefinition.Name.Name == "WinRT.Runtime")
{
winRTRuntimeAssembly = targetAssemblyDefinition;
}
else
{
var winrtAssembly = targetAssemblyDefinition
.MainModule
.AssemblyReferences
.Where(refAssembly => refAssembly.Name == "WinRT.Runtime")
.First();

winRTRuntimeAssembly = resolver.Resolve(winrtAssembly);
}

return winRTRuntimeAssembly;
}

private static int GuidPatch(string targetAssembly, string outputDirectory, IEnumerable<FileInfo> references)
{
var resolver = new ReferenceAssemblyResolver(references);
try
{
var readerParameters = MakeReaderParams(resolver);

var targetAssemblyDefinition = AssemblyDefinition.ReadAssembly(targetAssembly, readerParameters);

/// Don't patch twice
if (targetAssemblyDefinition.MainModule.Types.Any(typeDef => typeDef.Name == "<GuidPatcherImplementationDetails>"))
{
Console.WriteLine("Target assembly has already been patched. Exiting early as there is no work to do.");
return -2;
}

var winRTRuntimeAssembly = ResolveWinRTRuntime(targetAssemblyDefinition, resolver);
if (winRTRuntimeAssembly is null)
{
Console.WriteLine("Failed to resolve WinRT.Runtime.dll.");
return -1;
}

var guidPatcher = new GuidPatcher(winRTRuntimeAssembly, targetAssemblyDefinition);

int numPatches = guidPatcher.ProcessAssembly();
Expand Down
26 changes: 26 additions & 0 deletions src/Tests/UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ public void TestLongClassNameEventSource()
Assert.True(flag);
}

[Fact]
public void TestEventArgsVector()
{
var eventArgsVector = TestObject.GetEventArgsVector();
Assert.Equal(1, eventArgsVector.Count);
foreach (var dataErrorChangedEventArgs in eventArgsVector)
{
var propName = dataErrorChangedEventArgs.PropertyName;
Assert.Equal("name", propName);
}
}

[Fact]
public void TestNonGenericDelegateVector()
{
var provideUriVector = TestObject.GetNonGenericDelegateVector();

Assert.Equal(1, provideUriVector.Count);

foreach (var provideUri in provideUriVector)
{
Uri delegateTarget = provideUri.Invoke();
Assert.Equal("http://microsoft.com", delegateTarget.OriginalString);
}
}

[Fact]
public void TestEnums()
{
Expand Down
5 changes: 4 additions & 1 deletion src/Tests/UnitTest/UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SimulateCsWinRTNugetReference>true</SimulateCsWinRTNugetReference>
<CsWinRTEnabled>false</CsWinRTEnabled>
<CsWinRTIIDOptimizerOptOut>true</CsWinRTIIDOptimizerOptOut>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<CsWinRTIIDOptimizerOptOut>true</CsWinRTIIDOptimizerOptOut>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Projections\Windows\Windows.csproj" />
<ProjectReference Include="..\..\Projections\WinUI\WinUI.csproj" />
Expand Down
8 changes: 8 additions & 0 deletions src/WinRT.Runtime/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!--
***********************************************************************************************
Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)../../nuget/Microsoft.Windows.CsWinRT.IIDOptimizer.targets" Condition="'$(TargetFramework)' != 'netstandard2.0'"/>
</Project>
7 changes: 4 additions & 3 deletions src/cswinrt.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestComponent", "TestWinRT\
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinRT.Runtime", "WinRT.Runtime\WinRT.Runtime.csproj", "{25244CED-966E-45F2-9711-1F51E951FF89}"
ProjectSection(ProjectDependencies) = postProject
{AE3B0611-2FBB-42AB-A245-B4E79868A5F9} = {AE3B0611-2FBB-42AB-A245-B4E79868A5F9}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinUIDesktopSample", "Samples\WinUIDesktopSample\WinUIDesktopSample.csproj", "{8E6FBCB2-B0C1-4E92-8AEB-2A11564E6E0D}"
EndProject
Expand All @@ -53,6 +56,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuget", "Nuget", "{967889B0-4C40-4EA2-8F11-26ECB88205FF}"
ProjectSection(SolutionItems) = preProject
..\nuget\LICENSE = ..\nuget\LICENSE
..\nuget\Microsoft.Windows.CsWinRT.IIDOptimizer.targets = ..\nuget\Microsoft.Windows.CsWinRT.IIDOptimizer.targets
..\nuget\Microsoft.Windows.CsWinRT.nuspec = ..\nuget\Microsoft.Windows.CsWinRT.nuspec
..\nuget\Microsoft.Windows.CsWinRT.props = ..\nuget\Microsoft.Windows.CsWinRT.props
..\nuget\Microsoft.Windows.CsWinRT.targets = ..\nuget\Microsoft.Windows.CsWinRT.targets
Expand Down Expand Up @@ -108,9 +112,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Perf", "Perf", "{539DBDEF-3B49-4503-9BD3-7EB83C2179CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIDOptimizer", "Perf\IIDOptimizer\IIDOptimizer.csproj", "{AE3B0611-2FBB-42AB-A245-B4E79868A5F9}"
ProjectSection(ProjectDependencies) = postProject
{25244CED-966E-45F2-9711-1F51E951FF89} = {25244CED-966E-45F2-9711-1F51E951FF89}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reunion", "Projections\Reunion\Reunion.csproj", "{B6312AD1-A59E-4F3B-AA39-20B780FE9E15}"
EndProject
Expand Down

0 comments on commit 3507d24

Please sign in to comment.