1 /*
2 //@HEADER
3 // ************************************************************************
4 //
5 //                        Kokkos v. 3.0
6 //       Copyright (2020) National Technology & Engineering
7 //               Solutions of Sandia, LLC (NTESS).
8 //
9 // Under the terms of Contract DE-NA0003525 with NTESS,
10 // the U.S. Government retains certain rights in this software.
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions are
14 // met:
15 //
16 // 1. Redistributions of source code must retain the above copyright
17 // notice, this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright
20 // notice, this list of conditions and the following disclaimer in the
21 // documentation and/or other materials provided with the distribution.
22 //
23 // 3. Neither the name of the Corporation nor the names of the
24 // contributors may be used to endorse or promote products derived from
25 // this software without specific prior written permission.
26 //
27 // THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
28 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
31 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 //
39 // Questions? Contact Christian R. Trott (crtrott@sandia.gov)
40 //
41 // ************************************************************************
42 //@HEADER
43 */
44 
45 #ifndef KOKKOS_TEST_DUALVIEW_HPP
46 #define KOKKOS_TEST_DUALVIEW_HPP
47 
48 #include <gtest/gtest.h>
49 #include <iostream>
50 #include <cstdlib>
51 #include <cstdio>
52 #include <impl/Kokkos_Timer.hpp>
53 #include <Kokkos_DualView.hpp>
54 
55 namespace Test {
56 
57 namespace Impl {
58 template <typename Scalar, class Device>
59 struct test_dualview_alloc {
60   using scalar_type     = Scalar;
61   using execution_space = Device;
62 
63   template <typename ViewType>
run_meTest::Impl::test_dualview_alloc64   bool run_me(unsigned int n, unsigned int m) {
65     if (n < 10) n = 10;
66     if (m < 3) m = 3;
67 
68     {
69       ViewType b1;
70       if (b1.is_allocated() == true) return false;
71 
72       b1 = ViewType("B1", n, m);
73       ViewType b2(b1);
74       ViewType b3("B3", n, m);
75 
76       if (b1.is_allocated() == false) return false;
77       if (b2.is_allocated() == false) return false;
78       if (b3.is_allocated() == false) return false;
79     }
80     return true;
81   }
82 
83   bool result = false;
84 
test_dualview_allocTest::Impl::test_dualview_alloc85   test_dualview_alloc(unsigned int size) {
86     result = run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(
87         size, 3);
88   }
89 };
90 
91 template <typename Scalar, class Device>
92 struct test_dualview_combinations {
93   using self_type = test_dualview_combinations<Scalar, Device>;
94 
95   using scalar_type     = Scalar;
96   using execution_space = Device;
97 
98   Scalar reference;
99   Scalar result;
100 
101   template <typename ViewType>
run_meTest::Impl::test_dualview_combinations102   Scalar run_me(unsigned int n, unsigned int m, bool with_init) {
103     if (n < 10) n = 10;
104     if (m < 3) m = 3;
105 
106     ViewType a;
107 
108     if (with_init) {
109       a = ViewType("A", n, m);
110     } else {
111       a = ViewType(Kokkos::view_alloc(Kokkos::WithoutInitializing, "A"), n, m);
112     }
113     Kokkos::deep_copy(a.d_view, 1);
114 
115     a.template modify<typename ViewType::execution_space>();
116     a.template sync<typename ViewType::host_mirror_space>();
117     a.template sync<typename ViewType::host_mirror_space>(
118         Kokkos::DefaultExecutionSpace{});
119 
120     a.h_view(5, 1) = 3;
121     a.h_view(6, 1) = 4;
122     a.h_view(7, 2) = 5;
123     a.template modify<typename ViewType::host_mirror_space>();
124     ViewType b = Kokkos::subview(a, std::pair<unsigned int, unsigned int>(6, 9),
125                                  std::pair<unsigned int, unsigned int>(0, 1));
126     a.template sync<typename ViewType::execution_space>();
127     a.template sync<typename ViewType::execution_space>(
128         Kokkos::DefaultExecutionSpace{});
129     b.template modify<typename ViewType::execution_space>();
130 
131     Kokkos::deep_copy(b.d_view, 2);
132 
133     a.template sync<typename ViewType::host_mirror_space>();
134     a.template sync<typename ViewType::host_mirror_space>(
135         Kokkos::DefaultExecutionSpace{});
136     Scalar count = 0;
137     for (unsigned int i = 0; i < a.d_view.extent(0); i++)
138       for (unsigned int j = 0; j < a.d_view.extent(1); j++)
139         count += a.h_view(i, j);
140     return count - a.d_view.extent(0) * a.d_view.extent(1) - 2 - 4 - 3 * 2;
141   }
142 
test_dualview_combinationsTest::Impl::test_dualview_combinations143   test_dualview_combinations(unsigned int size, bool with_init) {
144     result = run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(
145         size, 3, with_init);
146   }
147 };
148 
149 template <typename Scalar, class ViewType>
150 struct SumViewEntriesFunctor {
151   using value_type = Scalar;
152 
153   ViewType fv;
154 
SumViewEntriesFunctorTest::Impl::SumViewEntriesFunctor155   SumViewEntriesFunctor(const ViewType& fv_) : fv(fv_) {}
156 
157   KOKKOS_INLINE_FUNCTION
operator ()Test::Impl::SumViewEntriesFunctor158   void operator()(const int i, value_type& total) const {
159     for (size_t j = 0; j < fv.extent(1); ++j) {
160       total += fv(i, j);
161     }
162   }
163 };
164 
165 template <typename Scalar, class Device>
166 struct test_dual_view_deep_copy {
167   using scalar_type     = Scalar;
168   using execution_space = Device;
169 
170   template <typename ViewType>
run_meTest::Impl::test_dual_view_deep_copy171   void run_me(int n, const int m, const bool use_templ_sync) {
172     ViewType a, b;
173     if (n >= 0) {
174       a = ViewType("A", n, m);
175       b = ViewType("B", n, m);
176     } else {
177       n = 0;
178     }
179     const scalar_type sum_total = scalar_type(n * m);
180 
181     Kokkos::deep_copy(a.d_view, 1);
182 
183     if (use_templ_sync) {
184       a.template modify<typename ViewType::execution_space>();
185       a.template sync<typename ViewType::host_mirror_space>();
186     } else {
187       a.modify_device();
188       a.sync_host();
189       a.sync_host(Kokkos::DefaultExecutionSpace{});
190     }
191 
192     // Check device view is initialized as expected
193     scalar_type a_d_sum = 0;
194     // Execute on the execution_space associated with t_dev's memory space
195     using t_dev_exec_space =
196         typename ViewType::t_dev::memory_space::execution_space;
197     Kokkos::parallel_reduce(
198         Kokkos::RangePolicy<t_dev_exec_space>(0, n),
199         SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
200         a_d_sum);
201     ASSERT_EQ(a_d_sum, sum_total);
202 
203     // Check host view is synced as expected
204     scalar_type a_h_sum = 0;
205     for (size_t i = 0; i < a.h_view.extent(0); ++i)
206       for (size_t j = 0; j < a.h_view.extent(1); ++j) {
207         a_h_sum += a.h_view(i, j);
208       }
209 
210     ASSERT_EQ(a_h_sum, sum_total);
211 
212     // Test deep_copy
213     Kokkos::deep_copy(b, a);
214     if (use_templ_sync) {
215       b.template sync<typename ViewType::host_mirror_space>();
216     } else {
217       b.sync_host();
218       b.sync_host(Kokkos::DefaultExecutionSpace{});
219     }
220 
221     // Perform same checks on b as done on a
222     // Check device view is initialized as expected
223     scalar_type b_d_sum = 0;
224     // Execute on the execution_space associated with t_dev's memory space
225     Kokkos::parallel_reduce(
226         Kokkos::RangePolicy<t_dev_exec_space>(0, n),
227         SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(b.d_view),
228         b_d_sum);
229     ASSERT_EQ(b_d_sum, sum_total);
230 
231     // Check host view is synced as expected
232     scalar_type b_h_sum = 0;
233     for (size_t i = 0; i < b.h_view.extent(0); ++i)
234       for (size_t j = 0; j < b.h_view.extent(1); ++j) {
235         b_h_sum += b.h_view(i, j);
236       }
237 
238     ASSERT_EQ(b_h_sum, sum_total);
239 
240   }  // end run_me
241 
test_dual_view_deep_copyTest::Impl::test_dual_view_deep_copy242   test_dual_view_deep_copy() {
243     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(10, 5,
244                                                                     true);
245     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(10, 5,
246                                                                     false);
247     // Test zero length but allocated (a.d_view.data!=nullptr but
248     // a.d_view.span()==0)
249     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(0, 5, true);
250     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(0, 5,
251                                                                     false);
252 
253     // Test default constructed view
254     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(-1, 5,
255                                                                     true);
256     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(-1, 5,
257                                                                     false);
258   }
259 };
260 
261 template <typename Scalar, class Device>
262 struct test_dualview_resize {
263   using scalar_type     = Scalar;
264   using execution_space = Device;
265 
266   template <typename ViewType>
run_meTest::Impl::test_dualview_resize267   void run_me() {
268     const unsigned int n      = 10;
269     const unsigned int m      = 5;
270     const unsigned int factor = 2;
271 
272     ViewType a("A", n, m);
273     Kokkos::deep_copy(a.d_view, 1);
274 
275     /* Covers case "Resize on Device" */
276     a.modify_device();
277     Kokkos::resize(a, factor * n, factor * m);
278     ASSERT_EQ(a.extent(0), n * factor);
279     ASSERT_EQ(a.extent(1), m * factor);
280 
281     Kokkos::deep_copy(a.d_view, 1);
282     a.sync_host();
283 
284     // Check device view is initialized as expected
285     scalar_type a_d_sum = 0;
286     // Execute on the execution_space associated with t_dev's memory space
287     using t_dev_exec_space =
288         typename ViewType::t_dev::memory_space::execution_space;
289     Kokkos::parallel_reduce(
290         Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
291         SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
292         a_d_sum);
293 
294     // Check host view is synced as expected
295     scalar_type a_h_sum = 0;
296     for (size_t i = 0; i < a.h_view.extent(0); ++i)
297       for (size_t j = 0; j < a.h_view.extent(1); ++j) {
298         a_h_sum += a.h_view(i, j);
299       }
300 
301     // Check
302     ASSERT_EQ(a_h_sum, a_d_sum);
303     ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
304 
305     /* Covers case "Resize on Host" */
306     a.modify_host();
307 
308     Kokkos::resize(a, n / factor, m / factor);
309     ASSERT_EQ(a.extent(0), n / factor);
310     ASSERT_EQ(a.extent(1), m / factor);
311 
312     a.sync_device();
313     a.sync_device(Kokkos::DefaultExecutionSpace{});
314 
315     // Check device view is initialized as expected
316     a_d_sum = 0;
317     // Execute on the execution_space associated with t_dev's memory space
318     using t_dev_exec_space =
319         typename ViewType::t_dev::memory_space::execution_space;
320     Kokkos::parallel_reduce(
321         Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
322         SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
323         a_d_sum);
324 
325     // Check host view is synced as expected
326     a_h_sum = 0;
327     for (size_t i = 0; i < a.h_view.extent(0); ++i)
328       for (size_t j = 0; j < a.h_view.extent(1); ++j) {
329         a_h_sum += a.h_view(i, j);
330       }
331 
332     // Check
333     ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
334     ASSERT_EQ(a_h_sum, a_d_sum);
335 
336   }  // end run_me
337 
test_dualview_resizeTest::Impl::test_dualview_resize338   test_dualview_resize() {
339     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >();
340   }
341 };
342 
343 template <typename Scalar, class Device>
344 struct test_dualview_realloc {
345   using scalar_type     = Scalar;
346   using execution_space = Device;
347 
348   template <typename ViewType>
run_meTest::Impl::test_dualview_realloc349   void run_me() {
350     const unsigned int n = 10;
351     const unsigned int m = 5;
352 
353     ViewType a("A", n, m);
354     Kokkos::realloc(a, n, m);
355 
356     Kokkos::deep_copy(a.d_view, 1);
357     a.modify_device();
358     a.sync_host();
359 
360     // Check device view is initialized as expected
361     scalar_type a_d_sum = 0;
362     // Execute on the execution_space associated with t_dev's memory space
363     using t_dev_exec_space =
364         typename ViewType::t_dev::memory_space::execution_space;
365     Kokkos::parallel_reduce(
366         Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
367         SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
368         a_d_sum);
369 
370     // Check host view is synced as expected
371     scalar_type a_h_sum = 0;
372     for (size_t i = 0; i < a.h_view.extent(0); ++i)
373       for (size_t j = 0; j < a.h_view.extent(1); ++j) {
374         a_h_sum += a.h_view(i, j);
375       }
376 
377     // Check
378     ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
379     ASSERT_EQ(a_h_sum, a_d_sum);
380   }  // end run_me
381 
test_dualview_reallocTest::Impl::test_dualview_realloc382   test_dualview_realloc() {
383     run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >();
384   }
385 };
386 
387 }  // namespace Impl
388 
389 template <typename Scalar, typename Device>
test_dualview_combinations(unsigned int size,bool with_init)390 void test_dualview_combinations(unsigned int size, bool with_init) {
391   Impl::test_dualview_combinations<Scalar, Device> test(size, with_init);
392   ASSERT_EQ(test.result, 0);
393 }
394 
395 template <typename Scalar, typename Device>
test_dualview_alloc(unsigned int size)396 void test_dualview_alloc(unsigned int size) {
397   Impl::test_dualview_alloc<Scalar, Device> test(size);
398   ASSERT_TRUE(test.result);
399 }
400 
401 template <typename Scalar, typename Device>
test_dualview_deep_copy()402 void test_dualview_deep_copy() {
403   Impl::test_dual_view_deep_copy<Scalar, Device>();
404 }
405 
406 template <typename Scalar, typename Device>
test_dualview_realloc()407 void test_dualview_realloc() {
408   Impl::test_dualview_realloc<Scalar, Device>();
409 }
410 
411 template <typename Scalar, typename Device>
test_dualview_resize()412 void test_dualview_resize() {
413   Impl::test_dualview_resize<Scalar, Device>();
414 }
415 
TEST(TEST_CATEGORY,dualview_combination)416 TEST(TEST_CATEGORY, dualview_combination) {
417   test_dualview_combinations<int, TEST_EXECSPACE>(10, true);
418 }
419 
TEST(TEST_CATEGORY,dualview_alloc)420 TEST(TEST_CATEGORY, dualview_alloc) {
421   test_dualview_alloc<int, TEST_EXECSPACE>(10);
422 }
423 
TEST(TEST_CATEGORY,dualview_combinations_without_init)424 TEST(TEST_CATEGORY, dualview_combinations_without_init) {
425   test_dualview_combinations<int, TEST_EXECSPACE>(10, false);
426 }
427 
TEST(TEST_CATEGORY,dualview_deep_copy)428 TEST(TEST_CATEGORY, dualview_deep_copy) {
429   test_dualview_deep_copy<int, TEST_EXECSPACE>();
430   test_dualview_deep_copy<double, TEST_EXECSPACE>();
431 }
432 
TEST(TEST_CATEGORY,dualview_realloc)433 TEST(TEST_CATEGORY, dualview_realloc) {
434   test_dualview_realloc<int, TEST_EXECSPACE>();
435 }
436 
TEST(TEST_CATEGORY,dualview_resize)437 TEST(TEST_CATEGORY, dualview_resize) {
438   test_dualview_resize<int, TEST_EXECSPACE>();
439 }
440 
441 namespace {
442 /**
443  *
444  * The following tests are a response to
445  * https://github.com/kokkos/kokkos/issues/3850
446  * and
447  * https://github.com/kokkos/kokkos/pull/3857
448  *
449  * DualViews were returning incorrect view types and taking
450  * inappropriate actions based on the templated view methods.
451  *
452  * Specifically, template view methods were always returning
453  * a device view if the memory space was UVM and a Kokkos::Device was passed.
454  * Sync/modify methods completely broke down So these tests exist to make sure
455  * that we keep the semantics of UVM DualViews intact.
456  */
457 // modify if we have other UVM enabled backends
458 #ifdef KOKKOS_ENABLE_CUDA  // OR other UVM builds
459 #define UVM_ENABLED_BUILD
460 #endif
461 
462 #ifdef UVM_ENABLED_BUILD
463 template <typename ExecSpace>
464 struct UVMSpaceFor;
465 #endif
466 
467 #ifdef KOKKOS_ENABLE_CUDA  // specific to CUDA
468 template <>
469 struct UVMSpaceFor<Kokkos::Cuda> {
470   using type = Kokkos::CudaUVMSpace;
471 };
472 #endif
473 
474 #ifdef UVM_ENABLED_BUILD
475 template <>
476 struct UVMSpaceFor<Kokkos::DefaultHostExecutionSpace> {
477   using type = typename UVMSpaceFor<Kokkos::DefaultExecutionSpace>::type;
478 };
479 #else
480 template <typename ExecSpace>
481 struct UVMSpaceFor {
482   using type = typename ExecSpace::memory_space;
483 };
484 #endif
485 
486 using ExecSpace  = Kokkos::DefaultExecutionSpace;
487 using MemSpace   = typename UVMSpaceFor<Kokkos::DefaultExecutionSpace>::type;
488 using DeviceType = Kokkos::Device<ExecSpace, MemSpace>;
489 
490 using DualViewType = Kokkos::DualView<double*, Kokkos::LayoutLeft, DeviceType>;
491 using d_device     = DeviceType;
492 using h_device     = Kokkos::Device<
493     Kokkos::DefaultHostExecutionSpace,
494     typename UVMSpaceFor<Kokkos::DefaultHostExecutionSpace>::type>;
495 
TEST(TEST_CATEGORY,dualview_device_correct_kokkos_device)496 TEST(TEST_CATEGORY, dualview_device_correct_kokkos_device) {
497   DualViewType dv("myView", 100);
498   dv.clear_sync_state();
499   auto v_d      = dv.template view<d_device>();
500   using vdt     = decltype(v_d);
501   using vdt_d   = vdt::device_type;
502   using vdt_d_e = vdt_d::execution_space;
503   ASSERT_STREQ(vdt_d_e::name(), Kokkos::DefaultExecutionSpace::name());
504 }
TEST(TEST_CATEGORY,dualview_host_correct_kokkos_device)505 TEST(TEST_CATEGORY, dualview_host_correct_kokkos_device) {
506   DualViewType dv("myView", 100);
507   dv.clear_sync_state();
508   auto v_h      = dv.template view<h_device>();
509   using vht     = decltype(v_h);
510   using vht_d   = vht::device_type;
511   using vht_d_e = vht_d::execution_space;
512   ASSERT_STREQ(vht_d_e::name(), Kokkos::DefaultHostExecutionSpace::name());
513 }
514 
TEST(TEST_CATEGORY,dualview_host_modify_template_device_sync)515 TEST(TEST_CATEGORY, dualview_host_modify_template_device_sync) {
516   DualViewType dv("myView", 100);
517   dv.clear_sync_state();
518   dv.modify_host();
519   dv.template sync<d_device>();
520   EXPECT_TRUE(!dv.need_sync_device());
521   EXPECT_TRUE(!dv.need_sync_host());
522   dv.clear_sync_state();
523 }
524 
TEST(TEST_CATEGORY,dualview_host_modify_template_device_execspace_sync)525 TEST(TEST_CATEGORY, dualview_host_modify_template_device_execspace_sync) {
526   DualViewType dv("myView", 100);
527   dv.clear_sync_state();
528   dv.modify_host();
529   dv.template sync<d_device::execution_space>();
530   EXPECT_TRUE(!dv.need_sync_device());
531   EXPECT_TRUE(!dv.need_sync_host());
532   dv.clear_sync_state();
533 }
534 
TEST(TEST_CATEGORY,dualview_device_modify_template_host_sync)535 TEST(TEST_CATEGORY, dualview_device_modify_template_host_sync) {
536   DualViewType dv("myView", 100);
537   dv.clear_sync_state();
538   dv.modify_device();
539   dv.template sync<h_device>();
540   EXPECT_TRUE(!dv.need_sync_device());
541   EXPECT_TRUE(!dv.need_sync_host());
542   dv.clear_sync_state();
543 }
TEST(TEST_CATEGORY,dualview_device_modify_template_host_execspace_sync)544 TEST(TEST_CATEGORY, dualview_device_modify_template_host_execspace_sync) {
545   DualViewType dv("myView", 100);
546   dv.clear_sync_state();
547   dv.modify_device();
548   dv.template sync<h_device::execution_space>();
549   EXPECT_TRUE(!dv.need_sync_device());
550   EXPECT_TRUE(!dv.need_sync_host());
551   dv.clear_sync_state();
552 }
553 
TEST(TEST_CATEGORY,dualview_template_views_return_correct_executionspace_views)554 TEST(TEST_CATEGORY,
555      dualview_template_views_return_correct_executionspace_views) {
556   DualViewType dv("myView", 100);
557   dv.clear_sync_state();
558   using hvt = decltype(dv.view<typename Kokkos::DefaultHostExecutionSpace>());
559   using dvt = decltype(dv.view<typename Kokkos::DefaultExecutionSpace>());
560   ASSERT_STREQ(Kokkos::DefaultExecutionSpace::name(),
561                dvt::device_type::execution_space::name());
562   ASSERT_STREQ(Kokkos::DefaultHostExecutionSpace::name(),
563                hvt::device_type::execution_space::name());
564 }
565 
566 }  // anonymous namespace
567 }  // namespace Test
568 
569 #endif  // KOKKOS_TEST_DUALVIEW_HPP
570