Skip to content

Commit

Permalink
[Perf] Add IID Lookup Optimization (#849)
Browse files Browse the repository at this point in the history
  • Loading branch information
j0shuams authored Jul 19, 2021
1 parent d146899 commit 2f2e3fc
Show file tree
Hide file tree
Showing 29 changed files with 1,871 additions and 251 deletions.
2 changes: 2 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</metadata>
<files>
<file src="LICENSE"/>
<file src="NOTICE.txt"/>
<file src="$cswinrt_exe$"/>
<file src="Microsoft.Windows.CsWinRT.props" target="build"/>
<file src="Microsoft.Windows.CsWinRT.targets" target="build"/>
Expand All @@ -34,5 +35,6 @@
<file src="$winrt_host_arm$" target ="runtimes\win-arm\native"/>
<file src="$winrt_host_arm64$" target ="runtimes\win-arm64\native"/>
<file src="$winrt_shim$" target ="lib\net5.0\"/>
<file src="$guid_patch$" target ="build\tools\IIDOptimizer"/>
</files>
</package>
74 changes: 73 additions & 1 deletion nuget/Microsoft.Windows.CsWinRT.targets
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Target Name="CsWinRTGenerateProjection" DependsOnTargets="CsWinRTPrepareProjection;CsWinRTRemoveWinMDReferences" Condition="'$(CsWinRTGenerateProjection)' == 'true'">
<PropertyGroup>
<CsWinRTResponseFile>$(CsWinRTGeneratedFilesDir)cswinrt.rsp</CsWinRTResponseFile>
<CsWinRTCommand>"$(CsWinRTExe)" %40"$(CsWinRTResponseFile)"</CsWinRTCommand>
<!-- %40 is an MSBuild escape code for the @ character;
https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/msbuild/msbuild-special-characters?view=vs-2015&redirectedfrom=MSDN
-->
<CsWinRTCommand>"$(CsWinRTExe)" %40"$(CsWinRTResponseFile)"</CsWinRTCommand>
<CsWinRTWindowsMetadata Condition="'$(CsWinRTWindowsMetadata)' == ''">$(WindowsSDKVersion.TrimEnd('\'))</CsWinRTWindowsMetadata>
<CsWinRTWindowsMetadata Condition="'$(CsWinRTWindowsMetadata)' == ''">$(TargetPlatformVersion)</CsWinRTWindowsMetadata>
<CsWinRTWindowsMetadataInput Condition="'$(CsWinRTWindowsMetadata)' != ''">-input $(CsWinRTWindowsMetadata)</CsWinRTWindowsMetadataInput>
Expand Down Expand Up @@ -126,6 +129,75 @@ $(CsWinRTIncludeWinRTInterop)
</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"
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'"/>

Expand Down
42 changes: 42 additions & 0 deletions nuget/NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
NOTICES AND INFORMATION
Do Not Translate or Localize

This software incorporates material from third parties.
Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com,
or you may send a check or money order for US $5.00, including the product name,
the open source component name, platform, and version number, to:

Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA

Notwithstanding any other terms, you may reverse engineer this software to the extent
required to debug changes to any libraries licensed under the GNU Lesser General Public License.

---------------------------------------------------------

Mono.Cecil 0.11.4 - MIT

Copyright (c) 2008 - 2015 Jb Evain
Copyright (c) 2008 - 2011 Novell, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4 changes: 2 additions & 2 deletions src/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<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>
<BenchmarkTargetFramework Condition="'$(BenchmarkTargetFramework)' != 'net5.0'">netstandard2.0</BenchmarkTargetFramework>
<IsDotnetBuild Condition="'$(IsDotnetBuild)' == ''">false</IsDotnetBuild>
<LangVersion Condition="$(IsDotnetBuild) == true">9.0</LangVersion>
</PropertyGroup>
Expand All @@ -22,7 +22,7 @@
</ItemGroup>

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

<ProjectReference Include="..\Projections\Windows\Windows.csproj" Condition="$(UseWinmd) == false And $(IsDotnetBuild) == false" />
Expand Down
4 changes: 4 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<DefineConstants>$(DefineConstants);MANUAL_IUNKNOWN</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<CsWinRTIIDOptimizerPath>$(MSBuildThisFileDirectory)Perf\IIDOptimizer\bin\$(Configuration)\net5.0\</CsWinRTIIDOptimizerPath>
</PropertyGroup>

<PropertyGroup>
<VersionNumber Condition="'$(VersionNumber)'==''">0.0.0.0</VersionNumber>
<VersionString Condition="'$(VersionString)'==''">0.0.0-private.0</VersionString>
Expand Down
110 changes: 110 additions & 0 deletions src/Perf/IIDOptimizer/CecilExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Linq;

namespace GuidPatch
{
static class CecilExtensions
{
internal static Guid? ReadGuidFromAttribute(this TypeReference type, TypeReference guidAttributeType, AssemblyDefinition winrtRuntimeAssembly)
{
TypeDefinition def = type.Resolve();
var guidAttr = def.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.Resolve() == guidAttributeType);
if (guidAttr is null)
{
TypeDefinition abiType = def.GetCswinrtAbiTypeDefinition(winrtRuntimeAssembly);
if (abiType is not null)
{
return abiType.ReadGuidFromAttribute(guidAttributeType, winrtRuntimeAssembly);
}
return null;
}
return new Guid((string)guidAttr.ConstructorArguments[0].Value);
}

internal static TypeDefinition GetCswinrtAbiTypeDefinition(this TypeReference type, AssemblyDefinition winrtRuntimeAssembly)
{
var resolvedType = type.Resolve();

return resolvedType.Module.GetType($"ABI.{resolvedType.FullName}") ??
winrtRuntimeAssembly.MainModule.GetType($"ABI.{resolvedType.FullName}");
}

internal static MethodDefinition CreateIIDDataGetter(TypeReference type, Guid iidValue, TypeDefinition dataBlockType, TypeDefinition parentType, TypeReference readOnlySpanOfByte, MethodReference readOnlySpanOfByteCtor)
{
var guidDataMethod = new MethodDefinition($"<IIDData>{type.FullName}", MethodAttributes.Assembly | MethodAttributes.Static, readOnlySpanOfByte);

WriteIIDDataGetterBody(guidDataMethod, type, iidValue, dataBlockType, parentType, readOnlySpanOfByteCtor);
return guidDataMethod;
}

internal static void WriteIIDDataGetterBody(MethodDefinition method, TypeReference type, Guid iidValue, TypeDefinition dataBlockType, TypeDefinition parentType, MethodReference readOnlySpanOfByteCtor)
{
var guidDataField = new FieldDefinition($"<IIDDataField>{type.FullName}", FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly | FieldAttributes.HasFieldRVA, dataBlockType)
{
InitialValue = iidValue.ToByteArray()
};
parentType.Fields.Add(guidDataField);

var ilProcessor = method.Body.GetILProcessor();
ilProcessor.Clear();
ilProcessor.Append(Instruction.Create(OpCodes.Ldsflda, guidDataField));
ilProcessor.Append(Instruction.Create(OpCodes.Ldc_I4, 16));
ilProcessor.Append(Instruction.Create(OpCodes.Newobj, readOnlySpanOfByteCtor));
ilProcessor.Append(Instruction.Create(OpCodes.Ret));
}

internal static TypeDefinition GetOrCreateDataBlockType(TypeDefinition parentType, int size)
{
if (size < 0 || size > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(size));
}

string typeName = $"__StaticDataBlock<>Size={size}";

var typeRef = new TypeReference(null, typeName, parentType.Module, parentType.Module)
{
DeclaringType = parentType
};

if (typeRef.Resolve() is TypeDefinition td)
{
return td;
}

td = new TypeDefinition(null, typeName, TypeAttributes.AutoClass | TypeAttributes.Sealed | TypeAttributes.NestedAssembly | TypeAttributes.SequentialLayout | TypeAttributes.AnsiClass, new TypeReference("System", "ValueType", parentType.Module, parentType.Module.TypeSystem.CoreLibrary))
{
PackingSize = 1,
ClassSize = size
};

parentType.NestedTypes.Add(td);

return td;
}

internal static TypeReference? FindTypeReference(ModuleDefinition module, string ns, string name, string basicAssemblyName, bool isValueType)
{
foreach (var asm in module.AssemblyReferences)
{
if (asm.Name == basicAssemblyName || asm.Name.StartsWith($"{basicAssemblyName},"))
{
TypeReference typeRef = new TypeReference(ns, name, module, asm, isValueType);
if (typeRef.Resolve() != null)
{
return module.ImportReference(typeRef);
}
break;
}
}
var resolved = module.AssemblyResolver.Resolve(new AssemblyNameReference(basicAssemblyName, default));
if (resolved is null)
{
return null;
}
return resolved.MainModule.Types.FirstOrDefault(t => t.Namespace == ns && t.Name == name);
}
}
}
Loading

0 comments on commit 2f2e3fc

Please sign in to comment.