Low-Level Abstraction of Memory Access
DumpMapping.hpp
Go to the documentation of this file.
1 // Copyright 2022 Bernhard Manfred Gruber
2 // SPDX-License-Identifier: MPL-2.0
3 
4 #pragma once
5 
6 #if __has_include(<fmt/format.h>)
7 # include "ArrayIndexRange.hpp"
8 # include "Core.hpp"
9 # include "StructName.hpp"
10 # include "View.hpp"
11 
12 # include <fmt/format.h>
13 # include <functional>
14 # include <optional>
15 # include <string>
16 # include <string_view>
17 # include <vector>
18 
19 namespace llama
20 {
21  namespace internal
22  {
23  inline auto color(std::string_view recordCoordTags) -> std::uint32_t
24  {
25  auto c = static_cast<uint32_t>(std::hash<std::string_view>{}(recordCoordTags) &std::size_t{0xFFFFFF});
26  c |= 0x404040u; // ensure color per channel is at least 0x40.
27  return c;
28  }
29 
30  // from: https://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string
31  inline auto xmlEscape(const std::string& str) -> std::string
32  {
33  std::string result;
34  result.reserve(str.size());
35  for(const char c : str)
36  {
37  switch(c)
38  {
39  case '&':
40  result.append("&amp;");
41  break;
42  case '\"':
43  result.append("&quot;");
44  break;
45  case '\'':
46  result.append("&apos;");
47  break;
48  case '<':
49  result.append("&lt;");
50  break;
51  case '>':
52  result.append("&gt;");
53  break;
54  default:
55  result += c;
56  break;
57  }
58  }
59  return result;
60  }
61 
62  template<typename T, std::size_t Dim>
63  auto formatArrayIndex(const ArrayIndex<T, Dim>& ai)
64  {
65  if constexpr(Dim == 1)
66  return std::to_string(ai[0]);
67  else
68  {
69  std::string s = "{";
70  for(auto v : ai)
71  {
72  if(s.size() >= 2)
73  s += ",";
74  s += std::to_string(v);
75  }
76  s += "}";
77  return s;
78  }
79  }
80 
81  template<typename ArrayIndex>
82  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
83  struct FieldBox
84  {
85  ArrayIndex arrayIndex;
86  std::size_t flatRecordCoord;
87  std::string_view recordCoordTags;
88  NrAndOffset<std::size_t> nrAndOffset;
89  std::size_t size;
90  };
91 
92  template<typename View>
93  void fillBlobsWithPattern(View& view, uint8_t pattern)
94  {
95  const auto& mapping = view.mapping();
96  for(std::size_t i = 0; i < View::Mapping::blobCount; i++)
97  std::memset(&view.blobs()[i][0], pattern, mapping.blobSize(i));
98  }
99 
100  template<typename View, typename RecordCoord>
101  void boxesFromComputedField(
102  View& view,
103  typename View::Mapping::ArrayExtents::Index ai,
104  RecordCoord rc,
105  std::vector<FieldBox<typename View::Mapping::ArrayExtents::Index>>& infos)
106  {
107  using Mapping = typename View::Mapping;
108  using RecordDim = typename Mapping::RecordDim;
109 
110  auto emitInfo = [&](auto nrAndOffset, std::size_t size)
111  {
112  infos.push_back(
113  {ai,
114  flatRecordCoord<RecordDim, RecordCoord>,
115  prettyRecordCoord<RecordDim>(rc),
116  nrAndOffset,
117  size});
118  };
119 
120  using Type = GetType<RecordDim, decltype(rc)>;
121  // computed values can come from anywhere, so we can only apply heuristics
122  auto& blobs = view.blobs();
123  auto&& ref = view.mapping().compute(ai, rc, blobs);
124 
125  // if we get a reference, try to find the mapped address in one of the blobs
126  if constexpr(std::is_lvalue_reference_v<decltype(ref)>)
127  {
128  auto address = reinterpret_cast<std::intptr_t>(&ref);
129  for(std::size_t i = 0; i < blobs.size(); i++)
130  {
131  // TODO(bgruber): this is UB, because we are comparing pointers from unrelated
132  // allocations
133  const auto front = reinterpret_cast<std::intptr_t>(&blobs[i][0]);
134  const auto back = reinterpret_cast<std::intptr_t>(&blobs[i][view.mapping().blobSize(i) - 1]);
135  if(front <= address && address <= back)
136  {
137  emitInfo(NrAndOffset{i, static_cast<std::size_t>(address - front)}, sizeof(Type));
138  return; // a mapping can only map to one location in the blobs
139  }
140  }
141  }
142 
143  if constexpr(std::is_default_constructible_v<Type>)
144  {
145  const auto infosBefore = infos.size();
146 
147  // try to observe written bytes
148  const auto pattern = std::uint8_t{0xFF};
149  fillBlobsWithPattern(view, pattern);
150  ref = Type{}; // a broad range of types is default constructible and should write
151  // something zero-ish
152  auto wasTouched = [&](auto b) { return static_cast<std::uint8_t>(b) != pattern; };
153  for(std::size_t i = 0; i < Mapping::blobCount; i++)
154  {
155  const auto blobSize = view.mapping().blobSize(i);
156  const auto* begin = &blobs[i][0];
157  const auto* end = begin + blobSize;
158 
159  auto* searchBegin = begin;
160  while(true)
161  {
162  const auto* touchedBegin = std::find_if(searchBegin, end, wasTouched);
163  if(touchedBegin == end)
164  break;
165  const auto& touchedEnd = std::find_if_not(touchedBegin + 1, end, wasTouched);
166  emitInfo(
167  NrAndOffset{i, static_cast<std::size_t>(touchedBegin - begin)},
168  touchedEnd - touchedBegin);
169  if(touchedEnd == end)
170  break;
171  searchBegin = touchedEnd + 1;
172  }
173  }
174 
175  if(infosBefore != infos.size())
176  return;
177  }
178 
179  // if we come here, we could not find out where the value is coming from
180  emitInfo(NrAndOffset{Mapping::blobCount, std::size_t{0}}, sizeof(Type));
181  }
182 
183  template<typename Mapping>
184  auto boxesFromMapping(const Mapping& mapping) -> std::vector<FieldBox<typename Mapping::ArrayExtents::Index>>
185  {
186  std::vector<FieldBox<typename Mapping::ArrayExtents::Index>> infos;
187 
188  std::optional<decltype(allocView(mapping))> view;
189  if constexpr(hasAnyComputedField<Mapping>)
190  view = allocView(mapping);
191 
192  using RecordDim = typename Mapping::RecordDim;
193  for(auto ai : ArrayIndexRange{mapping.extents()})
194  forEachLeafCoord<RecordDim>(
195  [&](auto rc)
196  {
197  using Type = GetType<RecordDim, decltype(rc)>;
198  if constexpr(llama::isComputed<Mapping, decltype(rc)>)
199  boxesFromComputedField(view.value(), ai, rc, infos);
200  else
201  {
202  const auto [nr, off] = mapping.blobNrAndOffset(ai, rc);
203  infos.push_back(
204  {ai,
205  flatRecordCoord<RecordDim, decltype(rc)>,
206  prettyRecordCoord<RecordDim>(rc),
207  {static_cast<std::size_t>(nr), static_cast<std::size_t>(off)},
208  sizeof(Type)});
209  }
210  });
211 
212  return infos;
213  }
214 
215  template<typename ArrayIndex>
216  auto breakBoxes(std::vector<FieldBox<ArrayIndex>> boxes, std::size_t wrapByteCount)
217  -> std::vector<FieldBox<ArrayIndex>>
218  {
219  for(std::size_t i = 0; i < boxes.size(); i++)
220  {
221  auto& fb = boxes[i];
222  if(fb.nrAndOffset.offset / wrapByteCount != (fb.nrAndOffset.offset + fb.size - 1) / wrapByteCount)
223  {
224  const auto remainingSpace = wrapByteCount - fb.nrAndOffset.offset % wrapByteCount;
225  auto newFb = fb;
226  newFb.nrAndOffset.offset = fb.nrAndOffset.offset + remainingSpace;
227  newFb.size = fb.size - remainingSpace;
228  fb.size = remainingSpace;
229  boxes.push_back(newFb);
230  }
231  }
232  return boxes;
233  }
234 
235  inline auto cssClass(std::string tags)
236  {
237  std::replace(begin(tags), end(tags), '.', '_');
238  std::replace(begin(tags), end(tags), '<', '_');
239  std::replace(begin(tags), end(tags), '>', '_');
240  return tags;
241  };
242  } // namespace internal
243 
249  template<typename Mapping>
250  auto toSvg(
251  const Mapping& mapping,
252  std::size_t wrapByteCount = 64,
253  bool breakBoxes = true,
254  const std::vector<std::uint32_t>& palette = {},
255  std::string_view textColor = "black") -> std::string
256  {
257  constexpr auto byteSizeInPixel = 30;
258  constexpr auto blobBlockWidth = 60;
259 
260  auto infos = internal::boxesFromMapping(mapping);
261  if(breakBoxes)
262  infos = internal::breakBoxes(std::move(infos), wrapByteCount);
263  std::stable_sort(
264  begin(infos),
265  end(infos),
266  [](const auto& a, const auto& b) {
267  return std::tie(a.nrAndOffset.nr, a.nrAndOffset.offset)
268  < std::tie(b.nrAndOffset.nr, b.nrAndOffset.offset);
269  });
270 
271  std::string svg;
272 
273  std::array<int, Mapping::blobCount + hasAnyComputedField<Mapping> + 1> blobYOffset{};
274  auto writeBlobHeader = [&](std::size_t i, std::size_t size, std::string_view name)
275  {
276  const auto blobRows = (size + wrapByteCount - 1) / wrapByteCount;
277  blobYOffset[i + 1] = blobYOffset[i] + (blobRows + 1) * byteSizeInPixel; // one row gap between blobs
278  const auto height = blobRows * byteSizeInPixel;
279  svg += fmt::format(
280  R"a(<rect x="0" y="{}" width="{}" height="{}" fill="#AAA" stroke="#000"/>
281 <text x="{}" y="{}" fill="{}" text-anchor="middle">{}</text>
282 )a",
283  blobYOffset[i],
284  blobBlockWidth,
285  height,
286  blobBlockWidth / 2,
287  blobYOffset[i] + height / 2,
288  textColor,
289  name);
290  };
291  for(std::size_t i = 0; i < Mapping::blobCount; i++)
292  writeBlobHeader(i, mapping.blobSize(i), "Blob: " + std::to_string(i));
293 
294  svg = fmt::format(
295  R"(<?xml version="1.0" encoding="UTF-8" standalone="no"?>
296 <svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">
297  <style>
298  .label {{ font: {}px sans-serif; }}
299  </style>
300 )",
301  blobBlockWidth + wrapByteCount * byteSizeInPixel,
302  blobYOffset.back() == 0 ? 987654321 : blobYOffset.back() - byteSizeInPixel,
303  byteSizeInPixel / 2)
304  + svg;
305 
306  std::size_t computedSizeSoFar = 0;
307  std::size_t lastBlobNr = std::numeric_limits<std::size_t>::max();
308  std::size_t usedBytesInBlobSoFar = 0;
309  for(const auto& info : infos)
310  {
311  if(lastBlobNr != info.nrAndOffset.nr)
312  {
313  usedBytesInBlobSoFar = 0;
314  lastBlobNr = info.nrAndOffset.nr;
315  }
316 
317  const auto blobY = blobYOffset[info.nrAndOffset.nr];
318  const auto offset = [&]
319  {
320  if(info.nrAndOffset.nr < Mapping::blobCount)
321  return info.nrAndOffset.offset;
322 
323  const auto offset = computedSizeSoFar;
324  computedSizeSoFar += info.size;
325  return offset;
326  }();
327  auto x = (offset % wrapByteCount) * byteSizeInPixel + blobBlockWidth;
328  auto y = (offset / wrapByteCount) * byteSizeInPixel + blobY;
329  const auto fillColor = [&]
330  {
331  if(palette.empty())
332  return internal::color(info.recordCoordTags);
333  return palette[info.flatRecordCoord % palette.size()];
334  }();
335  const auto width = byteSizeInPixel * info.size;
336 
337  const auto nextOffset = [&]
338  {
339  if(&info == &infos.back())
340  return std::numeric_limits<std::size_t>::max();
341  const auto& nextInfo = (&info)[1];
342  if(info.nrAndOffset.nr < Mapping::blobCount && info.nrAndOffset.nr == nextInfo.nrAndOffset.nr)
343  return nextInfo.nrAndOffset.offset;
344 
345  return std::numeric_limits<std::size_t>::max();
346  }();
347  const auto isOverlapped = offset < usedBytesInBlobSoFar || nextOffset < offset + info.size;
348  usedBytesInBlobSoFar = offset + info.size;
349 
350  constexpr auto cropBoxes = true;
351  if(cropBoxes)
352  {
353  svg += fmt::format(
354  R"(<svg x="{}" y="{}" width="{}" height="{}">
355 )",
356  x,
357  y,
358  width,
359  byteSizeInPixel);
360  x = 0;
361  y = 0;
362  }
363  svg += fmt::format(
364  R"(<rect x="{}" y="{}" width="{}" height="{}" fill="#{:06X}" stroke="#000" fill-opacity="{}"/>
365 )",
366  x,
367  y,
368  width,
369  byteSizeInPixel,
370  fillColor,
371  isOverlapped ? 0.3 : 1.0);
372  for(std::size_t i = 1; i < info.size; i++)
373  {
374  svg += fmt::format(
375  R"(<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="#777"/>
376 )",
377  x + i * byteSizeInPixel,
378  y + byteSizeInPixel * 2 / 3,
379  x + i * byteSizeInPixel,
380  y + byteSizeInPixel);
381  }
382  svg += fmt::format(
383  R"(<text x="{}" y="{}" fill="{}" text-anchor="middle" class="label">{} {}</text>
384 )",
385  x + width / 2,
386  y + byteSizeInPixel * 3 / 4,
387  textColor,
388  internal::formatArrayIndex(info.arrayIndex),
389  internal::xmlEscape(std::string{info.recordCoordTags}));
390  if(cropBoxes)
391  svg += R"(</svg>
392 )";
393  }
394 
395  if(hasAnyComputedField<Mapping>)
396  {
397  if(computedSizeSoFar > 0)
398  writeBlobHeader(Mapping::blobCount, computedSizeSoFar, "Comp.");
399  else
400  {
401  const auto blobRows = (wrapByteCount - 1) / wrapByteCount;
402  blobYOffset[Mapping::blobCount + 1]
403  = blobYOffset[Mapping::blobCount] + blobRows * byteSizeInPixel; // fix-up, omit gap
404  }
405 
406  // fix total SVG size
407  const auto i = svg.find("987654321");
408  assert(i != std::string::npos);
409  svg.replace(i, 9, std::to_string(blobYOffset.back() - byteSizeInPixel));
410  }
411 
412  svg += "</svg>";
413  return svg;
414  }
415 
417  template<typename Mapping>
418  auto toSvg(const Mapping& mapping, const std::vector<std::uint32_t>& palette, std::string_view textColor = "#000")
419  -> std::string
420  {
421  return toSvg(mapping, 64, true, palette, textColor);
422  }
423 
427  template<typename Mapping>
428  auto toHtml(const Mapping& mapping) -> std::string
429  {
430  constexpr auto byteSizeInPixel = 30;
431  constexpr auto rulerLengthInBytes = 512;
432  constexpr auto rulerByteInterval = 8;
433 
434  auto infos = internal::boxesFromMapping(mapping);
435  std::stable_sort(
436  begin(infos),
437  end(infos),
438  [](const auto& a, const auto& b) {
439  return std::tie(a.nrAndOffset.nr, a.nrAndOffset.offset)
440  < std::tie(b.nrAndOffset.nr, b.nrAndOffset.offset);
441  });
442  infos.erase(
443  std::unique(
444  begin(infos),
445  end(infos),
446  [](const auto& a, const auto& b) { return a.nrAndOffset == b.nrAndOffset; }),
447  end(infos));
448 
449  std::string html;
450  html += fmt::format(
451  R"(<!DOCTYPE html>
452 <html>
453 <head>
454 <style>
455 .box {{
456  outline: 1px solid;
457  display: inline-block;
458  white-space: nowrap;
459  height: {}px;
460  background: repeating-linear-gradient(90deg, #0000, #0000 29px, #777 29px, #777 30px);
461  text-align: center;
462  overflow: hidden;
463  vertical-align: middle;
464 }}
465 #ruler {{
466  background: repeating-linear-gradient(90deg, #0000, #0000 29px, #000 29px, #000 30px);
467  border-bottom: 1px solid;
468  height: 20px;
469  margin-bottom: 20px;
470 }}
471 #ruler div {{
472  position: absolute;
473  display: inline-block;
474 }}
475 )",
476  byteSizeInPixel);
477  using RecordDim = typename Mapping::RecordDim;
478  forEachLeafCoord<RecordDim>(
479  [&](auto rc)
480  {
481  constexpr int size = sizeof(GetType<RecordDim, decltype(rc)>);
482 
483  html += fmt::format(
484  R"(.{} {{
485  width: {}px;
486  background-color: #{:X};
487 }}
488 )",
489  internal::cssClass(std::string{prettyRecordCoord<RecordDim>(rc)}),
490  byteSizeInPixel * size,
491  internal::color(prettyRecordCoord<RecordDim>(rc)));
492  });
493 
494  html += fmt::format(R"(</style>
495 </head>
496 <body>
497  <header id="ruler">
498 )");
499  for(auto i = 0; i < rulerLengthInBytes; i += rulerByteInterval)
500  html += fmt::format(
501  R"(</style>
502  <div style="margin-left: {}px;">{}</div>)",
503  i * byteSizeInPixel,
504  i);
505  html += fmt::format(R"(
506  </header>
507 )");
508 
509  auto currentBlobNr = std::numeric_limits<std::size_t>::max();
510  for(const auto& info : infos)
511  {
512  if(currentBlobNr != info.nrAndOffset.nr)
513  {
514  currentBlobNr = info.nrAndOffset.nr;
515  html += fmt::format("<h1>Blob: {}</h1>", currentBlobNr);
516  }
517  html += fmt::format(
518  R"(<div class="box {0}" title="{1} {2}">{1} {2}</div>)",
519  internal::cssClass(std::string{info.recordCoordTags}),
520  internal::formatArrayIndex(info.arrayIndex),
521  internal::xmlEscape(std::string{info.recordCoordTags}));
522  }
523  html += R"(</body>
524 </html>)";
525  return html;
526  }
527 } // namespace llama
528 
529 #endif
#define LLAMA_EXPORT
Definition: macros.hpp:192
ArrayIndex(Args...) -> ArrayIndex< typename internal::IndexTypeFromArgs< std::size_t, Args... >::type, sizeof...(Args)>
NrAndOffset(Int, Int) -> NrAndOffset< Int >
constexpr std::size_t flatRecordCoord
Definition: Core.hpp:517
auto allocView(Mapping mapping={}, const Allocator &alloc={}, Accessor accessor={}) -> View< Mapping, internal::AllocatorBlobType< Allocator, typename Mapping::RecordDim >, Accessor >
Definition: View.hpp:153
constexpr bool isComputed
Returns true if the field accessed via the given mapping and record coordinate is a computed value.
Definition: View.hpp:84
typename internal::GetTypeImpl< RecordDim, RecordCoordOrTags... >::type GetType
Definition: Core.hpp:388
TMapping Mapping
Definition: View.hpp:396