1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! data structure related code
//!
//! This module contains code used for the implementations of `Views`, a data structure
//! defined and used by the Kokkos library. There are different types of views, all
//! implemented using the same backend, [ViewBase].
//!
//! Eventually, the different types of Views should be removed and replaced by a single
//! type. The distinction between original and mirrors doesn't seem necessary in a Rust
//! implementation where the ownership system handles all memory transaction.
//!
//! In order to have thread-safe structures to use in parallel statement, the inner data
//! type of views is adjusted implicitly when compiling using parallelization features.
//! To match the adjusted data type, view access is done through `get` and `set` methods,
//! allowing for feature-specific mutability in signatures while keeping a consistent user
//! API.
//!
//! Parameters of aforementionned views are defined in the [`parameters`] sub-module.
//!
//! ### Example
//!
//! Initialize and fill a 2D matrix:
//! ```rust
//! use poc_kokkos_rs::view::{
//!     parameters::Layout,
//!     ViewOwned,
//! };
//!
//! let mut viewA: ViewOwned<'_, 2, f64> = ViewOwned::new(
//!         Layout::Right, // see parameters & Kokkos doc
//!         [3, 5],        // 3 rows, 5 columns
//!     );
//!
//! for row in 0..3 {
//!     for col in 0..5 {
//!         viewA.set([row, col], row as f64);
//!     }
//! }
//!
//! // viewA:
//! // (0.0 0.0 0.0 0.0 0.0)
//! // (1.0 1.0 1.0 1.0 1.0)
//! // (2.0 2.0 2.0 2.0 2.0)
//! ```

pub mod parameters;

#[cfg(any(feature = "rayon", feature = "threads", feature = "gpu"))]
use atomic::{Atomic, Ordering};

#[cfg(any(doc, not(any(feature = "rayon", feature = "threads", feature = "gpu"))))]
use std::ops::IndexMut;

use self::parameters::{compute_stride, DataTraits, DataType, InnerDataType, Layout};
use std::{fmt::Debug, ops::Index};

#[derive(Debug)]
/// Enum used to classify view-related errors.
///
/// In all variants, the internal value is a description of the error.
pub enum ViewError<'a> {
    ValueError(&'a str),
    DoubleMirroring(&'a str),
}

#[derive(Debug, PartialEq)]
/// Common structure used as the backend of all `View` types. The main differences between
/// usable types is the type of the `data` field.
pub struct ViewBase<'a, const N: usize, T>
where
    T: DataTraits,
{
    /// Data container. Depending on the type, it can be a vector (`Owned`), a reference
    /// (`ReadOnly`) or a mutable reference (`ReadWrite`).
    pub data: DataType<'a, T>,
    /// Memory layout of the view. Refer to Kokkos documentation for more information.
    pub layout: Layout<N>,
    /// Dimensions of the data represented by the view. The view can:
    /// - be a vector (1 dimension)
    /// - be a multi-dimensionnal array (up to 8 dimensions)
    /// The number of dimensions is referred to as the _depth_. Dimension 0, i.e. scalar,
    /// is not directly supported.
    pub dim: [usize; N],
    /// Stride between each element of a given dimension. Computed automatically for
    /// [Layout::Left] and [Layout::Right].
    pub stride: [usize; N],
}

#[cfg(not(any(feature = "rayon", feature = "threads", feature = "gpu")))]
// ~~~~~~~~ Constructors
impl<'a, const N: usize, T> ViewBase<'a, N, T>
where
    T: DataTraits, // fair assumption imo
{
    /// Constructor used to create owned views. See dedicated methods for others.
    pub fn new(layout: Layout<N>, dim: [usize; N]) -> Self {
        // compute stride & capacity
        let stride = compute_stride(&dim, &layout);
        let capacity: usize = dim.iter().product();

        // build & return
        Self {
            data: DataType::Owned(vec![T::default(); capacity]), // should this be allocated though?
            layout,
            dim,
            stride,
        }
    }

    /// Constructor used to create owned views. See dedicated methods for others.
    pub fn new_from_data(data: Vec<T>, layout: Layout<N>, dim: [usize; N]) -> Self {
        // compute stride if necessary
        let stride = compute_stride(&dim, &layout);

        // checks
        let capacity: usize = dim.iter().product();
        assert_eq!(capacity, data.len());

        // build & return
        Self {
            data: DataType::Owned(data),
            layout,
            dim,
            stride,
        }
    }
}

#[cfg(any(feature = "rayon", feature = "threads", feature = "gpu"))]
// ~~~~~~~~ Constructors
impl<'a, const N: usize, T> ViewBase<'a, N, T>
where
    T: DataTraits, // fair assumption imo
{
    /// Constructor used to create owned views. See dedicated methods for others.
    pub fn new(layout: Layout<N>, dim: [usize; N]) -> Self {
        // compute stride & capacity
        let stride = compute_stride(&dim, &layout);
        let capacity: usize = dim.iter().product();

        // build & return
        Self {
            data: DataType::Owned((0..capacity).map(|_| Atomic::new(T::default())).collect()), // should this be allocated though?
            layout,
            dim,
            stride,
        }
    }

    /// Constructor used to create owned views. See dedicated methods for others.
    pub fn new_from_data(data: Vec<T>, layout: Layout<N>, dim: [usize; N]) -> Self {
        // compute stride if necessary
        let stride = compute_stride(&dim, &layout);

        // checks
        let capacity: usize = dim.iter().product();
        assert_eq!(capacity, data.len());

        // build & return
        Self {
            data: DataType::Owned(data.into_iter().map(|elem| Atomic::new(elem)).collect()),
            layout,
            dim,
            stride,
        }
    }
}

// ~~~~~~~~ Uniform writing interface across all features
impl<'a, const N: usize, T> ViewBase<'a, N, T>
where
    T: DataTraits,
{
    #[inline(always)]
    #[cfg(not(any(feature = "rayon", feature = "threads", feature = "gpu")))]
    /// Writing interface.
    ///
    /// Two different implementations of this method are defined in order to satisfy
    /// the (im)mutability requirements when using parallelization features & keep a
    /// consistent user API:
    ///
    /// - any feature enabled: implictly use an atomic store operation on top of the
    /// regular [Index] trait implementation to prevent a mutable borrow. The store
    /// currently uses relaxed ordering, this may change.
    /// - no feature enabled: uses a regular [IndexMut] trait implementation.
    ///
    /// Note that [Index] is always implemented while [IndexMut] only is when no
    /// features are enabled.
    ///
    /// **Current version**: no feature
    pub fn set(&mut self, index: [usize; N], val: T) {
        self[index] = val;
    }

    #[inline(always)]
    #[cfg(any(feature = "rayon", feature = "threads", feature = "gpu"))]
    /// Writing interface.
    ///
    /// Two different implementations of this method are defined in order to satisfy
    /// the (im)mutability requirements when using parallelization features & keep a
    /// consistent user API:
    ///
    /// - any feature enabled: implictly use an atomic store operation on top of the
    /// regular [Index] trait implementation to prevent a mutable borrow. The store
    /// currently uses relaxed ordering, this may change.
    /// - no feature enabled: uses a regular [IndexMut] trait implementation.
    ///
    /// Note that [Index] is always implemented while [IndexMut] only is when no
    /// features are enabled.
    ///
    /// **Current version**: thread-safe
    pub fn set(&self, index: [usize; N], val: T) {
        self[index].store(val, Ordering::Relaxed);
    }

    #[inline(always)]
    #[cfg(not(any(feature = "rayon", feature = "threads", feature = "gpu")))]
    /// Reading interface.
    ///
    /// Two different implementations of this method are defined in order to keep a
    /// consistent user API across features:
    ///
    /// - any feature enabled: implictly use an atomic load operation on top of the
    /// regular [Index] trait implementation. The load currently uses relaxed ordering,
    /// this may change.
    /// - no feature enabled: uses the regular [Index] trait implementation.
    ///
    /// Note that [Index] is always implemented while [IndexMut] only is when no
    /// features are enabled.
    ///
    /// **Current version**: no feature
    pub fn get(&self, index: [usize; N]) -> T {
        self[index]
    }

    #[inline(always)]
    #[cfg(any(feature = "rayon", feature = "threads", feature = "gpu"))]
    /// Reading interface.
    ///
    /// Two different implementations of this method are defined in order to keep a
    /// consistent user API across features:
    ///
    /// - any feature enabled: implictly use an atomic load operation on top of the
    /// regular [Index] trait implementation. The load currently uses relaxed ordering,
    /// this may change.
    /// - no feature enabled: uses the regular [Index] trait implementation.
    ///
    /// Note that [Index] is always implemented while [IndexMut] only is when no
    /// features are enabled.
    ///
    /// **Current version**: thread-safe
    pub fn get(&self, index: [usize; N]) -> T {
        self[index].load(atomic::Ordering::Relaxed)
    }

    // ~~~~~~~~ Mirrors

    /// Create a new View mirroring `self`, i.e. referencing the same data. This mirror
    /// is always immutable, but it inner values might still be writable if they are
    /// atomic types.
    ///
    /// Note that mirrors currently can only be created from the "original" view,
    /// i.e. the view owning the data.
    pub fn create_mirror<'b>(&'a self) -> Result<ViewRO<'b, N, T>, ViewError>
    where
        'a: 'b, // 'a outlives 'b
    {
        let inner = if let DataType::Owned(v) = &self.data {
            v
        } else {
            return Err(ViewError::DoubleMirroring(
                "Cannot create a mirror from a non-data-owning View",
            ));
        };

        Ok(Self {
            data: DataType::Borrowed(inner),
            layout: self.layout,
            dim: self.dim,
            stride: self.stride,
        })
    }

    #[cfg(not(any(feature = "rayon", feature = "threads", feature = "gpu")))]
    /// Create a new View mirroring `self`, i.e. referencing the same data. This mirror
    /// uses a mutable reference, hence the serial-only definition
    ///
    /// Note that mirrors currently can only be created from the "original" view,
    /// i.e. the view owning the data.
    ///
    /// Only defined when no feature are enabled since all interfaces should be immutable
    /// otherwise.
    pub fn create_mutable_mirror<'b>(&'a mut self) -> Result<ViewRW<'b, N, T>, ViewError>
    where
        'a: 'b, // 'a outlives 'b
    {
        let inner = if let DataType::Owned(v) = &mut self.data {
            v
        } else {
            return Err(ViewError::DoubleMirroring(
                "Cannot create a mirror from a non-data-owning View",
            ));
        };

        Ok(Self {
            data: DataType::MutBorrowed(inner),
            layout: self.layout,
            dim: self.dim,
            stride: self.stride,
        })
    }

    // ~~~~~~~~ Convenience

    #[cfg(all(
        test,
        not(any(feature = "rayon", feature = "threads", feature = "gpu"))
    ))]
    /// Consumes the view to return a `Vec` containing its raw data content.
    ///
    /// This method is meant to be used in tests
    pub fn raw_val<'b>(self) -> Result<Vec<T>, ViewError<'b>> {
        if let DataType::Owned(v) = self.data {
            Ok(v)
        } else {
            Err(ViewError::ValueError(
                "Cannot fetch raw values of a non-data-owning views",
            ))
        }
    }

    #[cfg(all(test, any(feature = "rayon", feature = "threads", feature = "gpu")))]
    /// Consumes the view to return a `Vec` containing its raw data content.
    ///
    /// This method is meant to be used in tests
    pub fn raw_val<'b>(self) -> Result<Vec<T>, ViewError<'b>> {
        if let DataType::Owned(v) = self.data {
            Ok(v.iter()
                .map(|elem| elem.load(atomic::Ordering::Relaxed))
                .collect::<Vec<T>>())
        } else {
            Err(ViewError::ValueError(
                "Cannot fetch raw values of a non-data-owning views",
            ))
        }
    }

    #[inline(always)]
    /// Mapping function between N-indices and the flat offset.
    pub fn flat_idx(&self, index: [usize; N]) -> usize {
        index
            .iter()
            .zip(self.stride.iter())
            .map(|(i, s_i)| *i * *s_i)
            .sum()
    }
}

/// **Read-only access is always implemented.**
impl<'a, const N: usize, T> Index<[usize; N]> for ViewBase<'a, N, T>
where
    T: DataTraits,
{
    type Output = InnerDataType<T>;

    fn index(&self, index: [usize; N]) -> &Self::Output {
        let flat_idx: usize = self.flat_idx(index);
        match &self.data {
            DataType::Owned(v) => {
                assert!(flat_idx < v.len()); // remove bounds check
                &v[flat_idx]
            }
            DataType::Borrowed(slice) => {
                assert!(flat_idx < slice.len()); // remove bounds check
                &slice[flat_idx]
            }
            DataType::MutBorrowed(mut_slice) => {
                assert!(flat_idx < mut_slice.len()); // remove bounds check
                &mut_slice[flat_idx]
            }
        }
    }
}

#[cfg(not(any(feature = "rayon", feature = "threads", feature = "gpu")))]
/// **Read-write access is only implemented when no parallel features are enabled.**
impl<'a, const N: usize, T> IndexMut<[usize; N]> for ViewBase<'a, N, T>
where
    T: DataTraits,
{
    fn index_mut(&mut self, index: [usize; N]) -> &mut Self::Output {
        let flat_idx: usize = self.flat_idx(index);
        match &mut self.data {
            DataType::Owned(v) => {
                assert!(flat_idx < v.len()); // remove bounds check
                &mut v[flat_idx]
            }
            DataType::Borrowed(_) => unimplemented!("Cannot mutably access a read-only view!"),
            DataType::MutBorrowed(mut_slice) => {
                assert!(flat_idx < mut_slice.len()); // remove bounds check
                &mut mut_slice[flat_idx]
            }
        }
    }
}

/// View type owning the data it yields access to, i.e. "original" view.
pub type ViewOwned<'a, const N: usize, T> = ViewBase<'a, N, T>;

/// View type owning a read-only borrow to the data it yields access to, i.e. a
/// read-only mirror.
pub type ViewRO<'a, const N: usize, T> = ViewBase<'a, N, T>;

/// View type owning a mutable borrow to the data it yields access to, i.e. a
/// read-write mirror.
pub type ViewRW<'a, const N: usize, T> = ViewBase<'a, N, T>;