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.