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
//! Provides the interface for running parallel computations on one ore many devices.
//!
//! This is the abstraction over which you are interacting with your devices. You can create a
//! backend for computation by first choosing a specifc [Framework][frameworks] such as OpenCL and
//! afterwards selecting one or many available hardwares to create a backend.
//!
//! A backend provides you with the functionality of managing the memory of the devices and copying
//! your objects from host to devices and the other way around. Additionally you can execute
//! operations in parallel through kernel functions on the device(s) of the backend.
//!
//! ## Architecture
//!
//! The initialization of a backend happens through the BackendConfig, which defines which
//! [framework][framework] should be used and which [programs][program] should be available for
//! parallel execution.
//!
//! [frameworks]: ../frameworks/index.html
//! [framework]: ../framework/index.html
//! [program]: ../program/index.html
//!
//! ## Examples
//!
//! ```
//! extern crate collenchyma as co;
//! use co::framework::*;
//! use co::backend::{Backend, BackendConfig};
//! use co::frameworks::Native;
//! #[allow(unused_variables)]
//! fn main() {
//!     // Initialize a new Framewok.
//!     let framework = Native::new();
//!     // After initialization, the available hardware through the Framework can be obtained.
//!     let hardwares = &framework.hardwares().to_vec();
//!     // Create a Backend configuration with
//!     // - a Framework and
//!     // - the available hardwares you would like to use for computation (turn into a device).
//!     let backend_config = BackendConfig::new(framework, hardwares);
//!     // Create a ready to go backend from the configuration.
//!     let backend = Backend::new(backend_config);
//! }
//! ```

use error::Error;
use framework::IFramework;
use device::{IDevice, DeviceType};

#[derive(Debug, Clone)]
/// Defines the main and highest struct of Collenchyma.
pub struct Backend<F: IFramework> {
    /// Provides the Framework.
    ///
    /// The Framework implementation such as OpenCL, CUDA, etc. defines, which should be used and
    /// determines which hardwares will be available and how parallel kernel functions can be
    /// executed.
    ///
    /// Default: [Native][native]
    ///
    /// [native]: ../frameworks/native/index.html
    framework: Box<F>,
    /// Provides a device, created from one or many hardwares, which are ready to execute kernel
    /// methods and synchronize memory.
    device: DeviceType,
}

/// Defines the functionality of the Backend.
impl<F: IFramework + Clone> Backend<F> {
    /// Initialize a new native Backend from a BackendConfig.
    pub fn new(config: BackendConfig<F>) -> Result<Backend<F>, Error> {
        let device = try!(config.framework.new_device(config.hardwares));
        Ok(
            Backend {
                framework: Box::new(config.framework),
                device: device,
            }
        )
    }

    /// Returns the available hardware.
    pub fn hardwares(&self) -> &[F::H] {
        self.framework.hardwares()
    }

    /// Returns the backend framework.
    pub fn framework(&self) -> &Box<F> {
        &self.framework
    }

    /// Returns the backend device.
    pub fn device(&self) -> &DeviceType {
        &self.device
    }
}

/// Describes a Backend.
///
/// Serves as a marker trait and helps for extern implementation.
pub trait IBackend {
    /// Represents the Framework of a Backend.
    type F: IFramework + Clone;

    /// Returns the backend device.
    fn device(&self) -> &DeviceType;

    /// Try to create a default backend.
    fn default() -> Result<Backend<Self::F>, Error> where Self: Sized {
        let hw_framework = Self::F::new();
        let hardwares = hw_framework.hardwares();
        let framework = Self::F::new(); // dirty dirty hack to get around borrowing
        let backend_config = BackendConfig::new(framework, hardwares);
        Backend::new(backend_config)
    }

    /// Synchronize backend.
    fn synchronize(&self) -> Result<(), ::framework::Error> { Ok(()) }
}

#[derive(Debug, Clone)]
/// Provides Backend Configuration.
///
/// Use it to initialize a new Backend.
pub struct BackendConfig<'a, F: IFramework + 'a> {
    framework: F,
    hardwares: &'a [F::H],
}

impl<'a, F: IFramework + Clone> BackendConfig<'a, F> {
    /// Creates a new BackendConfig.
    pub fn new(framework: F, hardwares: &'a [F::H]) -> BackendConfig<'a, F> {
        BackendConfig {
            framework: framework.clone(),
            hardwares: hardwares,
        }
    }
}