1 | /* |
2 | * Copyright 2008-2009 Katholieke Universiteit Leuven |
3 | * Copyright 2010 INRIA Saclay |
4 | * Copyright 2012-2013 Ecole Normale Superieure |
5 | * Copyright 2014 INRIA Rocquencourt |
6 | * Copyright 2021-2022 Cerebras Systems |
7 | * |
8 | * Use of this software is governed by the MIT license |
9 | * |
10 | * Written by Sven Verdoolaege, K.U.Leuven, Departement |
11 | * Computerwetenschappen, Celestijnenlaan 200A, B-3001 Leuven, Belgium |
12 | * and INRIA Saclay - Ile-de-France, Parc Club Orsay Universite, |
13 | * ZAC des vignes, 4 rue Jacques Monod, 91893 Orsay, France |
14 | * and Ecole Normale Superieure, 45 rue d'Ulm, 75230 Paris, France |
15 | * and Inria Paris - Rocquencourt, Domaine de Voluceau - Rocquencourt, |
16 | * B.P. 105 - 78153 Le Chesnay, France |
17 | * and Cerebras Systems, 1237 E Arques Ave, Sunnyvale, CA, USA |
18 | */ |
19 | |
20 | #include <assert.h> |
21 | #include <stdlib.h> |
22 | |
23 | #include <functional> |
24 | #include <iostream> |
25 | #include <sstream> |
26 | #include <string> |
27 | #include <type_traits> |
28 | #include <utility> |
29 | #include <vector> |
30 | |
31 | #include <isl/cpp.h> |
32 | |
33 | /* A binary isl function that appears in the C++ bindings |
34 | * as a unary method in a class T, taking an extra argument |
35 | * of type A1 and returning an object of type R. |
36 | */ |
37 | template <typename A1, typename R, typename T> |
38 | using binary_fn = R (T::*)(A1) const; |
39 | |
40 | /* A function for selecting an overload of a pointer to a unary C++ method |
41 | * based on the single argument type. |
42 | * The object type and the return type are meant to be deduced. |
43 | */ |
44 | template <typename A1, typename R, typename T> |
45 | static binary_fn<A1, R, T> const arg(const binary_fn<A1, R, T> &fn) |
46 | { |
47 | return fn; |
48 | } |
49 | |
50 | /* A ternary isl function that appears in the C++ bindings |
51 | * as a binary method in a class T, taking extra arguments |
52 | * of type A1 and A2 and returning an object of type R. |
53 | */ |
54 | template <typename A1, typename A2, typename R, typename T> |
55 | using ternary_fn = R (T::*)(A1, A2) const; |
56 | |
57 | /* A function for selecting an overload of a pointer to a binary C++ method |
58 | * based on the (first) argument type(s). |
59 | * The object type and the return type are meant to be deduced. |
60 | */ |
61 | template <typename A1, typename A2, typename R, typename T> |
62 | static ternary_fn<A1, A2, R, T> const arg(const ternary_fn<A1, A2, R, T> &fn) |
63 | { |
64 | return fn; |
65 | } |
66 | |
67 | /* A description of the input and the output of a unary operation. |
68 | */ |
69 | struct unary { |
70 | const char *arg; |
71 | const char *res; |
72 | }; |
73 | |
74 | /* A description of the inputs and the output of a binary operation. |
75 | */ |
76 | struct binary { |
77 | const char *arg1; |
78 | const char *arg2; |
79 | const char *res; |
80 | }; |
81 | |
82 | /* A description of the inputs and the output of a ternary operation. |
83 | */ |
84 | struct ternary { |
85 | const char *arg1; |
86 | const char *arg2; |
87 | const char *arg3; |
88 | const char *res; |
89 | }; |
90 | |
91 | /* A template function for checking whether two objects |
92 | * of the same (isl) type are (obviously) equal. |
93 | * The spelling depends on the isl type and |
94 | * in particular on whether an equality method is available or |
95 | * whether only obvious equality can be tested. |
96 | */ |
97 | template <typename T, typename std::decay<decltype( |
98 | std::declval<T>().is_equal(std::declval<T>()))>::type = true> |
99 | static bool is_equal(const T &a, const T &b) |
100 | { |
101 | return a.is_equal(b); |
102 | } |
103 | template <typename T, typename std::decay<decltype( |
104 | std::declval<T>().plain_is_equal(std::declval<T>()))>::type = true> |
105 | static bool is_equal(const T &a, const T &b) |
106 | { |
107 | return a.plain_is_equal(b); |
108 | } |
109 | |
110 | /* A helper macro for throwing an isl::exception_invalid with message "msg". |
111 | */ |
112 | #define THROW_INVALID(msg) \ |
113 | isl::exception::throw_error(isl_error_invalid, msg, __FILE__, __LINE__) |
114 | |
115 | /* Run a sequence of tests of method "fn" with stringification "name" and |
116 | * with input and output described by "test", |
117 | * throwing an exception when an unexpected result is produced. |
118 | */ |
119 | template <typename R, typename T> |
120 | static void test(isl::ctx ctx, R (T::*fn)() const, const std::string &name, |
121 | const std::vector<unary> &tests) |
122 | { |
123 | for (const auto &test : tests) { |
124 | T obj(ctx, test.arg); |
125 | R expected(ctx, test.res); |
126 | const auto &res = (obj.*fn)(); |
127 | std::ostringstream ss; |
128 | |
129 | if (is_equal(expected, res)) |
130 | continue; |
131 | |
132 | ss << name << "(" << test.arg << ") =\n" |
133 | << res << "\n" |
134 | << "expecting:\n" |
135 | << expected; |
136 | THROW_INVALID(ss.str().c_str()); |
137 | } |
138 | } |
139 | |
140 | /* Run a sequence of tests of method "fn" with stringification "name" and |
141 | * with inputs and output described by "test", |
142 | * throwing an exception when an unexpected result is produced. |
143 | */ |
144 | template <typename R, typename T, typename A1> |
145 | static void test(isl::ctx ctx, R (T::*fn)(A1) const, const std::string &name, |
146 | const std::vector<binary> &tests) |
147 | { |
148 | for (const auto &test : tests) { |
149 | T obj(ctx, test.arg1); |
150 | A1 arg1(ctx, test.arg2); |
151 | R expected(ctx, test.res); |
152 | const auto &res = (obj.*fn)(arg1); |
153 | std::ostringstream ss; |
154 | |
155 | if (is_equal(expected, res)) |
156 | continue; |
157 | |
158 | ss << name << "(" << test.arg1 << ", " << test.arg2 << ") =\n" |
159 | << res << "\n" |
160 | << "expecting:\n" |
161 | << test.res; |
162 | THROW_INVALID(ss.str().c_str()); |
163 | } |
164 | } |
165 | |
166 | /* Run a sequence of tests of method "fn" with stringification "name" and |
167 | * with inputs and output described by "test", |
168 | * throwing an exception when an unexpected result is produced. |
169 | */ |
170 | template <typename R, typename T, typename A1, typename A2> |
171 | static void test(isl::ctx ctx, R (T::*fn)(A1, A2) const, |
172 | const std::string &name, const std::vector<ternary> &tests) |
173 | { |
174 | for (const auto &test : tests) { |
175 | T obj(ctx, test.arg1); |
176 | A1 arg1(ctx, test.arg2); |
177 | A2 arg2(ctx, test.arg3); |
178 | R expected(ctx, test.res); |
179 | const auto &res = (obj.*fn)(arg1, arg2); |
180 | std::ostringstream ss; |
181 | |
182 | if (is_equal(expected, res)) |
183 | continue; |
184 | |
185 | ss << name << "(" << test.arg1 << ", " << test.arg2 << ", " |
186 | << test.arg3 << ") =\n" |
187 | << res << "\n" |
188 | << "expecting:\n" |
189 | << expected; |
190 | THROW_INVALID(ss.str().c_str()); |
191 | } |
192 | } |
193 | |
194 | /* A helper macro that calls test with as implicit initial argument "ctx" and |
195 | * as extra argument a stringification of "FN". |
196 | */ |
197 | #define C(FN, ...) test(ctx, FN, #FN, __VA_ARGS__) |
198 | |
199 | /* Perform some basic isl::space tests. |
200 | */ |
201 | static void test_space(isl::ctx ctx) |
202 | { |
203 | C(&isl::space::domain, { |
204 | { "{ A[] -> B[] }" , "{ A[] }" }, |
205 | { "{ A[C[] -> D[]] -> B[E[] -> F[]] }" , "{ A[C[] -> D[]] }" }, |
206 | }); |
207 | |
208 | C(&isl::space::range, { |
209 | { "{ A[] -> B[] }" , "{ B[] }" }, |
210 | { "{ A[C[] -> D[]] -> B[E[] -> F[]] }" , "{ B[E[] -> F[]] }" }, |
211 | }); |
212 | |
213 | C(&isl::space::params, { |
214 | { "{ A[] -> B[] }" , "{ : }" }, |
215 | { "{ A[C[] -> D[]] -> B[E[] -> F[]] }" , "{ : }" }, |
216 | }); |
217 | } |
218 | |
219 | /* Perform some basic conversion tests. |
220 | */ |
221 | static void test_conversion(isl::ctx ctx) |
222 | { |
223 | C(&isl::multi_pw_aff::as_set, { |
224 | { "[n] -> { [] : n >= 0 } " , |
225 | "[n] -> { [] : n >= 0 } " }, |
226 | }); |
227 | } |
228 | |
229 | /* Perform some basic preimage tests. |
230 | */ |
231 | static void test_preimage(isl::ctx ctx) |
232 | { |
233 | C(arg<isl::multi_aff>(&isl::set::preimage), { |
234 | { "{ B[i,j] : 0 <= i < 10 and 0 <= j < 100 }" , |
235 | "{ A[j,i] -> B[i,j] }" , |
236 | "{ A[j,i] : 0 <= i < 10 and 0 <= j < 100 }" }, |
237 | { "{ rat: B[i,j] : 0 <= i, j and 3 i + 5 j <= 100 }" , |
238 | "{ A[a,b] -> B[a/2,b/6] }" , |
239 | "{ rat: A[a,b] : 0 <= a, b and 9 a + 5 b <= 600 }" }, |
240 | { "{ B[i,j] : 0 <= i, j and 3 i + 5 j <= 100 }" , |
241 | "{ A[a,b] -> B[a/2,b/6] }" , |
242 | "{ A[a,b] : 0 <= a, b and 9 a + 5 b <= 600 and " |
243 | "exists i,j : a = 2 i and b = 6 j }" }, |
244 | { "[n] -> { S[i] : 0 <= i <= 100 }" , "[n] -> { S[n] }" , |
245 | "[n] -> { : 0 <= n <= 100 }" }, |
246 | { "{ B[i] : 0 <= i < 100 and exists a : i = 4 a }" , |
247 | "{ A[a] -> B[2a] }" , |
248 | "{ A[a] : 0 <= a < 50 and exists b : a = 2 b }" }, |
249 | { "{ B[i] : 0 <= i < 100 and exists a : i = 4 a }" , |
250 | "{ A[a] -> B[([a/2])] }" , |
251 | "{ A[a] : 0 <= a < 200 and exists b : [a/2] = 4 b }" }, |
252 | { "{ B[i,j,k] : 0 <= i,j,k <= 100 }" , |
253 | "{ A[a] -> B[a,a,a/3] }" , |
254 | "{ A[a] : 0 <= a <= 100 and exists b : a = 3 b }" }, |
255 | { "{ B[i,j] : j = [(i)/2] } " , "{ A[i,j] -> B[i/3,j] }" , |
256 | "{ A[i,j] : j = [(i)/6] and exists a : i = 3 a }" }, |
257 | }); |
258 | |
259 | C(arg<isl::multi_aff>(&isl::union_map::preimage_domain), { |
260 | { "{ B[i,j] -> C[2i + 3j] : 0 <= i < 10 and 0 <= j < 100 }" , |
261 | "{ A[j,i] -> B[i,j] }" , |
262 | "{ A[j,i] -> C[2i + 3j] : 0 <= i < 10 and 0 <= j < 100 }" }, |
263 | { "{ B[i] -> C[i]; D[i] -> E[i] }" , |
264 | "{ A[i] -> B[i + 1] }" , |
265 | "{ A[i] -> C[i + 1] }" }, |
266 | { "{ B[i] -> C[i]; B[i] -> E[i] }" , |
267 | "{ A[i] -> B[i + 1] }" , |
268 | "{ A[i] -> C[i + 1]; A[i] -> E[i + 1] }" }, |
269 | { "{ B[i] -> C[([i/2])] }" , |
270 | "{ A[i] -> B[2i] }" , |
271 | "{ A[i] -> C[i] }" }, |
272 | { "{ B[i,j] -> C[([i/2]), ([(i+j)/3])] }" , |
273 | "{ A[i] -> B[([i/5]), ([i/7])] }" , |
274 | "{ A[i] -> C[([([i/5])/2]), ([(([i/5])+([i/7]))/3])] }" }, |
275 | { "[N] -> { B[i] -> C[([N/2]), i, ([N/3])] }" , |
276 | "[N] -> { A[] -> B[([N/5])] }" , |
277 | "[N] -> { A[] -> C[([N/2]), ([N/5]), ([N/3])] }" }, |
278 | { "{ B[i] -> C[i] : exists a : i = 5 a }" , |
279 | "{ A[i] -> B[2i] }" , |
280 | "{ A[i] -> C[2i] : exists a : 2i = 5 a }" }, |
281 | { "{ B[i] -> C[i] : exists a : i = 2 a; " |
282 | "B[i] -> D[i] : exists a : i = 2 a + 1 }" , |
283 | "{ A[i] -> B[2i] }" , |
284 | "{ A[i] -> C[2i] }" }, |
285 | { "{ A[i] -> B[i] }" , "{ C[i] -> A[(i + floor(i/3))/2] }" , |
286 | "{ C[i] -> B[j] : 2j = i + floor(i/3) }" }, |
287 | }); |
288 | |
289 | C(arg<isl::multi_aff>(&isl::union_map::preimage_range), { |
290 | { "[M] -> { A[a] -> B[a] }" , "[M] -> { C[] -> B[floor(M/2)] }" , |
291 | "[M] -> { A[floor(M/2)] -> C[] }" }, |
292 | }); |
293 | } |
294 | |
295 | /* Perform some basic fixed power tests. |
296 | */ |
297 | static void test_fixed_power(isl::ctx ctx) |
298 | { |
299 | C(arg<isl::val>(&isl::map::fixed_power), { |
300 | { "{ [i] -> [i + 1] }" , "23" , |
301 | "{ [i] -> [i + 23] }" }, |
302 | { "{ [a = 0:1, b = 0:15, c = 0:1, d = 0:1, 0] -> [a, b, c, d, 1]; " |
303 | "[a = 0:1, b = 0:15, c = 0:1, 0, 1] -> [a, b, c, 1, 0]; " |
304 | "[a = 0:1, b = 0:15, 0, 1, 1] -> [a, b, 1, 0, 0]; " |
305 | "[a = 0:1, b = 0:14, 1, 1, 1] -> [a, 1 + b, 0, 0, 0]; " |
306 | "[0, 15, 1, 1, 1] -> [1, 0, 0, 0, 0] }" , |
307 | "128" , |
308 | "{ [0, b = 0:15, c = 0:1, d = 0:1, e = 0:1] -> [1, b, c, d, e] }" }, |
309 | }); |
310 | } |
311 | |
312 | /* Perform some basic intersection tests. |
313 | */ |
314 | static void test_intersect(isl::ctx ctx) |
315 | { |
316 | C(&isl::union_map::intersect_domain_wrapped_domain, { |
317 | { "{ [A[x] -> B[y]] -> C[z]; [D[x] -> A[y]] -> E[z] }" , |
318 | "{ A[0] }" , |
319 | "{ [A[0] -> B[y]] -> C[z] }" }, |
320 | { "{ C[z] -> [A[x] -> B[y]]; E[z] -> [D[x] -> A[y]] }" , |
321 | "{ A[0] }" , |
322 | "{ }" }, |
323 | }); |
324 | |
325 | C(&isl::union_map::intersect_range_wrapped_domain, { |
326 | { "{ [A[x] -> B[y]] -> C[z]; [D[x] -> A[y]] -> E[z] }" , |
327 | "{ A[0] }" , |
328 | "{ }" }, |
329 | { "{ C[z] -> [A[x] -> B[y]]; E[z] -> [D[x] -> A[y]] }" , |
330 | "{ A[0] }" , |
331 | "{ C[z] -> [A[0] -> B[y]] }" }, |
332 | }); |
333 | } |
334 | |
335 | /* Perform some basic gist tests. |
336 | */ |
337 | static void test_gist(isl::ctx ctx) |
338 | { |
339 | C(&isl::pw_aff::gist_params, { |
340 | { "[N] -> { D[x] -> [x] : N >= 0; D[x] -> [0] : N < 0 }" , |
341 | "[N] -> { : N >= 0 }" , |
342 | "[N] -> { D[x] -> [x] }" }, |
343 | }); |
344 | } |
345 | |
346 | /* Perform tests that project out parameters. |
347 | */ |
348 | static void test_project(isl::ctx ctx) |
349 | { |
350 | C(arg<isl::id>(&isl::union_map::project_out_param), { |
351 | { "[N] -> { D[i] -> A[0:N-1]; D[i] -> B[i] }" , "N" , |
352 | "{ D[i] -> A[0:]; D[i] -> B[i] }" }, |
353 | { "[N] -> { D[i] -> A[0:N-1]; D[i] -> B[i] }" , "M" , |
354 | "[N] -> { D[i] -> A[0:N-1]; D[i] -> B[i] }" }, |
355 | }); |
356 | |
357 | C(arg<isl::id_list>(&isl::union_map::project_out_param), { |
358 | { "[M, N, O] -> { D[i] -> A[j] : i <= j < M, N, O }" , "(M, N)" , |
359 | "[O] -> { D[i] -> A[j] : i <= j < O }" }, |
360 | }); |
361 | } |
362 | |
363 | /* Perform some basic scaling tests. |
364 | */ |
365 | static void test_scale(isl::ctx ctx) |
366 | { |
367 | C(arg<isl::multi_val>(&isl::pw_multi_aff::scale), { |
368 | { "{ A[a] -> B[a, a + 1, a - 1] : a >= 0 }" , "{ B[2, 7, 0] }" , |
369 | "{ A[a] -> B[2a, 7a + 7, 0] : a >= 0 }" }, |
370 | }); |
371 | C(arg<isl::multi_val>(&isl::pw_multi_aff::scale), { |
372 | { "{ A[a] -> B[1, a - 1] : a >= 0 }" , "{ B[1/2, 7] }" , |
373 | "{ A[a] -> B[1/2, 7a - 7] : a >= 0 }" }, |
374 | }); |
375 | |
376 | C(arg<isl::multi_val>(&isl::pw_multi_aff::scale_down), { |
377 | { "{ A[a] -> B[a, a + 1] : a >= 0 }" , "{ B[2, 7] }" , |
378 | "{ A[a] -> B[a/2, (a + 1)/7] : a >= 0 }" }, |
379 | }); |
380 | C(arg<isl::multi_val>(&isl::pw_multi_aff::scale_down), { |
381 | { "{ A[a] -> B[a, a - 1] : a >= 0 }" , "{ B[2, 1/7] }" , |
382 | "{ A[a] -> B[a/2, 7a - 7] : a >= 0 }" }, |
383 | }); |
384 | } |
385 | |
386 | /* Perform some basic isl::id_to_id tests. |
387 | */ |
388 | static void test_id_to_id(isl::ctx ctx) |
389 | { |
390 | C((arg<isl::id, isl::id>(&isl::id_to_id::set)), { |
391 | { "{ }" , "a" , "b" , |
392 | "{ a: b }" }, |
393 | { "{ a: b }" , "a" , "b" , |
394 | "{ a: b }" }, |
395 | { "{ a: c }" , "a" , "b" , |
396 | "{ a: b }" }, |
397 | { "{ a: b }" , "b" , "a" , |
398 | "{ a: b, b: a }" }, |
399 | { "{ a: b }" , "b" , "a" , |
400 | "{ b: a, a: b }" }, |
401 | }); |
402 | } |
403 | |
404 | /* The list of tests to perform. |
405 | */ |
406 | static std::vector<std::pair<const char *, void (*)(isl::ctx)>> tests = |
407 | { |
408 | { "space" , &test_space }, |
409 | { "conversion" , &test_conversion }, |
410 | { "preimage" , &test_preimage }, |
411 | { "fixed power" , &test_fixed_power }, |
412 | { "intersect" , &test_intersect }, |
413 | { "gist" , &test_gist }, |
414 | { "project out parameters" , &test_project }, |
415 | { "scale" , &test_scale }, |
416 | { "id-to-id" , &test_id_to_id }, |
417 | }; |
418 | |
419 | /* Perform some basic checks by means of the C++ bindings. |
420 | */ |
421 | int main(int argc, char **argv) |
422 | { |
423 | int ret = EXIT_SUCCESS; |
424 | struct isl_ctx *ctx; |
425 | struct isl_options *options; |
426 | |
427 | options = isl_options_new_with_defaults(); |
428 | assert(options); |
429 | argc = isl_options_parse(options, argc, argv, ISL_ARG_ALL); |
430 | ctx = isl_ctx_alloc_with_options(&isl_options_args, options); |
431 | |
432 | try { |
433 | for (const auto &f : tests) { |
434 | std::cout << f.first << "\n" ; |
435 | f.second(ctx); |
436 | } |
437 | } catch (const isl::exception &e) { |
438 | std::cerr << e.what() << "\n" ; |
439 | ret = EXIT_FAILURE; |
440 | } |
441 | |
442 | isl_ctx_free(ctx); |
443 | return ret; |
444 | } |
445 | |