diff --git a/YantraJS.Core.Tests/ClrObjects/DisposableTests.cs b/YantraJS.Core.Tests/ClrObjects/DisposableTests.cs index b007e038..1fb5c9e4 100644 --- a/YantraJS.Core.Tests/ClrObjects/DisposableTests.cs +++ b/YantraJS.Core.Tests/ClrObjects/DisposableTests.cs @@ -73,6 +73,15 @@ function use(d) { Assert.IsFalse(d.Open); } + [TestMethod] + public void FieldAccess() + { + var c = new JSTestContext(); + JSValue guidTypeValue = ClrType.From(typeof(Guid)); + c["guid"] = guidTypeValue; + string result = c.Eval("guid.empty.toString()").ToString(); + } + // [TestMethod] public void AsyncDispose() { diff --git a/YantraJS.Core/Core/Clr/ClrProxy.cs b/YantraJS.Core/Core/Clr/ClrProxy.cs index bffa57be..170cca8a 100644 --- a/YantraJS.Core/Core/Clr/ClrProxy.cs +++ b/YantraJS.Core/Core/Clr/ClrProxy.cs @@ -73,6 +73,7 @@ public override bool ConvertTo(Type type, out object value) && x.ReturnType == typeof(JSValue) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType != typeof(object)).ToArray(); + public static Func GetDelegate() { var method = methods.FirstOrDefault(x => x.GetParameters()[0].ParameterType == typeof(TInput)); diff --git a/YantraJS.Core/Core/Clr/ClrType.cs b/YantraJS.Core/Core/Clr/ClrType.cs index cf16d6b1..87baac57 100644 --- a/YantraJS.Core/Core/Clr/ClrType.cs +++ b/YantraJS.Core/Core/Clr/ClrType.cs @@ -478,39 +478,20 @@ JSValue Factory(in Arguments a) private JSFunctionDelegate GenerateConstructor(ConstructorInfo m, JSObject prototype) { - var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); - - var parameters = new List(); - var pList = m.GetParameters(); - for (int i = 0; i < pList.Length; i++) + var jfs = m.CompileToJSFunctionDelegate(m.DeclaringType.Name); + JSValue Factory(in Arguments a) { - var ai = ArgumentsBuilder.GetAt(args, i); - var pi = pList[i]; - Expression defValue; - if (pi.HasDefaultValue) - { - defValue = Expression.Constant(pi.DefaultValue); - if (pi.ParameterType.IsValueType) - { - defValue = Expression.Box(Expression.Constant(pi.DefaultValue)); - } - parameters.Add(JSValueToClrConverter.Get(ai, pi.ParameterType, defValue, pi.Name)); - continue; - } - defValue = null; - if(pi.ParameterType.IsValueType) - { - defValue = Expression.Box(Expression.Constant(Activator.CreateInstance(pi.ParameterType))); - } else - { - defValue = Expression.Null; - } - parameters.Add(JSValueToClrConverter.Get(ai, pi.ParameterType, defValue, pi.Name)); + var r = jfs(in a); + return ClrProxy.From(r, prototype); } - var call = Expression.TypeAs( Expression.New(m, parameters), typeof(object)); - var lambda = Expression.Lambda(m.DeclaringType.Name, call, args); - var factory = lambda.Compile(); - return JSValueFactoryDelegate(factory, prototype); + return Factory; + + //var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); + //var parameters = m.GetArgumentsExpression(args); + //var call = Expression.TypeAs( Expression.New(m, parameters), typeof(object)); + //var lambda = Expression.Lambda(m.DeclaringType.Name, call, args); + //var factory = lambda.Compile(); + //return JSValueFactoryDelegate(factory, prototype); } diff --git a/YantraJS.Core/Core/Clr/ClrTypeBuilder.cs b/YantraJS.Core/Core/Clr/ClrTypeBuilder.cs new file mode 100644 index 00000000..e6feb570 --- /dev/null +++ b/YantraJS.Core/Core/Clr/ClrTypeBuilder.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using YantraJS.Core.Clr; +using YantraJS.ExpHelper; +using YantraJS.LinqExpressions; +using YantraJS.Runtime; +using Expression = YantraJS.Expressions.YExpression; + +namespace YantraJS.Core; + +internal static class ClrTypeBuilder +{ + + internal delegate JSValue InstanceDelegate(T @this, in Arguments a); + + internal delegate object ClrProxyFactory(in Arguments a); + + private static JSFunctionDelegate CreateInstanceDelegate(this MethodInfo method) + { + var d = method.CreateDelegate>(); + var thisDelegate = JSValueToClrConverter.ToFastClrDelegate(); + return (in Arguments a) => { + var @this = thisDelegate(a.This, "this"); + return d(@this, in a); + }; + } + internal static ClrProxyFactory CompileToJSFunctionDelegate(this ConstructorInfo m, string name = null) + { + var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); + var parameters = m.GetArgumentsExpression(args); + Expression body = Expression.New(m, parameters); + body = m.DeclaringType.IsValueType + ? Expression.Box(body) + : body; + var lambda = Expression.Lambda(name, body, args); + return lambda.Compile(); + } + + internal static JSFunctionDelegate CompileToJSFunctionDelegate(this MethodInfo m, string name = null) + { + + if (m.IsJSFunctionDelegate()) { + if (m.IsStatic) + { + return (JSFunctionDelegate)m.CreateDelegate(typeof(JSFunctionDelegate)); + } + else + { + // we can directly create a delegate here... + return Generic.InvokeAs(m.DeclaringType, CreateInstanceDelegate, m); + } + } + + // We cannot use delegates as Arguments to CLR and CLR to JSValue + // will be slower as it will use reflection internally to dispatch + // actual conversion method. + + name ??= m.Name.ToCamelCase(); + + // To speed up, we will use compilation. + + var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); + var parameters = m.GetArgumentsExpression(args); + + Expression body; + + Type returnType; + + var @this = ArgumentsBuilder.This(args); + var convertedThis = m.IsStatic + ? null + : JSValueToClrConverter.Get(@this, m.DeclaringType, "this"); + body = Expression.Call(convertedThis, m, parameters); + returnType = m.ReturnType; + + // unless return type is JSValue + // we need to marshal it + if (returnType == typeof(void)) + { + body = Expression.Block(body, JSUndefinedBuilder.Value); + } + else + { + body = ClrProxyBuilder.Marshal(body); + } + + var lambda = Expression.Lambda(name, body, args); + return lambda.Compile(); + } + + private static List GetArgumentsExpression(this MethodBase m, Expression args) + { + var parameters = new List(); + var pList = m.GetParameters(); + for (int i = 0; i < pList.Length; i++) + { + var ai = ArgumentsBuilder.GetAt(args, i); + var pi = pList[i]; + Expression defValue; + if (pi.HasDefaultValue) + { + defValue = Expression.Constant(pi.DefaultValue); + if (pi.ParameterType.IsValueType) + { + defValue = Expression.Box(Expression.Constant(pi.DefaultValue)); + } + parameters.Add(JSValueToClrConverter.GetArgument(args, i, pi.ParameterType, defValue, pi.Name)); + continue; + } + defValue = null; + if (pi.ParameterType.IsValueType) + { + defValue = Expression.Constant(Activator.CreateInstance(pi.ParameterType)); + } + else + { + defValue = Expression.Null; + } + parameters.Add(JSValueToClrConverter.GetArgument(args, i, pi.ParameterType, defValue, pi.Name)); + } + return parameters; + } + +} diff --git a/YantraJS.Core/Core/Function/JSFieldInfo.cs b/YantraJS.Core/Core/Clr/JSFieldInfo.cs similarity index 91% rename from YantraJS.Core/Core/Function/JSFieldInfo.cs rename to YantraJS.Core/Core/Clr/JSFieldInfo.cs index 04411db7..e4476fc5 100644 --- a/YantraJS.Core/Core/Function/JSFieldInfo.cs +++ b/YantraJS.Core/Core/Clr/JSFieldInfo.cs @@ -3,8 +3,9 @@ using YantraJS.ExpHelper; using YantraJS.LinqExpressions; using YantraJS.Runtime; +using YantraJS.Core.Clr; -namespace YantraJS.Core.Clr +namespace YantraJS.Core.Core.Clr { internal readonly struct JSFieldInfo { @@ -17,15 +18,16 @@ public JSFieldInfo(ClrMemberNamingConvention namingConvention, FieldInfo field) { this.field = field; var (name, export) = ClrTypeExtensions.GetJSName(namingConvention, field); - this.Name = name; - this.Export = export; + Name = name; + Export = export; } public JSFunction GenerateFieldGetter() { var name = $"get {Name}"; var field = this.field; - return new JSFunction(() => { + return new JSFunction(() => + { var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); Expression convertedThis = field.IsStatic ? null @@ -44,7 +46,8 @@ public JSFunction GenerateFieldSetter() { var name = $"set {Name}"; var field = this.field; - return new JSFunction(() => { + return new JSFunction(() => + { var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); var a1 = ArgumentsBuilder.Get1(args); var convert = field.IsStatic diff --git a/YantraJS.Core/Core/Clr/JSMethodInfo.cs b/YantraJS.Core/Core/Clr/JSMethodInfo.cs new file mode 100644 index 00000000..dc5f246a --- /dev/null +++ b/YantraJS.Core/Core/Clr/JSMethodInfo.cs @@ -0,0 +1,72 @@ +using Exp = YantraJS.Expressions.YExpression; +using Expression = YantraJS.Expressions.YExpression; +using ParameterExpression = YantraJS.Expressions.YParameterExpression; +using LambdaExpression = YantraJS.Expressions.YLambdaExpression; +using LabelTarget = YantraJS.Expressions.YLabelTarget; +using SwitchCase = YantraJS.Expressions.YSwitchCaseExpression; +using GotoExpression = YantraJS.Expressions.YGoToExpression; +using TryExpression = YantraJS.Expressions.YTryCatchFinallyExpression; + +using System.Reflection; +using System.ComponentModel; +using System.Collections.Generic; +using System; +using YantraJS.ExpHelper; +using YantraJS.Expressions; +using YantraJS.Generator; +using YantraJS.LinqExpressions; +using YantraJS.Runtime; +using YantraJS.Core.Clr; + +namespace YantraJS.Core.Core.Clr +{ + internal class JSMethodInfo + { + public readonly MethodInfo Method; + + public readonly string Name; + public readonly bool Export; + + public JSMethodInfo(ClrMemberNamingConvention namingConvention, MethodInfo method) + { + Method = method; + var (name, export) = ClrTypeExtensions.GetJSName(namingConvention, method); + Name = name; + Export = export; + } + + internal JSValue GenerateInvokeJSFunction() + { + return this.InvokeAs(Method.DeclaringType, ToInstanceJSFunctionDelegate); + } + + public delegate JSValue InstanceDelegate(T @this, in Arguments a); + + [EditorBrowsable(EditorBrowsableState.Never)] + public JSFunction ToInstanceJSFunctionDelegate() + { + return new JSFunction(Method.CompileToJSFunctionDelegate(), Name); + //if (Method.IsStatic) + //{ + // var staticDel = (JSFunctionDelegate)Method.CreateDelegate(typeof(JSFunctionDelegate)); + // return new JSFunction((in Arguments a) => + // { + // return staticDel(a); + // }, Name); + //} + //var del = (InstanceDelegate)Method.CreateDelegate(typeof(InstanceDelegate)); + //var type = typeof(T); + //return new JSFunction((in Arguments a) => + //{ + // var @this = (T)a.This.ForceConvert(type); + // return del(@this, a); + //}, Name); + } + + public JSFunctionDelegate GenerateMethod() + { + return Method.CompileToJSFunctionDelegate(); + } + + } +} diff --git a/YantraJS.Core/Core/Function/JSPropertyInfo.cs b/YantraJS.Core/Core/Clr/JSPropertyInfo.cs similarity index 56% rename from YantraJS.Core/Core/Function/JSPropertyInfo.cs rename to YantraJS.Core/Core/Clr/JSPropertyInfo.cs index 821227d1..c7affeb0 100644 --- a/YantraJS.Core/Core/Function/JSPropertyInfo.cs +++ b/YantraJS.Core/Core/Clr/JSPropertyInfo.cs @@ -14,8 +14,9 @@ using YantraJS.LinqExpressions; using YantraJS.Runtime; using static YantraJS.Core.Clr.ClrType; +using YantraJS.Core.Clr; -namespace YantraJS.Core.Clr +namespace YantraJS.Core.Core.Clr { internal class JSPropertyInfo @@ -33,88 +34,27 @@ internal class JSPropertyInfo public JSPropertyInfo(ClrMemberNamingConvention namingConvention, PropertyInfo property) { - this.Property = property; + Property = property; var (name, export) = ClrTypeExtensions.GetJSName(namingConvention, property); - this.Name = name; - this.Export = export; - this.PropertyType = property.PropertyType; - this.GetMethod = property.GetMethod; - this.SetMethod = property.SetMethod; - this.CanRead = property.CanRead; - this.CanWrite = property.CanWrite; + Name = name; + Export = export; + PropertyType = property.PropertyType; + GetMethod = property.GetMethod; + SetMethod = property.SetMethod; + CanRead = property.CanRead; + CanWrite = property.CanWrite; } - public JSFunction GetValue() + public JSFunction GeneratePropertyGetter() { var name = $"get {Name}"; - var getter = (Func)Property.GetMethod.CreateDelegate(typeof(Func)); - var del = ClrProxy.GetDelegate(); - return new JSFunction((in Arguments a) => { - var owner = a.This; - var value = getter((TOwner)owner.ForceConvert(typeof(TOwner))); - return del(value); - }, name); + return new JSFunction(Property.GetMethod.CompileToJSFunctionDelegate(name), name); } - public JSFunction GetStaticValue() - { - var name = $"get {Name}"; - var getter = (Func)Property.GetMethod.CreateDelegate(typeof(Func)); - var del = ClrProxy.GetDelegate(); - return new JSFunction( (in Arguments a) => { - var value = getter(); - return del(value); - }, name); - } - - public JSFunction SetValue() - { - var name = $"set {Name}"; - var setter = (Action)Property.SetMethod.CreateDelegate(typeof(Action)); - return new JSFunction((in Arguments a) => - { - var owner = a.This; - var value = a.Get1(); - setter( - (TOwner)owner.ForceConvert(typeof(TOwner)), - (TValue)value.ForceConvert(typeof(TValue))); - return JSUndefined.Value; - }, name); - } - - public JSFunction SetStaticValue() + public JSFunction GeneratePropertySetter() { var name = $"set {Name}"; - var setter = (Action)Property.SetMethod.CreateDelegate(typeof(Action)); - return new JSFunction((in Arguments a) => - { - var value = a.Get1(); - setter( - (TValue)value.ForceConvert(typeof(TValue))); - return JSUndefined.Value; - }, name); - } - - internal JSFunction GeneratePropertySetter() - { - if (Property.SetMethod.IsStatic) - { - return this.InvokeAs(Property.PropertyType, SetStaticValue); - } - // return (JSFunction)SetValueMethod - // .MakeGenericMethod(property.DeclaringType, property.PropertyType).Invoke(null, new object[] { property }); - return this.InvokeAs(Property.DeclaringType, Property.PropertyType, SetValue); - } - - internal JSFunction GeneratePropertyGetter() - { - if (Property.GetMethod.IsStatic) - { - return this.InvokeAs(Property.PropertyType, GetStaticValue); - } - // return (JSFunction)SetValueMethod - // .MakeGenericMethod(property.DeclaringType, property.PropertyType).Invoke(null, new object[] { property }); - return this.InvokeAs(Property.DeclaringType, Property.PropertyType, GetValue); + return new JSFunction(Property.SetMethod.CompileToJSFunctionDelegate(name), name); } internal Func GenerateIndexedGetter() diff --git a/YantraJS.Core/Core/Function/JSMethodInfo.cs b/YantraJS.Core/Core/Function/JSMethodInfo.cs deleted file mode 100644 index 98cdc3f0..00000000 --- a/YantraJS.Core/Core/Function/JSMethodInfo.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Exp = YantraJS.Expressions.YExpression; -using Expression = YantraJS.Expressions.YExpression; -using ParameterExpression = YantraJS.Expressions.YParameterExpression; -using LambdaExpression = YantraJS.Expressions.YLambdaExpression; -using LabelTarget = YantraJS.Expressions.YLabelTarget; -using SwitchCase = YantraJS.Expressions.YSwitchCaseExpression; -using GotoExpression = YantraJS.Expressions.YGoToExpression; -using TryExpression = YantraJS.Expressions.YTryCatchFinallyExpression; - -using System.Reflection; -using System.ComponentModel; -using System.Collections.Generic; -using System; -using YantraJS.ExpHelper; -using YantraJS.Expressions; -using YantraJS.Generator; -using YantraJS.LinqExpressions; -using YantraJS.Runtime; - -namespace YantraJS.Core.Clr -{ - internal class JSMethodInfo - { - public readonly MethodInfo Method; - - public readonly string Name; - public readonly bool Export; - - public JSMethodInfo(ClrMemberNamingConvention namingConvention, MethodInfo method) - { - this.Method = method; - var (name, export) = ClrTypeExtensions.GetJSName(namingConvention, method); - this.Name = name; - this.Export = export; - } - - internal JSValue GenerateInvokeJSFunction() - { - return this.InvokeAs(Method.DeclaringType, ToInstanceJSFunctionDelegate); - } - - public delegate JSValue InstanceDelegate(T @this, in Arguments a); - - [EditorBrowsable(EditorBrowsableState.Never)] - public JSFunction ToInstanceJSFunctionDelegate() - { - if (Method.IsStatic) - { - var staticDel = (JSFunctionDelegate)Method.CreateDelegate(typeof(JSFunctionDelegate)); - return new JSFunction((in Arguments a) => - { - return staticDel(a); - }, Name); - } - var del = (InstanceDelegate)Method.CreateDelegate(typeof(InstanceDelegate)); - var type = typeof(T); - return new JSFunction((in Arguments a) => - { - var @this = (T)a.This.ForceConvert(type); - return del(@this, a); - }, Name); - } - - public JSFunctionDelegate GenerateMethod() - { - var m = this.Method; - var args = Expression.Parameter(typeof(Arguments).MakeByRefType()); - var @this = ArgumentsBuilder.This(args); - - var convertedThis = m.IsStatic - ? null - : JSValueToClrConverter.Get(@this, m.DeclaringType, "this"); - var parameters = new List(); - var pList = m.GetParameters(); - for (int i = 0; i < pList.Length; i++) - { - var pi = pList[i]; - var defValue = pi.HasDefaultValue - ? Expression.Constant((object)pi.DefaultValue, typeof(object)) - : (pi.ParameterType.IsValueType - ? Expression.Constant((object)Activator.CreateInstance(pi.ParameterType), typeof(object)) - : null); - parameters.Add(JSValueToClrConverter.GetArgument(args, i, pi.ParameterType, defValue, pi.Name)); - } - var call = Expression.Call(convertedThis, m, parameters); - var marshal = call.Type == typeof(void) - ? YExpression.Block(call, JSUndefinedBuilder.Value) - : ClrProxyBuilder.Marshal(call); - var wrapTryCatch = JSExceptionBuilder.Wrap(marshal); - - ILCodeGenerator.GenerateLogs = true; - var lambda = Expression.Lambda(m.Name, wrapTryCatch, args); - var method = lambda.Compile(); - return method; - } - - } -} diff --git a/YantraJS.Core/Core/JSValue.cs b/YantraJS.Core/Core/JSValue.cs index e297b5ca..1e69ae74 100644 --- a/YantraJS.Core/Core/JSValue.cs +++ b/YantraJS.Core/Core/JSValue.cs @@ -122,7 +122,7 @@ public virtual int Length { public abstract JSValue TypeOf(); - public virtual int IntValue => (int)((long)this.DoubleValue << 32) >> 32; + public virtual int IntValue => (int)(((long)this.DoubleValue << 32) >> 32); /// /// Integer value restricts value within int.MaxValue and @@ -145,7 +145,7 @@ public virtual int IntegerValue public virtual long BigIntValue => (long)(ulong)this.DoubleValue; - public virtual uint UIntValue => (uint)this.DoubleValue; + public virtual uint UIntValue => (uint)(((long)this.DoubleValue << 32) >> 32); [EditorBrowsable(EditorBrowsableState.Never)] public JSPrototype prototypeChain; diff --git a/YantraJS.Core/Core/Number/JSNumber.cs b/YantraJS.Core/Core/Number/JSNumber.cs index cd41e373..69c4cef7 100644 --- a/YantraJS.Core/Core/Number/JSNumber.cs +++ b/YantraJS.Core/Core/Number/JSNumber.cs @@ -118,7 +118,7 @@ public JSNumber(double value) : base() this.value = value; } - public override int IntValue => (int)(((long)value << 32) >> 32); + // public override int IntValue => (int)((long)value << 32) >> 32; public override double DoubleValue => value; @@ -139,6 +139,7 @@ public override bool ConvertTo(Type type, out object value) return true; } if (type == typeof(int)) { + // value = (int)((long)this.value << 32) >> 32; value = (int)this.value; return true; } diff --git a/YantraJS.Core/Core/String/JSStringPrototype.cs b/YantraJS.Core/Core/String/JSStringPrototype.cs index 3d8b8f67..eac706d9 100644 --- a/YantraJS.Core/Core/String/JSStringPrototype.cs +++ b/YantraJS.Core/Core/String/JSStringPrototype.cs @@ -269,7 +269,7 @@ internal static JSValue LastIndexOF(in Arguments a) // if (pos.IsUndefined) // return new JSNumber(@this.LastIndexOf(searchStr.ToString())); // var startIndex = a.TryGetAt(1, out var n) ? (double.IsNaN(n.DoubleValue) ? int.MaxValue : n.IntValue ): n.IntValue; - var startIndex = double.IsNaN(fromIndex) ? int.MaxValue : (int)(uint)fromIndex; + var startIndex = double.IsNaN(fromIndex) ? int.MaxValue : (int)(((long)fromIndex << 32) >> 32); startIndex = Math.Min(startIndex, @this.Length - 1); startIndex = Math.Min(startIndex + searchStr.Length - 1, @this.Length - 1); if (startIndex < 0) @@ -493,7 +493,7 @@ internal static JSValue Split(in Arguments a) var limitMax = uint.MaxValue; if (!limit.IsUndefined) - limitMax = (uint)limit.DoubleValue; + limitMax = limit.UIntValue; if (_separator is JSRegExp jSRegExp) { diff --git a/YantraJS.Core/Utils/ArgumentsExtension.cs b/YantraJS.Core/Utils/ArgumentsExtension.cs index 5ba7441e..160c28c8 100644 --- a/YantraJS.Core/Utils/ArgumentsExtension.cs +++ b/YantraJS.Core/Utils/ArgumentsExtension.cs @@ -224,6 +224,17 @@ public static YExpression Get(YExpression target, Type type, YExpression default defaultValue); } + + public static Func ToFastClrDelegate() + { + var type = typeof(T); + if (methods.TryGetValue(type, out var m)) + { + return m.CreateDelegate>(); + } + return (v,n) => GetAsOrThrow(v, n); + } + public static T ToFastClrValue(this JSValue value) { var type = typeof(T);