| # FaultMaps and implicit checks | |
| ## Motivation | |
| Code generated by managed language runtimes tends 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 is put in a | |
| special "fault map" section. On Darwin this section is named | |
| `__llvm_faultmaps`. | |
| The format of this section is | |
| ```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: | |
| ```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: | |
| ```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.) |