Skip to content
Merged
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
18 changes: 14 additions & 4 deletions src/core/Microsoft.Dynamic/Interpreter/LightCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,6 @@ private void CompileIndexExpression(Expression expr) {
private void CompileIndexAssignment(BinaryExpression node, bool asVoid) {
var index = (IndexExpression)node.Left;

if (!asVoid) {
throw new NotImplementedException();
}

// instance:
if (index.Object is not null) {
Compile(index.Object);
Expand All @@ -424,6 +420,11 @@ private void CompileIndexAssignment(BinaryExpression node, bool asVoid) {

// value:
Compile(node.Right);
LocalDefinition local = default;
if (!asVoid) {
local = _locals.DefineLocal(Expression.Parameter(node.Right.Type), Instructions.Count);
Instructions.EmitAssignLocal(local.Index);
}

if (index.Indexer is not null) {
EmitCall(index.Indexer.GetSetMethod(true));
Expand All @@ -432,6 +433,11 @@ private void CompileIndexAssignment(BinaryExpression node, bool asVoid) {
} else {
Instructions.EmitSetArrayItem(index.Object.Type);
}

if (!asVoid) {
Instructions.EmitLoadLocal(local.Index);
_locals.UndefineLocal(local, Instructions.Count);
}
}

private void CompileMemberAssignment(BinaryExpression node, bool asVoid) {
Expand All @@ -458,6 +464,10 @@ private void CompileMemberAssignment(BinaryExpression node, bool asVoid) {
if (member.Member is FieldInfo fi) {
if (member.Expression is not null) {
Compile(member.Expression);
if (fi.DeclaringType.IsValueType && member.Expression.NodeType is not (ExpressionType.Parameter or ExpressionType.Unbox)) {
// the instance is a temporary value so the store must not update the original boxed value
EmitCall(typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetObjectValue)));
}
}
Compile(node.Right);

Expand Down
64 changes: 64 additions & 0 deletions tests/Microsoft.Dynamic.Test/InterpreterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,70 @@ public void PopTest() {
Scripting.Generation.CompilerHelpers.LightCompile(lambda).DynamicInvoke();
}

public struct MutableStruct {
public int Field;
}

[Test]
public void ValueTypeFieldAssignmentTest() {
// assigning a field of a converted (unboxed) value type updates a copy, not the boxed value
var obj = Expression.Parameter(typeof(object), "obj");
var lambda = Expression.Lambda<Func<object, int>>(
Expression.Block(
Expression.Assign(Expression.Field(Expression.Convert(obj, typeof(MutableStruct)), nameof(MutableStruct.Field)), Expression.Constant(42)),
Expression.Field(Expression.Convert(obj, typeof(MutableStruct)), nameof(MutableStruct.Field))
),
obj
);

object interpretedBox = new MutableStruct { Field = 1 };
object compiledBox = new MutableStruct { Field = 1 };
Assert.That(Scripting.Generation.CompilerHelpers.LightCompile(lambda)(interpretedBox), Is.EqualTo(lambda.Compile()(compiledBox)));
Assert.That(((MutableStruct)interpretedBox).Field, Is.EqualTo(((MutableStruct)compiledBox).Field));
}

[Test]
public void UnboxedValueTypeFieldAssignmentTest() {
// assigning a field through Unbox updates the boxed value
var obj = Expression.Parameter(typeof(object), "obj");
var lambda = Expression.Lambda<Action<object>>(
Expression.Assign(Expression.Field(Expression.Unbox(obj, typeof(MutableStruct)), nameof(MutableStruct.Field)), Expression.Constant(42)),
obj
);

object interpretedBox = new MutableStruct { Field = 1 };
object compiledBox = new MutableStruct { Field = 1 };
Scripting.Generation.CompilerHelpers.LightCompile(lambda)(interpretedBox);
lambda.Compile()(compiledBox);
Assert.That(((MutableStruct)interpretedBox).Field, Is.EqualTo(((MutableStruct)compiledBox).Field));
}

[Test]
public void IndexAssignmentValueTest() {
// the value of an index assignment is the assigned value
var array = Expression.Parameter(typeof(int[]), "array");
var arrayLambda = Expression.Lambda<Func<int[], int>>(
Expression.Assign(Expression.ArrayAccess(array, Expression.Constant(1)), Expression.Constant(42)),
array
);

var interpretedArray = new int[3];
var compiledArray = new int[3];
Assert.That(Scripting.Generation.CompilerHelpers.LightCompile(arrayLambda)(interpretedArray), Is.EqualTo(arrayLambda.Compile()(compiledArray)));
Assert.That(interpretedArray, Is.EqualTo(compiledArray));

var list = Expression.Parameter(typeof(List<int>), "list");
var listLambda = Expression.Lambda<Func<List<int>, int>>(
Expression.Assign(Expression.Property(list, "Item", Expression.Constant(1)), Expression.Constant(42)),
list
);

var interpretedList = new List<int> { 0, 0, 0 };
var compiledList = new List<int> { 0, 0, 0 };
Assert.That(Scripting.Generation.CompilerHelpers.LightCompile(listLambda)(interpretedList), Is.EqualTo(listLambda.Compile()(compiledList)));
Assert.That(interpretedList, Is.EqualTo(compiledList));
}

private static readonly Type[] PrimitiveTypes = {
typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double),
Expand Down
Loading