We now come to one of the central abstractions for data in Regent, logical regions (or just “regions” for short). As hinted in earlier tutorials, regions allow mutable data to be used (safely) within Regent. Idiomatic Regent programs usually store most or all of their data in regions.
At their most basic, regions are like arrays of structs in a language like C. The main differences are that regions are not fixed to a single memory allocation, but can seamlessly move around a distributed machine, be copied down to the GPU, etc. In most cases, this happens without any specific user intervention, and users need only be concerned with what data is placed in regions and how that data is used.
Regions consist of a field space (like a C struct) and an index space. The latter doesn’t have a first-class analog in C, but is conceptually the set of indices used to refer to elements within a region (like indices in an array). Each of these components is described in more detail below.
A field space (fspace) works much like a struct in C. The following code declares a field space with four fields:
As a convenience, multiple, consecutive fields with the same type may be collapsed into a single line. The code below is identical to the version above:
In Regent code,
fs names a type (the type of the field space), and can be instantiated to create values of this type. For example, the following code defines a variable
x of type
Such values can be used like values of any other type: they can be passed (by-value) to tasks, returned from tasks, modified, etc. To access the individual elements of a field space, use the
. (dot) operator.
There is also a syntax to unpack multiple values of a field space at the same time. The example below creates 4 new variables,
d with the values of
This syntax also supports creating variables with different names, if that is desired. In the code below,
x_a and so on name the new variables,
a etc. name the fields of the field space to be unpacked.
An index space (ispace) is a collection of index points. This is most directly analogous to the set of valid array indices for an array in C. While the latter is not a first-class feature of C, index spaces are first-class objects in Regent and can be created dynamically, passed to tasks and returned from tasks.
The example below creates a 1-D index space with 1024 elements. This would be similar to
int in C, except an index space refers to the set of valid indices only and contains no actual data.
Index spaces can be passed to and returned from tasks:
Index spaces can also be iterated. The code below executes 1024 iterations, for the values
One notable difference between index spaces and C arrays is that index spaces need not start at 0. The following index space contains the elements,
In its most general form, the arguments to the
ispace operator are as follows:
The type of the index (e.g.,
The extent (size) of the index space (e.g.,
12 indicates it contains 12 elements).
The offset (start) of the index space (e.g.,
-1 indicates the first element starts at index −1).
Index spaces support two operators,
.volume, to retrieve the bounding rectangle and volume of the index space, respectively.
Index spaces are not restricted to 1 dimension. Regent supports up to 9 dimensions, though for dimensions above 3, Regent must be recompiled with the appropriate support (set
MAX_DIM=N when building). Regent provides a set of built-in index types for each of these dimensions:
int3d, etc. up to
int9d (if compiled with the right support).
int1d is a special case in that it corresponds directly into an
int. For the other cases, each
intNd is actually a field space with
N fields. These fields always take the following ordering:
intNd types are field spaces, their elements can be accessed with the usual field space syntax.
Multi-dimensional index spaces can be created from multi-dimensional index types. In this case, the extent specifies the upper-rectangular corner of a bounding box, and the offset shifts the box by the specified amount. The offset is zero by default. Some examples are shown below:
For historical reasons, Regent supports a notion of an “unstructured” index space via the index type
ptr. It is functionally equivalent to
int1d in all respects and is being maintained for backwards compatibility only. Most programs can use
int1d instead with no negative consequences.
In general, index spaces need not be dense rectangles. Regent does not provide a syntax to directly create a sparse index space, but they can be created via partitioning, a feature discussed in a later tutorial.
Regions take a field space and an index space and put them together to get a data structure that is similar to an array in C. Here’s an example, using the
fs field space from earlier:
This region contains elements numbered
9 (inclusive), and each element contains fields
The index space may either be specified inline (as above) or out-of-line. The latter allows multiple regions to be created with the same index space (and the same or different field space).
Like index spaces, regions can be iterated. One difference is that when regions are iterated, the iteration ranges over pointers to the elements of the region. The
@ (dereference) operator may be used on pointers to access (read or write) the corresponding region element. In the following loop, each element of the region
i is assigned the value
In the case where the region’s field space is not a primitive type (i.e., not
float, etc.), the
. (dot) operator can be used to implicitly dereference a pointer. Below,
x.a is equivalent to writing
In addition to the pointer-style syntax shown above, regions also support an array-style syntax (
r[...]). The example below is identical to the previous example.
This form can be particularly helpful when it may not be obvious what region an index is contained in (e.g., when doing index arithmetic on the pointers).
Unlike other types, region variables in Regent cannot be assigned a new value. This is a type error. Every region created in a region program is given a unique type, and even if it seems compatible, cannot be mixed with any other region in the program.
These error messages can sometimes be confusing to read. To help disambiguate the regions in an error message, use the
-fdebug 1 flag. With the example above, this would produce something like:
Despite the restrictions on region assignment, regions can be passed to tasks. For example, the task:
Can be called as:
At the point where
take_region is called,
s is matched to
r. Even though they have different types, they are still compatible, and the call is valid.
Note that regions do allow mutation through task arguments (in fact, they are the only type in Regent that permits this). However, in order to do this, tasks must explicitly declare the privileges (
writes, etc.) they intend to use for their region arguments. As written,
task_region declares no privileges on
r and thus would be unable to either read or write its contents. Privileges and region access are discussed in a future tutorial.
Continue to the next tutorial to learn more about the mapping of regions to physical memory allocations in Regent.