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);
        }
    }
}