| ============================== | |
| 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.) |