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
|
/*========================== begin_copyright_notice ============================
Copyright (C) 2020-2021 Intel Corporation
SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/
//===----------------------------------------------------------------------===//
///
/// This is a simplified array type container that is suitable for use in an
/// interface with the UMD. We want to be able to mix and match debug/release
/// dlls with each other. Each dll statically links the CRT. While it would
/// be nice to use STL containers on the interface:
///
/// 1) Their layouts can differ between debug and release builds so, for
/// example, a std::vector place in an output structure by IGC in debug
/// could be interpreted differently by a release UMD and cause corruption.
///
/// 2) memory allocated in IGC must be freed in IGC (same with UMD). Having
/// destructors in the containers could cause accidental deletion across
/// domains if we're not careful.
///
/// In this Array class, we force deletion on explicitly via the destroy()
/// method. We also clearly don't have any #ifdef _DEBUG (or the like) that
/// would cause the memory layout to be different.
///
//===----------------------------------------------------------------------===//
#pragma once
#include <iterator>
#include <type_traits>
#include <assert.h>
namespace Interface {
template<typename T>
class Array
{
// We should only be placing objects in here that don't do anything
// interesting in the destructor to discourage accidental deletions.
static_assert(std::is_trivially_destructible_v<T>,
"Array contained type must be trivially destructible!");
private:
// Standard iterator style interface
class iterator
{
public:
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
private:
pointer Ptr;
iterator(pointer Ptr) : Ptr(Ptr) {}
public:
reference operator*() const { return *Ptr; }
pointer operator->() const { return Ptr; }
bool operator==(const iterator& RHS) const { return Ptr == RHS.Ptr; }
bool operator!=(const iterator& RHS) const { return !(*this == RHS); }
iterator operator++(int)
{
iterator V = *this;
++*this;
return V;
}
iterator& operator++()
{
Ptr++;
return *this;
}
friend Array;
};
// const version
class const_iterator
{
public:
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::random_access_iterator_tag;
private:
pointer Ptr;
const_iterator(pointer Ptr) : Ptr(Ptr) {}
public:
reference operator*() const { return *Ptr; }
pointer operator->() const { return Ptr; }
bool operator==(const const_iterator& RHS) const { return Ptr == RHS.Ptr; }
bool operator!=(const const_iterator& RHS) const { return !(*this == RHS); }
const_iterator operator++(int)
{
const_iterator V = *this;
++*this;
return V;
}
const_iterator& operator++()
{
Ptr++;
return *this;
}
friend Array;
};
public:
Array() = default;
Array(uint32_t NumElts)
{
if (NumElts == 0)
return;
Start = allocate(NumElts);
End = Start + NumElts;
for (uint32_t i = 0; i < NumElts; i++)
Start[i] = typename iterator::value_type();
}
Array& operator=(const Array&) = delete;
// no copying or copy assignment. It would only make sense to have copy
// assignment if we could delete the previous contents which may be unsafe.
Array(const Array&) = delete;
Array(Array&& RHS)
{
Start = RHS.Start;
End = RHS.End;
RHS.Start = nullptr;
RHS.End = nullptr;
}
// move it around instead.
Array& operator=(Array&& RHS) noexcept
{
Start = RHS.Start;
End = RHS.End;
RHS.Start = nullptr;
RHS.End = nullptr;
return *this;
}
// Convenince constructor to build your data up with the usual containers
// prior to filling this up.
template <typename InputIterator>
Array(InputIterator first, InputIterator last)
{
size_t NumElts = std::distance(first, last);
Start = allocate(NumElts);
End = Start + NumElts;
std::copy(first, last, this->begin());
}
size_t size() const {
return End - Start;
}
// Only way to delete, explicitly. Destructors are trivial so we don't
// need to explicitly call them here.
void destroy()
{
for (auto& v : *this)
{
v.destroy();
}
::operator delete(Start);
}
iterator begin() { return iterator(Start); }
iterator end() { return iterator(End); }
const_iterator begin() const { return const_iterator(Start); }
const_iterator end() const { return const_iterator(End); }
const typename iterator::reference operator[](size_t i) const {
assert((i < size()) && "out of bounds!");
return Start[i];
}
typename iterator::reference operator[](size_t i) {
assert((i < size()) && "out of bounds!");
return Start[i];
}
// Useful for when we slightly over allocate to avoid having to do two
// trips through a collection: one to the find the size and the other to
// write the data. Just shrink the size down after the fact.
void trimSize(uint32_t NewSize) {
if (NewSize > size())
return;
End = Start + NewSize;
}
bool empty() const {
return Start == End;
}
private:
typename iterator::pointer Start = nullptr;
typename iterator::pointer End = nullptr;
inline typename iterator::pointer allocate(size_t NumElts)
{
return static_cast<typename iterator::pointer>(
::operator new (NumElts * sizeof(typename iterator::value_type)));
}
};
} // Interface
|