diff --git a/Cargo.toml b/Cargo.toml index 6a18dd6d..95f4141b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,9 @@ enum_primitive = "0.1.0" byteorder = "0.4" num = "0.1" lazy_static = "0.1.15" -linear-map = "0.0.4" clippy = { version = "0.0.27", optional = true } +compiletest_rs = { version = "0.1", optional = true } [dev-dependencies] rand = "0.3" @@ -36,7 +36,7 @@ opencl = [] unstable_alloc = [] # faster but unstable memory allocation on native machines dev = [] -unstable = [] # for travis-cargo +unstable = ["compiletest_rs"] # for travis-cargo travis = ["native"] lint = ["clippy"] diff --git a/benches/shared_tensor.rs b/benches/shared_tensor.rs index cfb0ed70..4791d8e0 100644 --- a/benches/shared_tensor.rs +++ b/benches/shared_tensor.rs @@ -59,14 +59,14 @@ fn sync_back_and_forth( ) { b.iter(|| { for _ in 0..n { - match mem.sync(&cl_device) { + match mem.read_write(&cl_device) { Ok(_) => assert!(true), Err(err) => { println!("{:?}", err); assert!(false); } } - match mem.sync(&nt_device) { + match mem.read_write(&nt_device) { Ok(_) => assert!(true), Err(err) => { println!("{:?}", err); @@ -112,8 +112,8 @@ fn bench_256_sync_1mb_native_opencl(b: &mut Bencher) { // if let &DeviceType::OpenCL(ref cl_d) = cl_device { // println!("{:?}", cl_d.hardwares()[0].clone().load_name()); // } - let mem = &mut SharedTensor::::new(nt_device, &1_048_576).unwrap(); - mem.add_device(&cl_device); + let mem = &mut SharedTensor::::new(&1_048_576); + mem.write_only(&cl_device); bench_256_sync_1mb_native_opencl_profile(b, nt_device, cl_device, mem); } @@ -133,8 +133,8 @@ fn bench_256_sync_1mb_native_cuda(b: &mut Bencher) { // if let &DeviceType::Cuda(ref cl_d) = cl_device { // println!("{:?}", cl_d.hardwares()[0].clone().load_name()); // } - let mem = &mut SharedTensor::::new(nt_device, &1_048_576).unwrap(); - mem.add_device(&cl_device); + let mem = &mut SharedTensor::::new(&1_048_576); + mem.write_only(&cl_device); bench_256_sync_1mb_native_cuda_profile(b, nt_device, cl_device, mem); } @@ -154,8 +154,8 @@ fn bench_2_sync_128mb_native_cuda(b: &mut Bencher) { // if let &DeviceType::Cuda(ref cl_d) = cl_device { // println!("{:?}", cl_d.hardwares()[0].clone().load_name()); // } - let mem = &mut SharedTensor::::new(nt_device, &(128 * 1_048_576)).unwrap(); - mem.add_device(&cl_device); + let mem = &mut SharedTensor::::new(&(128 * 1_048_576)); + mem.write_only(&cl_device); bench_2_sync_128mb_native_cuda_profile(b, nt_device, cl_device, mem); } diff --git a/src/device.rs b/src/device.rs index 69b0b760..af65d090 100644 --- a/src/device.rs +++ b/src/device.rs @@ -64,7 +64,7 @@ pub enum DeviceType { Cuda(CudaContext), } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Defines a generic set of Memory Errors. pub enum Error { /// Failures related to the Native framework implementation. diff --git a/src/frameworks/cuda/api/driver/error.rs b/src/frameworks/cuda/api/driver/error.rs index 239c3782..06f8a244 100644 --- a/src/frameworks/cuda/api/driver/error.rs +++ b/src/frameworks/cuda/api/driver/error.rs @@ -2,7 +2,7 @@ use std::{fmt, error}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Defines OpenCL errors. pub enum Error { /// Failure with provided value. diff --git a/src/frameworks/native/error.rs b/src/frameworks/native/error.rs index b351f141..0d6f0693 100644 --- a/src/frameworks/native/error.rs +++ b/src/frameworks/native/error.rs @@ -2,7 +2,7 @@ use std::{fmt, error}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Defines the Native Error. pub enum Error { /// Failure related to allocation, syncing memory diff --git a/src/frameworks/opencl/api/error.rs b/src/frameworks/opencl/api/error.rs index 621d7c55..70b10a7c 100644 --- a/src/frameworks/opencl/api/error.rs +++ b/src/frameworks/opencl/api/error.rs @@ -2,7 +2,7 @@ use std::{fmt, error}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Defines OpenCL errors. pub enum Error { /// Failure with provided platform. diff --git a/src/lib.rs b/src/lib.rs index 5b203c49..eb9ed2a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,7 +157,6 @@ extern crate enum_primitive; extern crate lazy_static; extern crate num; extern crate byteorder; -extern crate linear_map; pub mod backend; pub mod device; diff --git a/src/plugin.rs b/src/plugin.rs index 8d0e573d..216445d9 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -30,6 +30,7 @@ //! [collenchyma-nn]: https://github.com/autumnai/collenchyma-nn pub use self::numeric_helpers::Float; +use tensor; /// Describes numeric types and traits for a Plugin. pub mod numeric_helpers { @@ -39,8 +40,9 @@ pub mod numeric_helpers { #[derive(Debug, Copy, Clone)] /// Defines a high-level Plugin Error. pub enum Error { - /// Failure at receiving the correct device memory from the SharedTensor. - MissingMemoryForDevice(&'static str), + /// Failure related to `SharedTensor`: use of uninitialized memory, + /// synchronization error or memory allocation failure. + SharedTensor(tensor::Error), /// Failure at the execution of the Operation. Operation(&'static str), /// Failure at the Plugin. @@ -50,7 +52,7 @@ pub enum Error { impl ::std::fmt::Display for Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match *self { - Error::MissingMemoryForDevice(ref err) => write!(f, "MissingMemoryForDevice error: {}", err), + Error::SharedTensor(ref err) => write!(f, "SharedTensor error: {}", err), Error::Operation(ref err) => write!(f, "Operation error: {}", err), Error::Plugin(ref err) => write!(f, "Plugin error: {}", err), } @@ -60,7 +62,7 @@ impl ::std::fmt::Display for Error { impl ::std::error::Error for Error { fn description(&self) -> &str { match *self { - Error::MissingMemoryForDevice(ref err) => err, + Error::SharedTensor(ref err) => err.description(), Error::Operation(ref err) => err, Error::Plugin(ref err) => err, } @@ -68,7 +70,7 @@ impl ::std::error::Error for Error { fn cause(&self) -> Option<&::std::error::Error> { match *self { - Error::MissingMemoryForDevice(_) => None, + Error::SharedTensor(ref err) => err.cause(), Error::Operation(_) => None, Error::Plugin(_) => None, } @@ -80,3 +82,9 @@ impl From for ::error::Error { ::error::Error::Plugin(err) } } + +impl From for Error { + fn from(err: tensor::Error) -> Error { + Error::SharedTensor(err) + } +} diff --git a/src/tensor.rs b/src/tensor.rs index 37fedd8a..93062513 100644 --- a/src/tensor.rs +++ b/src/tensor.rs @@ -40,30 +40,50 @@ //! // allocate memory //! let native = Native::new(); //! let device = native.new_device(native.hardwares()).unwrap(); -//! let shared_data = &mut SharedTensor::::new(&device, &5).unwrap(); +//! let shared_data = &mut SharedTensor::::new(&5); //! // fill memory with some numbers -//! let local_data = [0, 1, 2, 3, 4]; -//! let data = shared_data.get_mut(&device).unwrap().as_mut_native().unwrap(); +//! let mut mem = shared_data.write_only(&device).unwrap().as_mut_native().unwrap(); +//! mem.as_mut_slice::().clone_from_slice(&[0, 1, 2, 3, 4]); //! # } //! ``` -use linear_map::LinearMap; use device::{IDevice, DeviceType}; use memory::MemoryType; +use std::cell::{Cell, RefCell}; use std::marker::PhantomData; use std::{fmt, mem, error}; +use std::error::Error as StdError; /// Describes the Descriptor of a SharedTensor. pub type TensorDesc = Vec; -#[derive(Debug)] +/// BitMap type for keeping track of up-to-date locations. If number of +/// locations provided by the integer isn't enough, this type can be easily +/// replaced with BitSet at cost of a heap allocation and extra inderection +/// on access. +type BitMap = u64; + +/// Number of bits in `BitMap`. It's currently no possible to get this +/// information from `BitMap` cleanly. Though there are plans to add a +/// static method or associated constant. +const BIT_MAP_SIZE: usize = 64; + +struct TensorLocation { + device: DeviceType, + + // Box is required to keep references to MemoryType alive if + // SharedTensor::locations vec reallocates storage and moves elements. + // See also comment on `unsafe` near `SharedTensor::read()` impl. + mem: Box, +} + /// Container that handles synchronization of [Memory][1] of type `T`. /// [1]: ../memory/index.html pub struct SharedTensor { desc: TensorDesc, - latest_location: DeviceType, - latest_copy: MemoryType, - copies: LinearMap, + locations: RefCell>, + up_to_date: Cell, + phantom: PhantomData, } @@ -236,20 +256,23 @@ impl ITensorDesc for TensorDesc { } } + +impl fmt::Debug for SharedTensor { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "SharedTensor desc={:?}", self.desc) + } +} + impl SharedTensor { /// Create new Tensor by allocating [Memory][1] on a Device. /// [1]: ../memory/index.html - pub fn new(dev: &DeviceType, desc: &D) -> Result, Error> { - let copies = LinearMap::::new(); - let copy = try!(Self::alloc_on_device(dev, desc)); - let tensor_desc: TensorDesc = desc.into(); - Ok(SharedTensor { - desc: tensor_desc, - latest_location: dev.clone(), - latest_copy: copy, - copies: copies, + pub fn new(desc: &D) -> SharedTensor { + SharedTensor { + desc: desc.into(), + locations: RefCell::new(Vec::new()), + up_to_date: Cell::new(0), phantom: PhantomData, - }) + } } /// Change the shape of the Tensor. @@ -273,149 +296,184 @@ impl SharedTensor { /// 'reshape' is preffered over this method if the size of the old and new shape /// are identical because it will not reallocate memory. pub fn resize(&mut self, desc: &D) -> Result<(), Error> { - self.copies.clear(); - self.latest_copy = try!(Self::alloc_on_device(self.latest_device(), desc)); - let new_desc: TensorDesc = desc.into(); - self.desc = new_desc; + self.locations.borrow_mut().clear(); + self.up_to_date.set(0); + self.desc = desc.into(); Ok(()) } - /// Allocate memory on the provided DeviceType. - fn alloc_on_device(dev: &DeviceType, desc: &D) -> Result { - let tensor_desc: TensorDesc = desc.into(); - let alloc_size = Self::mem_size(tensor_desc.size()); - let copy = match *dev { - #[cfg(feature = "native")] - DeviceType::Native(ref cpu) => MemoryType::Native(try!(cpu.alloc_memory(alloc_size))), - #[cfg(feature = "opencl")] - DeviceType::OpenCL(ref context) => MemoryType::OpenCL(try!(context.alloc_memory(alloc_size))), - #[cfg(feature = "cuda")] - DeviceType::Cuda(ref context) => MemoryType::Cuda(try!(context.alloc_memory(alloc_size))), - }; - Ok(copy) + fn get_location_index(&self, device: &DeviceType) -> Option { + for (i, loc) in self.locations.borrow().iter().enumerate() { + if loc.device == *device { + return Some(i); + } + } + None } - /// Synchronize memory from latest location to `destination`. - pub fn sync(&mut self, destination: &DeviceType) -> Result<(), Error> { - if &self.latest_location != destination { - let latest = self.latest_location.clone(); - try!(self.sync_from_to(&latest, &destination)); - - let mut swap_location = destination.clone(); - let mut swap_copy = try!(self.copies.remove(destination).ok_or(Error::MissingDestination("Tensor does not hold a copy on destination device."))); - mem::swap(&mut self.latest_location, &mut swap_location); - mem::swap(&mut self.latest_copy, &mut swap_copy); - self.copies.insert(swap_location, swap_copy); + /// Looks up `device` in self.locations and returns its index. If lookup + /// fails then new location is created and its index is returned. + fn get_or_create_location_index(&self, device: &DeviceType) + -> Result { + if let Some(i) = self.get_location_index(device) { + return Ok(i); } - Ok(()) - } - /// Get a reference to the memory copy on the provided `device`. - /// - /// Returns `None` if there is no memory copy on the device. - pub fn get(&self, device: &DeviceType) -> Option<&MemoryType> { - // first check if device is not current location. This is cheaper than a lookup in `copies`. - if &self.latest_location == device { - return Some(&self.latest_copy) + if self.locations.borrow().len() == BIT_MAP_SIZE { + return Err(Error::CapacityExceeded); } - self.copies.get(device) - } - /// Get a mutable reference to the memory copy on the provided `device`. - /// - /// Returns `None` if there is no memory copy on the device. - pub fn get_mut(&mut self, device: &DeviceType) -> Option<&mut MemoryType> { - // first check if device is not current location. This is cheaper than a lookup in `copies`. - if &self.latest_location == device { - return Some(&mut self.latest_copy) + let mem = try!(Self::alloc_on_device(device, self.desc().size())); + self.locations.borrow_mut().push(TensorLocation { + device: device.clone(), + mem: Box::new(mem), + }); + Ok(self.locations.borrow().len() - 1) + } + + // TODO: chose the best source to copy data from. + // That would require some additional traits that return costs for + // transferring data between different backends. + // Actually I think that there would be only transfers between + // `Native` <-> `Cuda` and `Native` <-> `OpenCL` in foreseeable future, + // so it's best to not overengineer here. + fn sync_if_needed(&self, dst_i: usize) -> Result<(), Error> { + if self.up_to_date.get() & (1 << dst_i) != 0 { + return Ok(()); } - self.copies.get_mut(device) - } - - /// Synchronize memory from `source` device to `destination` device. - fn sync_from_to(&mut self, source: &DeviceType, destination: &DeviceType) -> Result<(), Error> { - if source != destination { - match self.copies.get_mut(destination) { - Some(mut destination_copy) => { - match destination { - #[cfg(feature = "native")] - &DeviceType::Native(ref cpu) => { - match destination_copy.as_mut_native() { - Some(ref mut mem) => try!(cpu.sync_in(&self.latest_location, &self.latest_copy, mem)), - None => return Err(Error::InvalidMemory("Expected Native Memory (FlatBox)")) - } - }, - #[cfg(feature = "cuda")] - &DeviceType::Cuda(ref context) => { - match destination_copy.as_mut_cuda() { - Some(ref mut mem) => try!(context.sync_in(&self.latest_location, &self.latest_copy, mem)), - None => return Err(Error::InvalidMemory("Expected CUDA Memory.")) - } - }, - #[cfg(feature = "opencl")] - &DeviceType::OpenCL(ref context) => { - match destination_copy.as_mut_opencl() { - Some(ref mut mem) => try!(context.sync_in(&self.latest_location, &self.latest_copy, mem)), - None => return Err(Error::InvalidMemory("Expected OpenCL Memory.")) - } - } - } - Ok(()) - }, - None => Err(Error::MissingDestination("Tensor does not hold a copy on destination device.")) - } + + let src_i = self.up_to_date.get().trailing_zeros() as usize; + assert!(src_i != BIT_MAP_SIZE); + + // We need to borrow two different Vec elements: src and mut dst. + // Borrowck doesn't allow to do it in a straightforward way, so + // here is workaround. + assert!(src_i != dst_i); + let mut locs = self.locations.borrow_mut(); + let (src_loc, mut dst_loc) = if src_i < dst_i { + let (left, right) = locs.split_at_mut(dst_i); + (&left[src_i], &mut right[0]) } else { - Ok(()) - } - } + let (left, right) = locs.split_at_mut(src_i); + (&right[0], &mut left[dst_i]) + }; - /// Removes Copy from SharedTensor and therefore aquires ownership over the removed memory copy for synchronizing. - pub fn remove_copy(&mut self, destination: &DeviceType) -> Result<(MemoryType), Error> { - // If `destination` holds the latest data, sync to another memory first, before removing it. - if &self.latest_location == destination { - let first = self.copies.keys().nth(0).unwrap().clone(); - try!(self.sync(&first)); - } - match self.copies.remove(destination) { - Some(destination_cpy) => Ok(destination_cpy), - None => Err(Error::MissingDestination("Tensor does not hold a copy on destination device.")) + match &dst_loc.device { + #[cfg(feature = "native")] + &DeviceType::Native(ref cpu) => { + let mem = dst_loc.mem.as_mut_native() + .expect("Broken invariant: expected Native Memory"); + try!(cpu.sync_in(&src_loc.device, &src_loc.mem, mem)); + }, + #[cfg(feature = "cuda")] + &DeviceType::Cuda(ref context) => { + let mem = dst_loc.mem.as_mut_cuda() + .expect("Broken invariant: expected Cuda Memory"); + try!(context.sync_in(&src_loc.device, &src_loc.mem, mem)); + }, + #[cfg(feature = "opencl")] + &DeviceType::OpenCL(ref context) => { + let mem = dst_loc.mem.as_mut_opencl() + .expect("Broken invariant: expected OpenCL Memory"); + try!(context.sync_in(&src_loc.device, &src_loc.mem, mem)); + } } + + Ok(()) } - /// Return ownership over a memory copy after synchronizing. - fn return_copy(&mut self, dest: &DeviceType, dest_mem: MemoryType) { - self.copies.insert(dest.clone(), dest_mem); + /// Allocate memory on the provided DeviceType for `n` elements. + fn alloc_on_device(dev: &DeviceType, n: usize) -> Result { + let alloc_size = Self::mem_size(n); + let copy = match *dev { + #[cfg(feature = "native")] + DeviceType::Native(ref cpu) => MemoryType::Native(try!(cpu.alloc_memory(alloc_size))), + #[cfg(feature = "opencl")] + DeviceType::OpenCL(ref context) => MemoryType::OpenCL(try!(context.alloc_memory(alloc_size))), + #[cfg(feature = "cuda")] + DeviceType::Cuda(ref context) => MemoryType::Cuda(try!(context.alloc_memory(alloc_size))), + }; + Ok(copy) } - /// Track a new `device` and allocate memory on it. - /// - /// Returns an error if the Tensor is already tracking the `device`. - pub fn add_device(&mut self, device: &DeviceType) -> Result<&mut Self, Error> { - // first check if device is not current location. This is cheaper than a lookup in `copies`. - if &self.latest_location == device { - return Err(Error::InvalidMemoryAllocation("Tensor already tracks memory for this device. No memory allocation.")) + // Functions `read()`, `read_write()`, `write_only()` use `unsafe` to + // extend lifetime of retured reference to internally owned memory chunk. + // Borrowck guarantees that SharedTensor outlives all of its Tensors, and + // there is only one mutable borrow. So we only need to make sure that + // memory locations won't be dropped or moved while there are live Tensors. + // It's quite easy to do: by convention we only allow to remove elements from + // `self.locations` in methods with `&mut self`. Since we store `MemoryType` + // inside `Vec` in a `Box`, reference to it won't change during Vec + // reallocations. + + /// Get memory for reading on the specified `device`. + /// Can fail if memory allocation fails, or if tensor wasn't initialized yet. + pub fn read<'a>(&'a self, device: &DeviceType) -> Result<&'a MemoryType, Error> { + if self.up_to_date.get() == 0 { + return Err(Error::UninitializedMemory); } - match self.copies.get(device) { - Some(_) => Err(Error::InvalidMemoryAllocation("Tensor already tracks memory for this device. No memory allocation.")), - None => { - let copy: MemoryType; - match *device { - #[cfg(feature = "native")] - DeviceType::Native(ref cpu) => copy = MemoryType::Native(try!(cpu.alloc_memory(Self::mem_size(self.capacity())))), - #[cfg(feature = "opencl")] - DeviceType::OpenCL(ref context) => copy = MemoryType::OpenCL(try!(context.alloc_memory(Self::mem_size(self.capacity())))), - #[cfg(feature = "cuda")] - DeviceType::Cuda(ref context) => copy = MemoryType::Cuda(try!(context.alloc_memory(Self::mem_size(self.capacity())))), - }; - self.copies.insert(device.clone(), copy); - Ok(self) - } + let i = try!(self.get_or_create_location_index(device)); + try!(self.sync_if_needed(i)); + self.up_to_date.set(self.up_to_date.get() | (1 << i)); + + let locs = self.locations.borrow(); + let mem: &MemoryType = &locs[i].mem; + let mem_a: &'a MemoryType = unsafe { ::std::mem::transmute(mem) }; + Ok(mem_a) + } + + /// Get memory for reading and writing on the specified `device`. + /// Can fail if memory allocation fails, or if tensor wasn't initialized yet. + pub fn read_write<'a>(&'a mut self, device: &DeviceType) + -> Result<&'a mut MemoryType, Error> { + if self.up_to_date.get() == 0 { + return Err(Error::UninitializedMemory); + } + let i = try!(self.get_or_create_location_index(device)); + try!(self.sync_if_needed(i)); + self.up_to_date.set(1 << i); + + let mut locs = self.locations.borrow_mut(); + let mem: &mut MemoryType = &mut locs[i].mem; + let mem_a: &'a mut MemoryType = unsafe { ::std::mem::transmute(mem) }; + Ok(mem_a) + } + + /// Get memory for writing only. + /// This function skips synchronization and initialization checks, since + /// contents will be overwritten anyway. By convention caller must fully + /// initialize returned memory. Failure to do so may result in use of + /// uninitialized data later. If caller has failed to overwrite memory, + /// for some reason, it must call `invalidate()` to return vector to + /// uninitialized state. + pub fn write_only<'a>(&'a mut self, device: &DeviceType) + -> Result<&'a mut MemoryType, Error> { + let i = try!(self.get_or_create_location_index(device)); + self.up_to_date.set(1 << i); + + let mut locs = self.locations.borrow_mut(); + let mem: &mut MemoryType = &mut locs[i].mem; + let mem_a: &'a mut MemoryType = unsafe { ::std::mem::transmute(mem) }; + Ok(mem_a) + } + + /// Drops memory allocation on the specified device. Returns error if + /// no memory has been allocated on this device. + pub fn drop_device(&mut self, device: &DeviceType) -> Result<(), Error> { + match self.get_location_index(device) { + Some(i) => { + self.locations.borrow_mut().remove(i); + + let up_to_date = self.up_to_date.get(); + let mask = (1 << i) - 1; + let lower = up_to_date & mask; + let upper = (up_to_date >> 1) & (!mask); + self.up_to_date.set(lower | upper); + Ok(()) + }, + None => + Err(Error::InvalidRemove("Memory isn't allocated on this device")) } - } - - /// Returns the device that contains the up-to-date memory copy. - pub fn latest_device(&self) -> &DeviceType { - &self.latest_location } /// Returns the number of elements for which the Tensor has been allocated. @@ -435,16 +493,8 @@ impl SharedTensor { } /// Errors than can occur when synchronizing memory. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Error { - /// No copy on source device. - MissingSource(&'static str), - /// No copy on destination device. - MissingDestination(&'static str), - /// No valid MemoryType provided. Other than expected. - InvalidMemory(&'static str), - /// No memory allocation on specified device happened. - InvalidMemoryAllocation(&'static str), /// Unable to remove Memory copy from SharedTensor. InvalidRemove(&'static str), /// Framework error at memory allocation. @@ -452,48 +502,41 @@ pub enum Error { /// Framework error at memory synchronization. MemorySynchronizationError(::device::Error), /// Shape provided for reshaping is not compatible with old shape. - InvalidShape(&'static str) + InvalidShape(&'static str), + /// Maximal number of backing memories has been reached. + CapacityExceeded, + /// Memory is requested for reading, but it hasn't been initialized. + UninitializedMemory, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::MissingSource(ref err) => write!(f, "{:?}", err), - Error::MissingDestination(ref err) => write!(f, "{:?}", err), - Error::InvalidMemory(ref err) => write!(f, "{:?}", err), - Error::InvalidMemoryAllocation(ref err) => write!(f, "{:?}", err), - Error::InvalidRemove(ref err) => write!(f, "{:?}", err), - Error::MemoryAllocationError(ref err) => write!(f, "{}", err), - Error::MemorySynchronizationError(ref err) => write!(f, "{}", err), - Error::InvalidShape(ref err) => write!(f, "{}", err), - } + write!(f, "{}", self.description()) } } impl error::Error for Error { fn description(&self) -> &str { match *self { - Error::MissingSource(ref err) => err, - Error::MissingDestination(ref err) => err, - Error::InvalidMemory(ref err) => err, - Error::InvalidMemoryAllocation(ref err) => err, Error::InvalidRemove(ref err) => err, Error::MemoryAllocationError(ref err) => err.description(), Error::MemorySynchronizationError(ref err) => err.description(), Error::InvalidShape(ref err) => err, + Error::CapacityExceeded => + "Max number of backing memories has been reached", + Error::UninitializedMemory => + "Uninitialized memory is requested for reading", } } fn cause(&self) -> Option<&error::Error> { match *self { - Error::MissingSource(_) => None, - Error::MissingDestination(_) => None, - Error::InvalidMemory(_) => None, - Error::InvalidMemoryAllocation(_) => None, Error::InvalidRemove(_) => None, Error::MemoryAllocationError(ref err) => Some(err), Error::MemorySynchronizationError(ref err) => Some(err), Error::InvalidShape(_) => None, + Error::CapacityExceeded => None, + Error::UninitializedMemory => None, } } } diff --git a/tests/compile-fail/drop_live_memory.rs b/tests/compile-fail/drop_live_memory.rs new file mode 100644 index 00000000..86abfa07 --- /dev/null +++ b/tests/compile-fail/drop_live_memory.rs @@ -0,0 +1,13 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let x = &mut SharedTensor::::new(&10); + let m = x.write_only(&dev).unwrap(); + x.drop_device(&dev); + //~^ ERROR error: cannot borrow `*x` as mutable more than once at a time +} + diff --git a/tests/compile-fail/leak_read_reference.rs b/tests/compile-fail/leak_read_reference.rs new file mode 100644 index 00000000..7f7c41dd --- /dev/null +++ b/tests/compile-fail/leak_read_reference.rs @@ -0,0 +1,16 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let mem = { + let x = &mut SharedTensor::::new(&10); + //~^ ERROR error: borrowed value does not live long enough + x.write_only(&dev).unwrap(); + let m = x.read(&dev).unwrap(); + m + }; +} + diff --git a/tests/compile-fail/leak_write_reference.rs b/tests/compile-fail/leak_write_reference.rs new file mode 100644 index 00000000..07a8fae2 --- /dev/null +++ b/tests/compile-fail/leak_write_reference.rs @@ -0,0 +1,15 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let mem = { + let x = &mut SharedTensor::::new(&10); + //~^ ERROR error: borrowed value does not live long enough + let m = x.write_only(&dev).unwrap(); + m + }; +} + diff --git a/tests/compile-fail/read_write_borrows.rs b/tests/compile-fail/read_write_borrows.rs new file mode 100644 index 00000000..e7573600 --- /dev/null +++ b/tests/compile-fail/read_write_borrows.rs @@ -0,0 +1,13 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let x = &mut SharedTensor::::new(&10); + let m1 = x.write_only(&dev).unwrap(); + let m2 = x.read(&dev).unwrap(); + //~^ ERROR cannot borrow `*x` as immutable because it is also borrowed as mutable +} + diff --git a/tests/compile-fail/two_write_borrows.rs b/tests/compile-fail/two_write_borrows.rs new file mode 100644 index 00000000..ddbb17a1 --- /dev/null +++ b/tests/compile-fail/two_write_borrows.rs @@ -0,0 +1,13 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let x = &mut SharedTensor::::new(&10); + let m1 = x.write_only(&dev).unwrap(); + let m2 = x.write_only(&dev).unwrap(); + //~^ ERROR error: cannot borrow `*x` as mutable more than once at a time +} + diff --git a/tests/compiletests.rs b/tests/compiletests.rs new file mode 100644 index 00000000..0a51ff94 --- /dev/null +++ b/tests/compiletests.rs @@ -0,0 +1,22 @@ +#![cfg(feature = "compiletest_rs")] +extern crate compiletest_rs as compiletest; + +use std::path::PathBuf; + +fn run_mode(mode: &'static str) { + let mut config = compiletest::default_config(); + let cfg_mode = mode.parse().ok().expect("Invalid mode"); + + config.target_rustcflags = Some("-L target/debug/ -L target/debug/deps/" + .to_owned()); + config.mode = cfg_mode; + config.src_base = PathBuf::from(format!("tests/{}", mode)); + + compiletest::run_tests(&config); +} + +#[test] +fn compile_test() { + run_mode("compile-fail"); + run_mode("run-pass"); +} diff --git a/tests/framework_cuda_specs.rs b/tests/framework_cuda_specs.rs index 0c853dcb..26e543b9 100644 --- a/tests/framework_cuda_specs.rs +++ b/tests/framework_cuda_specs.rs @@ -48,7 +48,8 @@ mod framework_cuda_spec { let cuda = Cuda::new(); let device = cuda.new_device(&cuda.hardwares()[0..1]).unwrap(); for _ in 0..256 { - let _ = &mut SharedTensor::::new(&device, &vec![256, 1024, 128]).unwrap(); + let mut x = SharedTensor::::new(&vec![256, 1024, 128]); + x.write_only(&device).unwrap(); } } diff --git a/tests/run-pass/multiple_read_only_borrows.rs b/tests/run-pass/multiple_read_only_borrows.rs new file mode 100644 index 00000000..ce3015ff --- /dev/null +++ b/tests/run-pass/multiple_read_only_borrows.rs @@ -0,0 +1,15 @@ +extern crate collenchyma; +use collenchyma::prelude::*; + +fn main() { + let ntv = Native::new(); + let dev = ntv.new_device(ntv.hardwares()).unwrap(); + + let x = &mut SharedTensor::::new(&10); + x.write_only(&dev).unwrap(); + + let m1 = x.read(&dev); + let m2 = x.read(&dev); + let m3 = x.read(&dev); +} + diff --git a/tests/shared_memory_specs.rs b/tests/shared_memory_specs.rs index 56ada6a9..e263c3ee 100644 --- a/tests/shared_memory_specs.rs +++ b/tests/shared_memory_specs.rs @@ -4,6 +4,7 @@ extern crate libc; #[cfg(test)] mod shared_memory_spec { use co::prelude::*; + use co::tensor::Error; fn write_to_memory(mem: &mut MemoryType, data: &[T]) { match mem { @@ -23,9 +24,9 @@ mod shared_memory_spec { fn it_creates_new_shared_memory_for_native() { let ntv = Native::new(); let cpu = ntv.new_device(ntv.hardwares()).unwrap(); - let shared_data = &mut SharedTensor::::new(&cpu, &10).unwrap(); - match shared_data.get(&cpu).unwrap() { - &MemoryType::Native(ref dat) => { + let mut shared_data = SharedTensor::::new(&10); + match shared_data.write_only(&cpu).unwrap() { + &mut MemoryType::Native(ref dat) => { let data = dat.as_slice::(); assert_eq!(10, data.len()); }, @@ -39,10 +40,11 @@ mod shared_memory_spec { fn it_creates_new_shared_memory_for_cuda() { let ntv = Cuda::new(); let device = ntv.new_device(&ntv.hardwares()[0..1]).unwrap(); - let shared_data = &mut SharedTensor::::new(&device, &10).unwrap(); - match shared_data.get(&device) { - Some(&MemoryType::Cuda(_)) => assert!(true), - _ => assert!(false), + let mut shared_data = SharedTensor::::new(&10); + match shared_data.write_only(&device) { + Ok(&mut MemoryType::Cuda(_)) => {}, + #[cfg(any(feature = "cuda", feature = "opencl"))] + _ => assert!(false) } } @@ -51,13 +53,31 @@ mod shared_memory_spec { fn it_creates_new_shared_memory_for_opencl() { let ntv = OpenCL::new(); let device = ntv.new_device(&ntv.hardwares()[0..1]).unwrap(); - let shared_data = &mut SharedTensor::::new(&device, &10).unwrap(); - match shared_data.get(&device) { - Some(&MemoryType::OpenCL(_)) => assert!(true), + let mut shared_data = SharedTensor::::new(&10); + match shared_data.write_only(&device) { + Ok(&mut MemoryType::OpenCL(_)) => {}, _ => assert!(false), } } + #[test] + #[cfg(feature = "native")] + fn it_fails_on_initialized_memory_read() { + let ntv = Native::new(); + let cpu = ntv.new_device(ntv.hardwares()).unwrap(); + let mut shared_data = SharedTensor::::new(&10); + assert_eq!(shared_data.read(&cpu).unwrap_err(), + Error::UninitializedMemory); + assert_eq!(shared_data.read_write(&cpu).unwrap_err(), + Error::UninitializedMemory); + + shared_data.write_only(&cpu).unwrap(); + shared_data.drop_device(&cpu).unwrap(); + + assert_eq!(shared_data.read(&cpu).unwrap_err(), + Error::UninitializedMemory); + } + #[test] #[cfg(feature = "cuda")] fn it_syncs_from_native_to_cuda_and_back() { @@ -65,20 +85,22 @@ mod shared_memory_spec { let nt = Native::new(); let cu_device = cu.new_device(&cu.hardwares()[0..1]).unwrap(); let nt_device = nt.new_device(nt.hardwares()).unwrap(); - let mem = &mut SharedTensor::::new(&nt_device, &3).unwrap(); - write_to_memory(mem.get_mut(&nt_device).unwrap(), &[1, 2, 3]); - mem.add_device(&cu_device).unwrap(); - match mem.sync(&cu_device) { + let mut mem = SharedTensor::::new(&3); + write_to_memory(mem.write_only(&nt_device).unwrap(), + &[1.0f64, 2.0, 123.456]); + match mem.read(&cu_device) { Ok(_) => assert!(true), Err(err) => { println!("{:?}", err); assert!(false); } } - // It has not successfully synced to the device. + // It has successfully synced to the device. // Not the other way around. - match mem.sync(&nt_device) { - Ok(_) => assert!(true), + mem.drop_device(&nt_device).unwrap(); + match mem.read(&nt_device) { + Ok(m) => assert_eq!(m.as_native().unwrap().as_slice::(), + [1.0, 2.0, 123.456]), Err(err) => { println!("{:?}", err); assert!(false); @@ -93,10 +115,10 @@ mod shared_memory_spec { let nt = Native::new(); let cl_device = cl.new_device(&cl.hardwares()[0..1]).unwrap(); let nt_device = nt.new_device(nt.hardwares()).unwrap(); - let mem = &mut SharedTensor::::new(&nt_device, &3).unwrap(); - write_to_memory(mem.get_mut(&nt_device).unwrap(), &[1, 2, 3]); - mem.add_device(&cl_device).unwrap(); - match mem.sync(&cl_device) { + let mut mem = SharedTensor::::new(&3); + write_to_memory(mem.write_only(&nt_device).unwrap(), + &[1.0f64, 2.0, 123.456]); + match mem.read(&cl_device) { Ok(_) => assert!(true), Err(err) => { println!("{:?}", err); @@ -105,8 +127,10 @@ mod shared_memory_spec { } // It has not successfully synced to the device. // Not the other way around. - match mem.sync(&nt_device) { - Ok(_) => assert!(true), + mem.drop_device(&nt_device).unwrap(); + match mem.read(&nt_device) { + Ok(m) => assert_eq!(m.as_native().unwrap().as_slice::(), + [1.0, 2.0, 123.456]), Err(err) => { println!("{:?}", err); assert!(false); @@ -114,27 +138,15 @@ mod shared_memory_spec { } } - #[test] - fn it_has_correct_latest_device() { - let ntv = Native::new(); - let cpu_dev = ntv.new_device(ntv.hardwares()).unwrap(); - let shared_data = &mut SharedTensor::::new(&cpu_dev, &10).unwrap(); - assert_eq!(&cpu_dev, shared_data.latest_device()); - } - #[test] fn it_reshapes_correctly() { - let ntv = Native::new(); - let cpu_dev = ntv.new_device(ntv.hardwares()).unwrap(); - let mut shared_data = &mut SharedTensor::::new(&cpu_dev, &10).unwrap(); + let mut shared_data = SharedTensor::::new(&10); assert!(shared_data.reshape(&vec![5, 2]).is_ok()); } #[test] fn it_returns_err_for_invalid_size_reshape() { - let ntv = Native::new(); - let cpu_dev = ntv.new_device(ntv.hardwares()).unwrap(); - let mut shared_data = &mut SharedTensor::::new(&cpu_dev, &10).unwrap(); + let mut shared_data = SharedTensor::::new(&10); assert!(shared_data.reshape(&vec![10, 2]).is_err()); } } diff --git a/tests/tensor_specs.rs b/tests/tensor_specs.rs index 8590fc3d..8c85e976 100644 --- a/tests/tensor_specs.rs +++ b/tests/tensor_specs.rs @@ -31,8 +31,7 @@ mod tensor_spec { #[test] fn it_resizes_tensor() { - let native = Backend::::default().unwrap(); - let mut tensor = SharedTensor::::new(native.device(), &(10, 20, 30)).unwrap(); + let mut tensor = SharedTensor::::new(&(10, 20, 30)); assert_eq!(tensor.desc(), &[10, 20, 30]); tensor.resize(&(2, 3, 4, 5)).unwrap(); assert_eq!(tensor.desc(), &[2, 3, 4, 5]);