You've already forked linux-packaging-mono
Imported Upstream version 5.18.0.167
Former-commit-id: 289509151e0fee68a1b591a20c9f109c3c789d3a
This commit is contained in:
parent
e19d552987
commit
b084638f15
133
external/llvm/docs/FaultMaps.rst
vendored
133
external/llvm/docs/FaultMaps.rst
vendored
@ -1,133 +0,0 @@
|
||||
==============================
|
||||
FaultMaps and implicit checks
|
||||
==============================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Code generated by managed language runtimes tend to have checks that
|
||||
are required for safety but never fail in practice. In such cases, it
|
||||
is profitable to make the non-failing case cheaper even if it makes
|
||||
the failing case significantly more expensive. This asymmetry can be
|
||||
exploited by folding such safety checks into operations that can be
|
||||
made to fault reliably if the check would have failed, and recovering
|
||||
from such a fault by using a signal handler.
|
||||
|
||||
For example, Java requires null checks on objects before they are read
|
||||
from or written to. If the object is ``null`` then a
|
||||
``NullPointerException`` has to be thrown, interrupting normal
|
||||
execution. In practice, however, dereferencing a ``null`` pointer is
|
||||
extremely rare in well-behaved Java programs, and typically the null
|
||||
check can be folded into a nearby memory operation that operates on
|
||||
the same memory location.
|
||||
|
||||
The Fault Map Section
|
||||
=====================
|
||||
|
||||
Information about implicit checks generated by LLVM are put in a
|
||||
special "fault map" section. On Darwin this section is named
|
||||
``__llvm_faultmaps``.
|
||||
|
||||
The format of this section is
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Header {
|
||||
uint8 : Fault Map Version (current version is 1)
|
||||
uint8 : Reserved (expected to be 0)
|
||||
uint16 : Reserved (expected to be 0)
|
||||
}
|
||||
uint32 : NumFunctions
|
||||
FunctionInfo[NumFunctions] {
|
||||
uint64 : FunctionAddress
|
||||
uint32 : NumFaultingPCs
|
||||
uint32 : Reserved (expected to be 0)
|
||||
FunctionFaultInfo[NumFaultingPCs] {
|
||||
uint32 : FaultKind
|
||||
uint32 : FaultingPCOffset
|
||||
uint32 : HandlerPCOffset
|
||||
}
|
||||
}
|
||||
|
||||
FailtKind describes the reason of expected fault. Currently three kind
|
||||
of faults are supported:
|
||||
|
||||
1. ``FaultMaps::FaultingLoad`` - fault due to load from memory.
|
||||
2. ``FaultMaps::FaultingLoadStore`` - fault due to instruction load and store.
|
||||
3. ``FaultMaps::FaultingStore`` - fault due to store to memory.
|
||||
|
||||
The ``ImplicitNullChecks`` pass
|
||||
===============================
|
||||
|
||||
The ``ImplicitNullChecks`` pass transforms explicit control flow for
|
||||
checking if a pointer is ``null``, like:
|
||||
|
||||
.. code-block:: llvm
|
||||
|
||||
%ptr = call i32* @get_ptr()
|
||||
%ptr_is_null = icmp i32* %ptr, null
|
||||
br i1 %ptr_is_null, label %is_null, label %not_null, !make.implicit !0
|
||||
|
||||
not_null:
|
||||
%t = load i32, i32* %ptr
|
||||
br label %do_something_with_t
|
||||
|
||||
is_null:
|
||||
call void @HFC()
|
||||
unreachable
|
||||
|
||||
!0 = !{}
|
||||
|
||||
to control flow implicit in the instruction loading or storing through
|
||||
the pointer being null checked:
|
||||
|
||||
.. code-block:: llvm
|
||||
|
||||
%ptr = call i32* @get_ptr()
|
||||
%t = load i32, i32* %ptr ;; handler-pc = label %is_null
|
||||
br label %do_something_with_t
|
||||
|
||||
is_null:
|
||||
call void @HFC()
|
||||
unreachable
|
||||
|
||||
This transform happens at the ``MachineInstr`` level, not the LLVM IR
|
||||
level (so the above example is only representative, not literal). The
|
||||
``ImplicitNullChecks`` pass runs during codegen, if
|
||||
``-enable-implicit-null-checks`` is passed to ``llc``.
|
||||
|
||||
The ``ImplicitNullChecks`` pass adds entries to the
|
||||
``__llvm_faultmaps`` section described above as needed.
|
||||
|
||||
``make.implicit`` metadata
|
||||
--------------------------
|
||||
|
||||
Making null checks implicit is an aggressive optimization, and it can
|
||||
be a net performance pessimization if too many memory operations end
|
||||
up faulting because of it. A language runtime typically needs to
|
||||
ensure that only a negligible number of implicit null checks actually
|
||||
fault once the application has reached a steady state. A standard way
|
||||
of doing this is by healing failed implicit null checks into explicit
|
||||
null checks via code patching or recompilation. It follows that there
|
||||
are two requirements an explicit null check needs to satisfy for it to
|
||||
be profitable to convert it to an implicit null check:
|
||||
|
||||
1. The case where the pointer is actually null (i.e. the "failing"
|
||||
case) is extremely rare.
|
||||
|
||||
2. The failing path heals the implicit null check into an explicit
|
||||
null check so that the application does not repeatedly page
|
||||
fault.
|
||||
|
||||
The frontend is expected to mark branches that satisfy (1) and (2)
|
||||
using a ``!make.implicit`` metadata node (the actual content of the
|
||||
metadata node is ignored). Only branches that are marked with
|
||||
``!make.implicit`` metadata are considered as candidates for
|
||||
conversion into implicit null checks.
|
||||
|
||||
(Note that while we could deal with (1) using profiling data, dealing
|
||||
with (2) requires some information not present in branch profiles.)
|
Reference in New Issue
Block a user