-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathEmitConversion.cs
397 lines (352 loc) · 17.7 KB
/
EmitConversion.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Diagnostics;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
{
internal partial class CodeGenerator
{
private static bool IsNumeric(TypeSymbol type)
{
switch (type.PrimitiveTypeCode)
{
case Cci.PrimitiveTypeCode.Int8:
case Cci.PrimitiveTypeCode.UInt8:
case Cci.PrimitiveTypeCode.Int16:
case Cci.PrimitiveTypeCode.UInt16:
case Cci.PrimitiveTypeCode.Int32:
case Cci.PrimitiveTypeCode.UInt32:
case Cci.PrimitiveTypeCode.Int64:
case Cci.PrimitiveTypeCode.UInt64:
case Cci.PrimitiveTypeCode.Char:
case Cci.PrimitiveTypeCode.Float32:
case Cci.PrimitiveTypeCode.Float64:
return true;
case Cci.PrimitiveTypeCode.IntPtr:
case Cci.PrimitiveTypeCode.UIntPtr:
return type.IsNativeIntegerType;
default:
return false;
}
}
private void EmitConversionExpression(BoundConversion conversion, bool used)
{
switch (conversion.ConversionKind)
{
case ConversionKind.MethodGroup:
throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind);
case ConversionKind.ImplicitNullToPointer:
// The null pointer is represented as 0u.
_builder.EmitIntConstant(0);
_builder.EmitOpCode(ILOpCode.Conv_u);
EmitPopIfUnused(used);
return;
}
var operand = conversion.Operand;
if (!used && !conversion.ConversionHasSideEffects())
{
EmitExpression(operand, false); // just do expr side effects
return;
}
EmitExpression(operand, true);
EmitConversion(conversion);
EmitPopIfUnused(used);
}
private void EmitReadOnlySpanFromArrayExpression(BoundReadOnlySpanFromArray expression, bool used)
{
BoundExpression operand = expression.Operand;
var typeTo = (NamedTypeSymbol)expression.Type;
Debug.Assert((operand.Type.IsArray()) &&
this._module.Compilation.IsReadOnlySpanType(typeTo),
"only special kinds of conversions involving ReadOnlySpan may be handled in emit");
if (!TryEmitReadonlySpanAsBlobWrapper(typeTo, operand, used, inPlaceTarget: null, avoidInPlace: out _))
{
// there are several reasons that could prevent us from emitting a wrapper
// in such case we just emit the operand and then invoke the conversion method
EmitExpression(operand, used);
if (used)
{
// consumes 1 argument (array) and produces one result (span)
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
EmitSymbolToken(expression.ConversionMethod, expression.Syntax, optArgList: null);
}
}
}
private void EmitConversion(BoundConversion conversion)
{
switch (conversion.ConversionKind)
{
case ConversionKind.Identity:
EmitIdentityConversion(conversion);
break;
case ConversionKind.ImplicitNumeric:
case ConversionKind.ExplicitNumeric:
EmitNumericConversion(conversion);
break;
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
// from IL perspective ImplicitReference and Boxing conversions are the same thing.
// both force operand to be an object (O) - which may involve boxing
// and then assume that result has the target type - which may involve unboxing.
EmitImplicitReferenceConversion(conversion);
break;
case ConversionKind.ExplicitReference:
case ConversionKind.Unboxing:
// from IL perspective ExplicitReference and UnBoxing conversions are the same thing.
// both force operand to be an object (O) - which may involve boxing
// and then reinterpret result as the target type - which may involve unboxing.
EmitExplicitReferenceConversion(conversion);
break;
case ConversionKind.ImplicitEnumeration:
case ConversionKind.ExplicitEnumeration:
EmitEnumConversion(conversion);
break;
case ConversionKind.ImplicitUserDefined:
case ConversionKind.ExplicitUserDefined:
case ConversionKind.AnonymousFunction:
case ConversionKind.MethodGroup:
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ImplicitTuple:
case ConversionKind.ExplicitTupleLiteral:
case ConversionKind.ExplicitTuple:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ExplicitDynamic:
case ConversionKind.ImplicitThrow:
// None of these things should reach codegen (yet? maybe?)
throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind);
case ConversionKind.ImplicitPointerToVoid:
case ConversionKind.ExplicitPointerToPointer:
case ConversionKind.ImplicitPointer:
return; //no-op since they all have the same runtime representation
case ConversionKind.ExplicitPointerToInteger:
case ConversionKind.ExplicitIntegerToPointer:
var fromType = conversion.Operand.Type;
var fromPredefTypeKind = fromType.PrimitiveTypeCode;
var toType = conversion.Type;
var toPredefTypeKind = toType.PrimitiveTypeCode;
#if DEBUG
switch (fromPredefTypeKind)
{
case Microsoft.Cci.PrimitiveTypeCode.IntPtr when !fromType.IsNativeIntegerType:
case Microsoft.Cci.PrimitiveTypeCode.UIntPtr when !fromType.IsNativeIntegerType:
case Microsoft.Cci.PrimitiveTypeCode.Pointer:
case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
Debug.Assert(IsNumeric(toType));
break;
default:
Debug.Assert(IsNumeric(fromType));
Debug.Assert(
(toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.IntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.UIntPtr) && !toType.IsNativeIntegerWrapperType ||
toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.Pointer ||
toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.FunctionPointer);
break;
}
#endif
_builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked);
break;
case ConversionKind.PinnedObjectToPointer:
// CLR allows unsafe conversion from(O) to native int/uint.
// The conversion does not change the representation of the value,
// but the value will not be reported to subsequent GC operations (and therefore will not be updated by such operations)
_builder.EmitOpCode(ILOpCode.Conv_u);
break;
case ConversionKind.ImplicitNullToPointer:
throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); // Should be handled by caller.
case ConversionKind.ImplicitNullable:
case ConversionKind.ExplicitNullable:
default:
throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind);
}
}
private void EmitIdentityConversion(BoundConversion conversion)
{
// An _explicit_ identity conversion from double to double or float to float on
// non-constants must stay as a conversion. An _implicit_ identity conversion can be
// optimized away. Why? Because (double)d1 + d2 has different semantics than d1 + d2.
// The former rounds off to 64 bit precision; the latter is permitted to use higher
// precision math if d1 is enregistered.
if (conversion.ExplicitCastInCode)
{
switch (conversion.Type.PrimitiveTypeCode)
{
case Microsoft.Cci.PrimitiveTypeCode.Float32:
case Microsoft.Cci.PrimitiveTypeCode.Float64:
// For explicitly-written "identity conversions" from float to float or
// double to double, we require the generation of conv.r4 or conv.r8. The
// runtime can use these instructions to truncate precision, and csc.exe
// generates them. It's not ideal, we should consider the possibility of not
// doing this or marking somewhere else that this is necessary.
// Don't need to do this for constants, however.
if (conversion.Operand.ConstantValueOpt == null)
{
EmitNumericConversion(conversion);
}
break;
}
}
}
private void EmitNumericConversion(BoundConversion conversion)
{
var fromType = conversion.Operand.Type;
var fromPredefTypeKind = fromType.PrimitiveTypeCode;
Debug.Assert(IsNumeric(fromType));
var toType = conversion.Type;
var toPredefTypeKind = toType.PrimitiveTypeCode;
Debug.Assert(IsNumeric(toType));
_builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked);
}
private void EmitImplicitReferenceConversion(BoundConversion conversion)
{
// turn operand into an O(operandType)
// if the operand is already verifiably an O, we can use it as-is
// otherwise we need to box it, so that verifier will start tracking an O
if (!conversion.Operand.Type.IsVerifierReference())
{
EmitBox(conversion.Operand.Type, conversion.Operand.Syntax);
}
// here we have O(operandType) that must be compatible with O(targetType)
//
// if target type is verifiably a reference type, we can leave the value as-is otherwise
// we need to unbox to targetType to keep verifier happy.
var resultType = conversion.Type;
if (!resultType.IsVerifierReference())
{
_builder.EmitOpCode(ILOpCode.Unbox_any);
EmitSymbolToken(conversion.Type, conversion.Syntax);
}
else if (resultType.IsArray())
{
// need a static cast here to satisfy verifier
// Example: Derived[] can be used in place of Base[] for all purposes except for LDELEMA <Base>
// Even though it would be safe due to run time check, verifier requires that the static type of the array is Base[]
// We do not know why we are casting, so to be safe, lets make the cast explicit. JIT elides such casts.
EmitStaticCast(conversion.Type, conversion.Syntax);
}
return;
}
private void EmitExplicitReferenceConversion(BoundConversion conversion)
{
// turn operand into an O(operandType)
// if the operand is already verifiably an O, we can use it as-is
// otherwise we need to box it, so that verifier will start tracking an O
if (!conversion.Operand.Type.IsVerifierReference())
{
EmitBox(conversion.Operand.Type, conversion.Operand.Syntax);
}
// here we have O(operandType) that could be compatible with O(targetType)
//
// if target type is verifiably a reference type, we can just do a type check otherwise
// we unbox which will both do the type check and start tracking actual target type in
// verifier.
if (conversion.Type.IsVerifierReference())
{
_builder.EmitOpCode(ILOpCode.Castclass);
EmitSymbolToken(conversion.Type, conversion.Syntax);
}
else
{
_builder.EmitOpCode(ILOpCode.Unbox_any);
EmitSymbolToken(conversion.Type, conversion.Syntax);
}
}
private void EmitEnumConversion(BoundConversion conversion)
{
// Nullable enumeration conversions should have already been lowered into
// implicit or explicit nullable conversions.
Debug.Assert(!conversion.Type.IsNullableType());
var fromType = conversion.Operand.Type;
if (fromType.IsEnumType())
{
fromType = ((NamedTypeSymbol)fromType).EnumUnderlyingType;
}
var fromPredefTypeKind = fromType.PrimitiveTypeCode;
Debug.Assert(IsNumeric(fromType));
var toType = conversion.Type;
if (toType.IsEnumType())
{
toType = ((NamedTypeSymbol)toType).EnumUnderlyingType;
}
var toPredefTypeKind = toType.PrimitiveTypeCode;
Debug.Assert(IsNumeric(toType));
_builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked);
}
private void EmitDelegateCreation(BoundExpression node, BoundExpression receiver, bool isExtensionMethod, MethodSymbol method, TypeSymbol delegateType, bool used)
{
var isStatic = receiver == null || (!isExtensionMethod && method.IsStatic);
if (!used)
{
if (!isStatic)
{
EmitExpression(receiver, false);
}
return;
}
// emit the receiver
if (isStatic)
{
_builder.EmitNullConstant();
if (method.IsAbstract || method.IsVirtual)
{
if (receiver is not BoundTypeExpression { Type: { TypeKind: TypeKind.TypeParameter } })
{
throw ExceptionUtilities.Unreachable();
}
_builder.EmitOpCode(ILOpCode.Constrained);
EmitSymbolToken(receiver.Type, receiver.Syntax);
}
}
else
{
EmitExpression(receiver, true);
if (!receiver.Type.IsVerifierReference())
{
EmitBox(receiver.Type, receiver.Syntax);
}
}
// emit method pointer
// Metadata Spec (II.14.6):
// Delegates shall be declared sealed.
// The Invoke method shall be virtual.
if (!method.IsStatic && method.IsMetadataVirtual() && !method.ContainingType.IsDelegateType() && !receiver.SuppressVirtualCalls)
{
// NOTE: method.IsMetadataVirtual -> receiver != null
_builder.EmitOpCode(ILOpCode.Dup);
_builder.EmitOpCode(ILOpCode.Ldvirtftn);
// substitute the method with original virtual method
method = method.GetConstructedLeastOverriddenMethod(_method.ContainingType, requireSameReturnType: true);
}
else
{
_builder.EmitOpCode(ILOpCode.Ldftn);
}
EmitSymbolToken(method, node.Syntax, null);
// call delegate constructor
_builder.EmitOpCode(ILOpCode.Newobj, -1); // pop 2 args and push delegate object
var ctor = DelegateConstructor(node.Syntax, delegateType);
if ((object)ctor != null) EmitSymbolToken(ctor, node.Syntax, null);
}
private MethodSymbol DelegateConstructor(SyntaxNode syntax, TypeSymbol delegateType)
{
foreach (var possibleCtor in delegateType.GetMembers(WellKnownMemberNames.InstanceConstructorName))
{
var m = possibleCtor as MethodSymbol;
if ((object)m == null) continue;
var parameters = m.Parameters;
if (parameters.Length != 2) continue;
if (parameters[0].Type.SpecialType != SpecialType.System_Object) continue;
var p1t = parameters[1].Type.SpecialType;
if (p1t == SpecialType.System_IntPtr || p1t == SpecialType.System_UIntPtr)
{
return m;
}
}
// The delegate '{0}' does not have a valid constructor
_diagnostics.Add(ErrorCode.ERR_BadDelegateConstructor, syntax.Location, delegateType);
return null;
}
}
}