Commit 1aeee1ca authored by Marc Gravell's avatar Marc Gravell

replace IL-emit in scripting engine with Expression trees; remove dependency and work on UWP

parent 2629e253
...@@ -14,11 +14,10 @@ ...@@ -14,11 +14,10 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug' and '$(Computername)'=='OCHO'"> <PropertyGroup Condition="'$(Configuration)' == 'Debug' and '$(Computername)'=='OCHO'">
<DefineConstants>$(DefineConstants);LOGOUTPUT</DefineConstants> <!--<DefineConstants>$(DefineConstants);LOGOUTPUT</DefineConstants>-->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
<PackageReference Include="System.IO.Pipelines" Version="$(CoreFxVersion)" /> <PackageReference Include="System.IO.Pipelines" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="$(CoreFxVersion)" /> <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="$(CoreFxVersion)" />
</ItemGroup> </ItemGroup>
......
...@@ -157,7 +157,11 @@ public override int GetHashCode() ...@@ -157,7 +157,11 @@ public override int GetHashCode()
/// </summary> /// </summary>
public override string ToString() => ((string)this) ?? "(null)"; public override string ToString() => ((string)this) ?? "(null)";
internal RedisValue AsRedisValue() => (byte[])this; internal RedisValue AsRedisValue()
{
if (keyPrefix == null && keyValue is string) return (string)keyValue;
return (byte[])this;
}
internal void AssertNotNull() internal void AssertNotNull()
{ {
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
...@@ -106,85 +106,23 @@ private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] ar ...@@ -106,85 +106,23 @@ private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] ar
return ret.ToString(); return ret.ToString();
} }
private static void LoadMember(ILGenerator il, MemberInfo member) private static Dictionary<Type, MethodInfo> _conversionOperators;
static ScriptParameterMapper()
{ {
// stack starts: var tmp = new Dictionary<Type, MethodInfo>();
// T(*?) foreach(var method in typeof(RedisValue).GetMethods(BindingFlags.Public | BindingFlags.Static))
if (member is FieldInfo asField)
{ {
il.Emit(OpCodes.Ldfld, asField); // typeof(member) if(method.ReturnType == typeof(RedisValue) &&
return; (method.Name == "op_Implicit" || method.Name == "op_Explicit"))
}
if (member is PropertyInfo asProp)
{ {
var getter = asProp.GetGetMethod(); var p = method.GetParameters();
if (getter.IsVirtual) if (p != null && p.Length == 1)
{ {
il.Emit(OpCodes.Callvirt, getter); // typeof(member) tmp[p[0].ParameterType] = method;
}
else
{
il.Emit(OpCodes.Call, getter); // typeof(member)
}
return;
}
throw new Exception("Should't be possible");
} }
private static readonly MethodInfo RedisValue_FromInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int) });
private static readonly MethodInfo RedisValue_FromNullableInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int?) });
private static readonly MethodInfo RedisValue_FromLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long) });
private static readonly MethodInfo RedisValue_FromNullableLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long?) });
private static readonly MethodInfo RedisValue_FromDouble= typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double) });
private static readonly MethodInfo RedisValue_FromNullableDouble = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double?) });
private static readonly MethodInfo RedisValue_FromString = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(string) });
private static readonly MethodInfo RedisValue_FromByteArray = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(byte[]) });
private static readonly MethodInfo RedisValue_FromBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool) });
private static readonly MethodInfo RedisValue_FromNullableBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool?) });
private static readonly MethodInfo RedisKey_AsRedisValue = typeof(RedisKey).GetMethod("AsRedisValue", BindingFlags.NonPublic | BindingFlags.Instance);
private static void ConvertToRedisValue(MemberInfo member, ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc)
{
// stack starts:
// typeof(member)
var t = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;
if (t == typeof(RedisValue))
{
// They've already converted for us, don't do anything
return;
} }
if (t == typeof(RedisKey))
{
redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey));
PrefixIfNeeded(il, needsPrefixBool, ref redisKeyLoc); // RedisKey
il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty--
il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey*
il.Emit(OpCodes.Call, RedisKey_AsRedisValue); // RedisValue
return;
} }
_conversionOperators = tmp;
MethodInfo convertOp = null;
if (t == typeof(int)) convertOp = RedisValue_FromInt;
if (t == typeof(int?)) convertOp = RedisValue_FromNullableInt;
if (t == typeof(long)) convertOp = RedisValue_FromLong;
if (t == typeof(long?)) convertOp = RedisValue_FromNullableLong;
if (t == typeof(double)) convertOp = RedisValue_FromDouble;
if (t == typeof(double?)) convertOp = RedisValue_FromNullableDouble;
if (t == typeof(string)) convertOp = RedisValue_FromString;
if (t == typeof(byte[])) convertOp = RedisValue_FromByteArray;
if (t == typeof(bool)) convertOp = RedisValue_FromBool;
if (t == typeof(bool?)) convertOp = RedisValue_FromNullableBool;
il.Emit(OpCodes.Call, convertOp);
// stack ends:
// RedisValue
} }
/// <summary> /// <summary>
...@@ -249,28 +187,6 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string mis ...@@ -249,28 +187,6 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string mis
return true; return true;
} }
private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc)
{
// top of stack is
// RedisKey
var getVal = typeof(RedisKey?).GetProperty("Value").GetGetMethod();
var prepend = typeof(RedisKey).GetMethod("Prepend");
var doNothing = il.DefineLabel();
redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey));
il.Emit(OpCodes.Ldloc, needsPrefixBool); // RedisKey bool
il.Emit(OpCodes.Brfalse, doNothing); // RedisKey
il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty--
il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey*
il.Emit(OpCodes.Ldarga_S, 1); // RedisKey* RedisKey?*
il.Emit(OpCodes.Call, getVal); // RedisKey* RedisKey
il.Emit(OpCodes.Call, prepend); // RedisKey
il.MarkLabel(doNothing); // RedisKey
}
/// <summary> /// <summary>
/// <para>Creates a Func that extracts parameters from the given type for use by a LuaScript.</para> /// <para>Creates a Func that extracts parameters from the given type for use by a LuaScript.</para>
/// <para> /// <para>
...@@ -292,6 +208,18 @@ private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ...@@ -292,6 +208,18 @@ private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool,
{ {
if (!IsValidParameterHash(t, script, out _, out _)) throw new Exception("Shouldn't be possible"); if (!IsValidParameterHash(t, script, out _, out _)) throw new Exception("Shouldn't be possible");
Expression GetMember(Expression root, MemberInfo member)
{
switch(member.MemberType)
{
case MemberTypes.Property:
return Expression.Property(root, (PropertyInfo)member);
case MemberTypes.Field:
return Expression.Field(root, (FieldInfo)member);
default:
throw new ArgumentException(nameof(member));
}
}
var keys = new List<MemberInfo>(); var keys = new List<MemberInfo>();
var args = new List<MemberInfo>(); var args = new List<MemberInfo>();
...@@ -306,100 +234,74 @@ private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ...@@ -306,100 +234,74 @@ private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool,
{ {
keys.Add(member); keys.Add(member);
} }
else if (memberType != typeof(RedisValue) && !_conversionOperators.ContainsKey(memberType))
args.Add(member);
}
var nullableRedisKeyHasValue = typeof(RedisKey?).GetProperty("HasValue").GetGetMethod();
var dyn = new DynamicMethod("ParameterExtractor_" + t.FullName + "_" + script.OriginalScript.GetHashCode(), typeof(ScriptParameters), new[] { typeof(object), typeof(RedisKey?) }, restrictedSkipVisibility: true);
var il = dyn.GetILGenerator();
// only init'd if we use it
LocalBuilder redisKeyLoc = null;
var loc = il.DeclareLocal(t);
il.Emit(OpCodes.Ldarg_0); // object
if (t.IsValueType)
{ {
il.Emit(OpCodes.Unbox_Any, t); // T throw new InvalidCastException($"There is no conversion available from {memberType.Name} to {nameof(RedisValue)}");
} }
else args.Add(member);
{
il.Emit(OpCodes.Castclass, t); // T
} }
il.Emit(OpCodes.Stloc, loc); // --empty--
var needsKeyPrefixLoc = il.DeclareLocal(typeof(bool));
il.Emit(OpCodes.Ldarga_S, 1); // RedisKey?* var objUntyped = Expression.Parameter(typeof(object), "obj");
il.Emit(OpCodes.Call, nullableRedisKeyHasValue); // bool var objTyped = Expression.Convert(objUntyped, t);
il.Emit(OpCodes.Stloc, needsKeyPrefixLoc); // --empty-- var keyPrefix = Expression.Parameter(typeof(RedisKey?), "keyPrefix");
Expression keysResult, valuesResult;
MethodInfo asRedisValue = null;
Expression[] keysResultArr = null;
if (keys.Count == 0) if (keys.Count == 0)
{ {
// if there are no keys, don't allocate // if there are no keys, don't allocate
il.Emit(OpCodes.Ldnull); // null keysResult = Expression.Constant(null, typeof(RedisKey[]));
} }
else else
{ {
il.Emit(OpCodes.Ldc_I4, keys.Count); // int var needsKeyPrefix = Expression.Property(keyPrefix, nameof(Nullable<RedisKey>.HasValue));
il.Emit(OpCodes.Newarr, typeof(RedisKey)); // RedisKey[] var keyPrefixValueArr = new[] { Expression.Call(keyPrefix,
} nameof(Nullable<RedisKey>.GetValueOrDefault), null, null) };
var prepend = typeof(RedisKey).GetMethod(nameof(RedisKey.Prepend),
BindingFlags.Public | BindingFlags.Instance);
asRedisValue = typeof(RedisKey).GetMethod(nameof(RedisKey.AsRedisValue),
BindingFlags.NonPublic | BindingFlags.Instance);
for (var i = 0; i < keys.Count; i++) keysResultArr = new Expression[keys.Count];
for(int i = 0; i < keysResultArr.Length; i++)
{ {
il.Emit(OpCodes.Dup); // RedisKey[] RedisKey[] var member = GetMember(objTyped, keys[i]);
il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisKey[] int keysResultArr[i] = Expression.Condition(needsKeyPrefix,
if (t.IsValueType) Expression.Call(member, prepend, keyPrefixValueArr),
{ member);
il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisKey[] int T*
}
else
{
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisKey[] int T
} }
LoadMember(il, keys[i]); // RedisKey[] RedisKey[] int RedisKey keysResult = Expression.NewArrayInit(typeof(RedisKey), keysResultArr);
PrefixIfNeeded(il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisKey[] int RedisKey
il.Emit(OpCodes.Stelem, typeof(RedisKey)); // RedisKey[]
} }
if (args.Count == 0) if (args.Count == 0)
{ {
// if there are no args, don't allocate // if there are no args, don't allocate
il.Emit(OpCodes.Ldnull); // RedisKey[] null valuesResult = Expression.Constant(null, typeof(RedisValue[]));
} }
else else
{ {
il.Emit(OpCodes.Ldc_I4, args.Count); // RedisKey[] int valuesResult = Expression.NewArrayInit(typeof(RedisValue), args.Select(arg =>
il.Emit(OpCodes.Newarr, typeof(RedisValue)); // RedisKey[] RedisValue[]
}
for (var i = 0; i < args.Count; i++)
{
il.Emit(OpCodes.Dup); // RedisKey[] RedisValue[] RedisValue[]
il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisValue[] RedisValue[] int
if (t.IsValueType)
{
il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisValue[] RedisValue[] int T*
}
else
{ {
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisValue[] RedisValue[] int T var member = GetMember(objTyped, arg);
if (member.Type == typeof(RedisValue)) return member; // pass-thru
if (member.Type == typeof(RedisKey))
{ // need to apply prefix (note we can re-use the body from earlier)
var val = keysResultArr[keys.IndexOf(arg)];
return Expression.Call(val, asRedisValue);
} }
var member = args[i]; // otherwise: use the conversion operator
LoadMember(il, member); // RedisKey[] RedisValue[] RedisValue[] int memberType var conversion = _conversionOperators[member.Type];
ConvertToRedisValue(member, il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisValue[] RedisValue[] int RedisValue return Expression.Call(conversion, member);
}));
il.Emit(OpCodes.Stelem, typeof(RedisValue)); // RedisKey[] RedisValue[]
} }
il.Emit(OpCodes.Newobj, ScriptParameters.Cons); // ScriptParameters var body = Expression.Lambda<Func<object, RedisKey?, ScriptParameters>>(
il.Emit(OpCodes.Ret); // --empty-- Expression.New(ScriptParameters.Cons, keysResult, valuesResult),
objUntyped, keyPrefix);
var ret = (Func<object, RedisKey?, ScriptParameters>)dyn.CreateDelegate(typeof(Func<object, RedisKey?, ScriptParameters>)); return body.Compile();
return ret;
} }
} }
} }
using System.Threading.Tasks; using System.Threading.Tasks;
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment