This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
interpreter.cpp
12272 lines (10950 loc) · 409 KB
/
interpreter.cpp
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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
//
#include "common.h"
#ifdef FEATURE_INTERPRETER
#include "interpreter.h"
#include "interpreter.hpp"
#include "cgencpu.h"
#include "stublink.h"
#include "openum.h"
#include "fcall.h"
#include "frames.h"
#include "gc.h"
#include <float.h>
#include "jitinterface.h"
#include "safemath.h"
#include "exceptmacros.h"
#include "runtimeexceptionkind.h"
#include "runtimehandles.h"
#include "vars.hpp"
#include "cycletimer.h"
#ifdef FEATURE_REMOTING
#include "remoting.h"
#endif
inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2)
{
return (CORINFO_CALLINFO_FLAGS) (flag1 | flag2);
}
static CorInfoType asCorInfoType(CORINFO_CLASS_HANDLE clsHnd)
{
TypeHandle typeHnd(clsHnd);
return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, NULL);
}
InterpreterMethodInfo::InterpreterMethodInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo)
: m_method(methInfo->ftn),
m_module(methInfo->scope),
m_ILCode(methInfo->ILCode),
m_ILCodeEnd(methInfo->ILCode + methInfo->ILCodeSize),
m_maxStack(methInfo->maxStack),
m_numArgs(methInfo->args.numArgs),
m_flags(0),
m_argDescs(NULL),
m_numLocals(methInfo->locals.numArgs),
m_returnType(methInfo->args.retType),
m_invocations(0),
m_jittedCode(0),
#if INTERP_PROFILE
m_totIlInstructionsExeced(0),
m_maxIlInstructionsExeced(0),
#endif
m_ehClauseCount(methInfo->EHcount),
m_varArgHandleArgNum(NO_VA_ARGNUM),
m_methodCache(NULL)
{
// Overflow sanity check. (Can ILCodeSize ever be zero?)
assert(m_ILCode <= m_ILCodeEnd);
// Does the calling convention indicate an implicit "this" (first arg) or generic type context arg (last arg)?
SetFlag<Flag_hasThisArg>((methInfo->args.callConv & CORINFO_CALLCONV_HASTHIS) != 0);
if (GetFlag<Flag_hasThisArg>())
{
GCX_PREEMP();
CORINFO_CLASS_HANDLE methClass = comp->getMethodClass(methInfo->ftn);
DWORD attribs = comp->getClassAttribs(methClass);
SetFlag<Flag_thisArgIsObjPtr>((attribs & CORINFO_FLG_VALUECLASS) == 0);
}
#if INTERP_PROFILE || defined(_DEBUG)
{
const char* clsName;
#if defined(_DEBUG)
m_methName = ::eeGetMethodFullName(comp, methInfo->ftn, &clsName);
#else
m_methName = comp->getMethodName(methInfo->ftn, &clsName);
#endif
char* myClsName = new char[strlen(clsName) + 1];
strcpy(myClsName, clsName);
m_clsName = myClsName;
}
#endif // INTERP_PROFILE
// Do we have a ret buff? If its a struct or refany, then *maybe*, depending on architecture...
bool hasRetBuff = (methInfo->args.retType == CORINFO_TYPE_VALUECLASS || methInfo->args.retType == CORINFO_TYPE_REFANY);
#if defined(FEATURE_HFA)
// ... unless its an HFA type (and not varargs)...
if (hasRetBuff && CorInfoTypeIsFloatingPoint(comp->getHFAType(methInfo->args.retTypeClass)) && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG)
{
hasRetBuff = false;
}
#endif
#if defined(_ARM_) || defined(_AMD64_)|| defined(_ARM64_)
// ...or it fits into one register.
if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= sizeof(void*))
{
hasRetBuff = false;
}
#endif
SetFlag<Flag_hasRetBuffArg>(hasRetBuff);
MetaSig sig(reinterpret_cast<MethodDesc*>(methInfo->ftn));
SetFlag<Flag_hasGenericsContextArg>((methInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0);
SetFlag<Flag_isVarArg>((methInfo->args.callConv & CORINFO_CALLCONV_VARARG) != 0);
SetFlag<Flag_typeHasGenericArgs>(methInfo->args.sigInst.classInstCount > 0);
SetFlag<Flag_methHasGenericArgs>(methInfo->args.sigInst.methInstCount > 0);
_ASSERTE_MSG(!GetFlag<Flag_hasGenericsContextArg>()
|| ((GetFlag<Flag_typeHasGenericArgs>() & !(GetFlag<Flag_hasThisArg>() && GetFlag<Flag_thisArgIsObjPtr>())) || GetFlag<Flag_methHasGenericArgs>()),
"If the method takes a generic parameter, is a static method of generic class (or meth of a value class), and/or itself takes generic parameters");
if (GetFlag<Flag_hasThisArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_hasRetBuffArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_isVarArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
m_numArgs++;
}
if (m_numArgs == 0)
{
m_argDescs = NULL;
}
else
{
m_argDescs = new ArgDesc[m_numArgs];
}
// Now we'll do the locals.
m_localDescs = new LocalDesc[m_numLocals];
// Allocate space for the pinning reference bits (lazily).
m_localIsPinningRefBits = NULL;
// Now look at each local.
CORINFO_ARG_LIST_HANDLE localsPtr = methInfo->locals.args;
CORINFO_CLASS_HANDLE vcTypeRet;
unsigned curLargeStructOffset = 0;
for (unsigned k = 0; k < methInfo->locals.numArgs; k++)
{
// TODO: if this optimization succeeds, the switch below on localType
// can become much simpler.
m_localDescs[k].m_offset = 0;
#ifdef _DEBUG
vcTypeRet = NULL;
#endif
CorInfoTypeWithMod localTypWithMod = comp->getArgType(&methInfo->locals, localsPtr, &vcTypeRet);
// If the local vars is a pinning reference, set the bit to indicate this.
if ((localTypWithMod & CORINFO_TYPE_MOD_PINNED) != 0)
{
SetPinningBit(k);
}
CorInfoType localType = strip(localTypWithMod);
switch (localType)
{
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
{
InterpreterType tp = InterpreterType(comp, vcTypeRet);
unsigned size = static_cast<unsigned>(tp.Size(comp));
size = max(size, sizeof(void*));
m_localDescs[k].m_type = tp;
if (tp.IsLargeStruct(comp))
{
m_localDescs[k].m_offset = curLargeStructOffset;
curLargeStructOffset += size;
}
}
break;
case CORINFO_TYPE_VAR:
NYI_INTERP("argument of generic parameter type"); // Should not happen;
break;
default:
m_localDescs[k].m_type = InterpreterType(localType);
break;
}
m_localDescs[k].m_typeStackNormal = m_localDescs[k].m_type.StackNormalize();
localsPtr = comp->getArgNext(localsPtr);
}
m_largeStructLocalSize = curLargeStructOffset;
}
void InterpreterMethodInfo::InitArgInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo, short* argOffsets_)
{
unsigned numSigArgsPlusThis = methInfo->args.numArgs;
if (GetFlag<Flag_hasThisArg>())
{
numSigArgsPlusThis++;
}
// The m_argDescs array is constructed in the following "canonical" order:
// 1. 'this' pointer
// 2. signature arguments
// 3. return buffer
// 4. type parameter -or- vararg cookie
//
// argOffsets_ is passed in this order, and serves to establish the offsets to arguments
// when the interpreter is invoked using the native calling convention (i.e., not directly).
//
// When the interpreter is invoked directly, the arguments will appear in the same order
// and form as arguments passed to MethodDesc::CallDescr(). This ordering is as follows:
// 1. 'this' pointer
// 2. return buffer
// 3. signature arguments
//
// MethodDesc::CallDescr() does not support generic parameters or varargs functions.
_ASSERTE_MSG((methInfo->args.callConv & (CORINFO_CALLCONV_EXPLICITTHIS)) == 0,
"Don't yet handle EXPLICITTHIS calling convention modifier.");
switch (methInfo->args.callConv & CORINFO_CALLCONV_MASK)
{
case CORINFO_CALLCONV_DEFAULT:
case CORINFO_CALLCONV_VARARG:
{
unsigned k = 0;
ARG_SLOT* directOffset = NULL;
short directRetBuffOffset = 0;
short directVarArgOffset = 0;
short directTypeParamOffset = 0;
// If there's a "this" argument, handle it.
if (GetFlag<Flag_hasThisArg>())
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_UNDEF);
#ifdef FEATURE_STUBS_AS_IL
MethodDesc *pMD = reinterpret_cast<MethodDesc*>(methInfo->ftn);
// The signature of the ILStubs may be misleading.
// If a StubTarget is ever set, we'll find the correct type by inspecting the
// target, rather than the stub.
if (pMD->IsILStub())
{
if (pMD->AsDynamicMethodDesc()->IsUnboxingILStub())
{
// This is an unboxing stub where the thisptr is passed as a boxed VT.
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
else
{
MethodDesc *pTargetMD = pMD->AsDynamicMethodDesc()->GetILStubResolver()->GetStubTargetMethodDesc();
if (pTargetMD != NULL)
{
if (pTargetMD->GetMethodTable()->IsValueType())
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
}
else
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
}
}
}
#endif // FEATURE_STUBS_AS_IL
if (m_argDescs[k].m_type == InterpreterType(CORINFO_TYPE_UNDEF))
{
CORINFO_CLASS_HANDLE cls = comp->getMethodClass(methInfo->ftn);
DWORD attribs = comp->getClassAttribs(cls);
if (attribs & CORINFO_FLG_VALUECLASS)
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
}
else
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
}
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
directOffset++;
k++;
}
// If there is a return buffer, it will appear next in the arguments list for a direct call.
// Reserve its offset now, for use after the explicit arguments.
#if defined(_ARM_)
// On ARM, for direct calls we always treat HFA return types as having ret buffs.
// So figure out if we have an HFA return type.
bool hasHFARetType =
methInfo->args.retType == CORINFO_TYPE_VALUECLASS
&& CorInfoTypeIsFloatingPoint(comp->getHFAType(methInfo->args.retTypeClass))
&& methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG;
#endif // defined(_ARM_)
if (GetFlag<Flag_hasRetBuffArg>()
#if defined(_ARM_)
// On ARM, for direct calls we always treat HFA return types as having ret buffs.
|| hasHFARetType
#endif // defined(_ARM_)
)
{
directRetBuffOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
directOffset++;
}
#if defined(_AMD64_)
if (GetFlag<Flag_isVarArg>())
{
directVarArgOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
directOffset++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
directTypeParamOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
directOffset++;
}
#endif
// Now record the argument types for the rest of the arguments.
InterpreterType it;
CORINFO_CLASS_HANDLE vcTypeRet;
CORINFO_ARG_LIST_HANDLE argPtr = methInfo->args.args;
for (; k < numSigArgsPlusThis; k++)
{
CorInfoTypeWithMod argTypWithMod = comp->getArgType(&methInfo->args, argPtr, &vcTypeRet);
CorInfoType argType = strip(argTypWithMod);
switch (argType)
{
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
it = InterpreterType(comp, vcTypeRet);
break;
default:
// Everything else is just encoded as a shifted CorInfoType.
it = InterpreterType(argType);
break;
}
m_argDescs[k].m_type = it;
m_argDescs[k].m_typeStackNormal = it.StackNormalize();
m_argDescs[k].m_nativeOffset = argOffsets_[k];
// When invoking the interpreter directly, large value types are always passed by reference.
if (it.IsLargeStruct(comp))
{
m_argDescs[k].m_directOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
}
else
{
m_argDescs[k].m_directOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, it.Size(comp)));
}
argPtr = comp->getArgNext(argPtr);
directOffset++;
}
if (GetFlag<Flag_hasRetBuffArg>())
{
// The generic type context is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directRetBuffOffset;
k++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
// The vararg cookie is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directTypeParamOffset;
directOffset++;
k++;
}
if (GetFlag<Flag_isVarArg>())
{
// The generic type context is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directVarArgOffset;
k++;
}
}
break;
case CORINFO_CALLCONV_C:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_C");
break;
case CORINFO_CALLCONV_STDCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_STDCALL");
break;
case CORINFO_CALLCONV_THISCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_THISCALL");
break;
case CORINFO_CALLCONV_FASTCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_FASTCALL");
break;
case CORINFO_CALLCONV_FIELD:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_FIELD");
break;
case CORINFO_CALLCONV_LOCAL_SIG:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_LOCAL_SIG");
break;
case CORINFO_CALLCONV_PROPERTY:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_PROPERTY");
break;
case CORINFO_CALLCONV_NATIVEVARARG:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_NATIVEVARARG");
break;
default:
_ASSERTE_ALL_BUILDS(__FILE__, false); // shouldn't get here
}
}
InterpreterMethodInfo::~InterpreterMethodInfo()
{
if (m_methodCache != NULL)
{
delete m_methodCache;
}
}
void InterpreterMethodInfo::AllocPinningBitsIfNeeded()
{
if (m_localIsPinningRefBits != NULL)
return;
unsigned numChars = (m_numLocals + 7) / 8;
m_localIsPinningRefBits = new char[numChars];
for (unsigned i = 0; i < numChars; i++)
{
m_localIsPinningRefBits[i] = char(0);
}
}
void InterpreterMethodInfo::SetPinningBit(unsigned locNum)
{
_ASSERTE_MSG(locNum < m_numLocals, "Precondition");
AllocPinningBitsIfNeeded();
unsigned ind = locNum / 8;
unsigned bitNum = locNum - (ind * 8);
m_localIsPinningRefBits[ind] |= (1 << bitNum);
}
bool InterpreterMethodInfo::GetPinningBit(unsigned locNum)
{
_ASSERTE_MSG(locNum < m_numLocals, "Precondition");
if (m_localIsPinningRefBits == NULL)
return false;
unsigned ind = locNum / 8;
unsigned bitNum = locNum - (ind * 8);
return (m_localIsPinningRefBits[ind] & (1 << bitNum)) != 0;
}
void Interpreter::ArgState::AddArg(unsigned canonIndex, short numSlots, bool noReg, bool twoSlotAlign)
{
#if defined(_AMD64_)
assert(!noReg);
assert(!twoSlotAlign);
AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/false);
#else // !_AMD64_
#if defined(_X86_) || defined(_ARM64_)
assert(!twoSlotAlign); // Shouldn't use this flag on x86 (it wouldn't work right in the stack, at least).
#endif
// If the argument requires two-slot alignment, make sure we have it. This is the
// ARM model: both in regs and on the stack.
if (twoSlotAlign)
{
if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
{
if ((numRegArgs % 2) != 0)
{
numRegArgs++;
}
}
else
{
if ((callerArgStackSlots % 2) != 0)
{
callerArgStackSlots++;
}
}
}
#if defined(_ARM64_)
// On ARM64 we're not going to place an argument 'partially' on the stack
// if all slots fits into registers, they go into registers, otherwise they go into stack.
if (!noReg && numRegArgs+numSlots <= NumberOfIntegerRegArgs())
#else
if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
#endif
{
argIsReg[canonIndex] = ARS_IntReg;
argOffsets[canonIndex] = numRegArgs * sizeof(void*);
numRegArgs += numSlots;
// If we overflowed the regs, we consume some stack arg space.
if (numRegArgs > NumberOfIntegerRegArgs())
{
callerArgStackSlots += (numRegArgs - NumberOfIntegerRegArgs());
}
}
else
{
#if defined(_X86_)
// On X86, stack args are pushed in order. We will add the total size of the arguments to this offset,
// so we set this to a negative number relative to the SP before the first arg push.
callerArgStackSlots += numSlots;
ClrSafeInt<short> offset(-callerArgStackSlots);
#elif defined(_ARM_) || defined(_ARM64_)
// On ARM, args are pushed in *reverse* order. So we will create an offset relative to the address
// of the first stack arg; later, we will add the size of the non-stack arguments.
ClrSafeInt<short> offset(callerArgStackSlots);
#endif
offset *= static_cast<short>(sizeof(void*));
assert(!offset.IsOverflow());
argOffsets[canonIndex] = offset.Value();
#if defined(_ARM_) || defined(_ARM64_)
callerArgStackSlots += numSlots;
#endif;
}
#endif // !_AMD64_
}
#if defined(_AMD64_)
// AMD64 calling convention allows any type that can be contained in 64 bits to be passed in registers,
// if not contained or they are of a size not a power of 2, then they are passed by reference on the stack.
// RCX, RDX, R8, R9 are the int arg registers. XMM0-3 overlap with the integer registers and are used
// for floating point arguments.
void Interpreter::ArgState::AddArgAmd64(unsigned canonIndex, unsigned short numSlots, bool isFloatingType)
{
// If floating type and there are slots use a float reg slot.
if (isFloatingType && (numFPRegArgSlots < MaxNumFPRegArgSlots))
{
assert(numSlots == 1);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
fpArgsUsed |= (0x1 << (numFPRegArgSlots + 1));
numFPRegArgSlots += 1;
numRegArgs += 1; // Increment int reg count due to shadowing.
return;
}
// If we have an integer/aligned-struct arg or a reference of a struct that got copied on
// to the stack, it would go into a register or a stack slot.
if (numRegArgs != NumberOfIntegerRegArgs())
{
argIsReg[canonIndex] = ARS_IntReg;
argOffsets[canonIndex] = numRegArgs * sizeof(void*);
numRegArgs += 1;
numFPRegArgSlots += 1; // Increment FP reg count due to shadowing.
}
else
{
argIsReg[canonIndex] = ARS_NotReg;
ClrSafeInt<short> offset(callerArgStackSlots * sizeof(void*));
assert(!offset.IsOverflow());
argOffsets[canonIndex] = offset.Value();
callerArgStackSlots += 1;
}
}
#endif
void Interpreter::ArgState::AddFPArg(unsigned canonIndex, unsigned short numSlots, bool twoSlotAlign)
{
#if defined(_AMD64_)
assert(!twoSlotAlign);
assert(numSlots == 1);
AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/ true);
#elif defined(_X86_)
assert(false); // Don't call this on x86; we pass all FP on the stack.
#elif defined(_ARM_)
// We require "numSlots" alignment.
assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
argIsReg[canonIndex] = ARS_FloatReg;
if (twoSlotAlign)
{
// If we require two slot alignment, the number of slots must be a multiple of two.
assert((numSlots % 2) == 0);
// Skip a slot if necessary.
if ((numFPRegArgSlots % 2) != 0)
{
numFPRegArgSlots++;
}
// We always use new slots for two slot aligned args precision...
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned short i = 0; i < numSlots/2; i++)
{
fpArgsUsed |= (0x3 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
}
else
{
if (numSlots == 1)
{
// A single-precision (float) argument. We must do "back-filling" where possible, searching
// for previous unused registers.
unsigned slot = 0;
while (slot < 32 && (fpArgsUsed & (1 << slot))) slot++;
assert(slot < 32); // Search succeeded.
assert(slot <= numFPRegArgSlots); // No bits at or above numFPRegArgSlots are set (regs used).
argOffsets[canonIndex] = slot * sizeof(void*);
fpArgsUsed |= (0x1 << slot);
if (slot == numFPRegArgSlots)
numFPRegArgSlots += numSlots;
}
else
{
// We can always allocate at after the last used slot.
argOffsets[numFPRegArgSlots] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
}
}
#elif defined(_ARM64_)
assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
assert(!twoSlotAlign);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
#else
#error "Unsupported architecture"
#endif
}
// static
CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp,
CORINFO_METHOD_INFO* info,
/*OUT*/ BYTE **nativeEntry,
/*OUT*/ ULONG *nativeSizeOfCode,
InterpreterMethodInfo** ppInterpMethodInfo,
bool jmpCall)
{
//
// First, ensure that the compiler-specific statics are initialized.
//
InitializeCompilerStatics(comp);
//
// Next, use switches and IL scanning to determine whether to interpret this method.
//
#if INTERP_TRACING
#define TRACE_SKIPPED(cls, meth, reason) \
if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs)) { \
fprintf(GetLogFile(), "Skipping %s:%s (%s).\n", cls, meth, reason); \
}
#else
#define TRACE_SKIPPED(cls, meth, reason)
#endif
// If jmpCall, we only need to do computations involving method info.
if (!jmpCall)
{
const char* clsName;
const char* methName = comp->getMethodName(info->ftn, &clsName);
if ( !s_InterpretMeths.contains(methName, clsName, info->args.pSig)
|| s_InterpretMethsExclude.contains(methName, clsName, info->args.pSig))
{
TRACE_SKIPPED(clsName, methName, "not in set of methods to interpret");
return CORJIT_SKIPPED;
}
unsigned methHash = comp->getMethodHash(info->ftn);
if ( methHash < s_InterpretMethHashMin.val(CLRConfig::INTERNAL_InterpreterMethHashMin)
|| methHash > s_InterpretMethHashMax.val(CLRConfig::INTERNAL_InterpreterMethHashMax))
{
TRACE_SKIPPED(clsName, methName, "hash not within range to interpret");
return CORJIT_SKIPPED;
}
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);
#if !INTERP_ILSTUBS
if (pMD->IsILStub())
{
TRACE_SKIPPED(clsName, methName, "interop stubs not supported");
return CORJIT_SKIPPED;
}
else
#endif // !INTERP_ILSTUBS
if (!s_InterpreterDoLoopMethods && MethodMayHaveLoop(info->ILCode, info->ILCodeSize))
{
TRACE_SKIPPED(clsName, methName, "has loop, not interpreting loop methods.");
return CORJIT_SKIPPED;
}
s_interpreterStubNum++;
#if INTERP_TRACING
if (s_interpreterStubNum < s_InterpreterStubMin.val(CLRConfig::INTERNAL_InterpreterStubMin)
|| s_interpreterStubNum > s_InterpreterStubMax.val(CLRConfig::INTERNAL_InterpreterStubMax))
{
TRACE_SKIPPED(clsName, methName, "stub num not in range, not interpreting.");
return CORJIT_SKIPPED;
}
if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs))
{
unsigned hash = comp->getMethodHash(info->ftn);
fprintf(GetLogFile(), "Generating interpretation stub (# %d = 0x%x, hash = 0x%x) for %s:%s.\n",
s_interpreterStubNum, s_interpreterStubNum, hash, clsName, methName);
fflush(GetLogFile());
}
#endif
}
//
// Finally, generate an interpreter entry-point stub.
//
// @TODO: this structure clearly needs some sort of lifetime management. It is the moral equivalent
// of compiled code, and should be associated with an app domain. In addition, when I get to it, we should
// delete it when/if we actually compile the method. (Actually, that's complicated, since there may be
// VSD stubs still bound to the interpreter stub. The check there will get to the jitted code, but we want
// to eventually clean those up at some safe point...)
InterpreterMethodInfo* interpMethInfo = new InterpreterMethodInfo(comp, info);
if (ppInterpMethodInfo != nullptr)
{
*ppInterpMethodInfo = interpMethInfo;
}
interpMethInfo->m_stubNum = s_interpreterStubNum;
MethodDesc* methodDesc = reinterpret_cast<MethodDesc*>(info->ftn);
if (!jmpCall)
{
interpMethInfo = RecordInterpreterMethodInfoForMethodHandle(info->ftn, interpMethInfo);
}
#if FEATURE_INTERPRETER_DEADSIMPLE_OPT
unsigned offsetOfLd;
if (IsDeadSimpleGetter(comp, methodDesc, &offsetOfLd))
{
interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetter>(true);
if (offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterDbg)
{
interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetterIsDbgForm>(true);
}
else
{
assert(offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt);
}
}
#endif // FEATURE_INTERPRETER_DEADSIMPLE_OPT
// Used to initialize the arg offset information.
Stub* stub = NULL;
// We assume that the stack contains (with addresses growing upwards, assuming a downwards-growing stack):
//
// [Non-reg arg N-1]
// ...
// [Non-reg arg <# of reg args>]
// [return PC]
//
// Then push the register args to get:
//
// [Non-reg arg N-1]
// ...
// [Non-reg arg <# of reg args>]
// [return PC]
// [reg arg <# of reg args>-1]
// ...
// [reg arg 0]
//
// Pass the address of this argument array, and the MethodDesc pointer for the method, as arguments to
// Interpret.
//
// So the structure of the code will look like this (in the non-ILstub case):
//
#if defined(_X86_) || defined(_AMD64_)
// First do "short-circuiting" if the method has JITted code, and we couldn't find/update the call site:
// eax = &interpMethInfo
// eax = [eax + offsetof(m_jittedCode)]
// if (eax == zero) goto doInterpret:
// /*else*/ jmp [eax]
// doInterpret:
// push ebp
// mov ebp, esp
// [if there are register arguments in ecx or edx, push them]
// ecx := addr of InterpretMethodInfo for the method to be intepreted.
// edx = esp /*pointer to argument structure*/
// call to Interpreter::InterpretMethod
// [if we pushed register arguments, increment esp by the right amount.]
// pop ebp
// ret <n> ; where <n> is the number of argument stack slots in the call to the stub.
#elif defined (_ARM_)
// TODO.
#endif
// The IL stub case is hard. The portion of the interpreter stub that short-circuits
// to JITted code requires an extra "scratch" volatile register, not an argument register;
// in the IL stub case, it too is using such a register, as an extra argument, to hold the stub context.
// On x86 and ARM, there is only one such extra volatile register, and we've got a conundrum.
// The cases where this short-circuiting is important is when the address of an interpreter stub
// becomes "embedded" in other code. The examples I know of are VSD stubs and delegates.
// The first of these is not a problem for IL stubs -- methods invoked via p/Invoke (the ones that
// [I think!] use IL stubs) are static, and cannot be invoked via VSD stubs. Delegates, on the other
// remain a problem [I believe].
// For the short term, we'll ignore this issue, and never do short-circuiting for IL stubs.
// So interpreter stubs embedded in delegates will continue to interpret the IL stub, even after
// the stub has been JITted.
// The long-term intention is that when we JIT a method with an interpreter stub, we keep a mapping
// from interpreter stub address to corresponding native code address. If this mapping is non-empty,
// at GC time we would visit the locations in which interpreter stub addresses might be located, like
// VSD stubs and delegate objects, and update them to point to new addresses. This would be a necessary
// part of any scheme to GC interpreter stubs, and InterpreterMethodInfos.
// If we *really* wanted to make short-circuiting work for the IL stub case, we would have to
// (in the x86 case, which should be sufficiently illustrative):
// push eax
// <get the address of JITted code, if any, into eax>
// if there is JITted code in eax, we'd have to
// push 2 non-volatile registers, say esi and edi.
// copy the JITted code address from eax into esi.
// copy the method arguments (without the return address) down the stack, using edi
// as a scratch register.
// restore the original stub context value into eax from the stack
// call (not jmp) to the JITted code address in esi
// pop esi and edi from the stack.
// now the stack has original args, followed by original return address. Do a "ret"
// that returns to the return address, and also pops the original args from the stack.
// If we did this, we'd have to give this portion of the stub proper unwind info.
// Also, we'd have to adjust the rest of the stub to pop eax from the stack.
// TODO: much of the interpreter stub code should be is shareable. In the non-IL stub case,
// at least, we could have a small per-method stub that puts the address of the method-specific
// InterpreterMethodInfo into eax, and then branches to a shared part. Probably we would want to
// always push all integer args on x86, as we do already on ARM. On ARM, we'd need several versions
// of the shared stub, for different numbers of floating point register args, cross different kinds of
// HFA return values. But these could still be shared, and the per-method stub would decide which of
// these to target.
//
// In the IL stub case, which uses eax, it would be problematic to do this sharing.
StubLinkerCPU sl;
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);
if (!jmpCall)
{
sl.Init();
#if defined(_X86_) || defined(_AMD64_)
// First we do "short-circuiting" if the method has JITted code.
#if INTERP_ILSTUBS
if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
#endif
{
// First read the m_jittedCode field.
sl.X86EmitRegLoad(kEAX, UINT_PTR(interpMethInfo));
sl.X86EmitOffsetModRM(0x8b, kEAX, kEAX, offsetof(InterpreterMethodInfo, m_jittedCode));
// If it is still zero, then go on to do the interpretation.
sl.X86EmitCmpRegImm32(kEAX, 0);
CodeLabel* doInterpret = sl.NewCodeLabel();
sl.X86EmitCondJump(doInterpret, X86CondCode::kJE);
// Otherwise...
sl.X86EmitJumpReg(kEAX); // tail call to JITted code.
sl.EmitLabel(doInterpret);
}
#if defined(_X86_)
// Start regular interpretation
sl.X86EmitPushReg(kEBP);
sl.X86EmitMovRegReg(kEBP, static_cast<X86Reg>(kESP_Unsafe));
#endif
#elif defined(_ARM_)
// On ARM we use R12 as a "scratch" register -- callee-trashed, not used
// for arguments.
ThumbReg r11 = ThumbReg(11);
ThumbReg r12 = ThumbReg(12);
#if INTERP_ILSTUBS
if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
#endif
{
// But we also have to use r4, because ThumbEmitCondRegJump below requires a low register.
sl.ThumbEmitMovConstant(r12, UINT_PTR(interpMethInfo));
sl.ThumbEmitLoadRegIndirect(r12, r12, offsetof(InterpreterMethodInfo, m_jittedCode));
sl.ThumbEmitCmpImm(r12, 0); // Set condition codes.
// If r12 is zero, then go on to do the interpretation.
CodeLabel* doInterpret = sl.NewCodeLabel();
sl.ThumbEmitCondFlagJump(doInterpret, thumbCondEq.cond);
sl.ThumbEmitJumpRegister(r12); // If non-zero, tail call to JITted code.
sl.EmitLabel(doInterpret);
}
// Start regular interpretation
#elif defined(_ARM64_)
// x8 through x15 are scratch registers on ARM64.
IntReg x8 = IntReg(8);
IntReg x9 = IntReg(9);
#if INTERP_ILSTUBS
if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
#endif
{
sl.EmitMovConstant(x8, UINT64(interpMethInfo));
sl.EmitLoadStoreRegImm(StubLinkerCPU::eLOAD, x9, x8, offsetof(InterpreterMethodInfo, m_jittedCode));
sl.EmitCmpImm(x9, 0);
CodeLabel* doInterpret = sl.NewCodeLabel();
sl.EmitCondFlagJump(doInterpret, CondEq.cond);
sl.EmitJumpRegister(x9);
sl.EmitLabel(doInterpret);
}
// Start regular interpretation
#else
#error unsupported platform
#endif
}
MetaSig sig(methodDesc);
unsigned totalArgs = info->args.numArgs;
unsigned sigArgsPlusThis = totalArgs;
bool hasThis = false;
bool hasRetBuff = false;
bool isVarArg = false;
bool hasGenericsContextArg = false;
// Below, we will increment "totalArgs" for any of the "this" argument,
// a ret buff argument, and/or a generics context argument.
//
// There will be four arrays allocated below, each with this increased "totalArgs" elements:
// argOffsets, argIsReg, argPerm, and, later, m_argDescs.
//
// They will be indexed in the order (0-based, [] indicating optional)
//
// [this] sigArgs [retBuff] [VASigCookie] [genCtxt]
//
// We will call this "canonical order". It is architecture-independent, and
// does not necessarily correspond to the architecture-dependent physical order
// in which the registers are actually passed. (That's actually the purpose of
// "argPerm": to record the correspondence between canonical order and physical
// order.) We could have chosen any order for the first three of these, but it's
// simplest to let m_argDescs have all the passed IL arguments passed contiguously
// at the beginning, allowing it to be indexed by IL argument number.
int genericsContextArgIndex = 0;
int retBuffArgIndex = 0;
int vaSigCookieIndex = 0;
if (sig.HasThis())
{
assert(info->args.callConv & CORINFO_CALLCONV_HASTHIS);
hasThis = true;
totalArgs++; sigArgsPlusThis++;
}
if (methodDesc->HasRetBuffArg())
{
hasRetBuff = true;
retBuffArgIndex = totalArgs;
totalArgs++;
}
if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_VARARG)
{
isVarArg = true;
vaSigCookieIndex = totalArgs;
totalArgs++;
}
if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE)
{
assert(info->args.callConv & CORINFO_CALLCONV_PARAMTYPE);
hasGenericsContextArg = true;
genericsContextArgIndex = totalArgs;
totalArgs++;