1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
18 #define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
19
20 #include <chrono>
21 #include <fstream>
22 #include <limits>
23 #include <memory>
24 #include <random>
25 #include <sstream>
26 #include <string>
27 #include <vector>
28
29 #include "RandomGraphGenerator.h"
30 #include "RandomVariable.h"
31 #include "TestHarness.h"
32 #include "TestNeuralNetworksWrapper.h"
33
34 namespace android {
35 namespace nn {
36 namespace fuzzing_test {
37
38 #define NN_FUZZER_LOG_INIT(filename) Logger::get()->init((filename))
39 #define NN_FUZZER_LOG_CLOSE Logger::get()->close()
40 #define NN_FUZZER_LOG \
41 if (!Logger::get()->enabled()) \
42 ; \
43 else \
44 LoggerStream(false) << alignedString(__FUNCTION__, 20)
45 #define NN_FUZZER_CHECK(condition) \
46 if ((condition)) \
47 ; \
48 else \
49 LoggerStream(true) << alignedString(__FUNCTION__, 20) << "Check failed " << #condition \
50 << ": "
51
52 // A Singleton manages the global configurations of logging.
53 class Logger {
54 public:
get()55 static Logger* get() {
56 static Logger instance;
57 return &instance;
58 }
init(const std::string & filename)59 void init(const std::string& filename) {
60 os.open(filename);
61 mStart = std::chrono::high_resolution_clock::now();
62 }
enabled()63 bool enabled() { return os.is_open(); }
close()64 void close() {
65 if (os.is_open()) os.close();
66 }
log(const std::string & str)67 void log(const std::string& str) {
68 if (os.is_open()) os << getElapsedTime() << str << std::flush;
69 }
70
71 private:
72 Logger() = default;
73 Logger(const Logger&) = delete;
74 Logger& operator=(const Logger&) = delete;
75 std::string getElapsedTime();
76 std::ofstream os;
77 std::chrono::time_point<std::chrono::high_resolution_clock> mStart;
78 };
79
80 // Controls logging of a single line.
81 class LoggerStream {
82 public:
LoggerStream(bool abortAfterLog)83 LoggerStream(bool abortAfterLog) : mAbortAfterLog(abortAfterLog) {}
~LoggerStream()84 ~LoggerStream() {
85 Logger::get()->log(ss.str() + '\n');
86 if (mAbortAfterLog) {
87 std::cout << ss.str() << std::endl;
88 abort();
89 }
90 }
91
92 template <typename T>
93 LoggerStream& operator<<(const T& str) {
94 ss << str;
95 return *this;
96 }
97
98 private:
99 LoggerStream(const LoggerStream&) = delete;
100 LoggerStream& operator=(const LoggerStream&) = delete;
101 std::stringstream ss;
102 bool mAbortAfterLog;
103 };
104
105 template <typename T>
toString(const T & obj)106 inline std::string toString(const T& obj) {
107 return std::to_string(obj);
108 }
109
110 template <typename T>
joinStr(const std::string & joint,const std::vector<T> & items)111 inline std::string joinStr(const std::string& joint, const std::vector<T>& items) {
112 std::stringstream ss;
113 for (uint32_t i = 0; i < items.size(); i++) {
114 if (i == 0) {
115 ss << toString(items[i]);
116 } else {
117 ss << joint << toString(items[i]);
118 }
119 }
120 return ss.str();
121 }
122
123 template <typename T, class Function>
joinStr(const std::string & joint,const std::vector<T> & items,Function str)124 inline std::string joinStr(const std::string& joint, const std::vector<T>& items, Function str) {
125 std::stringstream ss;
126 for (uint32_t i = 0; i < items.size(); i++) {
127 if (i != 0) ss << joint;
128 ss << str(items[i]);
129 }
130 return ss.str();
131 }
132
133 template <typename T>
joinStr(const std::string & joint,int limit,const std::vector<T> & items)134 inline std::string joinStr(const std::string& joint, int limit, const std::vector<T>& items) {
135 if (items.size() > static_cast<size_t>(limit)) {
136 std::vector<T> topMax(items.begin(), items.begin() + limit);
137 return joinStr(joint, topMax) + ", (" + toString(items.size() - limit) + " ommited), " +
138 toString(items.back());
139 } else {
140 return joinStr(joint, items);
141 }
142 }
143
144 static const char* kLifeTimeNames[6] = {
145 "TEMPORARY_VARIABLE", "SUBGRAPH_INPUT", "SUBGRAPH_OUTPUT",
146 "CONSTANT_COPY", "CONSTANT_REFERENCE", "NO_VALUE",
147 };
148
149 static const bool kScalarDataType[]{
150 true, // ANEURALNETWORKS_FLOAT32
151 true, // ANEURALNETWORKS_INT32
152 true, // ANEURALNETWORKS_UINT32
153 false, // ANEURALNETWORKS_TENSOR_FLOAT32
154 false, // ANEURALNETWORKS_TENSOR_INT32
155 false, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
156 true, // ANEURALNETWORKS_BOOL
157 false, // ANEURALNETWORKS_TENSOR_QUANT16_SYMM
158 false, // ANEURALNETWORKS_TENSOR_FLOAT16
159 false, // ANEURALNETWORKS_TENSOR_BOOL8
160 true, // ANEURALNETWORKS_FLOAT16
161 false, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
162 false, // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
163 false, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM
164 false, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED
165 };
166
167 static const uint32_t kSizeOfDataType[]{
168 4, // ANEURALNETWORKS_FLOAT32
169 4, // ANEURALNETWORKS_INT32
170 4, // ANEURALNETWORKS_UINT32
171 4, // ANEURALNETWORKS_TENSOR_FLOAT32
172 4, // ANEURALNETWORKS_TENSOR_INT32
173 1, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
174 1, // ANEURALNETWORKS_BOOL
175 2, // ANEURALNETWORKS_TENSOR_QUANT16_SYMM
176 2, // ANEURALNETWORKS_TENSOR_FLOAT16
177 1, // ANEURALNETWORKS_TENSOR_BOOL8
178 2, // ANEURALNETWORKS_FLOAT16
179 1, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
180 2, // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
181 1, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM
182 1, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED
183 };
184
185 template <>
186 inline std::string toString<RandomVariableType>(const RandomVariableType& type) {
187 static const std::string typeNames[] = {"FREE", "CONST", "OP"};
188 return typeNames[static_cast<int>(type)];
189 }
190
alignedString(std::string str,int width)191 inline std::string alignedString(std::string str, int width) {
192 str.push_back(':');
193 str.resize(width + 1, ' ');
194 return str;
195 }
196
197 template <>
198 inline std::string toString<RandomVariableRange>(const RandomVariableRange& range) {
199 return "[" + joinStr(", ", 20, range.getChoices()) + "]";
200 }
201
202 template <>
203 inline std::string toString<RandomOperandType>(const RandomOperandType& type) {
204 static const std::string typeNames[] = {"Input", "Output", "Internal", "Parameter", "No Value"};
205 return typeNames[static_cast<int>(type)];
206 }
207
208 template <>
209 inline std::string toString<RandomVariableNode>(const RandomVariableNode& var) {
210 std::stringstream ss;
211 ss << "var" << var->index << " = ";
212 switch (var->type) {
213 case RandomVariableType::FREE:
214 ss << "FREE " << toString(var->range);
215 break;
216 case RandomVariableType::CONST:
217 ss << "CONST " << toString(var->value);
218 break;
219 case RandomVariableType::OP:
220 ss << "var" << var->parent1->index << " " << var->op->getName();
221 if (var->parent2 != nullptr) ss << " var" << var->parent2->index;
222 ss << ", " << toString(var->range);
223 break;
224 default:
225 NN_FUZZER_CHECK(false);
226 }
227 ss << ", timestamp = " << var->timestamp;
228 return ss.str();
229 }
230
231 template <>
232 inline std::string toString<RandomVariable>(const RandomVariable& var) {
233 return "var" + std::to_string(var.get()->index);
234 }
235
236 template <>
237 inline std::string toString<RandomOperand>(const RandomOperand& op) {
238 return toString(op.type) + ", dimension = [" +
239 joinStr(", ", op.dimensions,
240 [](const RandomVariable& var) { return std::to_string(var.getValue()); }) +
241 "], scale = " + toString(op.scale) + " , zero_point = " + toString(op.zeroPoint);
242 }
243
244 // This class is a workaround for two issues our code relies on:
245 // 1. sizeof(bool) is implementation defined.
246 // 2. vector<bool> does not allow direct pointer access via the data() method.
247 class bool8 {
248 public:
bool8()249 bool8() : mValue() {}
bool8(bool value)250 /* implicit */ bool8(bool value) : mValue(value) {}
251 inline operator bool() const { return mValue != 0; }
252
253 private:
254 uint8_t mValue;
255 };
256 static_assert(sizeof(bool8) == 1, "size of bool8 must be 8 bits");
257
258 struct RandomNumberGenerator {
259 static std::mt19937 generator;
260 };
261
getBernoulli(double p)262 inline bool getBernoulli(double p) {
263 std::bernoulli_distribution dis(p);
264 return dis(RandomNumberGenerator::generator);
265 }
266
267 template <typename T>
268 inline constexpr bool nnIsFloat = std::is_floating_point_v<T> || std::is_same_v<T, _Float16>;
269
270 // getUniform for floating point values operates on a open interval (lower, upper).
271 // This is important for generating a scale that is greater than but not equal to a lower bound.
272 template <typename T>
getUniform(T lower,T upper)273 inline std::enable_if_t<nnIsFloat<T>, T> getUniform(T lower, T upper) {
274 float nextLower = std::nextafter(static_cast<float>(lower), std::numeric_limits<float>::max());
275 std::uniform_real_distribution<float> dis(nextLower, upper);
276 return dis(RandomNumberGenerator::generator);
277 }
278 template <typename T>
getUniformNonZero(T lower,T upper,T zeroPoint)279 inline std::enable_if_t<nnIsFloat<T>, T> getUniformNonZero(T lower, T upper, T zeroPoint) {
280 if (upper >= zeroPoint) {
281 upper = std::nextafter(static_cast<float>(upper), std::numeric_limits<float>::min());
282 }
283 std::uniform_real_distribution<float> dis(lower, upper);
284 const float value = dis(RandomNumberGenerator::generator);
285 return value >= zeroPoint ? std::nextafter(value, std::numeric_limits<float>::max()) : value;
286 }
287
288 // getUniform for integers operates on a closed interval [lower, upper].
289 // This is important that 255 should be included as a valid candidate for QUANT8_ASYMM values.
290 template <typename T>
getUniform(T lower,T upper)291 inline std::enable_if_t<std::is_integral_v<T>, T> getUniform(T lower, T upper) {
292 std::uniform_int_distribution<T> dis(lower, upper);
293 return dis(RandomNumberGenerator::generator);
294 }
295 template <typename T>
getUniformNonZero(T lower,T upper,T zeroPoint)296 inline std::enable_if_t<std::is_integral_v<T>, T> getUniformNonZero(T lower, T upper, T zeroPoint) {
297 if (upper >= zeroPoint) upper--;
298 std::uniform_int_distribution<T> dis(lower, upper);
299 const T value = dis(RandomNumberGenerator::generator);
300 return value >= zeroPoint ? value + 1 : value;
301 }
302
303 template <typename T>
getRandomChoice(const std::vector<T> & choices)304 inline const T& getRandomChoice(const std::vector<T>& choices) {
305 NN_FUZZER_CHECK(!choices.empty()) << "Empty choices!";
306 std::uniform_int_distribution<size_t> dis(0, choices.size() - 1);
307 size_t i = dis(RandomNumberGenerator::generator);
308 return choices[i];
309 }
310
311 template <typename T>
randomShuffle(std::vector<T> * vec)312 inline void randomShuffle(std::vector<T>* vec) {
313 std::shuffle(vec->begin(), vec->end(), RandomNumberGenerator::generator);
314 }
315
316 } // namespace fuzzing_test
317 } // namespace nn
318 } // namespace android
319
320 #endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
321