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