Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

ILEmit backend for DependencyInjeciton #630

Merged
merged 10 commits into from
Mar 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/DI.Performance/DI.Performance.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Microsoft.Extensions.DependencyInjection.Performance</RootNamespace>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/DI.Performance/GetServiceBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ public class GetServiceBenchmark
private IServiceProvider _emptyEnumerable;
private ServiceProviderMode _mode;

[Params("Compiled", "Dynamic", "Runtime")]
[Params("Expressions", "Dynamic", "Runtime", "ILEmit")]
public string Mode {
set {
_mode = Enum.Parse<ServiceProviderMode>(value);
_mode = (ServiceProviderMode)Enum.Parse(typeof(ServiceProviderMode), value);
}
}

Expand Down
4 changes: 2 additions & 2 deletions benchmarks/DI.Performance/TimeToFirstServiceBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ public class TimeToFirstServiceBenchmark
private ServiceCollection _singletonServices;
private ServiceProviderMode _mode;

[Params("Compiled", "Dynamic", "Runtime")]
[Params("Expressions", "Dynamic", "Runtime", "ILEmit")]
public string Mode {
set {
_mode = Enum.Parse<ServiceProviderMode>(value);
_mode = (ServiceProviderMode)Enum.Parse(typeof(ServiceProviderMode), value);
}
}

Expand Down
1 change: 1 addition & 0 deletions build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26314-02</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
<SystemReflectionEmitPackageVersion>4.3.0</SystemReflectionEmitPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
<XunitAssertPackageVersion>2.3.1</XunitAssertPackageVersion>
Expand Down
16 changes: 15 additions & 1 deletion src/DI/DI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,35 @@

<PropertyGroup>
<Description>Default implementation of dependency injection for Microsoft.Extensions.DependencyInjection.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netcoreapp2.0;net461;netstandard2.0</TargetFrameworks>
<AssemblyName>Microsoft.Extensions.DependencyInjection</AssemblyName>
<RootNamespace>Microsoft.Extensions.DependencyInjection</RootNamespace>

<ILEmitBackend Condition="$(TargetFramework) != 'netstandard2.0'">True</ILEmitBackend>
<DefineConstants Condition="'$(ILEmitBackend)' == 'True'">$(DefineConstants);IL_EMIT</DefineConstants>

<!-- Debug IL generation -->
<ILEmitBackendSaveAssemblies>False</ILEmitBackendSaveAssemblies>
<DefineConstants Condition="'$(ILEmitBackendSaveAssemblies)' == 'True'">$(DefineConstants);SAVE_ASSEMBLIES</DefineConstants>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DI.Abstractions\DI.Abstractions.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="ServiceLookup\ILEmit\**\*.cs" />

<Compile Condition="'$(ILEmitBackend)' == 'True'" Include="ServiceLookup\ILEmit\*.cs" />

<Compile Include="..\..\shared\Microsoft.Extensions.ParameterDefaultValue.Sources\*.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.TypeNameHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsTypeNameHelperSourcesPackageVersion)" />

<PackageReference Condition="'$(ILEmitBackendSaveAssemblies)' == 'True'" Include="System.Reflection.Emit" PrivateAssets="All" Version="$(SystemReflectionEmitPackageVersion)" />
<PackageReference Condition="'$(ILEmitBackend)' == 'True'" Include="System.Reflection.Emit.Lightweight" PrivateAssets="All" Version="$(SystemReflectionEmitPackageVersion)" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions src/DI/ServiceLookup/CallSiteFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Internal;

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
Expand Down
25 changes: 25 additions & 0 deletions src/DI/ServiceLookup/CallSiteKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal enum CallSiteKind
{
Factory,

Constructor,

Constant,

IEnumerable,

ServiceProvider,

Scope,

Transient,

CreateInstance,

ServiceScopeFactory,

Singleton
}
}
5 changes: 2 additions & 3 deletions src/DI/ServiceLookup/CallSiteValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class CallSiteValidator: CallSiteVisitor<CallSiteValidator.CallSiteVali

public void ValidateCallSite(IServiceCallSite callSite)
{
var scoped = VisitCallSite(callSite, default(CallSiteValidatorState));
var scoped = VisitCallSite(callSite, default);
if (scoped != null)
{
_scopedServices[callSite.ServiceType] = scoped;
Expand All @@ -22,9 +22,8 @@ public void ValidateCallSite(IServiceCallSite callSite)

public void ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
{
Type scopedService;
if (ReferenceEquals(scope, rootScope)
&& _scopedServices.TryGetValue(serviceType, out scopedService))
&& _scopedServices.TryGetValue(serviceType, out var scopedService))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PranavPoints++

{
if (serviceType == scopedService)
{
Expand Down
42 changes: 21 additions & 21 deletions src/DI/ServiceLookup/CallSiteVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ internal abstract class CallSiteVisitor<TArgument, TResult>
{
protected virtual TResult VisitCallSite(IServiceCallSite callSite, TArgument argument)
{
switch (callSite)
switch (callSite.Kind)
{
case FactoryCallSite factoryCallSite:
return VisitFactory(factoryCallSite, argument);
case IEnumerableCallSite enumerableCallSite:
return VisitIEnumerable(enumerableCallSite, argument);
case ConstructorCallSite constructorCallSite:
return VisitConstructor(constructorCallSite, argument);
case TransientCallSite transientCallSite:
return VisitTransient(transientCallSite, argument);
case SingletonCallSite singletonCallSite:
return VisitSingleton(singletonCallSite, argument);
case ScopedCallSite scopedCallSite:
return VisitScoped(scopedCallSite, argument);
case ConstantCallSite constantCallSite:
return VisitConstant(constantCallSite, argument);
case CreateInstanceCallSite createInstanceCallSite:
return VisitCreateInstance(createInstanceCallSite, argument);
case ServiceProviderCallSite serviceProviderCallSite:
return VisitServiceProvider(serviceProviderCallSite, argument);
case ServiceScopeFactoryCallSite scopeFactoryCallSite:
return VisitServiceScopeFactory(scopeFactoryCallSite, argument);
case CallSiteKind.Factory:
return VisitFactory((FactoryCallSite)callSite, argument);
case CallSiteKind.IEnumerable:
return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
case CallSiteKind.Constructor:
return VisitConstructor((ConstructorCallSite)callSite, argument);
case CallSiteKind.Transient:
return VisitTransient((TransientCallSite)callSite, argument);
case CallSiteKind.Singleton:
return VisitSingleton((SingletonCallSite)callSite, argument);
case CallSiteKind.Scope:
return VisitScoped((ScopedCallSite)callSite, argument);
case CallSiteKind.Constant:
return VisitConstant((ConstantCallSite)callSite, argument);
case CallSiteKind.CreateInstance:
return VisitCreateInstance((CreateInstanceCallSite)callSite, argument);
case CallSiteKind.ServiceProvider:
return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
case CallSiteKind.ServiceScopeFactory:
return VisitServiceScopeFactory((ServiceScopeFactoryCallSite)callSite, argument);
default:
throw new NotSupportedException($"Call site type {callSite.GetType()} is not supported");
}
Expand Down
14 changes: 12 additions & 2 deletions src/DI/ServiceLookup/CompiledServiceProviderEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal class CompiledServiceProviderEngine : ServiceProviderEngine
internal abstract class CompiledServiceProviderEngine : ServiceProviderEngine
{
#if IL_EMIT
public ILEmitResolverBuilder ExpressionResolverBuilder { get; }
#else
public ExpressionResolverBuilder ExpressionResolverBuilder { get; }
#endif
public CompiledServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback) : base(serviceDescriptors, callback)
{
#if IL_EMIT
ExpressionResolverBuilder = new ILEmitResolverBuilder(RuntimeResolver, this, Root);
#else
ExpressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root);
#endif
}

protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
{
var realizedService = ExpressionBuilder.Build(callSite);
var realizedService = ExpressionResolverBuilder.Build(callSite);
RealizedServices[callSite.ServiceType] = realizedService;
return realizedService;
}
Expand Down
1 change: 1 addition & 0 deletions src/DI/ServiceLookup/ConstantCallSite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public ConstantCallSite(Type serviceType, object defaultValue)

public Type ServiceType => DefaultValue.GetType();
public Type ImplementationType => DefaultValue.GetType();
public CallSiteKind Kind { get; } = CallSiteKind.Constant;
Copy link
Member

@davidfowl davidfowl Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public CallSiteKind Kind => CallSiteKind.Constant; Why do you want a backing field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So JIT can do it's JITty stuff

}
}
1 change: 1 addition & 0 deletions src/DI/ServiceLookup/ConstructorCallSite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public ConstructorCallSite(Type serviceType, ConstructorInfo constructorInfo, IS
public Type ServiceType { get; }

public Type ImplementationType => ConstructorInfo.DeclaringType;
public CallSiteKind Kind { get; } = CallSiteKind.Constructor;
}
}
1 change: 1 addition & 0 deletions src/DI/ServiceLookup/CreateInstanceCallSite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class CreateInstanceCallSite : IServiceCallSite
public Type ServiceType { get; }

public Type ImplementationType { get; }
public CallSiteKind Kind { get; } = CallSiteKind.CreateInstance;

public CreateInstanceCallSite(Type serviceType, Type implementationType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal class CallSiteExpressionBuilder : CallSiteVisitor<CallSiteExpressionBuilderContext, Expression>
internal class ExpressionResolverBuilder : CallSiteVisitor<CallSiteExpressionBuilderContext, Expression>
{
private static readonly MethodInfo CaptureDisposableMethodInfo = GetMethodInfo<Func<ServiceProviderEngineScope, object, object>>((a, b) => a.CaptureDisposable(b));
private static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo<Func<IDictionary<object, object>, object, object, bool>>((a, b, c) => a.TryGetValue(b, out c));
private static readonly MethodInfo AddMethodInfo = GetMethodInfo<Action<IDictionary<object, object>, object, object>>((a, b, c) => a.Add(b, c));
private static readonly MethodInfo MonitorEnterMethodInfo = GetMethodInfo<Action<object, bool>>((lockObj, lockTaken) => Monitor.Enter(lockObj, ref lockTaken));
private static readonly MethodInfo MonitorExitMethodInfo = GetMethodInfo<Action<object>>(lockObj => Monitor.Exit(lockObj));
private static readonly MethodInfo CallSiteRuntimeResolverResolve =
internal static readonly MethodInfo InvokeFactoryMethodInfo = GetMethodInfo<Action<Func<IServiceProvider, object>, IServiceProvider>>((a, b) => a.Invoke(b));
internal static readonly MethodInfo CaptureDisposableMethodInfo = GetMethodInfo<Func<ServiceProviderEngineScope, object, object>>((a, b) => a.CaptureDisposable(b));
internal static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo<Func<IDictionary<object, object>, object, object, bool>>((a, b, c) => a.TryGetValue(b, out c));
internal static readonly MethodInfo AddMethodInfo = GetMethodInfo<Action<IDictionary<object, object>, object, object>>((a, b, c) => a.Add(b, c));
internal static readonly MethodInfo MonitorEnterMethodInfo = GetMethodInfo<Action<object, bool>>((lockObj, lockTaken) => Monitor.Enter(lockObj, ref lockTaken));
internal static readonly MethodInfo MonitorExitMethodInfo = GetMethodInfo<Action<object>>(lockObj => Monitor.Exit(lockObj));
internal static readonly MethodInfo CallSiteRuntimeResolverResolve =
GetMethodInfo<Func<CallSiteRuntimeResolver, IServiceCallSite, ServiceProviderEngineScope, object>>((r, c, p) => r.Resolve(c, p));

private static readonly MethodInfo ArrayEmptyMethodInfo = typeof(Array).GetMethod(nameof(Array.Empty));
internal static readonly MethodInfo ArrayEmptyMethodInfo = typeof(Array).GetMethod(nameof(Array.Empty));

private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope));

Expand All @@ -40,7 +41,7 @@ internal class CallSiteExpressionBuilder : CallSiteVisitor<CallSiteExpressionBui

private readonly ServiceProviderEngineScope _rootScope;

public CallSiteExpressionBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope)
public ExpressionResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope)
{
if (runtimeResolver == null)
{
Expand Down Expand Up @@ -166,7 +167,6 @@ protected override Expression VisitTransient(TransientCallSite callSite, CallSit

private Expression TryCaptureDisposible(Type implType, ParameterExpression scope, Expression service)
{

if (implType != null &&
!typeof(IDisposable).GetTypeInfo().IsAssignableFrom(implType.GetTypeInfo()))
{
Expand All @@ -180,10 +180,12 @@ private Expression TryCaptureDisposible(Type implType, ParameterExpression scope
protected override Expression VisitConstructor(ConstructorCallSite callSite, CallSiteExpressionBuilderContext context)
{
var parameters = callSite.ConstructorInfo.GetParameters();
return Expression.New(
callSite.ConstructorInfo,
callSite.ParameterCallSites.Select((c, index) =>
Convert(VisitCallSite(c, context), parameters[index].ParameterType)));
var parameterExpressions = new Expression[callSite.ParameterCallSites.Length];
for (int i = 0; i < parameterExpressions.Length; i++)
{
parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType);
}
return Expression.New(callSite.ConstructorInfo, parameterExpressions);
}

private static Expression Convert(Expression expression, Type type)
Expand All @@ -198,6 +200,12 @@ private static Expression Convert(Expression expression, Type type)
}

protected override Expression VisitScoped(ScopedCallSite callSite, CallSiteExpressionBuilderContext context)
{
return BuildScopedExpression(callSite, context, VisitCallSite(callSite.ServiceCallSite, context));
}

// Move off the main stack
private Expression BuildScopedExpression(ScopedCallSite callSite, CallSiteExpressionBuilderContext context, Expression service)
{
var keyExpression = Expression.Constant(
callSite.CacheKey,
Expand All @@ -213,7 +221,6 @@ protected override Expression VisitScoped(ScopedCallSite callSite, CallSiteExpre
keyExpression,
resolvedVariable);

var service = VisitCallSite(callSite.ServiceCallSite, context);
var captureDisposible = TryCaptureDisposible(callSite.ImplementationType, context.ScopeParameter, service);

var assignExpression = Expression.Assign(
Expand All @@ -228,7 +235,8 @@ protected override Expression VisitScoped(ScopedCallSite callSite, CallSiteExpre

var blockExpression = Expression.Block(
typeof(object),
new[] {
new[]
{
resolvedVariable
},
Expression.IfThen(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal class ExpressionsServiceProviderEngine : ServiceProviderEngine
{
private readonly ExpressionResolverBuilder _expressionResolverBuilder;
public ExpressionsServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback) : base(serviceDescriptors, callback)
{
_expressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root);
}

protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
{
var realizedService = _expressionResolverBuilder.Build(callSite);
RealizedServices[callSite.ServiceType] = realizedService;
return realizedService;
}
}
}
2 changes: 2 additions & 0 deletions src/DI/ServiceLookup/FactoryCallSite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ public FactoryCallSite(Type serviceType, Func<IServiceProvider, object> factory)

public Type ServiceType { get; }
public Type ImplementationType => null;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove space

public CallSiteKind Kind { get; } = CallSiteKind.Factory;
}
}
1 change: 1 addition & 0 deletions src/DI/ServiceLookup/IEnumerableCallSite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ public IEnumerableCallSite(Type itemType, IServiceCallSite[] serviceCallSites)

public Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType);
public Type ImplementationType => ItemType.MakeArrayType();
public CallSiteKind Kind { get; } = CallSiteKind.IEnumerable;
}
}
26 changes: 26 additions & 0 deletions src/DI/ServiceLookup/ILEmit/ILEmitCallSiteAnalysisResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal readonly struct ILEmitCallSiteAnalysisResult
{
public ILEmitCallSiteAnalysisResult(int size) : this()
{
Size = size;
}

public ILEmitCallSiteAnalysisResult(int size, bool hasScope)
{
Size = size;
HasScope = hasScope;
}

public readonly int Size;

public readonly bool HasScope;

public ILEmitCallSiteAnalysisResult Add(in ILEmitCallSiteAnalysisResult other) =>
new ILEmitCallSiteAnalysisResult(Size + other.Size, HasScope | other.HasScope);
}
}
Loading