Search
Phoenix Pass and Plug-In Architecture - Creating a New Pass

Sample code is available here

Multi-Pass support is a major new feature of this release. Unlike a Phase which is applied to a functionUnit, a Pass is executed on a ModuleUnit or PEModuleUnit level.  The following diagram illustrates the difference between Pass and Phase.

Phoenix C2 Pass Architecture
As you can see, Phoenix C2 can be composed of several passes. In each pass, C2 processes every function once in a predefined order. The order of passes can be different. Currently C2 is configured with two passes: one Pass 0 and one code-generation Pass.  You can see the detailed pass & phase configuration with option –d2dumpphaselist: 

               “cl  hello.cpp –O2 –d2dumpphaselist”

The compilation order of Pass 0 is top-down order along the call graph while the final code-generation pass follows the bottom-up order.  Since C2 is multi-threaded at functionUnit level, more than one functionUnits can be processed simultaneously in a C2 Pass assuming they are independent for the process.  For example, in a top-down order, all callees of function foo can be processed at the same time after the compilation of foo assuming its callees are not calling each other directly or indirectly.

Similar to adding/updating a phase as we have done before via PlugIn::BuildPhases API, we can add/replace a pass with PlugIn::BuildPasses  in this new SDK.  While PlugIn::BuildPhases only applies to the final bottom-up Code Gen Pass, PlugIn::BuildPasses  can configure every pass including the final code generation pass. You can also add a new phase in an existent pass as well as a completely new pass at the same time as shown in the picture below.
Phoenix Pass Illustration

A typical inter-procedural analysis is composed of two or more passes. It’s usually led by a pass (top-down or bottom-up order) that analyzes IR at function level, summarize and save information on the corresponding call node/edge.  Later it’s followed by a pass that follows call-edges on the call graph and propagates, merges or solves the information previously saved on call node/edge. To store and retrieve information on call-graph thread-safely, it is encouraged that plug-in users use Phx::Graphs:: SummaryRecord primitives.   You can extend and create different kinds of summaryRecords for your purpose and annotate them on a call-node, call-edge or call-site.

Since currently Phoenix C2 does not cache IR in memory between Passes, a pass that needs to analyze IR must re-read IR from input file.  If the existent two-pass configuration does not meet your need, creating a new pass with IR reading phase is also straightforward.  There are two ways to do that: 

  1. Create a C2 Pass and leverage C2’s multi-threaded call graph compilation. To do this, you have to fill in six PhaseLists of a C2 Pass.
  2. Create a generic Phoenix pass and do the compilation yourself:  you have to control the compilation order, create a function unit for each function and apply PhaseList of the new Pass to each function.

Sample code that illustrates both methods can be found in the project located here.  In this sample code,  both passes use C2::Phases::CxxILReaderPhase as their reader phase.  Note that since C2.dll is referenced by this plugin, C2.dll must be in the project’s Reference list.