| File: Expression\MethodGenerator.cs | Web Access |
| Project: src\src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj (Microsoft.ML.Transforms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Reflection.Emit; using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Runtime; namespace Microsoft.ML.Transforms { /// <summary> /// MethodGenerator is used to build a method by specifying the IL. /// </summary> internal sealed class MethodGenerator : IDisposable { private DynamicMethod _method; public ILGenerator Il { get; private set; } public MethodGenerator(string name, Type thisType, Type returnType, params Type[] parameterTypes) { Contracts.CheckNonEmpty(name, nameof(name)); Contracts.CheckValue(thisType, nameof(thisType)); Contracts.AssertValueOrNull(returnType); Contracts.AssertValueOrNull(parameterTypes); _method = new DynamicMethod(name, returnType, parameterTypes, thisType); Il = _method.GetILGenerator(); } /// <summary> /// Create and return a delegate of the given "delegateType" and currying the /// given "thisObj". Note that "thisObj" may be null and "delegateType" should /// match the types indicated when the MethodGenerator was created. /// </summary> public Delegate CreateDelegate(Type delegateType) { Contracts.CheckValue(delegateType, nameof(delegateType)); var del = _method.CreateDelegate(delegateType, null); Dispose(); return del; } /// <summary> /// This is idempotent (calling multiple times has the same affect as calling once). /// </summary> public void Dispose() { _method = null; Il = null; _locals = null; } /// <summary> /// Represents a temporary local. A MethodGenerator maintains a cache of temporary locals. /// Clients can 'check-out' temporaries from the cache, use them, then release (Dispose) when /// they are no longer needed so other clients can reuse the temporary to cut down on the number /// of locals created. Note that Temporary is a struct, but should NOT be copied around. It is /// critical that a stray copy of a Temporary is not disposed! /// /// We have two 'types' of temporaries: 'Regular' and 'Ref'. 'Ref' temporaries are special in that /// they should be used when you need to use ldloca. Grouping this way should help the JIT optimize /// local usage. /// </summary> public struct Temporary : IDisposable { private Action<LocalBuilder, bool> _dispose; private readonly bool _isRef; // Should only be created by MethodGenerator. Too bad C# can't enforce this without // reversing the class nesting. internal Temporary(Action<LocalBuilder, bool> dispose, LocalBuilder localBuilder, bool isRef) { Contracts.AssertValue(dispose); Contracts.AssertValue(localBuilder); _dispose = dispose; Local = localBuilder; _isRef = isRef; } public LocalBuilder Local { get; private set; } public void Dispose() { if (Local == null) return; Contracts.AssertValue(_dispose); _dispose(Local, _isRef); Local = null; _dispose = null; } } private struct LocalKey : IEquatable<LocalKey> { public readonly Type Type; public readonly bool IsRef; public LocalKey(Type type, bool isRef) { Contracts.AssertValue(type); Type = type; IsRef = isRef; } public static bool operator ==(LocalKey key0, LocalKey key1) { return key0.Equals(key1); } public static bool operator !=(LocalKey key0, LocalKey key1) { return !key0.Equals(key1); } public override int GetHashCode() { return Hashing.CombineHash(Type.GetHashCode(), Hashing.HashInt(IsRef.GetHashCode())); } public override bool Equals(object obj) { if (obj == null || !(obj is LocalKey)) return false; return Equals((LocalKey)obj); } public bool Equals(LocalKey other) { return IsRef == other.IsRef && Type == other.Type; } } private Dictionary<LocalKey, List<LocalBuilder>> _locals; private Action<LocalBuilder, bool> _tempDisposer; public Temporary AcquireTemporary(Type type, bool isRef = false) { Contracts.CheckValue(type, nameof(type)); Contracts.Check(Il != null, "Cannot access IL for a method that has already been created"); if (_tempDisposer == null) _tempDisposer = ReleaseLocal; LocalKey key = new LocalKey(type, isRef); List<LocalBuilder> locals; if (_locals != null && _locals.TryGetValue(key, out locals) && locals.Count > 0) { var temp = locals[locals.Count - 1]; locals.RemoveAt(locals.Count - 1); return new Temporary(_tempDisposer, temp, key.IsRef); } return new Temporary(_tempDisposer, Il.DeclareLocal(key.Type), key.IsRef); } /// <summary> /// Called by the Temporary struct's Dispose method, through the _tempDisposer delegate. /// </summary> private void ReleaseLocal(LocalBuilder localBuilder, bool isRef) { Contracts.AssertValue(localBuilder); LocalKey key = new LocalKey(localBuilder.LocalType, isRef); List<LocalBuilder> locals; if (_locals == null) _locals = new Dictionary<LocalKey, List<LocalBuilder>>(); else if (_locals.TryGetValue(key, out locals)) { Contracts.Assert(!locals.Contains(localBuilder)); locals.Add(localBuilder); return; } locals = new List<LocalBuilder>(4); _locals.Add(key, locals); locals.Add(localBuilder); } } }