File: Utils\ApiUtils.cs
Web Access
Project: src\src\Microsoft.ML.Data\Microsoft.ML.Data.csproj (Microsoft.ML.Data)
// 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.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.ML.Data;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML
{
    internal delegate void Peek<in TRow, TValue>(TRow row, long position, ref TValue value);
 
    internal delegate void Poke<TRow, TValue>(TRow dst, TValue src);
 
    internal static class ApiUtils
    {
        private static readonly FuncStaticMethodInfo3<FieldInfo, OpCode, Delegate> _generatePeekFieldMethodInfo
            = new FuncStaticMethodInfo3<FieldInfo, OpCode, Delegate>(GeneratePeek<int, int, int>);
 
        private static readonly FuncStaticMethodInfo3<PropertyInfo, OpCode, Delegate> _generatePeekPropertyMethodInfo
            = new FuncStaticMethodInfo3<PropertyInfo, OpCode, Delegate>(GeneratePeek<int, int, int>);
 
        private static readonly FuncStaticMethodInfo3<FieldInfo, OpCode, Delegate> _generatePokeFieldMethodInfo
            = new FuncStaticMethodInfo3<FieldInfo, OpCode, Delegate>(GeneratePoke<int, int, int>);
 
        private static readonly FuncStaticMethodInfo3<PropertyInfo, Delegate> _generatePokePropertyMethodInfo
            = new FuncStaticMethodInfo3<PropertyInfo, Delegate>(GeneratePoke<int, int, int>);
 
        private static OpCode GetAssignmentOpCode(Type t, IEnumerable<Attribute> attributes)
        {
            // REVIEW: This should be a Dictionary<Type, OpCode> based solution.
            // DvTypes, strings, arrays, all nullable types, VBuffers and RowId.
            if (t == typeof(ReadOnlyMemory<char>) || t == typeof(string) || t.IsArray ||
                (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(VBuffer<>)) ||
                (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) ||
                t == typeof(DateTime) || t == typeof(DateTimeOffset) || t == typeof(TimeSpan) ||
                t == typeof(DataViewRowId) || DataViewTypeManager.Knows(t, attributes))
            {
                return OpCodes.Stobj;
            }
 
            // Simple primitive types.
            if (t == typeof(Single))
                return OpCodes.Stind_R4;
            if (t == typeof(Double))
                return OpCodes.Stind_R8;
            if (t == typeof(sbyte) || t == typeof(byte) || t == typeof(bool))
                return OpCodes.Stind_I1;
            if (t == typeof(short) || t == typeof(ushort))
                return OpCodes.Stind_I2;
            if (t == typeof(int) || t == typeof(uint))
                return OpCodes.Stind_I4;
            if (t == typeof(long) || t == typeof(ulong))
                return OpCodes.Stind_I8;
            throw Contracts.ExceptNotSupp("Type '{0}' is not supported.", t.FullName);
        }
 
        /// <summary>
        /// Each of the specialized 'peek' methods copies the appropriate field value of an instance of T
        /// into the provided buffer. So, the call is 'peek(userObject, ref destination)' and the logic is
        /// indentical to 'destination = userObject.##FIELD##', where ##FIELD## is defined per peek method.
        /// </summary>
        internal static Delegate GeneratePeek<TOwn, TRow>(InternalSchemaDefinition.Column column)
        {
            switch (column.MemberInfo)
            {
                case FieldInfo fieldInfo:
                    Type fieldType = fieldInfo.FieldType;
                    var assignmentOpCode = GetAssignmentOpCode(fieldType, fieldInfo.GetCustomAttributes());
                    return Utils.MarshalInvoke(_generatePeekFieldMethodInfo, typeof(TOwn), typeof(TRow), fieldType, fieldInfo, assignmentOpCode);
 
                case PropertyInfo propertyInfo:
                    Type propertyType = propertyInfo.PropertyType;
                    var assignmentOpCodeProp = GetAssignmentOpCode(propertyType, propertyInfo.GetCustomAttributes());
                    return Utils.MarshalInvoke(_generatePeekPropertyMethodInfo, typeof(TOwn), typeof(TRow), propertyType, propertyInfo, assignmentOpCodeProp);
 
                default:
                    Contracts.Assert(false);
                    throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo");
 
            }
        }
 
        private static Delegate GeneratePeek<TOwn, TRow, TValue>(FieldInfo fieldInfo, OpCode assignmentOpCode)
        {
            // REVIEW: It seems like we really should cache these, instead of generating them per cursor.
            Type[] args = { typeof(TOwn), typeof(TRow), typeof(long), typeof(TValue).MakeByRefType() };
            var mb = new DynamicMethod("Peek", null, args, typeof(TOwn), true);
            var il = mb.GetILGenerator();
 
            il.Emit(OpCodes.Ldarg_3);               // push arg3
            il.Emit(OpCodes.Ldarg_1);               // push arg1
            il.Emit(OpCodes.Ldfld, fieldInfo);      // push [stack top].[fieldInfo]
            // Stobj needs to coupled with a type.
            if (assignmentOpCode == OpCodes.Stobj)  // [stack top-1] = [stack top]
                il.Emit(assignmentOpCode, fieldInfo.FieldType);
            else
                il.Emit(assignmentOpCode);
            il.Emit(OpCodes.Ret);                   // ret
 
            return mb.CreateDelegate(typeof(Peek<TRow, TValue>));
        }
 
        private static Delegate GeneratePeek<TOwn, TRow, TValue>(PropertyInfo propertyInfo, OpCode assignmentOpCode)
        {
            // REVIEW: It seems like we really should cache these, instead of generating them per cursor.
            Type[] args = { typeof(TOwn), typeof(TRow), typeof(long), typeof(TValue).MakeByRefType() };
            var mb = new DynamicMethod("Peek", null, args, typeof(TOwn), true);
            var il = mb.GetILGenerator();
            var minfo = propertyInfo.GetGetMethod();
            var opcode = (minfo.IsVirtual || minfo.IsAbstract) ? OpCodes.Callvirt : OpCodes.Call;
 
            il.Emit(OpCodes.Ldarg_3);               // push arg3
            il.Emit(OpCodes.Ldarg_1);               // push arg1
            il.Emit(opcode, minfo);                 // call [stack top].get_[propertyInfo]()
            // Stobj needs to coupled with a type.
            if (assignmentOpCode == OpCodes.Stobj)  // [stack top-1] = [stack top]
                il.Emit(assignmentOpCode, propertyInfo.PropertyType);
            else
                il.Emit(assignmentOpCode);
            il.Emit(OpCodes.Ret);                   // ret
 
            return mb.CreateDelegate(typeof(Peek<TRow, TValue>));
        }
 
        /// <summary>
        /// Each of the specialized 'poke' methods sets the appropriate field value of an instance of T
        /// to the provided value. So, the call is 'peek(userObject, providedValue)' and the logic is
        /// identical to 'userObject.##FIELD## = providedValue', where ##FIELD## is defined per poke method.
        /// </summary>
        internal static Delegate GeneratePoke<TOwn, TRow>(InternalSchemaDefinition.Column column)
        {
            switch (column.MemberInfo)
            {
                case FieldInfo fieldInfo:
                    Type fieldType = fieldInfo.FieldType;
                    var assignmentOpCode = GetAssignmentOpCode(fieldType, fieldInfo.GetCustomAttributes());
                    return Utils.MarshalInvoke(_generatePokeFieldMethodInfo, typeof(TOwn), typeof(TRow), fieldType, fieldInfo, assignmentOpCode);
 
                case PropertyInfo propertyInfo:
                    Type propertyType = propertyInfo.PropertyType;
                    var assignmentOpCodeProp = GetAssignmentOpCode(propertyType, propertyInfo.GetCustomAttributes());
                    return Utils.MarshalInvoke(_generatePokePropertyMethodInfo, typeof(TOwn), typeof(TRow), propertyType, propertyInfo);
 
                default:
                    Contracts.Assert(false);
                    throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo");
            }
        }
 
        private static Delegate GeneratePoke<TOwn, TRow, TValue>(FieldInfo fieldInfo, OpCode assignmentOpCode)
        {
            Type[] args = { typeof(TOwn), typeof(TRow), typeof(TValue) };
            var mb = new DynamicMethod("Poke", null, args, typeof(TOwn), true);
            var il = mb.GetILGenerator();
 
            il.Emit(OpCodes.Ldarg_1);               // push arg1
            il.Emit(OpCodes.Ldflda, fieldInfo);     // push addr([stack top].[fieldInfo])
            il.Emit(OpCodes.Ldarg_2);               // push arg2
            // Stobj needs to coupled with a type.
            if (assignmentOpCode == OpCodes.Stobj)  // [stack top-1] = [stack top]
                il.Emit(assignmentOpCode, fieldInfo.FieldType);
            else
                il.Emit(assignmentOpCode);
            il.Emit(OpCodes.Ret);                   // ret
            return mb.CreateDelegate(typeof(Poke<TRow, TValue>), null);
        }
 
        private static Delegate GeneratePoke<TOwn, TRow, TValue>(PropertyInfo propertyInfo)
        {
            Type[] args = { typeof(TOwn), typeof(TRow), typeof(TValue) };
            var mb = new DynamicMethod("Poke", null, args, typeof(TOwn), true);
            var il = mb.GetILGenerator();
            var minfo = propertyInfo.GetSetMethod();
            var opcode = (minfo.IsVirtual || minfo.IsAbstract) ? OpCodes.Callvirt : OpCodes.Call;
 
            il.Emit(OpCodes.Ldarg_1);               // push arg1
            il.Emit(OpCodes.Ldarg_2);               // push arg2
            il.Emit(opcode, minfo);                 // call [stack top-1].set_[propertyInfo]([stack top])
            il.Emit(OpCodes.Ret);                   // ret
            return mb.CreateDelegate(typeof(Poke<TRow, TValue>), null);
        }
    }
}