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