-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Expand file tree
/
Copy pathcompiler.h
More file actions
1183 lines (984 loc) · 44.1 KB
/
Copy pathcompiler.h
File metadata and controls
1183 lines (984 loc) · 44.1 KB
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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#ifndef _COMPILER_H_
#define _COMPILER_H_
#include "intops.h"
#include "datastructs.h"
#include "enum_class_flags.h"
#include <new>
#include "failures.h"
#include "simdhash.h"
#include "intrinsics.h"
#include "interpalloc.h"
#include "interpmethoddata.h"
struct InterpException
{
InterpException(const char* message, CorJitResult result)
: m_message(message), m_result(result)
{
assert(result != CORJIT_OK);
}
const char* const m_message;
const CorJitResult m_result;
};
class InterpreterStackMap;
class InterpCompiler;
class InterpDataItemIndexMap
{
struct VarSizedData
{
VarSizedData(size_t size) : size(size)
{
}
const size_t size;
uint32_t SizeOf()
{
return (uint32_t)(size * sizeof(void*));
}
};
template<typename T>
struct VarSizedDataWithPayload : public VarSizedData
{
VarSizedDataWithPayload() : VarSizedData(sizeof(VarSizedDataWithPayload<T>)/sizeof(void*))
{
assert(SizeOf() == sizeof(VarSizedDataWithPayload<T>));
}
T payload;
};
dn_simdhash_ght_t* _hash = nullptr;
TArray<void*, MemPoolAllocator> *_dataItems = nullptr; // Actual data items stored here, indexed by the value in the hash table. This pointer is owned by the InterpCompiler class.
InterpCompiler* _compiler = nullptr;
static unsigned int HashVarSizedData(const void *voidKey)
{
VarSizedData* key = (VarSizedData*)voidKey;
return MurmurHash3_32((const uint8_t*)key, key->SizeOf(), 0);
}
static int32_t KeyEqualVarSizeData(const void * aVoid, const void * bVoid)
{
VarSizedData* keyA = (VarSizedData*)aVoid;
VarSizedData* keyB = (VarSizedData*)bVoid;
if (keyA->size != keyB->size)
return 0;
if (memcmp(aVoid, bVoid, keyA->SizeOf()) == 0)
return 1;
else
return 0;
}
dn_simdhash_ght_t* GetHash()
{
if (_hash == nullptr)
_hash = dn_simdhash_ght_new(HashVarSizedData, KeyEqualVarSizeData, 0, NULL);
if (_hash == nullptr)
NOMEM();
return _hash;
}
public:
InterpDataItemIndexMap() = default;
InterpDataItemIndexMap(const InterpDataItemIndexMap&) = delete;
InterpDataItemIndexMap& operator=(const InterpDataItemIndexMap&) = delete;
void Init(TArray<void*, MemPoolAllocator> *dataItems, InterpCompiler* compiler)
{
_compiler = compiler;
_dataItems = dataItems;
}
// Allocates a slot in the data items that is not shared with other opcodes
// Typically used for caching data at runtime.
int32_t GetNewDataItemIndex(void* data)
{
return _dataItems->Add(data);
}
int32_t GetDataItemIndex(const InterpGenericLookup& lookup)
{
const size_t sizeOfFieldsConcatenated = sizeof(InterpGenericLookup::offsets) +
sizeof(InterpGenericLookup::indirections) +
sizeof(InterpGenericLookup::sizeOffset) +
sizeof(InterpGenericLookup::lookupType) +
sizeof(InterpGenericLookup::signature);
const size_t sizeOfStruct = sizeof(InterpGenericLookup);
static_assert(sizeOfFieldsConcatenated == sizeOfStruct); // Assert that there is no padding in the struct, so a fixed size hash unaware of padding is safe to use
return GetDataItemIndexForT(lookup);
}
int32_t GetDataItemIndex(void* lookup)
{
// TODO: this is a bit more expensive than necessary size we are allocating a full varsized struct for a single pointer
// Consider optimizing this to use a seperate hashtable like a dn_simdhash_ptr_ptr_t if it becomes a bottleneck
return GetDataItemIndexForT(lookup);
}
private:
template<typename T>
int32_t GetDataItemIndexForT(const T& lookup);
};
TArray<char, MallocAllocator> PrintMethodName(COMP_HANDLE comp,
CORINFO_CLASS_HANDLE clsHnd,
CORINFO_METHOD_HANDLE methHnd,
CORINFO_SIG_INFO* sig,
bool includeAssembly,
bool includeClass,
bool includeClassInstantiation,
bool includeMethodInstantiation,
bool includeSignature,
bool includeReturnType,
bool includeThisSpecifier);
// Types that can exist on the IL execution stack. They are used only during
// IL import compilation stage.
enum StackType {
StackTypeI4 = 0,
StackTypeI8,
StackTypeR4,
StackTypeR8,
StackTypeO,
StackTypeVT,
StackTypeByRef,
StackTypeF,
StackTypeLocalVariableAddress, // LocalVariableAddress, The result of ldloca or ldarga is a byref per spec, but is also permitted to be treated as a nint in some cases. Keep track of that here.
#ifdef TARGET_64BIT
StackTypeI = StackTypeI8,
#else
StackTypeI = StackTypeI4,
#endif
};
// Types relevant for interpreter vars and opcodes. They are used in the final
// stages of the codegen and can be used during execution.
enum InterpType {
InterpTypeI1 = 0,
InterpTypeU1,
InterpTypeI2,
InterpTypeU2,
InterpTypeI4,
InterpTypeI8,
InterpTypeR4,
InterpTypeR8,
InterpTypeO,
InterpTypeVT,
InterpTypeByRef,
InterpTypeVoid,
#ifdef TARGET_64BIT
InterpTypeI = InterpTypeI8
#else
InterpTypeI = InterpTypeI4
#endif
};
#ifdef DEBUG
extern thread_local bool t_interpDump;
class InterpDumpScope
{
bool m_prev;
public:
InterpDumpScope(bool enable)
{
m_prev = t_interpDump;
t_interpDump = enable;
}
~InterpDumpScope()
{
t_interpDump = m_prev;
}
};
#define INTERP_DUMP(...) \
{ \
if (t_interpDump) \
printf(__VA_ARGS__); \
}
#else // !DEBUG
#define INTERP_DUMP(...)
#endif // DEBUG
#ifdef DEBUG
static const char* const PointerIsClassHandle = (const char*)0x1;
static const char* const PointerIsMethodHandle = (const char*)0x2;
static const char* const PointerIsStringLiteral = (const char*)0x3;
#endif // DEBUG
struct InterpInst;
struct InterpBasicBlock;
struct InterpCallInfo
{
// For call instructions, this represents an array of all call arg vars
// in the order they are pushed to the stack. This makes it easy to find
// all source vars for these types of opcodes. This is terminated with -1.
int32_t *pCallArgs = nullptr;
int32_t callOffset = 0;
union {
// Array of call dependencies that need to be resolved before
TSList<InterpInst*> *callDeps = nullptr;
// Stack end offset of call arguments
int32_t callEndOffset;
};
};
enum InterpInstFlags
{
INTERP_INST_FLAG_CALL = 0x01,
// Flag used internally by the var offset allocator
INTERP_INST_FLAG_ACTIVE_CALL = 0x02,
// The IL stack is empty at this instruction
INTERP_INST_FLAG_EMPTY_IL_STACK = 0x04,
// Marks a value-returning managed call site whose return value is reportable to the debugger
INTERP_INST_FLAG_DBG_CALL_INSTRUCTION = 0x08
};
struct InterpCallReturnInfo
{
uint32_t ilOffset;
uint32_t postCallNativeOffset;
int32_t returnValueVarOffset;
};
struct InterpInst
{
InterpInst *pNext, *pPrev;
union
{
InterpBasicBlock *pTargetBB; // target basic block for branch instructions
InterpBasicBlock **ppTargetBBTable; // basic block table for switch instruction
InterpCallInfo *pCallInfo; // additional information for call instructions
} info;
int32_t opcode;
int32_t ilOffset;
int32_t nativeOffset;
uint32_t flags;
int32_t dVar;
int32_t sVars[3]; // Currently all instructions have at most 3 sregs
int32_t data[];
void SetDVar(int32_t dv)
{
dVar = dv;
}
void SetSVar(int32_t sv1)
{
sVars[0] = sv1;
}
void SetSVars2(int32_t sv1, int32_t sv2)
{
sVars[0] = sv1;
sVars[1] = sv2;
}
void SetSVars3(int32_t sv1, int32_t sv2, int32_t sv3)
{
sVars[0] = sv1;
sVars[1] = sv2;
sVars[2] = sv3;
}
};
#define CALL_ARGS_SVAR -2
#define CALL_ARGS_TERMINATOR -1
struct StackInfo;
enum InterpBBState
{
BBStateNotEmitted,
BBStateEmitting,
BBStateEmitted
};
enum InterpBBClauseType
{
BBClauseNone,
BBClauseCatch,
BBClauseFinally,
BBClauseFilter,
};
struct InterpBasicBlock
{
int32_t index;
int32_t ilOffset, nativeOffset;
int32_t nativeEndOffset;
int32_t stackHeight;
StackInfo *pStackState;
InterpInst *pFirstIns, *pLastIns;
InterpBasicBlock *pNextBB;
// * If this basic block is a finally, this points to a finally call island that is located where the finally
// was before all funclets were moved to the end of the method.
// * If this basic block is a catch, this points to a catch leave island that is located where the catch
// was before all funclets were moved to the end of the method.
// * If this basic block is a call island, this points to the next finally call / catch leave island basic block.
// * Otherwise, this is NULL.
InterpBasicBlock *pLeaveChainIslandBB;
// Target of a leave instruction that is located in this basic block. NULL if there is none.
InterpBasicBlock *pLeaveTargetBB;
int inCount, outCount;
InterpBasicBlock **ppInBBs;
InterpBasicBlock **ppOutBBs;
InterpBBState emitState;
// Type of the innermost try block, catch, filter, or finally that contains this basic block.
uint8_t clauseType;
// True indicates that this basic block is the first block of a filter, catch or filtered handler funclet.
bool isFilterOrCatchFuncletEntry;
// Valid only for BBs of call islands. It is set to true if it is a finally call island, false if is is a catch leave island.
bool isFinallyCallIsland;
// Is a leave chain island basic block
bool isLeaveChainIsland;
// If this basic block is a catch or filter funclet entry, this is the index of the variable
// that holds the exception object.
int clauseVarIndex;
// Number of catch, filter or finally clauses that overlap with this basic block.
int32_t overlappingEHClauseCount;
// Number of try blocks that enclose this basic block.
int32_t enclosingTryBlockCount;
InterpBasicBlock(int32_t index, int32_t ilOffset)
{
this->index = index;
this->ilOffset = ilOffset;
nativeOffset = -1;
nativeEndOffset = -1;
stackHeight = -1;
pFirstIns = pLastIns = NULL;
pNextBB = NULL;
pLeaveChainIslandBB = NULL;
pLeaveTargetBB = NULL;
inCount = 0;
outCount = 0;
emitState = BBStateNotEmitted;
clauseType = BBClauseNone;
isFilterOrCatchFuncletEntry = false;
isFinallyCallIsland = false;
isLeaveChainIsland = false;
clauseVarIndex = -1;
overlappingEHClauseCount = 0;
enclosingTryBlockCount = -1;
}
};
#define UNALLOCATED_VAR_OFFSET -1
struct InterpVar
{
CORINFO_CLASS_HANDLE clsHnd = nullptr;
InterpType interpType = (InterpType)0;
int offset = 0;
int size = 0;
// live_start and live_end are used by the offset allocator
InterpInst* liveStart = nullptr;
InterpInst* liveEnd = nullptr;
// index of first basic block where this var is used
int bbIndex = -1;
// If var is callArgs, this is the call instruction using it.
// Only used by the var offset allocator
InterpInst *call = nullptr;
unsigned int callArgs : 1; // Var used as argument to a call
unsigned int noCallArgs : 1; // Var can't be used as argument to a call, needs to be copied to temp
unsigned int global : 1; // Dedicated stack offset throughout method execution
unsigned int ILGlobal : 1; // Args and IL locals
unsigned int alive : 1; // Used internally by the var offset allocator
unsigned int pinned : 1; // Indicates that the var had the 'pinned' modifier in IL
InterpVar(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int size)
{
this->interpType = interpType;
this->clsHnd = clsHnd;
this->size = size;
offset = UNALLOCATED_VAR_OFFSET;
liveStart = NULL;
bbIndex = -1;
callArgs = false;
noCallArgs = false;
global = false;
ILGlobal = false;
alive = false;
pinned = false;
}
};
struct StackInfo
{
private:
StackType type = (StackType)0;
public:
// The var associated with the value of this stack entry. Every time we push on
// the stack a new var is created.
int32_t var = 0;
CORINFO_CLASS_HANDLE clsHnd = nullptr;
StackType GetStackType()
{
if (type == StackTypeLocalVariableAddress)
{
// Transient pointers are treated as byrefs for stack type purposes
return StackTypeByRef;
}
return type;
}
void SetAsLocalVariableAddress()
{
assert(type == StackTypeByRef);
type = StackTypeLocalVariableAddress;
}
bool IsLocalVariableAddress()
{
return type == StackTypeLocalVariableAddress;
}
// Used before a use of a value where the value on the stack would be correctly handled if the type on the stack
// was of type I. This is done to allow the use of the address of a local variable as a pointer which is common
// in older IL testing.
void BashStackTypeToI_ForLocalVariableAddress()
{
if (type == StackTypeLocalVariableAddress)
{
type = StackTypeI;
}
}
// Used before a conversion operation to ensure that transient pointers, byrefs, and object references are
// treated as integers for the purpose of the conversion. The Byref/O behavior here does not seem to have
// justification in the ECMA-335 spec, but it is needed to match the behavior of the JIT.
void BashStackTypeToI_ForConvert()
{
if ((type == StackTypeLocalVariableAddress) || (type == StackTypeByRef) || (type == StackTypeO))
{
type = StackTypeI;
}
}
StackInfo() = default;
StackInfo(StackType type, CORINFO_CLASS_HANDLE clsHnd, int var)
{
this->type = type;
this->clsHnd = clsHnd;
this->var = var;
}
};
enum RelocType
{
RelocLongBranch,
RelocSwitch
};
struct Reloc
{
RelocType type;
// For branch relocation, how many sVar slots to skip
int skip;
// Base offset that the relative offset to be embedded in IR applies to
int32_t offset;
InterpBasicBlock *pTargetBB;
Reloc(RelocType type, int32_t offset, InterpBasicBlock *pTargetBB, int skip)
{
this->type = type;
this->offset = offset;
this->pTargetBB = pTargetBB;
this->skip = skip;
}
};
class InterpIAllocator;
// Entry of the table where for each leave instruction we store the first finally call island
// to be executed when the leave instruction is executed.
struct LeavesTableEntry
{
// offset of the CEE_LEAVE instruction
int32_t ilOffset;
// The BB of the call island BB that will be the first to call when the leave
// instruction is executed.
InterpBasicBlock *pLeaveChainIslandBB;
};
struct OpcodePeepElement
{
uint16_t offsetIntoPeep;
OPCODE opcode; // If CEE_ILLEGAL this is the end marker, and the total size of the pattern is offsetIntoPeep
};
typedef bool (InterpCompiler::*CheckIfTokensAllowPeepToBeUsedFunc_t)(const uint8_t* ip, OpcodePeepElement*, void** outComputedInfo);
typedef int (InterpCompiler::*ApplyPeepFunc_t)(const uint8_t* ip, OpcodePeepElement*, void* computedInfo);
struct OpcodePeep
{
OpcodePeepElement* const pattern;
const CheckIfTokensAllowPeepToBeUsedFunc_t CheckIfTokensAllowPeepToBeUsedFunc;
const ApplyPeepFunc_t ApplyPeepFunc;
const char * const Name;
size_t GetPeepSize() const
{
OpcodePeepElement* patternIterator = this->pattern;
while (patternIterator->opcode != CEE_ILLEGAL)
patternIterator++;
return patternIterator->offsetIntoPeep;
}
};
class InterpreterRetryData
{
bool m_needsRetry = false;
int32_t m_tryCount = 0;
const char *m_reasonString = "";
InterpArenaAllocator *m_arenaAllocator;
dn_simdhash_u32_ptr_holder m_ilMergePointStackTypes;
// Returns an allocator for the specified memory kind. Use this for categorized
// allocations to enable memory profiling when MEASURE_MEM_ALLOC is defined.
InterpAllocator getAllocator(InterpMemKind imk)
{
return InterpAllocator(m_arenaAllocator, imk);
}
public:
InterpreterRetryData(InterpArenaAllocator* arenaAllocator)
: m_arenaAllocator(arenaAllocator),
m_ilMergePointStackTypes(getAllocator(IMK_RetryData))
{
}
bool NeedsRetry() const
{
return m_needsRetry;
}
const char *GetReasonString() const
{
return m_reasonString;
}
void SetNeedsRetry(const char *reasonString)
{
assert(reasonString != nullptr);
m_reasonString = reasonString;
m_needsRetry = true;
}
void StartCompilationAttempt()
{
m_reasonString = "";
m_needsRetry = false;
m_tryCount++;
if (m_tryCount > 1000)
{
BADCODE("Exceeded maximum number of compilation attempts");
}
}
void SetOverrideILMergePointStack(int32_t ilOffset, uint32_t stackHeight, StackInfo *pStackInfo);
bool GetOverrideILMergePointStackType(int32_t ilOffset, uint32_t* stackHeight, StackInfo** stack);
};
class InterpCompiler
{
friend class InterpIAllocator;
friend class InterpGcSlotAllocator;
friend class InterpILOpcodePeeps;
friend class InterpAsyncCallPeeps;
private:
// Arena allocator for compilation-phase memory.
// All memory allocated via AllocMemPool is freed when the compiler is destroyed.
InterpArenaAllocator *m_arenaAllocator;
// Builder for the unified method data allocation
InterpMethodDataBuilder m_methodDataBuilder;
CORINFO_METHOD_HANDLE m_methodHnd;
CORINFO_MODULE_HANDLE m_compScopeHnd;
COMP_HANDLE m_compHnd;
CORINFO_METHOD_INFO* m_methodInfo;
CORJIT_FLAGS m_corJitFlags;
void DeclarePointerIsClass(CORINFO_CLASS_HANDLE clsHnd)
{
#ifdef DEBUG
void *ptr = (void*)clsHnd;
if (!PointerInNameMap(ptr))
{
AddPointerToNameMap(ptr, PointerIsClassHandle);
}
#endif // DEBUG
}
void DeclarePointerIsMethod(CORINFO_METHOD_HANDLE methodHnd)
{
#ifdef DEBUG
void *ptr = (void*)methodHnd;
if (!PointerInNameMap(ptr))
{
AddPointerToNameMap(ptr, PointerIsMethodHandle);
}
#endif // DEBUG
}
void DeclarePointerIsString(void* stringLiteral)
{
#ifdef DEBUG
void *ptr = (void*)stringLiteral;
if (!PointerInNameMap(ptr))
{
AddPointerToNameMap(ptr, PointerIsStringLiteral);
}
#endif // DEBUG
}
CORINFO_CLASS_HANDLE m_classHnd;
dn_simdhash_ptr_ptr_holder m_stackmapsByClass;
InterpreterStackMap* GetInterpreterStackMap(CORINFO_CLASS_HANDLE classHandle);
static int32_t InterpGetMovForType(InterpType interpType, bool signExtend);
InterpreterRetryData *m_pRetryData;
const uint8_t* m_ip;
CORINFO_RESOLVED_TOKEN* m_pConstrainedToken = NULL;
uint8_t* m_pILCode;
int32_t m_ILCodeSizeFromILHeader;
int32_t m_ILCodeSize; // This can differ from the size of the header if we add instructions for synchronized methods
int32_t m_currentILOffset;
InterpInst* m_pInitLocalsIns;
// Indicates that we are going to generate the first interpreter byte code instruction for an IL opcode with an empty stack.
bool m_isFirstInstForEmptyILStack = true;
// If the method has a hidden argument, GenerateCode allocates a var to store it and
// populates the var at method entry
int32_t m_hiddenArgumentVar;
// If RuntimeHelpers.SetNextCallGenericContext or SetNextCallAsyncContinuation were used
// then these contain the value that should be passed as those arguments.
int32_t m_nextCallGenericContextVar;
int32_t m_nextCallAsyncContinuationVar;
// If true, the next await should be done as a tail await that just
// directly returns the continuation of the call instead of creating a new
// suspension point.
bool m_nextAwaitIsTail = false;
// Table of mappings of leave instructions to the first finally call island the leave
// needs to execute.
TArray<LeavesTableEntry, MemPoolAllocator> m_leavesTable;
// This represents a mapping from indexes to pointer sized data. During compilation, an
// instruction can request an index for some data (like a MethodDesc pointer), that it
// will then embed in the instruction stream. The data item table will be referenced
// from the interpreter code header during execution.
TArray<void*, MemPoolAllocator> m_dataItems;
TArray<InterpAsyncSuspendData*, MemPoolAllocator> m_asyncSuspendDataItems;
TArray<int32_t, MemPoolAllocator> m_suspensionPointIPOffsets;
TArray<ICorDebugInfo::AsyncSuspensionPoint, MemPoolAllocator> m_asyncDebugSuspensionPoints;
TArray<ICorDebugInfo::AsyncContinuationVarInfo, MemPoolAllocator> m_asyncDebugContinuationVars;
// Tracks which data items contain pointers to async suspend data
// First = data item index, Second = index into m_asyncSuspendDataItems
struct DataItemAsyncSuspendRef
{
int32_t dataItemIndex;
int32_t asyncSuspendDataIndex;
};
TArray<DataItemAsyncSuspendRef, MemPoolAllocator> m_dataItemAsyncSuspendRefs;
// Prepared InterpMethod data (stored temporarily until finalization)
bool m_initLocals;
bool m_unmanagedCallersOnly;
bool m_publishSecretStubParam;
InterpDataItemIndexMap m_genericLookupToDataItemIndex;
int32_t GetDataItemIndex(void* data)
{
return m_genericLookupToDataItemIndex.GetDataItemIndex(data);
}
int32_t GetDataItemIndex(const InterpGenericLookup& data)
{
return m_genericLookupToDataItemIndex.GetDataItemIndex(data);
}
int32_t GetNewDataItemIndex(void* data)
{
return m_genericLookupToDataItemIndex.GetNewDataItemIndex(data);
}
void* GetDataItemAtIndex(int32_t index);
void* GetAddrOfDataItemAtIndex(int32_t index);
int32_t GetMethodDataItemIndex(CORINFO_METHOD_HANDLE mHandle);
int32_t GetDataForHelperFtn(CorInfoHelpFunc ftn);
void GenerateCode(CORINFO_METHOD_INFO* methodInfo);
InterpBasicBlock* GenerateCodeForLeaveChainIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB);
void PatchInitLocals(CORINFO_METHOD_INFO* methodInfo);
void ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken);
CORINFO_METHOD_HANDLE ResolveMethodToken(uint32_t token);
CORINFO_CLASS_HANDLE ResolveClassToken(uint32_t token);
CORINFO_CLASS_HANDLE getClassFromContext(CORINFO_CONTEXT_HANDLE context);
int32_t getParamArgIndex(); // Get the index into the m_pVars array of the Parameter argument. This is either the this pointer, a methoddesc or a class handle
struct InterpEmbedGenericResult
{
// If var is != -1, then the var holds the result of the lookup
int var = -1;
// If var == -1, then the data item holds the result of the lookup
int dataItemIndex = -1;
};
enum class GenericHandleEmbedOptions
{
support_use_as_flags = -1, // Magic value which in combination with enum_class_flags.h allows the use of bitwise operations and the HasFlag helper method
None = 0,
VarOnly = 1,
EmbedParent = 2,
};
enum class HelperArgType
{
GenericResolution,
Value
};
struct TokenArg
{
CORINFO_RESOLVED_TOKEN* token;
InterpCompiler::GenericHandleEmbedOptions options;
};
struct GenericHandleData
{
GenericHandleData(int genericVar, int dataItemIndex)
: argType(HelperArgType::GenericResolution), genericVar(genericVar), dataItemIndex(dataItemIndex) {}
GenericHandleData(int dataItemIndex)
: argType(HelperArgType::Value), genericVar(-1), dataItemIndex(dataItemIndex) {}
GenericHandleData() = default;
HelperArgType argType = HelperArgType::Value;
int genericVar = -1; // This will be set to the var of the generic context argument if argType == HelperArgType::GenericResolution
int dataItemIndex = 0;
};
GenericHandleData GenericHandleToGenericHandleData(const CORINFO_GENERICHANDLE_RESULT& embedInfo);
InterpEmbedGenericResult EmitGenericHandle(CORINFO_RESOLVED_TOKEN* resolvedToken, GenericHandleEmbedOptions options);
// Do a generic handle lookup and acquire the result as either a var or a data item.
int32_t EmitGenericHandleAsVar(const CORINFO_GENERICHANDLE_RESULT &embedInfo);
// Emit a generic dictionary lookup and push the result onto the interpreter stack
void CopyToInterpGenericLookup(InterpGenericLookup* dst, const CORINFO_RUNTIME_LOOKUP *src);
void EmitPushCORINFO_LOOKUP(const CORINFO_LOOKUP& lookup);
void EmitPushLdvirtftn(int thisVar, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo);
void EmitPushHelperCall_2(const CorInfoHelpFunc ftn, const CORINFO_GENERICHANDLE_RESULT& arg1, int arg2, StackType resultStackType, CORINFO_CLASS_HANDLE clsHndStack);
void EmitPushHelperCall_Addr2(const CorInfoHelpFunc ftn, const CORINFO_GENERICHANDLE_RESULT& arg1, int arg2, StackType resultStackType, CORINFO_CLASS_HANDLE clsHndStack);
void EmitPushHelperCall(const CorInfoHelpFunc ftn, const CORINFO_GENERICHANDLE_RESULT& arg1, StackType resultStackType, CORINFO_CLASS_HANDLE clsHndStack);
void EmitPushUnboxAny(const CORINFO_GENERICHANDLE_RESULT& arg1, int arg2, StackType resultStackType, CORINFO_CLASS_HANDLE clsHndStack);
void EmitPushUnboxAnyNullable(const CORINFO_GENERICHANDLE_RESULT& arg1, int arg2, StackType resultStackType, CORINFO_CLASS_HANDLE clsHndStack);
void* AllocMethodData(size_t numBytes);
public:
// Returns an allocator for the specified memory kind. Use this for categorized
// allocations to enable memory profiling when MEASURE_MEM_ALLOC is defined.
InterpAllocator getAllocator(InterpMemKind imk)
{
return InterpAllocator(m_arenaAllocator, imk);
}
// Convenience methods for common allocation kinds
InterpAllocator getAllocatorGC() { return getAllocator(IMK_GC); }
InterpAllocator getAllocatorBasicBlock() { return getAllocator(IMK_BasicBlock); }
InterpAllocator getAllocatorInstruction() { return getAllocator(IMK_Instruction); }
// Legacy allocation methods - use getAllocator() for new code
MemPoolAllocator GetMemPoolAllocator(InterpMemKind imk) { return MemPoolAllocator(getAllocator(imk)); }
private:
// Instructions
InterpBasicBlock *m_pCBB, *m_pEntryBB;
InterpInst* m_pLastNewIns = nullptr;
int32_t GetInsLength(InterpInst *pIns);
bool InsIsNop(InterpInst *pIns);
InterpInst* AddIns(int opcode);
InterpInst* NewIns(int opcode, int len);
InterpInst* AddInsExplicit(int opcode, int dataLen);
InterpInst* InsertInsBB(InterpBasicBlock *pBB, InterpInst *pPrevIns, int opcode);
InterpInst* InsertIns(InterpInst *pPrevIns, int opcode);
InterpInst* FirstRealIns(InterpBasicBlock *pBB);
InterpInst* NextRealIns(InterpInst *pIns);
InterpInst* PrevRealIns(InterpInst *pIns);
void ClearIns(InterpInst *pIns);
void ForEachInsSVar(InterpInst *ins, void *pData, void (InterpCompiler::*callback)(int32_t*, void*));
void ForEachInsVar(InterpInst *ins, void *pData, void (InterpCompiler::*callback)(int32_t*, void*));
// Basic blocks
int m_BBCount = 0;
InterpBasicBlock** m_ppOffsetToBB;
ICorDebugInfo::OffsetMapping* m_pILToNativeMap = NULL;
#ifdef DEBUG
int32_t* m_pNativeMapIndexToILOffset = NULL;
#endif
int32_t m_ILToNativeMapSize = 0;
TArray<InterpCallReturnInfo, MemPoolAllocator> m_callReturnInfos;
InterpBasicBlock* AllocBB(int32_t ilOffset);
InterpBasicBlock* GetBB(int32_t ilOffset);
void LinkBBs(InterpBasicBlock *from, InterpBasicBlock *to);
void UnlinkBBs(InterpBasicBlock *from, InterpBasicBlock *to);
void EmitBranch(InterpOpcode opcode, int ilOffset);
void EmitOneArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
void EmitTwoArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
void EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB);
void EmitBBEndVarMoves(InterpBasicBlock *pTargetBB);
void InitBBStackState(InterpBasicBlock *pBB);
void UnlinkUnreachableBBlocks();
// Vars
InterpVar *m_pVars = NULL;
int32_t m_varsSize = 0;
int32_t m_varsCapacity = 0;
int32_t m_numILVars = 0;
int32_t m_paramArgIndex = -1; // Index of the type parameter argument in the m_pVars array.
// For each catch or filter clause, we create a variable that holds the exception object.
// This is the index of the first such variable.
int32_t m_continuationArgIndex = -1; // Index of the continuation argument in the m_pVars array for async methods.
int32_t m_clauseVarsIndex = 0;
int32_t m_synchronizedOrAsyncPostFinallyOffset = -1; // If the method is synchronized/async, this is the offset of the instruction after the finally which does the actual return
bool m_isSynchronized = false;
int32_t m_synchronizedFlagVarIndex = -1; // If the method is synchronized, this is the index of the argument that flag indicating if the lock was taken
int32_t m_synchronizedOrAsyncRetValVarIndex = -1; // If the method is synchronized, ret instructions are replaced with a store to this var and a leave to an epilog instruction.
int32_t m_synchronizedFinallyStartOffset = -1; // If the method is synchronized, this is the offset of the start of the finally epilog
int32_t m_threadObjVarIndex = -1; // If the method is async, this is the var index of the Thread local
int32_t m_execContextVarIndex = -1; // If the method is async, this is the var index of the ExecutionContext local
int32_t m_syncContextVarIndex = -1; // If the method is async, this is the var index of the SynchronizationContext local
void *m_asyncResumeFuncPtr = NULL;
bool m_isAsyncMethodWithContextSaveRestore = false;
int32_t m_asyncFinallyStartOffset = -1; // If the method is async, this is the offset of the start of the fault handler
bool m_shadowCopyOfThisPointerActuallyNeeded = false;
bool m_shadowCopyOfThisPointerHasVar = false;
int32_t m_shadowThisVar = -1; // If the method is an instance method and we need a shadow copy of the this pointer, this is the var index of the shadow copy
int32_t CreateVarExplicit(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int size);
int32_t m_totalVarsStackSize;
int32_t m_globalVarsWithRefsStackTop;
int32_t m_paramAreaOffset = 0;
int32_t m_ILLocalsOffset;
int32_t m_ILLocalsSize;
void AllocVarOffsetCB(int32_t *pVar, void *pData);
int32_t AllocVarOffset(int32_t var, int32_t *pPos);
int32_t GetLiveStartOffset(int32_t var);
int32_t GetLiveEndOffset(int32_t var);
int32_t GetInterpTypeStackSize(CORINFO_CLASS_HANDLE clsHnd, InterpType interpType, int32_t *pAlign);
void CreateILVars();
void CreateNextLocalVar(int iArgToSet, CORINFO_CLASS_HANDLE argClass, InterpType interpType, int32_t *pOffset, bool pinned = false);
// Stack
StackInfo *m_pStackPointer, *m_pStackBase;
int32_t m_stackCapacity;
void CheckStackHelper(int n);
void CheckStackExact(int n);
void EnsureStack(int additional);
void PushTypeExplicit(StackType stackType, CORINFO_CLASS_HANDLE clsHnd, int size);
void PushStackType(StackType stackType, CORINFO_CLASS_HANDLE clsHnd);
void PushInterpType(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd);
void PushTypeVT(CORINFO_CLASS_HANDLE clsHnd, int size);
void ConvertFloatingPointStackEntryToStackType(StackInfo* entry, StackType type);
bool DisallowTailCall(CORINFO_SIG_INFO* callerSig, CORINFO_SIG_INFO* calleeSig);
// Opcode peeps
bool FindAndApplyPeep(OpcodePeep* Peeps[]);
bool IsConvRUnR4Peep(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo) { return true; }
int ApplyConvRUnR4Peep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsStoreLoadPeep(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo);
int ApplyStoreLoadPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsTypeEqualityCheckPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyTypeEqualityCheckPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxUnboxPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxUnboxPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxBrTrueFalsePeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxBrTrueFalsePeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxIsInstPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxIsInstPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxIsInstBrTrueFalsePeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxIsInstBrTrueFalsePeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxIsInstLdNullCgtUnPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxIsInstLdNullCgtUnPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsBoxIsInstUnboxAnyPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyBoxIsInstUnboxAnyPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsTypeValueTypePeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyTypeValueTypePeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool IsLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);
bool ResolveAsyncCallToken(const uint8_t* ip);
enum class ContinuationContextHandling : uint8_t
{
ContinueOnCapturedContext,
ContinueOnThreadPool,
None
};
bool IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo);
bool IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo);
bool IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo);
bool IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo);
int ApplyRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo) { return -1; }
ContinuationContextHandling m_currentContinuationContextHandling = ContinuationContextHandling::None;
CORINFO_RESOLVED_TOKEN m_resolvedAsyncCallToken;