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