1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Diagnostics; 6 using System.Dynamic.Utils; 7 using System.Reflection; 8 using System.Reflection.Emit; 9 10 namespace System.Linq.Expressions.Compiler 11 { 12 internal partial class LambdaCompiler 13 { EmitAddress(Expression node, Type type)14 private void EmitAddress(Expression node, Type type) 15 { 16 EmitAddress(node, type, CompilationFlags.EmitExpressionStart); 17 } 18 19 // We don't want "ref" parameters to modify values of expressions 20 // except where it would in IL: locals, args, fields, and array elements 21 // (Unbox is an exception, it's intended to emit a ref to the original 22 // boxed value) EmitAddress(Expression node, Type type, CompilationFlags flags)23 private void EmitAddress(Expression node, Type type, CompilationFlags flags) 24 { 25 Debug.Assert(node != null); 26 bool emitStart = (flags & CompilationFlags.EmitExpressionStartMask) == CompilationFlags.EmitExpressionStart; 27 CompilationFlags startEmitted = emitStart ? EmitExpressionStart(node) : CompilationFlags.EmitNoExpressionStart; 28 29 switch (node.NodeType) 30 { 31 default: 32 EmitExpressionAddress(node, type); 33 break; 34 35 case ExpressionType.ArrayIndex: 36 AddressOf((BinaryExpression)node, type); 37 break; 38 39 case ExpressionType.Parameter: 40 AddressOf((ParameterExpression)node, type); 41 break; 42 43 case ExpressionType.MemberAccess: 44 AddressOf((MemberExpression)node, type); 45 break; 46 47 case ExpressionType.Unbox: 48 AddressOf((UnaryExpression)node, type); 49 break; 50 51 case ExpressionType.Call: 52 AddressOf((MethodCallExpression)node, type); 53 break; 54 55 case ExpressionType.Index: 56 AddressOf((IndexExpression)node, type); 57 break; 58 } 59 60 if (emitStart) 61 { 62 EmitExpressionEnd(startEmitted); 63 } 64 } 65 66 AddressOf(BinaryExpression node, Type type)67 private void AddressOf(BinaryExpression node, Type type) 68 { 69 Debug.Assert(node.NodeType == ExpressionType.ArrayIndex && node.Method == null); 70 71 if (TypeUtils.AreEquivalent(type, node.Type)) 72 { 73 EmitExpression(node.Left); 74 EmitExpression(node.Right); 75 Debug.Assert(node.Right.Type == typeof(int)); 76 _ilg.Emit(OpCodes.Ldelema, node.Type); 77 } 78 else 79 { 80 EmitExpressionAddress(node, type); 81 } 82 } 83 AddressOf(ParameterExpression node, Type type)84 private void AddressOf(ParameterExpression node, Type type) 85 { 86 if (TypeUtils.AreEquivalent(type, node.Type)) 87 { 88 if (node.IsByRef) 89 { 90 _scope.EmitGet(node); 91 } 92 else 93 { 94 _scope.EmitAddressOf(node); 95 } 96 } 97 else 98 { 99 // NB: Stack spilling can generate ref locals. 100 101 if (node.Type.IsByRef && node.Type.GetElementType() == type) 102 { 103 EmitExpression(node); 104 } 105 else 106 { 107 EmitExpressionAddress(node, type); 108 } 109 } 110 } 111 112 AddressOf(MemberExpression node, Type type)113 private void AddressOf(MemberExpression node, Type type) 114 { 115 if (TypeUtils.AreEquivalent(type, node.Type)) 116 { 117 // emit "this", if any 118 Type objectType = null; 119 if (node.Expression != null) 120 { 121 EmitInstance(node.Expression, out objectType); 122 } 123 EmitMemberAddress(node.Member, objectType); 124 } 125 else 126 { 127 EmitExpressionAddress(node, type); 128 } 129 } 130 131 // assumes the instance is already on the stack EmitMemberAddress(MemberInfo member, Type objectType)132 private void EmitMemberAddress(MemberInfo member, Type objectType) 133 { 134 FieldInfo field = member as FieldInfo; 135 if ((object)field != null) 136 { 137 // Verifiable code may not take the address of an init-only field. 138 // If we are asked to do so then get the value out of the field, stuff it 139 // into a local of the same type, and then take the address of the local. 140 // Typically this is what we want to do anyway; if we are saying 141 // Foo.bar.ToString() for a static value-typed field bar then we don't need 142 // the address of field bar to do the call. The address of a local which 143 // has the same value as bar is sufficient. 144 145 // The C# compiler will not compile a lambda expression tree 146 // which writes to the address of an init-only field. But one could 147 // probably use the expression tree API to build such an expression. 148 // (When compiled, such an expression would fail silently.) It might 149 // be worth it to add checking to the expression tree API to ensure 150 // that it is illegal to attempt to write to an init-only field, 151 // the same way that it is illegal to write to a read-only property. 152 // The same goes for literal fields. 153 if (!field.IsLiteral && !field.IsInitOnly) 154 { 155 _ilg.EmitFieldAddress(field); 156 return; 157 } 158 } 159 160 EmitMemberGet(member, objectType); 161 LocalBuilder temp = GetLocal(GetMemberType(member)); 162 _ilg.Emit(OpCodes.Stloc, temp); 163 _ilg.Emit(OpCodes.Ldloca, temp); 164 } 165 166 AddressOf(MethodCallExpression node, Type type)167 private void AddressOf(MethodCallExpression node, Type type) 168 { 169 // An array index of a multi-dimensional array is represented by a call to Array.Get, 170 // rather than having its own array-access node. This means that when we are trying to 171 // get the address of a member of a multi-dimensional array, we'll be trying to 172 // get the address of a Get method, and it will fail to do so. Instead, detect 173 // this situation and replace it with a call to the Address method. 174 if (!node.Method.IsStatic && 175 node.Object.Type.IsArray && 176 node.Method == node.Object.Type.GetMethod("Get", BindingFlags.Public | BindingFlags.Instance)) 177 { 178 MethodInfo mi = node.Object.Type.GetMethod("Address", BindingFlags.Public | BindingFlags.Instance); 179 180 EmitMethodCall(node.Object, mi, node); 181 } 182 else 183 { 184 EmitExpressionAddress(node, type); 185 } 186 } 187 AddressOf(IndexExpression node, Type type)188 private void AddressOf(IndexExpression node, Type type) 189 { 190 if (!TypeUtils.AreEquivalent(type, node.Type) || node.Indexer != null) 191 { 192 EmitExpressionAddress(node, type); 193 return; 194 } 195 196 if (node.ArgumentCount == 1) 197 { 198 EmitExpression(node.Object); 199 EmitExpression(node.GetArgument(0)); 200 _ilg.Emit(OpCodes.Ldelema, node.Type); 201 } 202 else 203 { 204 MethodInfo address = node.Object.Type.GetMethod("Address", BindingFlags.Public | BindingFlags.Instance); 205 EmitMethodCall(node.Object, address, node); 206 } 207 } 208 AddressOf(UnaryExpression node, Type type)209 private void AddressOf(UnaryExpression node, Type type) 210 { 211 Debug.Assert(node.NodeType == ExpressionType.Unbox); 212 Debug.Assert(type.IsValueType); 213 214 // Unbox leaves a pointer to the boxed value on the stack 215 EmitExpression(node.Operand); 216 _ilg.Emit(OpCodes.Unbox, type); 217 } 218 EmitExpressionAddress(Expression node, Type type)219 private void EmitExpressionAddress(Expression node, Type type) 220 { 221 Debug.Assert(TypeUtils.AreReferenceAssignable(type, node.Type)); 222 223 EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); 224 LocalBuilder tmp = GetLocal(type); 225 _ilg.Emit(OpCodes.Stloc, tmp); 226 _ilg.Emit(OpCodes.Ldloca, tmp); 227 } 228 229 230 // Emits the address of the expression, returning the write back if necessary 231 // 232 // For properties, we want to write back into the property if it's 233 // passed byref. EmitAddressWriteBack(Expression node, Type type)234 private WriteBack EmitAddressWriteBack(Expression node, Type type) 235 { 236 CompilationFlags startEmitted = EmitExpressionStart(node); 237 238 WriteBack result = null; 239 if (TypeUtils.AreEquivalent(type, node.Type)) 240 { 241 switch (node.NodeType) 242 { 243 case ExpressionType.MemberAccess: 244 result = AddressOfWriteBack((MemberExpression)node); 245 break; 246 case ExpressionType.Index: 247 result = AddressOfWriteBack((IndexExpression)node); 248 break; 249 } 250 } 251 if (result == null) 252 { 253 EmitAddress(node, type, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); 254 } 255 256 EmitExpressionEnd(startEmitted); 257 258 return result; 259 } 260 AddressOfWriteBack(MemberExpression node)261 private WriteBack AddressOfWriteBack(MemberExpression node) 262 { 263 var property = node.Member as PropertyInfo; 264 if ((object)property == null || !property.CanWrite) 265 { 266 return null; 267 } 268 269 return AddressOfWriteBackCore(node); // avoids closure allocation 270 } 271 AddressOfWriteBackCore(MemberExpression node)272 private WriteBack AddressOfWriteBackCore(MemberExpression node) 273 { 274 // emit instance, if any 275 LocalBuilder instanceLocal = null; 276 Type instanceType = null; 277 if (node.Expression != null) 278 { 279 EmitInstance(node.Expression, out instanceType); 280 281 // store in local 282 _ilg.Emit(OpCodes.Dup); 283 _ilg.Emit(OpCodes.Stloc, instanceLocal = GetInstanceLocal(instanceType)); 284 } 285 286 PropertyInfo pi = (PropertyInfo)node.Member; 287 288 // emit the get 289 EmitCall(instanceType, pi.GetGetMethod(nonPublic: true)); 290 291 // emit the address of the value 292 LocalBuilder valueLocal = GetLocal(node.Type); 293 _ilg.Emit(OpCodes.Stloc, valueLocal); 294 _ilg.Emit(OpCodes.Ldloca, valueLocal); 295 296 // Set the property after the method call 297 // don't re-evaluate anything 298 return @this => 299 { 300 if (instanceLocal != null) 301 { 302 @this._ilg.Emit(OpCodes.Ldloc, instanceLocal); 303 @this.FreeLocal(instanceLocal); 304 } 305 @this._ilg.Emit(OpCodes.Ldloc, valueLocal); 306 @this.FreeLocal(valueLocal); 307 @this.EmitCall(instanceLocal?.LocalType, pi.GetSetMethod(nonPublic: true)); 308 }; 309 } 310 AddressOfWriteBack(IndexExpression node)311 private WriteBack AddressOfWriteBack(IndexExpression node) 312 { 313 if (node.Indexer == null || !node.Indexer.CanWrite) 314 { 315 return null; 316 } 317 318 return AddressOfWriteBackCore(node); // avoids closure allocation 319 } 320 AddressOfWriteBackCore(IndexExpression node)321 private WriteBack AddressOfWriteBackCore(IndexExpression node) 322 { 323 // emit instance, if any 324 LocalBuilder instanceLocal = null; 325 Type instanceType = null; 326 if (node.Object != null) 327 { 328 EmitInstance(node.Object, out instanceType); 329 330 // store in local 331 _ilg.Emit(OpCodes.Dup); 332 _ilg.Emit(OpCodes.Stloc, instanceLocal = GetInstanceLocal(instanceType)); 333 } 334 335 // Emit indexes. We don't allow byref args, so no need to worry 336 // about write-backs or EmitAddress 337 int n = node.ArgumentCount; 338 var args = new LocalBuilder[n]; 339 for (var i = 0; i < n; i++) 340 { 341 Expression arg = node.GetArgument(i); 342 EmitExpression(arg); 343 344 LocalBuilder argLocal = GetLocal(arg.Type); 345 _ilg.Emit(OpCodes.Dup); 346 _ilg.Emit(OpCodes.Stloc, argLocal); 347 args[i] = argLocal; 348 } 349 350 // emit the get 351 EmitGetIndexCall(node, instanceType); 352 353 // emit the address of the value 354 LocalBuilder valueLocal = GetLocal(node.Type); 355 _ilg.Emit(OpCodes.Stloc, valueLocal); 356 _ilg.Emit(OpCodes.Ldloca, valueLocal); 357 358 // Set the property after the method call 359 // don't re-evaluate anything 360 return @this => 361 { 362 if (instanceLocal != null) 363 { 364 @this._ilg.Emit(OpCodes.Ldloc, instanceLocal); 365 @this.FreeLocal(instanceLocal); 366 } 367 foreach (LocalBuilder arg in args) 368 { 369 @this._ilg.Emit(OpCodes.Ldloc, arg); 370 @this.FreeLocal(arg); 371 } 372 @this._ilg.Emit(OpCodes.Ldloc, valueLocal); 373 @this.FreeLocal(valueLocal); 374 375 @this.EmitSetIndexCall(node, instanceLocal?.LocalType); 376 }; 377 } 378 GetInstanceLocal(Type type)379 private LocalBuilder GetInstanceLocal(Type type) 380 { 381 Type instanceLocalType = type.IsValueType ? type.MakeByRefType() : type; 382 return GetLocal(instanceLocalType); 383 } 384 } 385 } 386