Skip to content

Commit 54a677f

Browse files
committed
Cleanup HMF definitions
1 parent c630f27 commit 54a677f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+88
-6568
lines changed

docs/design/coreclr/botr/corelib.md

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ The CLR provides a [`mscorlib` binder](https://github.com/dotnet/runtime/blob/ma
4242

4343
Two techniques exist for calling into the CLR from managed code. FCall allows you to call directly into the CLR code, and provides a lot of flexibility in terms of manipulating objects, though it is easy to cause GC holes by not tracking object references correctly. QCall also allows you to call into the CLR via the P/Invoke, but is much harder to accidentally mis-use. FCalls are identified in managed code as extern methods with the [`MethodImplOptions.InternalCall`](https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.methodimploptions) bit set. QCalls are marked `static extern` methods similar to regular P/Invokes, but are directed toward a library called `"QCall"`.
4444

45-
There is a small variant of FCall called HCall (for Helper call) for implementing JIT helpers. The HCall is intended for doing things like accessing multi-dimensional array elements, range checks, etc. The only difference between HCall and FCall is that HCall methods won't show up in an exception stack trace.
46-
4745
### Choosing between FCall, QCall, P/Invoke, and writing in managed code
4846

4947
First, remember that you should be writing as much as possible in managed code. You avoid a raft of potential GC hole issues, you get a better debugging experience, and the code is often simpler.
@@ -54,7 +52,7 @@ If the only reason you're defining a FCall method is to call a native method, yo
5452

5553
If you still need to implement a feature inside the runtime, consider if there is a way to reduce the frequency of transitioning to native code. Can you write the common case in managed and only call into native for some rare corner cases? You're usually best off keeping as much as possible in managed code.
5654

57-
QCalls are the preferred mechanism going forward. You should only use FCalls when you are "forced" to. This happens when there is common "short path" through the code that is important to optimize. This short path should not be more than a few hundred instructions, cannot allocate GC memory, take locks or throw exceptions (`GC_NOTRIGGER`, `NOTHROWS`). In all other circumstances (and especially when you enter a FCall and then simply erect HelperMethodFrame), you should be using QCall.
55+
QCalls are the preferred mechanism going forward. You should only use FCalls when you are "forced" to. This happens when there is common "short path" through the code that is important to optimize. This short path should not be more than a few hundred instructions, cannot allocate GC memory, take locks or throw exceptions (`GC_NOTRIGGER`, `NOTHROWS`). In all other circumstances, you should be using QCall.
5856

5957
FCalls were specifically designed for short paths of code that must be optimized. They allowed explicit control over when erecting a frame was done. However, it is error prone and not worth the complexity for many APIs. QCalls are essentially P/Invokes into the CLR. In the event the performance of an FCall is required consider creating a QCall and marking it with [`SuppressGCTransitionAttribute`](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.suppressgctransitionattribute).
6058

@@ -64,8 +62,6 @@ As a result, QCalls give you some advantageous marshaling for `SafeHandle`s auto
6462

6563
QCalls are very much like a normal P/Invoke from CoreLib to CLR. Unlike FCalls, QCalls will marshal all arguments as unmanaged types like a normal P/Invoke. QCall also switch to preemptive GC mode like a normal P/Invoke. These two features should make QCalls easier to write reliably compared to FCalls. QCalls are not prone to GC holes and GC starvation bugs that are common with FCalls.
6664

67-
QCalls perform better than FCalls that erect a `HelperMethodFrame`. The overhead is about 1.4x less compared to FCall w/ `HelperMethodFrame` overhead on x86 and x64.
68-
6965
The preferred types for QCall arguments are primitive types that are efficiently handled by the P/Invoke marshaler (`INT32`, `LPCWSTR`, `BOOL`). Notice that `BOOL` is the correct boolean flavor for QCall arguments. On the other hand, `CLR_BOOL` is the correct boolean flavor for FCall arguments.
7066

7167
The pointers to common unmanaged EE structures should be wrapped into handle types. This is to make the managed implementation type safe and avoid falling into unsafe C# everywhere. See AssemblyHandle in [vm\qcall.h][qcall] for an example.
@@ -164,7 +160,7 @@ extern "C" BOOL QCALLTYPE Foo_BarInternal(int flags, LPCWSTR wszString, QCall::S
164160

165161
## FCall functional behavior
166162

167-
FCalls allow more flexibility in terms of passing object references around, but with higher code complexity and more opportunities to make mistakes. Additionally, FCall methods must either erect a helper method frame along their common code paths, or for any FCall of non-trivial length, explicitly poll for whether a garbage collection must occur. Failing to do so will lead to starvation issues if managed code repeatedly calls the FCall method in a tight loop, because FCalls execute while the thread only allows the GC to run in a cooperative manner.
163+
FCalls allow more flexibility in terms of passing object references around, but with higher code complexity and more opportunities to make mistakes. Additionally, for any FCall of non-trivial length, explicitly poll for whether a garbage collection must occur. Failing to do so will lead to starvation issues if managed code repeatedly calls the FCall method in a tight loop, because FCalls execute while the thread only allows the GC to run in a cooperative manner.
168164

169165
FCalls require a lot of boilerplate code, too much to describe here. Refer to [fcall.h][fcall] for details.
170166

@@ -176,8 +172,6 @@ A more complete discussion on GC holes can be found in the [CLR Code Guide](../.
176172

177173
Object references passed as parameters to FCall methods are not GC-protected, meaning that if a GC occurs, those references will point to the old location in memory of an object, not the new location. For this reason, FCalls usually follow the discipline of accepting something like `StringObject*` as their parameter type, then explicitly converting that to a `STRINGREF` before doing operations that may trigger a GC. If you expect to use an object reference later, you must GC protect object references before triggering a GC.
178174

179-
All GC heap allocations within an FCall method must happen within a helper method frame. If you allocate memory on the GC heap, the GC may collect dead objects and move objects around in unpredictable ways, with some low probability. For this reason, you must manually report any object references in your method to the GC, so that if a garbage collection occurs, your object reference will be updated to refer to the new location in memory. Any pointers into managed objects (like arrays or Strings) within your code will not be updated automatically, and must be re-fetched after any operation that may allocate memory and before your first usage. Reporting a reference can be done via the `GCPROTECT_*` macros or as parameters when erecting a helper method frame.
180-
181175
Failing to properly report an `OBJECTREF` or to update an interior pointer is commonly referred to as a "GC hole", because the `OBJECTREF` class will do some validation that it points to a valid object every time you dereference it in Debug and Checked builds. When an `OBJECTREF` pointing to an invalid object is dereferenced, an assert will trigger saying something like "Detected an invalid object reference. Possible GC hole?". This assert is unfortunately easy to hit when writing "manually managed" code.
182176

183177
Note that QCall's programming model is restrictive to sidestep GC holes by forcing you to pass in the address of an object reference on the stack. This guarantees that the object reference is GC protected by the JIT's reporting logic, and that the actual object reference will not move because it is not allocated in the GC heap. QCall is our recommended approach, precisely because it makes GC holes harder to write.
@@ -188,8 +182,6 @@ The managed stack walker needs to be able to find its way from FCalls. It is rel
188182

189183
Complex constructs like stack allocated objects with destructors or exception handling in the FCall implementation may confuse the epilog walker. This can lead to GC holes or crashes during stack walking. There is no comprehensive list of what constructs should be avoided to prevent this class of bugs. An FCall implementation that is fine one day may break with the next C++ compiler update. We depend on stress runs and code coverage to find bugs in this area.
190184

191-
Setting a breakpoint inside an FCall implementation may confuse the epilog walker. It leads to an "Invalid breakpoint in a helpermethod frame epilog" assert inside [vm\i386\gmsx86.cpp](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/i386/gmsx86.cpp).
192-
193185
### FCall example – managed
194186

195187
Here's a real-world example from the `String` class:
@@ -218,29 +210,18 @@ The FCall entrypoint has to be registered in tables in [vm\ecalllist.h][ecalllis
218210

219211
[ecalllist]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ecalllist.h
220212

221-
This method is an instance method in managed code, with the "this" parameter passed as the first argument. We use `StringObject*` as the argument type, then copy it into a `STRINGREF` so we get some error checking when we use it.
213+
This example shows an FCall method that takes a managed object (`Object*`) as a raw pointer. These raw inputs are considered "unsafe" and must be validated or converted if they’re used in a GC-sensitive context.
222214

223215
```C++
224-
FCIMPL1(Object*, AppDomainNative::IsStringInterned, StringObject* pStringUNSAFE)
216+
FCIMPL1(FC_BOOL_RET, ExceptionNative::IsImmutableAgileException, Object* pExceptionUNSAFE)
225217
{
226218
FCALL_CONTRACT;
227219

228-
STRINGREF refString = ObjectToSTRINGREF(pStringUNSAFE);
229-
STRINGREF* prefRetVal = NULL;
230-
231-
HELPER_METHOD_FRAME_BEGIN_RET_1(refString);
232-
233-
if (refString == NULL)
234-
COMPlusThrow(kArgumentNullException, W("ArgumentNull_String"));
235-
236-
prefRetVal = GetAppDomain()->IsStringInterned(&refString);
237-
238-
HELPER_METHOD_FRAME_END();
220+
ASSERT(pExceptionUNSAFE != NULL);
239221

240-
if (prefRetVal == NULL)
241-
return NULL;
222+
OBJECTREF pException = (OBJECTREF) pExceptionUNSAFE;
242223

243-
return OBJECTREFToObject(*prefRetVal);
224+
FC_RETURN_BOOL(CLRException::IsPreallocatedExceptionObject(pException));
244225
}
245226
FCIMPLEND
246227
```

docs/design/coreclr/botr/exceptions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,9 @@ This is the "fcall", "jit helper", and so forth. The typical way that the runtim
255255

256256
On the other hand, if an fcall function can do anything that might throw a CLR internal exception (one of the C++ exceptions), that exception must not be allowed to leak back out to managed code. To handle this case, the CLR has the UnwindAndContinueHandler (UACH), which is a set of code to catch the C++ EH exceptions, and re-raise them as managed exceptions.
257257

258-
Any runtime function that is called from managed code, and might throw a C++ EH exception, must wrap the throwing code in INSTALL_UNWIND_AND_CONTINUE_HANDLER / UNINSTALL_UNWIND_AND_CONTINUE_HANDLER. Installing a HELPER_METHOD_FRAME will automatically install the UACH. There is a non-trivial amount of overhead to installing a UACH, so they shouldn't be used everywhere. One technique that is used in performance critical code is to run without a UACH, and install one just before throwing an exception.
258+
Any runtime function that is called from managed code, and might throw a C++ EH exception, must wrap the throwing code in INSTALL_UNWIND_AND_CONTINUE_HANDLER / UNINSTALL_UNWIND_AND_CONTINUE_HANDLER. There is a non-trivial amount of overhead to installing a UACH, so they shouldn't be used everywhere. One technique that is used in performance critical code is to run without a UACH, and install one just before throwing an exception.
259259

260-
When a C++ exception is thrown, and there is a missing UACH, the typical failure will be a Contract Violation of "GC_TRIGGERS called in a GC_NOTRIGGER region" in CPFH_RealFirstPassHandler. To fix these, look for managed to runtime transitions, and check for INSTALL_UNWIND_AND_CONTINUE_HANDLER or HELPER_METHOD_FRAME_BEGIN_XXX.
260+
When a C++ exception is thrown, and there is a missing UACH, the typical failure will be a Contract Violation of "GC_TRIGGERS called in a GC_NOTRIGGER region" in CPFH_RealFirstPassHandler. To fix these, look for managed to runtime transitions, and check for INSTALL_UNWIND_AND_CONTINUE_HANDLER.
261261

262262
Runtime code into managed code
263263
------------------------------

docs/design/coreclr/botr/guide-for-porting.md

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -340,27 +340,22 @@ Here is an annotated list of the stubs implemented for Unix on Arm64.
340340
calls. Necessary for all applications as this is how the main method is
341341
called.
342342

343-
2. `LazyMachStateCaptureState`/`HelperMethodFrameRestoreState` – Needed to
344-
support a GC occurring with an FCALL or HCALL on the stack. (Incorrect
345-
implementations will cause unpredictable crashes during or after garbage
346-
collection)
347-
348-
3. `NDirectImportThunk` – Needed to support saving off a set of arguments to
343+
2. `NDirectImportThunk` – Needed to support saving off a set of arguments to
349344
a p/invoke so that the runtime can find the actual target. Also uses one
350345
of the secret arguments (Used by all p/invoke methods)
351346

352-
4. `PrecodeFixupThunk` – Needed to convert the secret argument from a
347+
3. `PrecodeFixupThunk` – Needed to convert the secret argument from a
353348
FixupPrecode\* to a MethodDesc\*. This function exists to reduce the
354349
code size of FixupPrecodes as there are (Used by many managed methods)
355350

356-
5. `ThePreStub` - Needed to support saving off a set of arguments to the
351+
4. `ThePreStub` - Needed to support saving off a set of arguments to the
357352
stack so that the runtime can find or jit the right target method.
358353
(Needed for any jitted method to execute Used by all managed methods)
359354

360-
6. `ThePreStubPatch` – Exists to provide a reliable spot for the managed
355+
5. `ThePreStubPatch` – Exists to provide a reliable spot for the managed
361356
debugger to put a breakpoint.
362357

363-
7. GC Write Barriers – These are used to provide the GC with information
358+
6. GC Write Barriers – These are used to provide the GC with information
364359
about what memory is being updated. The existing implementations of
365360
these are all complex, and there are a number of controls where the
366361
runtime can adjust to tweak the behavior of the barrier in various ways.
@@ -373,40 +368,40 @@ Here is an annotated list of the stubs implemented for Unix on Arm64.
373368
FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP can be implemented as
374369
performance needs require.
375370

376-
8. `ComCallPreStub`/ `COMToCLRDispatchHelper` /`GenericComCallStub` - not
371+
7. `ComCallPreStub`/ `COMToCLRDispatchHelper` /`GenericComCallStub` - not
377372
necessary for non-Windows platforms at this time
378373

379-
9. `TheUMEntryPrestub`/ `UMThunkStub` - used to enter the runtime from
374+
8. `TheUMEntryPrestub`/ `UMThunkStub` - used to enter the runtime from
380375
non-managed code through entrypoints generated from the
381376
Marshal.GetFunctionPointerForDelegate api.
382377

383-
10. `OnHijackTripThread` - needed for thread suspension to support GC + other
378+
9. `OnHijackTripThread` - needed for thread suspension to support GC + other
384379
suspension requiring events. This is typically not needed for very early
385380
stage bringup of the product, but will be needed for any decent size
386381
application
387382

388-
11. `CallEHFunclet` – Used to call catch, finally and fault funclets. Behavior
383+
10. `CallEHFunclet` – Used to call catch, finally and fault funclets. Behavior
389384
is specific to exactly how funclets are implemented.
390385

391-
12. `CallEHFilterFunclet` – Used to call filter funclets. Behavior is specific
386+
11. `CallEHFilterFunclet` – Used to call filter funclets. Behavior is specific
392387
to exactly how funclets are implemented.
393388

394-
13. `ResolveWorkerChainLookupAsmStub`/ `ResolveWorkerAsmStub` Used for virtual
389+
12. `ResolveWorkerChainLookupAsmStub`/ `ResolveWorkerAsmStub` Used for virtual
395390
stub dispatch (virtual call support for interface, and some virtual
396391
methods). These work in tandem with the logic in virtualcallstubcpu.h to
397392
implement the logic described in [Virtual Stub Dispatch](virtual-stub-dispatch.md)
398393

399-
14. `ProfileEnter`/ `ProfileLeave`/ `ProfileTailcall` – Used to call function
394+
13. `ProfileEnter`/ `ProfileLeave`/ `ProfileTailcall` – Used to call function
400395
entry/exit profile functions acquired through the ICorProfiler
401396
interface. Used in VERY rare circumstances. It is reasonable to wait to
402397
implement these until the final stages of productization. Most profilers
403398
do not use this functionality.
404399

405-
15. `JIT_PInvokeBegin`/`JIT_PInvokeEnd` – Leave/enter the managed runtime state. Necessary
400+
14. `JIT_PInvokeBegin`/`JIT_PInvokeEnd` – Leave/enter the managed runtime state. Necessary
406401
for ReadyToRun pre-compiled pinvoke calls, so that they do not cause GC
407402
starvation
408403

409-
16. `VarargPInvokeStub`/ `GenericPInvokeCalliHelper` Used to support calli
404+
15. `VarargPInvokeStub`/ `GenericPInvokeCalliHelper` Used to support calli
410405
pinvokes. It is expected that C\# 8.0 will increase use of this feature.
411406
Today use of this feature on Unix requires hand-written IL. On Windows
412407
this feature is commonly used by C++/CLI

docs/design/datacontracts/StackWalk.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,6 @@ HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platf
293293

294294
TailCallFrames are only used on Windows x86 which is not yet supported in the cDAC and therefore not implemented.
295295

296-
#### HelperMethodFrame
297-
298-
HelperMethodFrames are on the way to being removed. They are not currently supported in the cDAC.
299-
300296
### APIs
301297

302298
The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`.

src/coreclr/debug/daccess/dacdbiimpl.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
3+
34
//*****************************************************************************
45
// File: DacDbiImpl.cpp
5-
//
6-
76
//
87
// Implement DAC/DBI interface
9-
//
108
//*****************************************************************************
119

12-
1310
#include "stdafx.h"
1411

1512
#include "dacdbiinterface.h"
@@ -5640,7 +5637,7 @@ void DacDbiInterfaceImpl::GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContex
56405637

56415638
// Going through thread Frames and looking for first (deepest one) one that
56425639
// that has context available for stackwalking (SP and PC)
5643-
// For example: RedirectedThreadFrame, InlinedCallFrame, HelperMethodFrame, CLRToCOMMethodFrame
5640+
// For example: RedirectedThreadFrame, InlinedCallFrame, DynamicHelperFrame, CLRToCOMMethodFrame
56445641
Frame *frame = pThread->GetFrame();
56455642
while (frame != NULL && frame != FRAME_TOP)
56465643
{

src/coreclr/debug/di/shimremotedatatarget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,5 +371,5 @@ ShimRemoteDataTarget::ContinueStatusChanged(
371371
HRESULT STDMETHODCALLTYPE
372372
ShimRemoteDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
373373
{
374-
return m_pTransport->VirtualUnwind(threadId, contextSize, context);
374+
return E_NOTIMPL;
375375
}

0 commit comments

Comments
 (0)