/** Reserve some part of the pipe for reading \param[in] s is the number of element to reserve \param[out] rid is an iterator to a description of the reservation that has been done if successful \param[in] blocking specify if the call wait for the operation to succeed \return true if the reservation was successful */ bool reserve_read(std::size_t s, rid_iterator &rid, bool blocking = false) { // Lock the pipe to avoid being disturbed std::unique_lock<std::mutex> ul { cb_mutex }; TRISYCL_DUMP_T("Before read reservation cb.size() = " << cb.size() << " size() = " << size()); if (s == 0) // Empty reservation requested, so nothing to do return false; if (blocking) /* If in blocking mode, wait for enough elements to read in the pipe for the reservation. This condition can change when a write is done */ write_done.wait(ul, [&] { return s <= size(); }); else if (s > size()) // Not enough elements to read in the pipe for the reservation return false; // Compute the location of the first element of the reservation auto first = cb.begin() + read_reserved_frozen; // Increment the number of frozen elements read_reserved_frozen += s; /* Add a description of the reservation at the end of the reservation queue */ r_rid_q.emplace_back(first, s); // Return the iterator to the last reservation descriptor rid = r_rid_q.end() - 1; TRISYCL_DUMP_T("After reservation cb.size() = " << cb.size() << " size() = " << size()); return true; }
/** Try to read a value from the pipe \param[out] value is the reference to where to store what is read \param[in] blocking specify if the call wait for the operation to succeed \return true on success */ bool read(T &value, bool blocking = false) { // Lock the pipe to avoid being disturbed std::unique_lock<std::mutex> ul { cb_mutex }; TRISYCL_DUMP_T("Read pipe empty = " << empty()); if (blocking) /* If in blocking mode, wait for the not empty condition, that may be changed when a write is done */ write_done.wait(ul, [&] { return !empty(); }); else if (empty()) return false; TRISYCL_DUMP_T("Read pipe front = " << cb.front() << " back = " << cb.back() << " reserved_for_reading() = " << reserved_for_reading()); if (read_reserved_frozen) /** If there is a pending reservation, read the next element to be read and update the number of reserved elements */ value = cb.begin()[read_reserved_frozen++]; else { /* There is no pending read reservation, so pop the read value from the pipe */ value = cb.front(); cb.pop_front(); } TRISYCL_DUMP_T("Read pipe value = " << value); // Notify the clients waiting for some room to write in the pipe read_done.notify_all(); return true; }
/** Try to write a value to the pipe \param[in] value is what we want to write \param[in] blocking specify if the call wait for the operation to succeed \return true on success \todo provide a && version */ bool write(const T &value, bool blocking = false) { // Lock the pipe to avoid being disturbed std::unique_lock<std::mutex> ul { cb_mutex }; TRISYCL_DUMP_T("Write pipe full = " << full() << " value = " << value); if (blocking) /* If in blocking mode, wait for the not full condition, that may be changed when a read is done */ read_done.wait(ul, [&] { return !full(); }); else if (full()) return false; cb.push_back(value); TRISYCL_DUMP_T("Write pipe front = " << cb.front() << " back = " << cb.back() << " cb.begin() = " << (void *)&*cb.begin() << " cb.size() = " << cb.size() << " cb.end() = " << (void *)&*cb.end() << " reserved_for_reading() = " << reserved_for_reading() << " reserved_for_writing() = " << reserved_for_writing()); // Notify the clients waiting to read something from the pipe write_done.notify_all(); return true; }
/** Wait for the release of the buffer generation before the host can use it */ void wait_released() { TRISYCL_DUMP_T("buffer_customer::wait_released() user_number = " << user_number); { std::unique_lock<std::mutex> ul { released_mutex }; released_cv.wait(ul, [&] { return user_number == 0; }); } }
/// Notify the customer tasks this buffer generation is ready to use void notify_ready() { { std::unique_lock<std::mutex> ul { ready_mutex }; // \todo This lock can be avoided if ready_to_use is atomic ready_to_use = true; } TRISYCL_DUMP_T("buffer_customer::notify_ready()"); ready_cv.notify_all(); }
/** Get the current number of elements in the pipe that can be read This is obviously a volatile value which is constrained by the theory of restricted relativity. Note that on some devices it may be costly to implement (for example on FPGA). */ std::size_t size() const { TRISYCL_DUMP_T("size() cb.size() = " << cb.size() << " cb.end() = " << (void *)&*cb.end() << " reserved_for_reading() = " << reserved_for_reading() << " reserved_for_writing() = " << reserved_for_writing()); /* The actual number of available elements depends from the elements blocked by some reservations. This prevents a consumer to read into reserved area. */ return cb.size() - reserved_for_reading() - reserved_for_writing(); }
/** Construct a device accessor from an existing buffer \todo fix the specification to rename target that shadows template parm */ accessor(std::shared_ptr<detail::buffer<T, Dimensions>> target_buffer, handler &command_group_handler) : buf { target_buffer }, array { target_buffer->access } { TRISYCL_DUMP_T("Create a kernel accessor write = " << is_write_access()); static_assert(Target == access::target::global_buffer || Target == access::target::constant_buffer, "access target should be global_buffer or constant_buffer " "when a handler is used"); // Register the buffer to the task dependencies task = buffer_add_to_task(buf, &command_group_handler, is_write_access()); }
/** Reserve some part of the pipe for writing \param[in] s is the number of element to reserve \param[out] rid is an iterator to a description of the reservation that has been done if successful \param[in] blocking specify if the call wait for the operation to succeed \return true if the reservation was successful */ bool reserve_write(std::size_t s, rid_iterator &rid, bool blocking = false) { // Lock the pipe to avoid being disturbed std::unique_lock<std::mutex> ul { cb_mutex }; TRISYCL_DUMP_T("Before write reservation cb.size() = " << cb.size() << " size() = " << size()); if (s == 0) // Empty reservation requested, so nothing to do return false; if (blocking) /* If in blocking mode, wait for enough room in the pipe, that may be changed when a read is done. Do not use a difference here because it is only about unsigned values */ read_done.wait(ul, [&] { return cb.size() + s <= capacity(); }); else if (cb.size() + s > capacity()) // Not enough room in the pipe for the reservation return false; /* If there is enough room in the pipe, just create default values in it to do the reservation */ for (std::size_t i = 0; i != s; ++i) cb.push_back(); /* Compute the location of the first element a posteriori since it may not exist a priori if cb was empty before */ auto first = cb.end() - s; /* Add a description of the reservation at the end of the reservation queue */ w_rid_q.emplace_back(first, s); // Return the iterator to the last reservation descriptor rid = w_rid_q.end() - 1; TRISYCL_DUMP_T("After reservation cb.size() = " << cb.size() << " size() = " << size()); return true; }
/// Release the buffer generation usage by a kernel task void release() { user_number--; TRISYCL_DUMP_T("buffer_customer::release() now user_number = " << user_number); if (user_number == 0) { /* If there is no task using this generation of the buffer, first notify the host accessors waiting for it, if any */ released_cv.notify_all(); /* And then make the next generation ready if any. Note that if the SYCL program is race condition-free, there should be no host accessor waiting for a generation which is not the last one... \todo: add some SYCL semantics runtime verification */ if (next_generation) next_generation->notify_ready(); } // \todo Can we have UserNumber increasing again? }
/// Add a new task as a customer of the buffer generation void add(std::shared_ptr<task> task, bool is_write_access) { write_access = is_write_access; user_number++; TRISYCL_DUMP_T("buffer_customer::add() now user_number = " << user_number); }
/** Test if the pipe is empty This is obviously a volatile value which is constrained by restricted relativity. Note that on some devices it may be costly to implement on the write side (for example on FPGA). */ bool empty() const { TRISYCL_DUMP_T("empty() cb.size() = " << cb.size() << " size() = " << size()); // It is empty when the size is zero, taking into account reservations return size() == 0; }