You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Change temporary entrypoints to be lazily allocated (#101580)
* WorkingOnIt
* It basically works for a single example.
Baseline
Loader Heap:
----------------------------------------
System Domain: 7ffab916ec00
LoaderAllocator: 7ffab916ec00
LowFrequencyHeap: Size: 0xf0000 (983040) bytes total.
HighFrequencyHeap: Size: 0x16a000 (1482752) bytes total, 0x3000 (12288) bytes wasted.
StubHeap: Size: 0x1000 (4096) bytes total.
FixupPrecodeHeap: Size: 0x168000 (1474560) bytes total.
NewStubPrecodeHeap: Size: 0x18000 (98304) bytes total.
IndirectionCellHeap: Size: 0x1000 (4096) bytes total.
CacheEntryHeap: Size: 0x1000 (4096) bytes total.
Total size: Size: 0x3dd000 (4050944) bytes total, 0x3000 (12288) bytes wasted.
Compare
Loader Heap:
----------------------------------------
System Domain: 7ff9eb49dc00
LoaderAllocator: 7ff9eb49dc00
LowFrequencyHeap: Size: 0xef000 (978944) bytes total.
HighFrequencyHeap: Size: 0x1b2000 (1777664) bytes total, 0x3000 (12288) bytes wasted.
StubHeap: Size: 0x1000 (4096) bytes total.
FixupPrecodeHeap: Size: 0x70000 (458752) bytes total.
NewStubPrecodeHeap: Size: 0x10000 (65536) bytes total.
IndirectionCellHeap: Size: 0x1000 (4096) bytes total.
CacheEntryHeap: Size: 0x1000 (4096) bytes total.
Total size: Size: 0x324000 (3293184) bytes total, 0x3000 (12288) bytes wasted.
LowFrequencyHeap is 4KB bigger
HighFrequencyHeap is 288KB bigger
FixupPrecodeHeap is 992KB smaller
NewstubPrecodeHeap is 32KB smaller
* If there isn't a parent methodtable and the slot matches... then it by definition the method is defining the slot
* Fix a couple more issues found when running a subset of the coreclr tests
* Get X86 building again
* Attempt to use a consistent api to force slots to be set
* Put cache around RequiresStableEntryPoint
* Fix typo
* Fix interop identified issue where we sometime set a non Precode into an interface
* Move ARM and X86 to disable compact entry points
* Attempt to fix build breaks
* fix typo
* Fix another Musl validation issue
* More tweaks around NULL handling
* Hopefully the last NULL issue
* Fix more NULL issues
* Fixup obvious issues
* Fix allocation behavior so we don't free the data too early or too late
* Fix musl validation issue
* Fix tiered compilation
* Remove Compact Entrypoint logic
* Add new ISOSDacInterface15 api
* Fix some naming of NoAlloc to a more clear IfExists suffix
* Remove way in which GetTemporaryEntryPoint behaves differently for DAC builds, and then remove GetTemporaryEntrypoint usage from DAC entirely in favor of GetTemporaryEntryPointIfExists
* Attempt to reduce most of the use of EnsureSlotFilled. Untested, but its late.
* Fix the build before sending to github
* Fix unix build break, and invalid assert
* Improve assertion checks to validate that we don't allocate temporary entrypoints that will be orphaned if the type doesn't actually end up published.
* Remove unused parameters and add contracts
* Update method-descriptor.md
* Fix musl validation issue
* Adjust SOS api to be an enumerator
* Fix assertion issues noted
Fix ISOSDacInterface15 to actually work
* Remove GetRestoredSlotIfExists
- Its the same as GetSlot .... just replace it with that function.
* Update src/coreclr/debug/daccess/daccess.cpp
Co-authored-by: Jan Kotas <[email protected]>
* Update docs/design/coreclr/botr/method-descriptor.md
Co-authored-by: Jan Kotas <[email protected]>
* Update src/coreclr/vm/methodtable.inl
Co-authored-by: Jan Kotas <[email protected]>
* Update src/coreclr/vm/methodtable.h
Co-authored-by: Jan Kotas <[email protected]>
* Fix GetMethodDescForSlot_NoThrow
Try removing EnsureSlotFilled
Implement IsEligibleForTieredCompilation in terms of IsEligibleForTieredCompilation_NoCheckMethodDescChunk
* Fix missing change intended in last commit
* Fix some more IsPublished memory use issues
* Call the right GetSlot method
* Move another scenario to NoThrow, I think this should clear up our tests...
* Add additional IsPublished check
* Fix MUSL validation build error and Windows x86 build error
* Address code review feedback
* Fix classcompat build
* Update src/coreclr/vm/method.cpp
Co-authored-by: Aaron Robinson <[email protected]>
* Remove assert that is invalid because TryGetMulticCallableAddrOfCode can return NULL ... and then another thread could produce a stable entrypoint and the assert could lose the race
* Final (hopefully) code review tweaks.
* Its possible for GetOrCreatePrecode to be called for cases where it isn't REQUIRED. we need to handle that case.
---------
Co-authored-by: Jan Kotas <[email protected]>
Co-authored-by: Aaron Robinson <[email protected]>
Copy file name to clipboardexpand all lines: docs/design/coreclr/botr/method-descriptor.md
+6-66
Original file line number
Diff line number
Diff line change
@@ -85,7 +85,9 @@ DWORD MethodDesc::GetAttrs()
85
85
Method Slots
86
86
------------
87
87
88
-
Each MethodDesc has a slot, which contains the entry point of the method. The slot and entry point must exist for all methods, even the ones that never run like abstract methods. There are multiple places in the runtime that depend on the 1:1 mapping between entry points and MethodDescs, making this relationship an invariant.
88
+
Each MethodDesc has a slot, which contains the current entry point of the method. The slot must exist for all methods, even the ones that never run like abstract methods. There are multiple places in the runtime that depend on mapping between entry points and MethodDescs.
89
+
90
+
Each MethodDesc logically has an entry point, but we do not allocate these eagerly at MethodDesc creation time. The invariant is that once the method is identified as a method to run, or is used in virtual overriding, we will allocate the entrypoint.
89
91
90
92
The slot is either in MethodTable or in MethodDesc itself. The location of the slot is determined by `mdcHasNonVtableSlot` bit on MethodDesc.
91
93
@@ -185,8 +187,6 @@ The target of the temporary entry point is a PreStub, which is a special kind of
185
187
186
188
The **stable entry point** is either the native code or the precode. The **native code** is either jitted code or code saved in NGen image. It is common to talk about jitted code when we actually mean native code.
187
189
188
-
Temporary entry points are never saved into NGen images. All entry points in NGen images are stable entry points that are never changed. It is an important optimization that reduced private working set.
189
-
190
190

191
191
192
192
Figure 2 Entry Point State Diagram
@@ -208,6 +208,7 @@ The methods to get callable entry points from MethodDesc are:
@@ -220,7 +221,7 @@ The type of precode has to be cheaply computable from the instruction sequence.
220
221
221
222
**StubPrecode**
222
223
223
-
StubPrecode is the basic precode type. It loads MethodDesc into a scratch register and then jumps. It must be implemented for precodes to work. It is used as fallback when no other specialized precode type is available.
224
+
StubPrecode is the basic precode type. It loads MethodDesc into a scratch register<sup>2</sup> and then jumps. It must be implemented for precodes to work. It is used as fallback when no other specialized precode type is available.
224
225
225
226
All other precodes types are optional optimizations that the platform specific files turn on via HAS\_XXX\_PRECODE defines.
226
227
@@ -236,7 +237,7 @@ StubPrecode looks like this on x86:
236
237
237
238
FixupPrecode is used when the final target does not require MethodDesc in scratch register<sup>2</sup>. The FixupPrecode saves a few cycles by avoiding loading MethodDesc into the scratch register.
238
239
239
-
The most common usage of FixupPrecode is for method fixups in NGen images.
240
+
Most stubs used are the more efficient form, we currently can use this form for everything but interop methods when a specialized form of Precode is not required.
240
241
241
242
The initial state of the FixupPrecode on x86:
242
243
@@ -254,67 +255,6 @@ Once it has been patched to point to final target:
254
255
255
256
<sup>2</sup> Passing MethodDesc in scratch register is sometimes referred to as **MethodDesc Calling Convention**.
256
257
257
-
**FixupPrecode chunks**
258
-
259
-
FixupPrecode chunk is a space efficient representation of multiple FixupPrecodes. It mirrors the idea of MethodDescChunk by hoisting the similar MethodDesc pointers from multiple FixupPrecodes to a shared area.
260
-
261
-
The FixupPrecode chunk saves space and improves code density of the precodes. The code density improvement from FixupPrecode chunks resulted in 1% - 2% gain in big server scenarios on x64.
262
-
263
-
The FixupPrecode chunks looks like this on x86:
264
-
265
-
jmp Target2
266
-
pop edi // dummy instruction that marks the type of the precode
267
-
db MethodDescChunkIndex
268
-
db 2 (PrecodeChunkIndex)
269
-
270
-
jmp Target1
271
-
pop edi
272
-
db MethodDescChunkIndex
273
-
db 1 (PrecodeChunkIndex)
274
-
275
-
jmp Target0
276
-
pop edi
277
-
db MethodDescChunkIndex
278
-
db 0 (PrecodeChunkIndex)
279
-
280
-
dw pMethodDescBase
281
-
282
-
One FixupPrecode chunk corresponds to one MethodDescChunk. There is no 1:1 mapping between the FixupPrecodes in the chunk and MethodDescs in MethodDescChunk though. Each FixupPrecode has index of the method it belongs to. It allows allocating the FixupPrecode in the chunk only for methods that need it.
283
-
284
-
**Compact entry points**
285
-
286
-
Compact entry point is a space efficient implementation of temporary entry points.
287
-
288
-
Temporary entry points implemented using StubPrecode or FixupPrecode can be patched to point to the actual code. Jitted code can call temporary entry point directly. The temporary entry point can be multicallable entry points in this case.
289
-
290
-
Compact entry points cannot be patched to point to the actual code. Jitted code cannot call them directly. They are trading off speed for size. Calls to these entry points are indirected via slots in a table (FuncPtrStubs) that are patched to point to the actual entry point eventually. A request for a multicallable entry point allocates a StubPrecode or FixupPrecode on demand in this case.
291
-
292
-
The raw speed difference is the cost of an indirect call for a compact entry point vs. the cost of one direct call and one direct jump on the given platform. The later used to be faster by a few percent in large server scenario since it can be predicted by the hardware better (2005). It is not always the case on current (2015) hardware.
293
-
294
-
The compact entry points have been historically implemented on x86 only. Their additional complexity, space vs. speed trade-off and hardware advancements made them unjustified on other platforms.
295
-
296
-
The compact entry point on x86 looks like this:
297
-
298
-
entrypoint0:
299
-
mov al,0
300
-
jmp short Dispatch
301
-
302
-
entrypoint1:
303
-
mov al,1
304
-
jmp short Dispatch
305
-
306
-
entrypoint2:
307
-
mov al,2
308
-
jmp short Dispatch
309
-
310
-
Dispatch:
311
-
movzx eax,al
312
-
shl eax, 3
313
-
add eax, pBaseMD
314
-
jmp PreStub
315
-
316
-
The allocation of temporary entry points always tries to pick the smallest temporary entry point from the available choices. For example, a single compact entry point is bigger than a single StubPrecode on x86. The StubPrecode will be preferred over the compact entry point in this case. The allocation of the precode for a stable entry point will try to reuse an allocated temporary entry point precode if one exists of the matching type.
317
-
318
258
**ThisPtrRetBufPrecode**
319
259
320
260
ThisPtrRetBufPrecode is used to switch a return buffer and the this pointer for open instance delegates returning valuetypes. It is used to convert the calling convention of MyValueType Bar(Foo x) to the calling convention of MyValueType Foo::Bar().
0 commit comments