1 //===- RunnerUtils.h - Utils for debugging MLIR execution -----------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file declares basic classes and functions to debug structured MLIR
10 // types at runtime. Entities in this file may not be compatible with targets
11 // without a C++ runtime. These may be progressively migrated to CRunnerUtils.h
12 // over time.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #ifndef MLIR_EXECUTIONENGINE_RUNNERUTILS_H
17 #define MLIR_EXECUTIONENGINE_RUNNERUTILS_H
18 
19 #ifdef _WIN32
20 #ifndef MLIR_RUNNERUTILS_EXPORT
21 #ifdef mlir_runner_utils_EXPORTS
22 // We are building this library
23 #define MLIR_RUNNERUTILS_EXPORT __declspec(dllexport)
24 #else
25 // We are using this library
26 #define MLIR_RUNNERUTILS_EXPORT __declspec(dllimport)
27 #endif // mlir_runner_utils_EXPORTS
28 #endif // MLIR_RUNNERUTILS_EXPORT
29 #else
30 // Non-windows: use visibility attributes.
31 #define MLIR_RUNNERUTILS_EXPORT __attribute__((visibility("default")))
32 #endif // _WIN32
33 
34 #include <assert.h>
35 #include <cmath>
36 #include <iostream>
37 
38 #include "mlir/ExecutionEngine/CRunnerUtils.h"
39 
40 template <typename T, typename StreamType>
printMemRefMetaData(StreamType & os,const DynamicMemRefType<T> & v)41 void printMemRefMetaData(StreamType &os, const DynamicMemRefType<T> &v) {
42   os << "base@ = " << reinterpret_cast<void *>(v.data) << " rank = " << v.rank
43      << " offset = " << v.offset;
44   auto print = [&](const int64_t *ptr) {
45     if (v.rank == 0)
46       return;
47     os << ptr[0];
48     for (int64_t i = 1; i < v.rank; ++i)
49       os << ", " << ptr[i];
50   };
51   os << " sizes = [";
52   print(v.sizes);
53   os << "] strides = [";
54   print(v.strides);
55   os << "]";
56 }
57 
58 template <typename StreamType, typename T, int N>
printMemRefMetaData(StreamType & os,StridedMemRefType<T,N> & v)59 void printMemRefMetaData(StreamType &os, StridedMemRefType<T, N> &v) {
60   static_assert(N >= 0, "Expected N > 0");
61   os << "MemRef ";
62   printMemRefMetaData(os, DynamicMemRefType<T>(v));
63 }
64 
65 template <typename StreamType, typename T>
printUnrankedMemRefMetaData(StreamType & os,UnrankedMemRefType<T> & v)66 void printUnrankedMemRefMetaData(StreamType &os, UnrankedMemRefType<T> &v) {
67   os << "Unranked MemRef ";
68   printMemRefMetaData(os, DynamicMemRefType<T>(v));
69 }
70 
71 ////////////////////////////////////////////////////////////////////////////////
72 // Templated instantiation follows.
73 ////////////////////////////////////////////////////////////////////////////////
74 namespace impl {
75 template <typename T, int M, int... Dims>
76 std::ostream &operator<<(std::ostream &os, const Vector<T, M, Dims...> &v);
77 
78 template <int... Dims>
79 struct StaticSizeMult {
80   static constexpr int value = 1;
81 };
82 
83 template <int N, int... Dims>
84 struct StaticSizeMult<N, Dims...> {
85   static constexpr int value = N * StaticSizeMult<Dims...>::value;
86 };
87 
88 static inline void printSpace(std::ostream &os, int count) {
89   for (int i = 0; i < count; ++i) {
90     os << ' ';
91   }
92 }
93 
94 template <typename T, int M, int... Dims>
95 struct VectorDataPrinter {
96   static void print(std::ostream &os, const Vector<T, M, Dims...> &val);
97 };
98 
99 template <typename T, int M, int... Dims>
100 void VectorDataPrinter<T, M, Dims...>::print(std::ostream &os,
101                                              const Vector<T, M, Dims...> &val) {
102   static_assert(M > 0, "0 dimensioned tensor");
103   static_assert(sizeof(val) == M * StaticSizeMult<Dims...>::value * sizeof(T),
104                 "Incorrect vector size!");
105   // First
106   os << "(" << val[0];
107   if (M > 1)
108     os << ", ";
109   if (sizeof...(Dims) > 1)
110     os << "\n";
111   // Kernel
112   for (unsigned i = 1; i + 1 < M; ++i) {
113     printSpace(os, 2 * sizeof...(Dims));
114     os << val[i] << ", ";
115     if (sizeof...(Dims) > 1)
116       os << "\n";
117   }
118   // Last
119   if (M > 1) {
120     printSpace(os, sizeof...(Dims));
121     os << val[M - 1];
122   }
123   os << ")";
124 }
125 
126 template <typename T, int M, int... Dims>
127 std::ostream &operator<<(std::ostream &os, const Vector<T, M, Dims...> &v) {
128   VectorDataPrinter<T, M, Dims...>::print(os, v);
129   return os;
130 }
131 
132 template <typename T>
133 struct MemRefDataPrinter {
134   static void print(std::ostream &os, T *base, int64_t dim, int64_t rank,
135                     int64_t offset, const int64_t *sizes,
136                     const int64_t *strides);
137   static void printFirst(std::ostream &os, T *base, int64_t dim, int64_t rank,
138                          int64_t offset, const int64_t *sizes,
139                          const int64_t *strides);
140   static void printLast(std::ostream &os, T *base, int64_t dim, int64_t rank,
141                         int64_t offset, const int64_t *sizes,
142                         const int64_t *strides);
143 };
144 
145 template <typename T>
146 void MemRefDataPrinter<T>::printFirst(std::ostream &os, T *base, int64_t dim,
147                                       int64_t rank, int64_t offset,
148                                       const int64_t *sizes,
149                                       const int64_t *strides) {
150   os << "[";
151   print(os, base, dim - 1, rank, offset, sizes + 1, strides + 1);
152   // If single element, close square bracket and return early.
153   if (sizes[0] <= 1) {
154     os << "]";
155     return;
156   }
157   os << ", ";
158   if (dim > 1)
159     os << "\n";
160 }
161 
162 template <typename T>
163 void MemRefDataPrinter<T>::print(std::ostream &os, T *base, int64_t dim,
164                                  int64_t rank, int64_t offset,
165                                  const int64_t *sizes, const int64_t *strides) {
166   if (dim == 0) {
167     os << base[offset];
168     return;
169   }
170   printFirst(os, base, dim, rank, offset, sizes, strides);
171   for (unsigned i = 1; i + 1 < sizes[0]; ++i) {
172     printSpace(os, rank - dim + 1);
173     print(os, base, dim - 1, rank, offset + i * strides[0], sizes + 1,
174           strides + 1);
175     os << ", ";
176     if (dim > 1)
177       os << "\n";
178   }
179   if (sizes[0] <= 1)
180     return;
181   printLast(os, base, dim, rank, offset, sizes, strides);
182 }
183 
184 template <typename T>
185 void MemRefDataPrinter<T>::printLast(std::ostream &os, T *base, int64_t dim,
186                                      int64_t rank, int64_t offset,
187                                      const int64_t *sizes,
188                                      const int64_t *strides) {
189   printSpace(os, rank - dim + 1);
190   print(os, base, dim - 1, rank, offset + (sizes[0] - 1) * (*strides),
191         sizes + 1, strides + 1);
192   os << "]";
193 }
194 
195 template <typename T, int N>
196 void printMemRefShape(StridedMemRefType<T, N> &m) {
197   std::cout << "Memref ";
198   printMemRefMetaData(std::cout, DynamicMemRefType<T>(m));
199 }
200 
201 template <typename T>
202 void printMemRefShape(UnrankedMemRefType<T> &m) {
203   std::cout << "Unranked Memref ";
204   printMemRefMetaData(std::cout, DynamicMemRefType<T>(m));
205 }
206 
207 template <typename T>
208 void printMemRef(const DynamicMemRefType<T> &m) {
209   printMemRefMetaData(std::cout, m);
210   std::cout << " data = " << std::endl;
211   if (m.rank == 0)
212     std::cout << "[";
213   MemRefDataPrinter<T>::print(std::cout, m.data, m.rank, m.rank, m.offset,
214                               m.sizes, m.strides);
215   if (m.rank == 0)
216     std::cout << "]";
217   std::cout << std::endl;
218 }
219 
220 template <typename T, int N>
221 void printMemRef(StridedMemRefType<T, N> &m) {
222   std::cout << "Memref ";
223   printMemRef(DynamicMemRefType<T>(m));
224 }
225 
226 template <typename T>
227 void printMemRef(UnrankedMemRefType<T> &m) {
228   std::cout << "Unranked Memref ";
229   printMemRef(DynamicMemRefType<T>(m));
230 }
231 
232 /// Verify the result of two computations are equivalent up to a small
233 /// numerical error and return the number of errors.
234 template <typename T>
235 struct MemRefDataVerifier {
236   /// Maximum number of errors printed by the verifier.
237   static constexpr int printLimit = 10;
238 
239   /// Verify the relative difference of the values is smaller than epsilon.
240   static bool verifyRelErrorSmallerThan(T actual, T expected, T epsilon);
241 
242   /// Verify the values are equivalent (integers) or are close (floating-point).
243   static bool verifyElem(T actual, T expected);
244 
245   /// Verify the data element-by-element and return the number of errors.
246   static int64_t verify(std::ostream &os, T *actualBasePtr, T *expectedBasePtr,
247                         int64_t dim, int64_t offset, const int64_t *sizes,
248                         const int64_t *strides, int64_t &printCounter);
249 };
250 
251 template <typename T>
252 bool MemRefDataVerifier<T>::verifyRelErrorSmallerThan(T actual, T expected,
253                                                       T epsilon) {
254   // Return an error if one of the values is infinite or NaN.
255   if (!std::isfinite(actual) || !std::isfinite(expected))
256     return false;
257   // Return true if the relative error is smaller than epsilon.
258   T delta = std::abs(actual - expected);
259   return (delta <= epsilon * std::abs(expected));
260 }
261 
262 template <typename T>
263 bool MemRefDataVerifier<T>::verifyElem(T actual, T expected) {
264   return actual == expected;
265 }
266 
267 template <>
268 inline bool MemRefDataVerifier<double>::verifyElem(double actual,
269                                                    double expected) {
270   return verifyRelErrorSmallerThan(actual, expected, 1e-12);
271 }
272 
273 template <>
274 inline bool MemRefDataVerifier<float>::verifyElem(float actual,
275                                                   float expected) {
276   return verifyRelErrorSmallerThan(actual, expected, 1e-6f);
277 }
278 
279 template <typename T>
280 int64_t MemRefDataVerifier<T>::verify(std::ostream &os, T *actualBasePtr,
281                                       T *expectedBasePtr, int64_t dim,
282                                       int64_t offset, const int64_t *sizes,
283                                       const int64_t *strides,
284                                       int64_t &printCounter) {
285   int64_t errors = 0;
286   // Verify the elements at the current offset.
287   if (dim == 0) {
288     if (!verifyElem(actualBasePtr[offset], expectedBasePtr[offset])) {
289       if (printCounter < printLimit) {
290         os << actualBasePtr[offset] << " != " << expectedBasePtr[offset]
291            << " offset = " << offset << "\n";
292         printCounter++;
293       }
294       errors++;
295     }
296   } else {
297     // Iterate the current dimension and verify recursively.
298     for (int64_t i = 0; i < sizes[0]; ++i) {
299       errors +=
300           verify(os, actualBasePtr, expectedBasePtr, dim - 1,
301                  offset + i * strides[0], sizes + 1, strides + 1, printCounter);
302     }
303   }
304   return errors;
305 }
306 
307 /// Verify the equivalence of two dynamic memrefs and return the number of
308 /// errors or -1 if the shape of the memrefs do not match.
309 template <typename T>
310 int64_t verifyMemRef(const DynamicMemRefType<T> &actual,
311                      const DynamicMemRefType<T> &expected) {
312   // Check if the memref shapes match.
313   for (int64_t i = 0; i < actual.rank; ++i) {
314     if (expected.rank != actual.rank || actual.offset != expected.offset ||
315         actual.sizes[i] != expected.sizes[i] ||
316         actual.strides[i] != expected.strides[i]) {
317       printMemRefMetaData(std::cerr, actual);
318       printMemRefMetaData(std::cerr, expected);
319       return -1;
320     }
321   }
322   // Return the number of errors.
323   int64_t printCounter = 0;
324   return MemRefDataVerifier<T>::verify(
325       std::cerr, actual.basePtr, expected.basePtr, actual.rank, actual.offset,
326       actual.sizes, actual.strides, printCounter);
327 }
328 
329 /// Verify the equivalence of two unranked memrefs and return the number of
330 /// errors or -1 if the shape of the memrefs do not match.
331 template <typename T>
332 int64_t verifyMemRef(UnrankedMemRefType<T> &actual,
333                      UnrankedMemRefType<T> &expected) {
334   return verifyMemRef(DynamicMemRefType<T>(actual),
335                       DynamicMemRefType<T>(expected));
336 }
337 
338 } // namespace impl
339 
340 ////////////////////////////////////////////////////////////////////////////////
341 // Currently exposed C API.
342 ////////////////////////////////////////////////////////////////////////////////
343 extern "C" MLIR_RUNNERUTILS_EXPORT void
344 _mlir_ciface_printMemrefShapeI8(UnrankedMemRefType<int8_t> *m);
345 extern "C" MLIR_RUNNERUTILS_EXPORT void
346 _mlir_ciface_printMemrefShapeI32(UnrankedMemRefType<int32_t> *m);
347 extern "C" MLIR_RUNNERUTILS_EXPORT void
348 _mlir_ciface_printMemrefShapeI64(UnrankedMemRefType<int64_t> *m);
349 extern "C" MLIR_RUNNERUTILS_EXPORT void
350 _mlir_ciface_printMemrefShapeF32(UnrankedMemRefType<float> *m);
351 extern "C" MLIR_RUNNERUTILS_EXPORT void
352 _mlir_ciface_printMemrefShapeF64(UnrankedMemRefType<double> *m);
353 
354 extern "C" MLIR_RUNNERUTILS_EXPORT void
355 _mlir_ciface_printMemrefI8(UnrankedMemRefType<int8_t> *m);
356 extern "C" MLIR_RUNNERUTILS_EXPORT void
357 _mlir_ciface_printMemrefI32(UnrankedMemRefType<int32_t> *m);
358 extern "C" MLIR_RUNNERUTILS_EXPORT void
359 _mlir_ciface_printMemrefI64(UnrankedMemRefType<int64_t> *m);
360 extern "C" MLIR_RUNNERUTILS_EXPORT void
361 _mlir_ciface_printMemrefF32(UnrankedMemRefType<float> *m);
362 extern "C" MLIR_RUNNERUTILS_EXPORT void
363 _mlir_ciface_printMemrefF64(UnrankedMemRefType<double> *m);
364 
365 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_nanoTime();
366 
367 extern "C" MLIR_RUNNERUTILS_EXPORT void printMemrefI32(int64_t rank, void *ptr);
368 extern "C" MLIR_RUNNERUTILS_EXPORT void printMemrefI64(int64_t rank, void *ptr);
369 extern "C" MLIR_RUNNERUTILS_EXPORT void printMemrefF32(int64_t rank, void *ptr);
370 extern "C" MLIR_RUNNERUTILS_EXPORT void printMemrefF64(int64_t rank, void *ptr);
371 extern "C" MLIR_RUNNERUTILS_EXPORT void printCString(char *str);
372 
373 extern "C" MLIR_RUNNERUTILS_EXPORT void
374 _mlir_ciface_printMemref0dF32(StridedMemRefType<float, 0> *m);
375 extern "C" MLIR_RUNNERUTILS_EXPORT void
376 _mlir_ciface_printMemref1dF32(StridedMemRefType<float, 1> *m);
377 extern "C" MLIR_RUNNERUTILS_EXPORT void
378 _mlir_ciface_printMemref2dF32(StridedMemRefType<float, 2> *m);
379 extern "C" MLIR_RUNNERUTILS_EXPORT void
380 _mlir_ciface_printMemref3dF32(StridedMemRefType<float, 3> *m);
381 extern "C" MLIR_RUNNERUTILS_EXPORT void
382 _mlir_ciface_printMemref4dF32(StridedMemRefType<float, 4> *m);
383 
384 extern "C" MLIR_RUNNERUTILS_EXPORT void _mlir_ciface_printMemrefVector4x4xf32(
385     StridedMemRefType<Vector2D<4, 4, float>, 2> *m);
386 
387 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_verifyMemRefI32(
388     UnrankedMemRefType<int32_t> *actual, UnrankedMemRefType<int32_t> *expected);
389 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_verifyMemRefF32(
390     UnrankedMemRefType<float> *actual, UnrankedMemRefType<float> *expected);
391 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_verifyMemRefF64(
392     UnrankedMemRefType<double> *actual, UnrankedMemRefType<double> *expected);
393 
394 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t verifyMemRefI32(int64_t rank,
395                                                            void *actualPtr,
396                                                            void *expectedPtr);
397 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t verifyMemRefF32(int64_t rank,
398                                                            void *actualPtr,
399                                                            void *expectedPtr);
400 extern "C" MLIR_RUNNERUTILS_EXPORT int64_t verifyMemRefF64(int64_t rank,
401                                                            void *actualPtr,
402                                                            void *expectedPtr);
403 
404 #endif // MLIR_EXECUTIONENGINE_RUNNERUTILS_H
405