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;
...@@ -105,88 +105,26 @@ private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] ar ...@@ -105,88 +105,26 @@ 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)
return;
}
if (member is PropertyInfo asProp)
{ {
var getter = asProp.GetGetMethod(); if(method.ReturnType == typeof(RedisValue) &&
if (getter.IsVirtual) (method.Name == "op_Implicit" || method.Name == "op_Explicit"))
{ {
il.Emit(OpCodes.Callvirt, getter); // typeof(member) var p = method.GetParameters();
} if (p != null && p.Length == 1)
else {
{ tmp[p[0].ParameterType] = method;
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>
/// Turns a script with @namedParameters into a LuaScript that can be executed /// Turns a script with @namedParameters into a LuaScript that can be executed
/// against a given IDatabase(Async) object /// against a given IDatabase(Async) object
...@@ -248,29 +186,7 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string mis ...@@ -248,29 +186,7 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string mis
missingMember = badTypeMember = null; missingMember = badTypeMember = null;
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,9 +208,21 @@ private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ...@@ -292,9 +208,21 @@ 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>();
for (var i = 0; i < script.Arguments.Length; i++) for (var i = 0; i < script.Arguments.Length; i++)
{ {
var argName = script.Arguments[i]; var argName = script.Arguments[i];
...@@ -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))
{
throw new InvalidCastException($"There is no conversion available from {memberType.Name} to {nameof(RedisValue)}");
}
args.Add(member); args.Add(member);
} }
var nullableRedisKeyHasValue = typeof(RedisKey?).GetProperty("HasValue").GetGetMethod(); var objUntyped = Expression.Parameter(typeof(object), "obj");
var objTyped = Expression.Convert(objUntyped, t);
var dyn = new DynamicMethod("ParameterExtractor_" + t.FullName + "_" + script.OriginalScript.GetHashCode(), typeof(ScriptParameters), new[] { typeof(object), typeof(RedisKey?) }, restrictedSkipVisibility: true); var keyPrefix = Expression.Parameter(typeof(RedisKey?), "keyPrefix");
var il = dyn.GetILGenerator();
Expression keysResult, valuesResult;
// only init'd if we use it MethodInfo asRedisValue = null;
LocalBuilder redisKeyLoc = null; Expression[] keysResultArr = null;
var loc = il.DeclareLocal(t);
il.Emit(OpCodes.Ldarg_0); // object
if (t.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, t); // T
}
else
{
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?*
il.Emit(OpCodes.Call, nullableRedisKeyHasValue); // bool
il.Emit(OpCodes.Stloc, needsKeyPrefixLoc); // --empty--
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),
for (var i = 0; i < keys.Count; i++) BindingFlags.Public | BindingFlags.Instance);
{ asRedisValue = typeof(RedisKey).GetMethod(nameof(RedisKey.AsRedisValue),
il.Emit(OpCodes.Dup); // RedisKey[] RedisKey[] BindingFlags.NonPublic | BindingFlags.Instance);
il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisKey[] int
if (t.IsValueType) keysResultArr = new Expression[keys.Count];
for(int i = 0; i < keysResultArr.Length; i++)
{ {
il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisKey[] int T* var member = GetMember(objTyped, keys[i]);
keysResultArr[i] = Expression.Condition(needsKeyPrefix,
Expression.Call(member, prepend, keyPrefixValueArr),
member);
} }
else keysResult = Expression.NewArrayInit(typeof(RedisKey), keysResultArr);
{
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisKey[] int T
}
LoadMember(il, keys[i]); // RedisKey[] RedisKey[] int RedisKey
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* var member = GetMember(objTyped, arg);
} if (member.Type == typeof(RedisValue)) return member; // pass-thru
else if (member.Type == typeof(RedisKey))
{ { // need to apply prefix (note we can re-use the body from earlier)
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisValue[] RedisValue[] int T var val = keysResultArr[keys.IndexOf(arg)];
} return Expression.Call(val, asRedisValue);
}
var member = args[i];
LoadMember(il, member); // RedisKey[] RedisValue[] RedisValue[] int memberType
ConvertToRedisValue(member, il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisValue[] RedisValue[] int RedisValue
il.Emit(OpCodes.Stelem, typeof(RedisValue)); // RedisKey[] RedisValue[] // otherwise: use the conversion operator
var conversion = _conversionOperators[member.Type];
return Expression.Call(conversion, member);
}));
} }
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