Skip to content

Commit

Permalink
.NET 9 Upgrade and CLR Proxy improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
ackava committed Nov 30, 2024
1 parent eba9508 commit bad4571
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 213 deletions.
9 changes: 9 additions & 0 deletions YantraJS.Core.Tests/ClrObjects/DisposableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
1 change: 1 addition & 0 deletions YantraJS.Core/Core/Clr/ClrProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TInput,JSValue> GetDelegate<TInput>()
{
var method = methods.FirstOrDefault(x => x.GetParameters()[0].ParameterType == typeof(TInput));
Expand Down
43 changes: 12 additions & 31 deletions YantraJS.Core/Core/Clr/ClrType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Expression>();
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<JSValueFactory>(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<JSValueFactory>(m.DeclaringType.Name, call, args);
//var factory = lambda.Compile();
//return JSValueFactoryDelegate(factory, prototype);
}


Expand Down
126 changes: 126 additions & 0 deletions YantraJS.Core/Core/Clr/ClrTypeBuilder.cs
Original file line number Diff line number Diff line change
@@ -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>(T @this, in Arguments a);

internal delegate object ClrProxyFactory(in Arguments a);

private static JSFunctionDelegate CreateInstanceDelegate<T>(this MethodInfo method)
{
var d = method.CreateDelegate<InstanceDelegate<T>>();
var thisDelegate = JSValueToClrConverter.ToFastClrDelegate<T>();
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<ClrProxyFactory>(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<object>, 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<JSFunctionDelegate>(name, body, args);
return lambda.Compile();
}

private static List<Expression> GetArgumentsExpression(this MethodBase m, Expression args)
{
var parameters = new List<Expression>();
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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
Expand Down
72 changes: 72 additions & 0 deletions YantraJS.Core/Core/Clr/JSMethodInfo.cs
Original file line number Diff line number Diff line change
@@ -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<object>);
}

public delegate JSValue InstanceDelegate<T>(T @this, in Arguments a);

[EditorBrowsable(EditorBrowsableState.Never)]
public JSFunction ToInstanceJSFunctionDelegate<T>()
{
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<T>)Method.CreateDelegate(typeof(InstanceDelegate<T>));
//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();
}

}
}
Loading

0 comments on commit bad4571

Please sign in to comment.