Conceptually, index launches simply represent a loop of task
launches. Listing 2 lines 16-18 shows an example of a Re-
gent loop that can be transformed into an index launch (with
corresponding C++ code in Listing 4). However, the Legion
runtime places several restrictions on index launches to en-
sure that they are well-behaved:
1. Arguments to all tasks in the index launch must be
computed outside the launch, guaranteeing that argu-
ments are available and that no arguments depend on
side-effects from tasks within the launch.
2. Futures, if any, are added to the launch as a whole,
not to individual tasks.
3. Requirements can be in one of two forms:
• Individual region requirements add a single region
to all tasks in the launch.
• Partition requirements add a subregion of the par-
tition for each task.
Legion supports user-defined projection functions to
allow the programmer to dynamically select subregions
for each type of region requirement.
4. Because an index launch implies parallel execution, all
the tasks must be non-interfering.
5. If tasks within the launch return a value, then the
launch as a whole is allowed to either return a map
with all the resulting futures, or to reduce the futures
into a single value.
When executing an index launch, the runtime still per-
forms dynamic checks to ensure that the tasks within the
launch are non-interfering. However, Legion is able to amor-
tize these checks across the entire index launch instead of
performing them individually.
3.2
Regions
Logical regions are created as the cross product of an index
space (set of indices) and a field space (set of fields). Logi-
cal regions can be compared to arrays of objects or structs,
though this analogy falls short in several ways. In particular,
as discussed previously, because a logical region may have
multiple physical instances, there is no one-to-one mapping
between a logical region and its representation in memory.
3.2.1
Physical Instances
In Legion, a logical region may, at any given point in
time, map to zero or more physical instances. Access to
each field of a physical instance is mediated through a field-
specific accessor. In the Legion C++ interface, the program-
mer must manage distinct
LogicalRegion
,
PhysicalRegion
, and
Accessor
types.
In Regent, these differences disappear because the com-
piler manages the mapping from logical to physical regions
(and physical regions to accessors) transparently for the pro-
grammer. Field spaces can be constructed concisely from
struct types, and nested structs are automatically expanded
into their component fields. Accessors are created automat-
ically for whatever fields the programmer declared in the
privileges for the task. These differences are illustrated in
the difference between Listing 1 and Listing 3. In contrast
to users of the Legion C++ API, Regent programmers can
usually pretend that regions are simply arrays of structs or
objects.
Physical instances in Legion may be stored in one of a
number of layouts. Examples of common layouts include
array-of-structs and struct-of-arrays, while more esoteric lay-
outs may include arrays blocked for vectorized CPU instruc-
tions. Legion provides explicit accessor objects in order to
constant-fold compile-time information about instance lay-
outs for efficient access, as seen in Listing 3 lines 6-17. Re-
gent manages accessors, along with instances, on behalf of
the programmer.
Regent also manages the creation of region requirements
for each task.
For each task, Regent flattens the fields,
groups them by privilege, and issues a region requirement
for each privilege and set of fields; see the correspondence
between Listing 2 line 17 and Listing 4 lines 6-22.
3.2.2
Partitions
Partitioning a region using the Legion C++ interface hap-
pens in two steps. First, the user creates an index partition
of the index space to specify how the sets of indices are sub-
divided between the spaces. Second, the user applies the
index partition to a logical region created using that same
index space to obtain a corresponding logical partition. In
Regent, these operations are combined, as the correspon-
dence between logical regions and index spaces is managed
for the programmer.
3.3
Mapping
As briefly described previously, mapping is the process of
selecting a processor to run each task and a memory (and
data layout) for each logical region. Mapping is under the
control of the application, though Regent provides a default
mapper with sensible settings to allow users to get up and
running quickly.
Legion also provides a mapping interface, but because of
the distinction between physical and logical constructs ex-
posed in Legion, this mapping process is more involved.
In Legion, logical regions must be mapped to physical
instances in a specific memory before they can be used. By
default, at the start of a task Legion automatically maps
each region used by the task, and when the task ends each
of those regions is unmapped. Before launching a subtask
a parent task must also unmap any region that the child
task needs to use. By default, the Legion runtime unmaps
all of the parent’s regions before calling a child and remaps
them when the child terminates. While this default behavior
guarantees correct execution, if the parent and child have
interfering privileges for a region (e.g., both can write the
region) then the parent will most likely block until the child
terminates, as the parent cannot remap the region until the
child unmaps it (recall the discussion in Section 3.1.2).
For higher performance, Legion programmers can explic-
itly manage region mappings themselves through explicit
map and unmap calls provided by the Legion interface. By
unmapping a region, the programmer notifies the runtime
that the data in that region is not required by the parent
task until a corresponding map call is issued. In typical us-
age, programmers unmap all regions before entering a main
loop, and remap all regions once the loop completes, which
ensures that the runtime can avoid blocking when issuing
tasks within that loop. An example of such an unmap call