134 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
| ==============================
 | |
| 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.)
 |