FaultMaps and implicit checks

Motivation

Code generated by managed language runtimes tend to have checks thatare required for safety but never fail in practice. In such cases, itis profitable to make the non-failing case cheaper even if it makesthe failing case significantly more expensive. This asymmetry can beexploited by folding such safety checks into operations that can bemade to fault reliably if the check would have failed, and recoveringfrom such a fault by using a signal handler.

For example, Java requires null checks on objects before they are readfrom or written to. If the object is null then aNullPointerException has to be thrown, interrupting normalexecution. In practice, however, dereferencing a null pointer isextremely rare in well-behaved Java programs, and typically the nullcheck can be folded into a nearby memory operation that operates onthe same memory location.

The Fault Map Section

Information about implicit checks generated by LLVM are put in aspecial “fault map” section. On Darwin this section is named__llvm_faultmaps.

The format of this section is

  1. Header {
  2. uint8 : Fault Map Version (current version is 1)
  3. uint8 : Reserved (expected to be 0)
  4. uint16 : Reserved (expected to be 0)
  5. }
  6. uint32 : NumFunctions
  7. FunctionInfo[NumFunctions] {
  8. uint64 : FunctionAddress
  9. uint32 : NumFaultingPCs
  10. uint32 : Reserved (expected to be 0)
  11. FunctionFaultInfo[NumFaultingPCs] {
  12. uint32 : FaultKind
  13. uint32 : FaultingPCOffset
  14. uint32 : HandlerPCOffset
  15. }
  16. }

FailtKind describes the reason of expected fault. Currently three kindof 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 forchecking if a pointer is null, like:

  1. %ptr = call i32* @get_ptr()
  2. %ptr_is_null = icmp i32* %ptr, null
  3. br i1 %ptr_is_null, label %is_null, label %not_null, !make.implicit !0
  4.  
  5. not_null:
  6. %t = load i32, i32* %ptr
  7. br label %do_something_with_t
  8.  
  9. is_null:
  10. call void @HFC()
  11. unreachable
  12.  
  13. !0 = !{}

to control flow implicit in the instruction loading or storing throughthe pointer being null checked:

  1. %ptr = call i32* @get_ptr()
  2. %t = load i32, i32* %ptr ;; handler-pc = label %is_null
  3. br label %do_something_with_t
  4.  
  5. is_null:
  6. call void @HFC()
  7. unreachable

This transform happens at the MachineInstr level, not the LLVM IRlevel (so the above example is only representative, not literal). TheImplicitNullChecks 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 canbe a net performance pessimization if too many memory operations endup faulting because of it. A language runtime typically needs toensure that only a negligible number of implicit null checks actuallyfault once the application has reached a steady state. A standard wayof doing this is by healing failed implicit null checks into explicitnull checks via code patching or recompilation. It follows that thereare two requirements an explicit null check needs to satisfy for it tobe 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 explicitnull check so that the application does not repeatedly pagefault.

The frontend is expected to mark branches that satisfy (1) and (2)using a !make.implicit metadata node (the actual content of themetadata node is ignored). Only branches that are marked with!make.implicit metadata are considered as candidates forconversion into implicit null checks.

(Note that while we could deal with (1) using profiling data, dealingwith (2) requires some information not present in branch profiles.)