As part of the ranger development we have found that there are various pieces of the infrastructure that can be used independently, and we’d like to document them in the hopes that they are useful to others. We will be contributing short posts documenting parts of the ranger, which we hope can become part of a more persistent documentation (wiki? Internal docs?).

This first post is an introduction to accessing ranges throughout the compiler that will serve as a basis for the upcoming posts.

Please let us know if anything is not clear, or if you'd like us to expand on any particular topic...

Aldy & Andrew

The main goal of ranger is to provide a generic infrastructure for accessing ranges (global or on-demand) from anywhere in the compiler, with one common API. This API subsumes the global ranges we accessed through SSA_NAME_RANGE_INFO, as well as on-demand ones with the ranger proper.

The base class for querying ranges in the compiler is range_query and is defined in value-query.h. Instead of duplicating it here, I will list the more typical access points:

bool range_of_expr (irange &r, tree expr, gimple *stmt = NULL);

Returns the range of a tree expression as a range in R, with an optional gimple statement providing context. The tree expression EXPR can be anything from an SSA, or constant, to a full complex expression such as a binary or unary tree.

Upon return R will contain the range of EXPR as it would appear on entry to STMT. If STMT is not specified, then it will be the global range of EXPR. This function always returns true unless the type of EXPR is unsupported (we currently support integers and pointers).

bool range_on_edge (irange &r, edge, tree expr);

Like range_of_expr, but instead of returning the range as it appears on entry to a statement, this function returns the range as it would appear on an edge.

bool range_of_stmt (irange &r, gimple *, tree name = NULL);

Returns the range of a gimple statement in R. NAME is an optional SSA_NAME on the LHS of the statement. It is only required if there is more than one LHS/output. This query will trigger requests for the range of each operand on the statement and will use those to calculate the resulting range. (ie, range_of_expr() will be called for each operand using this statement as the context)

The above is the core API for anything range related. It can be used with any range_query object, which ranger is one, and which even the legacy vr_values is one.

Every struct function in the compiler has a range_query object associated with it. By default, it is configured such that it returns global ranges. That is, ranges that were globally assigned by previous passes, such as evrp or VRP. These global ranges are what SSA_NAME_RANGE_INFO and SSA_NAME_PTR_INFO used to be, but accessible with one common API, and flexible in that they can return global ranges for SSA names, constants, and even expressions.

To get the range_query object for a given function, use the following with the above API:

range_query *get_range_query (struct function *);

The first step in using ranges in a pass, is to structure all queries with the range API, on an object returned by get_range_query(). That’s it. Your pass will be able to access ranges, albeit initially with ranges that apply to the entire function (global ranges).

If your pass can benefit from context-aware ranges (on statements or edges) or on-demand ranges (more up to date than global ones), you must enable a ranger for your pass. This can be done by calling the following on entry to the pass:

gimple_ranger *enable_ranger (struct function *fun);

And a corresponding call on exit from the pass:

void disable_ranger (struct function *fun);

No other changes are needed in your pass if you’re already using the range_query API. You may continue using get_range_query(fun) since it will return the current active range_query object (the enabled ranger in this case).

You may notice that enable_ranger() returns a gimple_ranger object (which is a derived class of range_query). This can be used for more advanced operations on ranges (see gimple-range.h), but more importantly, it can be used to export any ranges found throughout the execution of your pass to the global space. If during range queries done in your pass, the ranger discovers any globally applicable ranges, they can be exported for use in subsequent passes by calling the export_global_ranges() method from a gimple_ranger object:

your_pass()
{
  gimple_ranger *ranger = enable-ranger (cfun);

  do_stuff();
  get_range_query ()->range_of_expr (.....);
  get_range_query ()->range_on_edge (....);
  do_stuff();

  // Export any known ranges to the global space.
  ranger->export_global_ranges ();

  disable_ranger (cfun);
}

This means that on exit from the pass, a get_range_query()->range* can be used to access globally applicable ranges that were found during your_pass().

Note that due to the caching mechanism in the ranger, on-demand ranges (available when enable_ranger() has been called in a pass) cannot survive changes in the IL. Specifically if your pass changes the flow control of the IL, you may have to delay altering the IL until after disable_ranger() has been called.

Finally, you may notice that get_range_query() requires a struct function, which may not be available in certain passes (i.e. RTL based passes). If this is the case, you may explicitly request the global range object, accessible with:

range_query *get_global_range_query ();

This function is only to be used when there is no struct function available, or when the overhead of an on-demand lookup is not desired. For example, when requesting the range of a tree expression in which you only care about global ranges when resolving any SSAs in said expression.

Reply via email to