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