Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions src/FastExpressionCompiler/FastExpressionCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4368,7 +4368,8 @@ private static bool TryEmitArithmeticAndOrAssign(
// required for calling the method on the value type parameter
var objType = objExpr.Type;
objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @wip avoid ad-hocking with parameter here
(objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef);
(objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef) &&
!objType.IsByRefLike(); // ref struct types cannot be stored in local variables
if (objVarByAddress)
objVar = EmitStoreAndLoadLocalVariableAddress(il, objType);
else
Expand Down Expand Up @@ -4938,7 +4939,7 @@ private static bool TryEmitMethodCall(Expression expr,
if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceAccess))
return false;
objIsValueType = objExpr.Type.IsValueType;
loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress;
loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress && !objExpr.Type.IsByRefLike();
}

var parCount = methodParams.Length;
Expand Down Expand Up @@ -5047,7 +5048,10 @@ public static bool TryEmitMemberGet(MemberExpression expr,
// Value type special treatment to load address of value instance in order to call a method.
// For the parameters, we will skip the address loading because the `LastEmitIsAddress == true` for `Ldarga`,
// so the condition here will be skipped
if (!closure.LastEmitIsAddress && objExpr.Type.IsValueType)
// Ref struct types cannot be stored in local variables, so we skip them here
if (!closure.LastEmitIsAddress &&
objExpr.Type.IsValueType &&
!objExpr.Type.IsByRefLike())
EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
}

Expand Down Expand Up @@ -5094,7 +5098,7 @@ public static bool TryEmitMemberGet(MemberExpression expr,
closure.LastEmitIsAddress = isByAddress;
if (!isByAddress)
{
if (objExpr.Type.IsEnum)
if (objExpr.Type.IsEnum && !objExpr.Type.IsByRefLike())
EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
il.Demit(OpCodes.Ldfld, field);
}
Expand Down Expand Up @@ -7816,6 +7820,33 @@ internal static bool IsFloatingPoint(this Type type) =>
type == typeof(float) ||
type == typeof(double);

/// <summary>Checks if the type is a ref struct (byref-like type).
/// Compatible with older frameworks that don't have Type.IsByRefLike property.</summary>
[MethodImpl((MethodImplOptions)256)]
[RequiresUnreferencedCode(Trimming.Message)]
internal static bool IsByRefLike(this Type type)
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER || NET6_0_OR_GREATER
return type.IsByRefLike;
#else
// For older frameworks (net472, netstandard2.0), check for IsByRefLikeAttribute via reflection
// IsByRefLike property was introduced in .NET Standard 2.1 / .NET Core 2.1
if (!type.IsValueType)
return false;

// Check if the type has IsByRefLikeAttribute
var attributes = type.GetCustomAttributes(inherit: false);
foreach (var attr in attributes)
{
var attrType = attr.GetType();
if (attrType.Name == "IsByRefLikeAttribute" ||
attrType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute")
return true;
}
return false;
#endif
}

internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type)
{
switch (Type.GetTypeCode(type))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;

#if LIGHT_EXPRESSION
using static FastExpressionCompiler.LightExpression.Expression;
namespace FastExpressionCompiler.LightExpression.IssueTests;
#else
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
namespace FastExpressionCompiler.IssueTests;
#endif

public struct Issue490_ref_struct_conditional : ITest, ITestX
{
public int Run()
{
Return_true_when_token_is_null();
Return_false_when_token_isnot_null();
return 1;
}

public void Run(TestRun t)
{
Return_true_when_token_is_null(t);
Return_false_when_token_isnot_null(t);
}

public delegate bool RefStructReaderDelegate(ref MyJsonReader reader);

public enum MyJsonTokenType : byte
{
None = 0,
StartObject = 1,
Null = 11
}

public ref struct MyJsonReader
{
public MyJsonTokenType TokenType { get; set; }
}

public void Return_true_when_token_is_null(TestContext t = default)
{
var readerParam = Parameter(typeof(MyJsonReader).MakeByRefType(), "reader");
var tokenType = Property(readerParam, nameof(MyJsonReader.TokenType));
var nullToken = Constant(MyJsonTokenType.Null);

var body = Condition(
Equal(tokenType, nullToken),
Constant(true),
Constant(false));

var lambda = Lambda<RefStructReaderDelegate>(body, readerParam);
var func = lambda.CompileFast();

var reader = new MyJsonReader();
reader.TokenType = MyJsonTokenType.Null;

var result = func(ref reader);
t.AreEqual(true, result);
}

public void Return_false_when_token_isnot_null(TestContext t = default)
{
var readerParam = Parameter(typeof(MyJsonReader).MakeByRefType(), "reader");
var tokenType = Property(readerParam, nameof(MyJsonReader.TokenType));
var nullToken = Constant(MyJsonTokenType.Null);

var body = Condition(
Equal(tokenType, nullToken),
Constant(true),
Constant(false));

var lambda = Lambda<RefStructReaderDelegate>(body, readerParam);
var func = lambda.CompileFast();

var reader = new MyJsonReader();
reader.TokenType = MyJsonTokenType.StartObject;

var result = func(ref reader);
t.AreEqual(false, result);
}
}
3 changes: 3 additions & 0 deletions test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ void Run(Func<int> run, string name = null)
Run(new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
Run(new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);

Run(new Issue490_ref_struct_conditional().Run);
Run(new LightExpression.IssueTests.Issue490_ref_struct_conditional().Run);

Console.WriteLine($"{Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
});

Expand Down
3 changes: 3 additions & 0 deletions test/FastExpressionCompiler.TestsRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ void Run(Func<int> run, string name = null)
Run(new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
Run(new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);

Run(new Issue490_ref_struct_conditional().Run);
Run(new LightExpression.IssueTests.Issue490_ref_struct_conditional().Run);

Console.WriteLine($"{Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
});

Expand Down