2 * Copyright 2016 Sven Verdoolaege
4 * Use of this software is governed by the MIT license
6 * Written by Sven Verdoolaege.
12 #include <isl/space.h>
16 #include <isl/union_set.h>
17 #include <isl/union_map.h>
18 #include <isl/schedule.h>
19 #include <isl/schedule_node.h>
24 /* Internal data structure for use during the detection of statements
25 * that can be grouped.
27 * "sc" contains the original schedule constraints (not a copy).
28 * The validity constraints of "sc" are adjusted based on the groups
30 * "dep" contains the intersection of the validity and the proximity
31 * constraints in "sc". It may be NULL if it has not been computed yet.
32 * "group_id" is the identifier for the next group that is extracted.
34 * "domain" is the set of statement instances that belong to any of the groups.
35 * "contraction" maps the elements of "domain" to the corresponding group
37 * "schedule" schedules the statements in each group relatively to each other.
38 * These last three fields are NULL if no groups have been found so far.
40 struct ppcg_grouping
{
41 isl_schedule_constraints
*sc
;
46 isl_union_set
*domain
;
47 isl_union_pw_multi_aff
*contraction
;
48 isl_schedule
*schedule
;
51 /* Clear all memory allocated by "grouping".
53 static void ppcg_grouping_clear(struct ppcg_grouping
*grouping
)
55 isl_union_map_free(grouping
->dep
);
56 isl_union_set_free(grouping
->domain
);
57 isl_union_pw_multi_aff_free(grouping
->contraction
);
58 isl_schedule_free(grouping
->schedule
);
61 /* Compute the intersection of the proximity and validity dependences
62 * in grouping->sc and store the result in grouping->dep, unless
63 * this intersection has been computed before.
65 static isl_stat
ppcg_grouping_compute_dep(struct ppcg_grouping
*grouping
)
67 isl_union_map
*validity
, *proximity
;
72 validity
= isl_schedule_constraints_get_validity(grouping
->sc
);
73 proximity
= isl_schedule_constraints_get_proximity(grouping
->sc
);
74 grouping
->dep
= isl_union_map_intersect(validity
, proximity
);
77 return isl_stat_error
;
82 /* Information extracted from one or more consecutive leaves
83 * in the input schedule.
85 * "list" contains the sets of statement instances in the leaves,
86 * one element in the list for each original leaf.
87 * "domain" contains the union of the sets in "list".
88 * "prefix" contains the prefix schedule of these elements.
90 struct ppcg_grouping_leaf
{
91 isl_union_set
*domain
;
92 isl_union_set_list
*list
;
93 isl_multi_union_pw_aff
*prefix
;
96 /* Free all memory allocated for "leaves".
98 static void ppcg_grouping_leaf_free(int n
, struct ppcg_grouping_leaf leaves
[n
])
105 for (i
= 0; i
< n
; ++i
) {
106 isl_union_set_free(leaves
[i
].domain
);
107 isl_union_set_list_free(leaves
[i
].list
);
108 isl_multi_union_pw_aff_free(leaves
[i
].prefix
);
114 /* Short-hand for retrieving the prefix schedule at "node"
115 * in the form of an isl_multi_union_pw_aff.
117 static __isl_give isl_multi_union_pw_aff
*get_prefix(
118 __isl_keep isl_schedule_node
*node
)
120 return isl_schedule_node_get_prefix_schedule_multi_union_pw_aff(node
);
123 /* Return an array of "n" elements with information extracted from
124 * the "n" children of "node" starting at "first", all of which
125 * are known to be filtered leaves.
127 struct ppcg_grouping_leaf
*extract_leaves(__isl_keep isl_schedule_node
*node
,
132 struct ppcg_grouping_leaf
*leaves
;
137 ctx
= isl_schedule_node_get_ctx(node
);
138 leaves
= isl_calloc_array(ctx
, struct ppcg_grouping_leaf
, n
);
142 for (i
= 0; i
< n
; ++i
) {
143 isl_schedule_node
*child
;
144 isl_union_set
*domain
;
146 child
= isl_schedule_node_get_child(node
, first
+ i
);
147 child
= isl_schedule_node_child(child
, 0);
148 domain
= isl_schedule_node_get_domain(child
);
149 leaves
[i
].domain
= isl_union_set_copy(domain
);
150 leaves
[i
].list
= isl_union_set_list_from_union_set(domain
);
151 leaves
[i
].prefix
= get_prefix(child
);
152 isl_schedule_node_free(child
);
158 /* Internal data structure used by merge_leaves.
160 * "src" and "dst" point to the two consecutive leaves that are
161 * under investigation for being merged.
162 * "merge" is initially set to 0 and is set to 1 as soon as
163 * it turns out that it is useful to merge the two leaves.
165 struct ppcg_merge_leaves_data
{
167 struct ppcg_grouping_leaf
*src
;
168 struct ppcg_grouping_leaf
*dst
;
171 /* Given a relation "map" between instances of two statements A and B,
172 * does it relate every instance of A (according to the domain of "src")
173 * to every instance of B (according to the domain of "dst")?
175 static isl_bool
covers_src_and_dst(__isl_keep isl_map
*map
,
176 struct ppcg_grouping_leaf
*src
, struct ppcg_grouping_leaf
*dst
)
179 isl_set
*set1
, *set2
;
182 space
= isl_space_domain(isl_map_get_space(map
));
183 set1
= isl_union_set_extract_set(src
->domain
, space
);
184 set2
= isl_map_domain(isl_map_copy(map
));
185 is_subset
= isl_set_is_subset(set1
, set2
);
188 if (is_subset
< 0 || !is_subset
)
191 space
= isl_space_range(isl_map_get_space(map
));
192 set1
= isl_union_set_extract_set(dst
->domain
, space
);
193 set2
= isl_map_range(isl_map_copy(map
));
194 is_subset
= isl_set_is_subset(set1
, set2
);
201 /* Given a relation "map" between instances of two statements A and B,
202 * are pairs of related instances executed together in the input schedule?
203 * That is, is each pair of instances assigned the same value
204 * by the corresponding prefix schedules?
206 * In particular, select the subset of "map" that has pairs of elements
207 * with the same value for the prefix schedules and then check
208 * if "map" is still a subset of the result.
210 static isl_bool
matches_prefix(__isl_keep isl_map
*map
,
211 struct ppcg_grouping_leaf
*src
, struct ppcg_grouping_leaf
*dst
)
213 isl_union_map
*umap
, *equal
;
214 isl_multi_union_pw_aff
*src_prefix
, *dst_prefix
, *prefix
;
217 src_prefix
= isl_multi_union_pw_aff_copy(src
->prefix
);
218 dst_prefix
= isl_multi_union_pw_aff_copy(dst
->prefix
);
219 prefix
= isl_multi_union_pw_aff_union_add(src_prefix
, dst_prefix
);
221 umap
= isl_union_map_from_map(isl_map_copy(map
));
222 equal
= isl_union_map_copy(umap
);
223 equal
= isl_union_map_eq_at_multi_union_pw_aff(equal
, prefix
);
225 is_subset
= isl_union_map_is_subset(umap
, equal
);
227 isl_union_map_free(umap
);
228 isl_union_map_free(equal
);
233 /* Given a set of validity and proximity schedule constraints "map"
234 * between statements in consecutive leaves in a valid schedule,
235 * should the two leaves be merged into one?
237 * In particular, the two are merged if the constraints form
238 * a bijection between every instance of the first statement and
239 * every instance of the second statement. Moreover, each
240 * pair of such dependent instances needs to be executed consecutively
241 * in the input schedule. That is, they need to be assigned
242 * the same value by their prefix schedules.
244 * What this means is that for each instance of the first statement
245 * there is exactly one instance of the second statement that
246 * is executed immediately after the instance of the first statement and
247 * that, moreover, both depends on this statement instance and
248 * should be brought as close as possible to this statement instance.
249 * In other words, it is both possible to execute the two instances
250 * together (according to the input schedule) and desirable to do so
251 * (according to the validity and proximity schedule constraints).
253 static isl_stat
check_merge(__isl_take isl_map
*map
, void *user
)
255 struct ppcg_merge_leaves_data
*data
= user
;
258 ok
= covers_src_and_dst(map
, data
->src
, data
->dst
);
260 ok
= isl_map_is_bijective(map
);
262 ok
= matches_prefix(map
, data
->src
, data
->dst
);
267 return isl_stat_error
;
272 return isl_stat_error
;
275 /* Merge the leaves at position "pos" and "pos + 1" in "leaves".
277 static isl_stat
merge_pair(int n
, struct ppcg_grouping_leaf leaves
[n
], int pos
)
281 leaves
[pos
].domain
= isl_union_set_union(leaves
[pos
].domain
,
282 leaves
[pos
+ 1].domain
);
283 leaves
[pos
].list
= isl_union_set_list_concat(leaves
[pos
].list
,
284 leaves
[pos
+ 1].list
);
285 leaves
[pos
].prefix
= isl_multi_union_pw_aff_union_add(
286 leaves
[pos
].prefix
, leaves
[pos
+ 1].prefix
);
287 for (i
= pos
+ 1; i
+ 1 < n
; ++i
)
288 leaves
[i
] = leaves
[i
+ 1];
289 leaves
[n
- 1].domain
= NULL
;
290 leaves
[n
- 1].list
= NULL
;
291 leaves
[n
- 1].prefix
= NULL
;
293 if (!leaves
[pos
].domain
|| !leaves
[pos
].list
|| !leaves
[pos
].prefix
)
294 return isl_stat_error
;
299 /* Merge pairs of consecutive leaves in "leaves" taking into account
300 * the intersection of validity and proximity schedule constraints "dep".
302 * If a leaf has been merged with the next leaf, then the combination
303 * is checked again for merging with the next leaf.
304 * That is, if the leaves are A, B and C, then B may not have been
305 * merged with C, but after merging A and B, it could still be useful
306 * to merge the combination AB with C.
308 * Two leaves A and B are merged if there are instances of at least
309 * one pair of statements, one statement in A and one B, such that
310 * the validity and proximity schedule constraints between them
311 * make them suitable for merging according to check_merge.
313 * Return the final number of leaves in the sequence, or -1 on error.
315 static int merge_leaves(int n
, struct ppcg_grouping_leaf leaves
[n
],
316 __isl_keep isl_union_map
*dep
)
319 struct ppcg_merge_leaves_data data
;
321 for (i
= n
- 1; i
>= 0; --i
) {
322 isl_union_map
*dep_i
;
328 dep_i
= isl_union_map_copy(dep
);
329 dep_i
= isl_union_map_intersect_domain(dep_i
,
330 isl_union_set_copy(leaves
[i
].domain
));
331 dep_i
= isl_union_map_intersect_range(dep_i
,
332 isl_union_set_copy(leaves
[i
+ 1].domain
));
334 data
.src
= &leaves
[i
];
335 data
.dst
= &leaves
[i
+ 1];
336 ok
= isl_union_map_foreach_map(dep_i
, &check_merge
, &data
);
337 isl_union_map_free(dep_i
);
338 if (ok
< 0 && !data
.merge
)
342 if (merge_pair(n
, leaves
, i
) < 0)
351 /* Construct a schedule with "domain" as domain, that executes
352 * the elements of "list" in order (as a sequence).
354 static __isl_give isl_schedule
*schedule_from_domain_and_list(
355 __isl_keep isl_union_set
*domain
, __isl_keep isl_union_set_list
*list
)
357 isl_schedule
*schedule
;
358 isl_schedule_node
*node
;
360 schedule
= isl_schedule_from_domain(isl_union_set_copy(domain
));
361 node
= isl_schedule_get_root(schedule
);
362 isl_schedule_free(schedule
);
363 node
= isl_schedule_node_child(node
, 0);
364 list
= isl_union_set_list_copy(list
);
365 node
= isl_schedule_node_insert_sequence(node
, list
);
366 schedule
= isl_schedule_node_get_schedule(node
);
367 isl_schedule_node_free(node
);
372 /* Construct a unique identifier for a group in "grouping".
374 * The name is of the form G_n, with n the first value starting at
375 * grouping->group_id that does not result in an identifier
376 * that is already in use in the domain of the original schedule
379 static isl_id
*construct_group_id(struct ppcg_grouping
*grouping
,
380 __isl_take isl_space
*space
)
385 isl_union_set
*domain
;
390 ctx
= isl_space_get_ctx(space
);
391 domain
= isl_schedule_constraints_get_domain(grouping
->sc
);
398 snprintf(buffer
, sizeof(buffer
), "G_%d", grouping
->group_id
);
399 grouping
->group_id
++;
400 id
= isl_id_alloc(ctx
, buffer
, NULL
);
401 space
= isl_space_set_tuple_id(space
, isl_dim_set
, id
);
402 set
= isl_union_set_extract_set(domain
, isl_space_copy(space
));
403 empty
= isl_set_plain_is_empty(set
);
405 } while (empty
>= 0 && !empty
);
408 space
= isl_space_free(space
);
410 id
= isl_space_get_tuple_id(space
, isl_dim_set
);
412 isl_space_free(space
);
413 isl_union_set_free(domain
);
418 /* Construct a contraction from "prefix" and "domain" for a new group
421 * The values of the prefix schedule "prefix" are used as instances
422 * of the new group. The identifier of the group is constructed
423 * in such a way that it does not conflict with those of earlier
424 * groups nor with statements in the domain of the original
425 * schedule constraints.
426 * The isl_multi_union_pw_aff "prefix" then simply needs to be
427 * converted to an isl_union_pw_multi_aff. However, this is not
428 * possible if "prefix" is zero-dimensional, so in this case,
429 * a contraction is constructed from "domain" instead.
431 static isl_union_pw_multi_aff
*group_contraction_from_prefix_and_domain(
432 struct ppcg_grouping
*grouping
,
433 __isl_keep isl_multi_union_pw_aff
*prefix
,
434 __isl_keep isl_union_set
*domain
)
440 space
= isl_multi_union_pw_aff_get_space(prefix
);
443 dim
= isl_space_dim(space
, isl_dim_set
);
444 id
= construct_group_id(grouping
, space
);
448 space
= isl_multi_union_pw_aff_get_space(prefix
);
449 space
= isl_space_set_tuple_id(space
, isl_dim_set
, id
);
450 mv
= isl_multi_val_zero(space
);
451 domain
= isl_union_set_copy(domain
);
452 return isl_union_pw_multi_aff_multi_val_on_domain(domain
, mv
);
454 prefix
= isl_multi_union_pw_aff_copy(prefix
);
455 prefix
= isl_multi_union_pw_aff_set_tuple_id(prefix
, isl_dim_out
, id
);
456 return isl_union_pw_multi_aff_from_multi_union_pw_aff(prefix
);
459 /* Remove the validity schedule constraints from "sc" between
460 * statement instances that get contracted to the same group instance
461 * by the contraction described by "prefix" and "domain".
463 * The values of the prefix schedule "prefix" are used as instances
464 * of the new group. This means that validity schedule constraints
465 * between instances with the same prefix schedule value need to be removed.
466 * If "prefix" is zero-dimensional, then it does not contain any
467 * information about the domain. Instead, those schedule constraints
468 * are removed that connect pairs of instances in "domain".
470 static __isl_give isl_schedule_constraints
*remove_group_validity(
471 __isl_take isl_schedule_constraints
*sc
,
472 __isl_keep isl_multi_union_pw_aff
*prefix
,
473 __isl_keep isl_union_set
*domain
)
476 isl_union_map
*validity
, *joined
;
478 validity
= isl_schedule_constraints_get_validity(sc
);
479 joined
= isl_union_map_copy(validity
);
480 n
= isl_multi_union_pw_aff_dim(prefix
, isl_dim_out
);
482 joined
= isl_union_map_intersect_domain(joined
,
483 isl_union_set_copy(domain
));
484 joined
= isl_union_map_intersect_range(joined
,
485 isl_union_set_copy(domain
));
487 joined
= isl_union_map_eq_at_multi_union_pw_aff(joined
,
488 isl_multi_union_pw_aff_copy(prefix
));
490 validity
= isl_union_map_subtract(validity
, joined
);
491 sc
= isl_schedule_constraints_set_validity(sc
, validity
);
495 /* Extend "grouping" with groups corresponding to merged
496 * leaves in the list of potentially merged leaves "leaves".
498 * The "list" field of each element in "leaves" contains a list
499 * of the instances sets of the original leaves that have been
500 * merged into this element. If at least two of the original leaves
501 * have been merged into a given element, then add the corresponding
502 * group to "grouping" and remove validity schedule constraints
503 * between statement instances that get mapped to the same group instance.
504 * In particular, the domain is extended with the statement instances
505 * of the merged leaves, the contraction is extended with a mapping
506 * of these statement instances to instances of a new group and
507 * the schedule is extended with a schedule that executes
508 * the statement instances according to the order of the leaves
509 * in which they appear.
510 * Since the instances of the groups should already be scheduled apart
511 * in the schedule into which this schedule will be plugged in,
512 * the schedules of the individual groups are combined independently
513 * of each other (as a set).
515 static isl_stat
add_groups(struct ppcg_grouping
*grouping
,
516 int n
, struct ppcg_grouping_leaf leaves
[n
])
520 for (i
= 0; i
< n
; ++i
) {
522 isl_schedule
*schedule
;
523 isl_union_set
*domain
;
524 isl_union_pw_multi_aff
*upma
;
526 n_leaf
= isl_union_set_list_n_union_set(leaves
[i
].list
);
528 return isl_stat_error
;
531 schedule
= schedule_from_domain_and_list(leaves
[i
].domain
,
533 upma
= group_contraction_from_prefix_and_domain(grouping
,
534 leaves
[i
].prefix
, leaves
[i
].domain
);
535 grouping
->sc
= remove_group_validity(grouping
->sc
,
536 leaves
[i
].prefix
, leaves
[i
].domain
);
538 domain
= isl_union_set_copy(leaves
[i
].domain
);
539 if (grouping
->domain
) {
540 domain
= isl_union_set_union(domain
, grouping
->domain
);
541 upma
= isl_union_pw_multi_aff_union_add(upma
,
542 grouping
->contraction
);
543 schedule
= isl_schedule_set(schedule
,
546 grouping
->domain
= domain
;
547 grouping
->contraction
= upma
;
548 grouping
->schedule
= schedule
;
550 if (!grouping
->domain
|| !grouping
->contraction
||
552 return isl_stat_error
;
558 /* Look for any pairs of consecutive leaves among the "n" children of "node"
559 * starting at "first" that should be merged together.
560 * Store the results in "grouping".
562 * First make sure the intersection of validity and proximity
563 * schedule constraints is available and extract the required
564 * information from the "n" leaves.
565 * Then try and merge consecutive leaves based on the validity
566 * and proximity constraints.
567 * If any pairs were successfully merged, then add groups
568 * corresponding to the merged leaves to "grouping".
570 static isl_stat
group_subsequence(__isl_keep isl_schedule_node
*node
,
571 int first
, int n
, struct ppcg_grouping
*grouping
)
574 struct ppcg_grouping_leaf
*leaves
;
576 if (ppcg_grouping_compute_dep(grouping
) < 0)
577 return isl_stat_error
;
579 leaves
= extract_leaves(node
, first
, n
);
581 return isl_stat_error
;
583 n_merge
= merge_leaves(n
, leaves
, grouping
->dep
);
584 if (n_merge
>= 0 && n_merge
< n
&&
585 add_groups(grouping
, n_merge
, leaves
) < 0)
586 return isl_stat_error
;
588 ppcg_grouping_leaf_free(n
, leaves
);
593 /* If "node" is a sequence, then check if it has any consecutive
594 * leaves that should be merged together and store the results
597 * In particular, call group_subsequence on each consecutive
598 * sequence of (filtered) leaves among the children of "node".
600 static isl_bool
detect_groups(__isl_keep isl_schedule_node
*node
, void *user
)
603 struct ppcg_grouping
*grouping
= user
;
605 if (isl_schedule_node_get_type(node
) != isl_schedule_node_sequence
)
606 return isl_bool_true
;
608 n
= isl_schedule_node_n_children(node
);
610 return isl_bool_error
;
613 for (i
= 0; i
< n
; ++i
) {
614 isl_schedule_node
*child
;
615 enum isl_schedule_node_type type
;
617 child
= isl_schedule_node_get_child(node
, i
);
618 child
= isl_schedule_node_child(child
, 0);
619 type
= isl_schedule_node_get_type(child
);
620 isl_schedule_node_free(child
);
622 if (first
>= 0 && type
!= isl_schedule_node_leaf
) {
623 if (group_subsequence(node
, first
, i
- first
,
625 return isl_bool_error
;
628 if (first
< 0 && type
== isl_schedule_node_leaf
)
632 if (group_subsequence(node
, first
, n
- first
, grouping
) < 0)
633 return isl_bool_error
;
636 return isl_bool_true
;
639 /* Complete "grouping" to cover all statement instances in the domain
642 * In particular, grouping->domain is set to the full set of statement
643 * instances; group->contraction is extended with an identity
644 * contraction on the additional instances and group->schedule
645 * is extended with an independent schedule on those additional instances.
646 * In the extension of group->contraction, the additional instances
647 * are split into those belong to different statements and those
648 * that belong to some of the same statements. The first group
649 * is replaced by its universe in order to simplify the contraction extension.
651 static void complete_grouping(struct ppcg_grouping
*grouping
)
653 isl_union_set
*domain
, *left
, *overlap
;
654 isl_union_pw_multi_aff
*upma
;
655 isl_schedule
*schedule
;
657 domain
= isl_schedule_constraints_get_domain(grouping
->sc
);
658 left
= isl_union_set_subtract(isl_union_set_copy(domain
),
659 isl_union_set_copy(grouping
->domain
));
660 schedule
= isl_schedule_from_domain(isl_union_set_copy(left
));
661 schedule
= isl_schedule_set(schedule
, grouping
->schedule
);
662 grouping
->schedule
= schedule
;
664 overlap
= isl_union_set_universe(grouping
->domain
);
665 grouping
->domain
= domain
;
666 overlap
= isl_union_set_intersect(isl_union_set_copy(left
), overlap
);
667 left
= isl_union_set_subtract(left
, isl_union_set_copy(overlap
));
668 left
= isl_union_set_universe(left
);
669 left
= isl_union_set_union(left
, overlap
);
670 upma
= isl_union_set_identity_union_pw_multi_aff(left
);
671 upma
= isl_union_pw_multi_aff_union_add(upma
, grouping
->contraction
);
672 grouping
->contraction
= upma
;
675 /* Report that the given grouping is used during scheduling
676 * (if the verbose options is set).
678 static void report_grouping(__isl_keep isl_union_pw_multi_aff
*contraction
,
679 struct ppcg_options
*options
)
684 if (!options
->debug
->verbose
)
687 ctx
= isl_union_pw_multi_aff_get_ctx(contraction
);
688 p
= isl_printer_to_file(ctx
, stdout
);
689 p
= isl_printer_print_str(p
, "Scheduling performed with grouping ");
690 p
= isl_printer_print_union_pw_multi_aff(p
, contraction
);
691 p
= isl_printer_print_str(p
, " (use --no-group-chains to disable)");
692 p
= isl_printer_end_line(p
);
696 /* Compute a schedule on the domain of "sc" that respects the schedule
697 * constraints in "sc", after trying to combine groups of statements.
699 * "schedule" is a known correct schedule that is used while combining
700 * groups of statements.
701 * In particular, statements that are executed consecutively in a sequence
702 * in this schedule and where all instances of the second depend on
703 * the instance of the first that is executed in the same iteration
704 * of outer band nodes are grouped together into a single statement.
705 * The schedule constraints are then mapped to these groups of statements
706 * and the resulting schedule is expanded again to refer to the original
709 __isl_give isl_schedule
*ppcg_compute_grouping_schedule(
710 __isl_take isl_schedule_constraints
*sc
,
711 __isl_keep isl_schedule
*schedule
, struct ppcg_options
*options
)
713 struct ppcg_grouping grouping
= { sc
};
714 isl_union_pw_multi_aff
*contraction
;
716 isl_schedule
*res
, *expansion
;
718 grouping
.group_id
= 0;
719 if (isl_schedule_foreach_schedule_node_top_down(schedule
,
720 &detect_groups
, &grouping
) < 0)
722 if (!grouping
.contraction
) {
723 ppcg_grouping_clear(&grouping
);
724 return ppcg_compute_non_grouping_schedule(grouping
.sc
, options
);
726 complete_grouping(&grouping
);
727 contraction
= isl_union_pw_multi_aff_copy(grouping
.contraction
);
728 report_grouping(contraction
, options
);
729 umap
= isl_union_map_from_union_pw_multi_aff(contraction
);
731 sc
= isl_schedule_constraints_apply(grouping
.sc
, umap
);
733 res
= ppcg_compute_non_grouping_schedule(sc
, options
);
735 contraction
= isl_union_pw_multi_aff_copy(grouping
.contraction
);
736 expansion
= isl_schedule_copy(grouping
.schedule
);
737 res
= isl_schedule_expand(res
, contraction
, expansion
);
739 ppcg_grouping_clear(&grouping
);
742 ppcg_grouping_clear(&grouping
);
743 isl_schedule_constraints_free(sc
);