Notes on Programming in Legion

David K. Zhang
Last Modified 2022-03-15

Construct a region R=I×FR = I \times F from an index space II and a field space FF:

    LogicalRegion rt->create_logical_region(*, IndexSpace I, FieldSpace F);

This function takes an optional argument bool task_local = false. Setting bool task_local = true informs Legion that the region being created will not outlive the task it is created in (i.e., it can be used by the current task and its children, but it cannot propagate up the task tree). In this case, Legion will automatically destroy the region at the end of the current task, and destroy_logical_region must not be called on a task-local region. This is only example I know of where a create_* call must not be paired with a corresponding destroy_* call.

Retrieve the underlying index space II and field space FF from a region R=I×FR = I \times F:

    IndexSpace R.get_index_space() const;
    FieldSpace R.get_field_space() const;

Note that these are not runtime calls rt->..., but rather, methods on the LogicalRegion object R. This is an inconsistency in the Legion API.

Retrieve the underlying index space II and color space CC of an index partition P:C2IP: C \to 2^I:

    IndexSpace rt->get_parent_index_space(IndexPartition P);
    IndexSpace rt->get_index_partition_color_space_name(IndexPartition P);

Construct a logical partition (R,P)(R, P) from a logical region RR and an index partition PP:

    LogicalPartition rt->get_logical_partition(LogicalRegion R, IndexPartition P);

For the purposes of Legion application development, a logical partition is simply a container that holds a logical region R=I×FR = I \times F and an index partition P:C2IP: C \to 2^I, both based on the same underlying index space II. The creation of a logical partition does not dynamically allocate any resources, so this function is called get_logical_partition rather than create_logical_partition, and there is no need for a corresponding destroy_logical_partition call. (The function rt->destroy_logical_partition does exist, but only for the purpose of backward compatibility; it is now a no-op.)

Retrieve the underlying field space FF, logical region RR, and index partition PP from a logical partition (R,P)(R, P):

    FieldSpace LP.get_field_space() const;
    LogicalRegion rt->get_parent_logical_region(LogicalPartition LP);
    IndexPartition LP.get_index_partition() const;

Note that the underlying logical region RR is obtained via a runtime call, while the index space FF and index partition PP are retrieved through methods on the LogicalPartition object LP itself. The Legion API does not provide a mechanism to directly obtain the index space II or color space CC; these must be retrieved through the index partition PP.

Reduce all the return values into a single value using the specified reduction operator into a single future value. The reduction operation must be a foldable reduction. * @param deterministic request that the reduced future value be computed * in a deterministic way (more expensive than non-deterministic)

Future execute_task(*, const TaskLauncher &launcher);
FutureMap execute_index_space(*, const IndexTaskLauncher &launcher);
Future execute_index_space(*, const IndexTaskLauncher &launcher,
                           ReductionOpID redop, bool deterministic = false);

* Create a new top-level index space based on the given domain bounds
* If the bounds contains a Realm index space then Legion will take ownership of any sparsity maps.
* @param bounds the bounds for the new index space
* @param type_tag optional type tag to use for the index space
IndexSpace create_index_space(*, const Domain &bounds, TypeTag type_tag = 0);
IndexSpaceT<DIM, COORD_T> create_index_space(*, const Rect<DIM, COORD_T> &bounds);
IndexSpaceT<DIM, COORD_T> create_index_space(*, const DomainT<DIM, COORD_T> &bounds);

* Create a new top-level index space from a future which contains a Domain object.
* If the Domain conaints a Realm index space then Legion will take ownership of any sparsity maps.
* @param dimensions number of dimensions for the created space
* @param future the future value containing the bounds
* @param type_tag optional type tag to use for the index space; defaults to 'coord_t'
IndexSpace create_index_space(*, size_t dimensions, const Future &f, TypeTag type_tag = 0);
IndexSpaceT<DIM,COORD_T> create_index_space(*, const Future &f);
* Create a new top-level index space from a vector of points
* @param points a vector of points to have in the index space
IndexSpace create_index_space(*, const std::vector<DomainPoint> &points);
IndexSpaceT<DIM, COORD_T> create_index_space(*, const std::vector<Point<DIM, COORD_T>> &points);
* Create a new top-level index space from a vector of rectangles
* @param rects a vector of rectangles to have in the index space
IndexSpace create_index_space(*, const std::vector<Domain> &rects);
IndexSpaceT<DIM, COORD_T> create_index_space(*, const std::vector<Rect<DIM, COORD_T>> &rects);

Resource Management

In C, every call to malloc should be matched with a corresponding call to free. Similarly, in Legion, every runtime call of the form create_* should be matched with a corresponding destroy_* call.

    void rt->destroy_index_space(*, IndexSpace I);
    void rt->destroy_index_partition(*, IndexPartition P);
    void rt->destroy_field_space(*, FieldSpace F);
    void rt->destroy_logical_region(*, LogicalRegion R);

All of the above destroy_* methods in Legion take an optional bool unordered = false parameter. Setting unordered = true informs Legion that this call to destroy_* may occur simultaneously with use of the resource being destroyed. This is necessary when the call to destroy_* is performed by a thread other than the one using the resource being destroyed, e.g., a garbage collector. In addition, destroy_index_space and destroy_index_partition take another optional parameter bool recurse = true that controls whether subspaces of the given index space II (i.e., children of II in the index tree) should also be destroyed.

Reference Counting

    void rt->create_shared_ownership(*, IndexSpace I);
    void rt->create_shared_ownership(*, IndexPartition P);
    void rt->create_shared_ownership(*, FieldSpace F);
    void rt->create_shared_ownership(*, LogicalRegion R);

    bool rt->is_index_partition_disjoint(IndexPartition P);
    bool rt->is_index_partition_complete(IndexPartition P);
    bool rt->has_parent_index_partition(IndexSpace I);
    size_t rt->get_field_size(FieldSpace F, FieldID fid);
    void rt->get_field_space_fields(FieldSpace F, std::vector<FieldID> &fids);
    void rt->get_field_space_fields(FieldSpace F, std::set<FieldID> &fids);
    bool rt->has_parent_logical_partition(LogicalRegion handle);

    LogicalPartition rt->get_parent_logical_partition(LogicalRegion handle);

These functions do exactly what you think they do.