1 // Copyright 2018 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/verifier.h"
16 
17 #include <cassert>
18 #include <cmath>
19 #include <cstring>
20 #include <string>
21 #include <vector>
22 
23 #include "src/command.h"
24 #include "src/float16_helper.h"
25 
26 namespace amber {
27 namespace {
28 
29 const uint32_t kBitsPerByte = 8;
30 const double kEpsilon = 0.000001;
31 const double kDefaultTexelTolerance = 0.002;
32 
33 // Copy [src_bit_offset, src_bit_offset + bits) bits of |src| to
34 // [0, bits) of |dst|.
CopyBitsOfMemoryToBuffer(uint8_t * dst,const uint8_t * src,uint32_t src_bit_offset,uint32_t bits)35 void CopyBitsOfMemoryToBuffer(uint8_t* dst,
36                               const uint8_t* src,
37                               uint32_t src_bit_offset,
38                               uint32_t bits) {
39   while (src_bit_offset > 7) {
40     ++src;
41     src_bit_offset = src_bit_offset - kBitsPerByte;
42   }
43 
44   // Number of bytes greater than or equal to |(src_bit_offset + bits) / 8|.
45   const uint32_t size_in_bytes = (src_bit_offset + bits + 7) / kBitsPerByte;
46   assert(size_in_bytes <= kBitsPerByte);
47 
48   uint64_t data = 0;
49   uint8_t* ptr = reinterpret_cast<uint8_t*>(&data);
50   for (uint32_t i = 0; i < size_in_bytes; ++i) {
51     ptr[i] = src[i];
52   }
53 
54   data >>= src_bit_offset;
55   if (bits != 64)
56     data &= (1ULL << bits) - 1ULL;
57 
58   std::memcpy(dst, &data, static_cast<size_t>((bits + 7) / kBitsPerByte));
59 }
60 
61 // This is based on "18.3. sRGB transfer functions" of
62 // https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html
SRGBToLinearValue(double sRGB)63 double SRGBToLinearValue(double sRGB) {
64   if (sRGB <= 0.04045)
65     return sRGB / 12.92;
66 
67   return pow((sRGB + 0.055) / 1.055, 2.4);
68 }
69 
70 // It returns true if the difference is within the given error.
71 // If |is_tolerance_percent| is true, the actual tolerance will be
72 // relative value i.e., |tolerance| / 100 * fabs(expected).
73 // Otherwise, this method uses the absolute value i.e., |tolerance|.
IsEqualWithTolerance(const double actual,const double expected,double tolerance,const bool is_tolerance_percent=true)74 bool IsEqualWithTolerance(const double actual,
75                           const double expected,
76                           double tolerance,
77                           const bool is_tolerance_percent = true) {
78   // Special case for NaN.
79   if (std::isunordered(actual, expected)) {
80     return std::isnan(actual) == std::isnan(expected);
81   }
82 
83   double difference = std::fabs(actual - expected);
84   if (is_tolerance_percent) {
85     if (difference > ((tolerance / 100.0) * std::fabs(expected))) {
86       return false;
87     }
88   } else if (difference > tolerance) {
89     return false;
90   }
91   return true;
92 }
93 
94 template <typename T>
CheckActualValue(const ProbeSSBOCommand * command,const T actual_value,const Value & value)95 Result CheckActualValue(const ProbeSSBOCommand* command,
96                         const T actual_value,
97                         const Value& value) {
98   const auto comp = command->GetComparator();
99   const auto& tolerance = command->GetTolerances();
100   const T val = value.IsInteger() ? static_cast<T>(value.AsUint64())
101                                   : static_cast<T>(value.AsDouble());
102   switch (comp) {
103     case ProbeSSBOCommand::Comparator::kEqual:
104       if (value.IsInteger()) {
105         if (static_cast<uint64_t>(actual_value) != static_cast<uint64_t>(val)) {
106           return Result(std::to_string(actual_value) +
107                         " == " + std::to_string(val));
108         }
109       } else {
110         if (!IsEqualWithTolerance(static_cast<const double>(actual_value),
111                                   static_cast<const double>(val), kEpsilon)) {
112           return Result(std::to_string(actual_value) +
113                         " == " + std::to_string(val));
114         }
115       }
116       break;
117     case ProbeSSBOCommand::Comparator::kNotEqual:
118       if (value.IsInteger()) {
119         if (static_cast<uint64_t>(actual_value) == static_cast<uint64_t>(val)) {
120           return Result(std::to_string(actual_value) +
121                         " != " + std::to_string(val));
122         }
123       } else {
124         if (IsEqualWithTolerance(static_cast<const double>(actual_value),
125                                  static_cast<const double>(val), kEpsilon)) {
126           return Result(std::to_string(actual_value) +
127                         " != " + std::to_string(val));
128         }
129       }
130       break;
131     case ProbeSSBOCommand::Comparator::kFuzzyEqual:
132       if (!IsEqualWithTolerance(
133               static_cast<const double>(actual_value),
134               static_cast<const double>(val),
135               command->HasTolerances() ? tolerance[0].value : kEpsilon,
136               command->HasTolerances() ? tolerance[0].is_percent : true)) {
137         return Result(std::to_string(actual_value) +
138                       " ~= " + std::to_string(val));
139       }
140       break;
141     case ProbeSSBOCommand::Comparator::kLess:
142       if (actual_value >= val)
143         return Result(std::to_string(actual_value) + " < " +
144                       std::to_string(val));
145       break;
146     case ProbeSSBOCommand::Comparator::kLessOrEqual:
147       if (actual_value > val)
148         return Result(std::to_string(actual_value) +
149                       " <= " + std::to_string(val));
150       break;
151     case ProbeSSBOCommand::Comparator::kGreater:
152       if (actual_value <= val)
153         return Result(std::to_string(actual_value) + " > " +
154                       std::to_string(val));
155       break;
156     case ProbeSSBOCommand::Comparator::kGreaterOrEqual:
157       if (actual_value < val)
158         return Result(std::to_string(actual_value) +
159                       " >= " + std::to_string(val));
160       break;
161   }
162   return {};
163 }
164 
165 template <typename T>
CheckValue(const ProbeSSBOCommand * command,const uint8_t * memory,const Value & value)166 Result CheckValue(const ProbeSSBOCommand* command,
167                   const uint8_t* memory,
168                   const Value& value) {
169   const T* ptr = reinterpret_cast<const T*>(memory);
170   return CheckActualValue<T>(command, *ptr, value);
171 }
172 
SetupToleranceForTexels(const ProbeCommand * command,double * tolerance,bool * is_tolerance_percent)173 void SetupToleranceForTexels(const ProbeCommand* command,
174                              double* tolerance,
175                              bool* is_tolerance_percent) {
176   if (command->HasTolerances()) {
177     const auto& tol = command->GetTolerances();
178     if (tol.size() == 4) {
179       tolerance[0] = tol[0].value;
180       tolerance[1] = tol[1].value;
181       tolerance[2] = tol[2].value;
182       tolerance[3] = tol[3].value;
183       is_tolerance_percent[0] = tol[0].is_percent;
184       is_tolerance_percent[1] = tol[1].is_percent;
185       is_tolerance_percent[2] = tol[2].is_percent;
186       is_tolerance_percent[3] = tol[3].is_percent;
187     } else {
188       tolerance[0] = tol[0].value;
189       tolerance[1] = tol[0].value;
190       tolerance[2] = tol[0].value;
191       tolerance[3] = tol[0].value;
192       is_tolerance_percent[0] = tol[0].is_percent;
193       is_tolerance_percent[1] = tol[0].is_percent;
194       is_tolerance_percent[2] = tol[0].is_percent;
195       is_tolerance_percent[3] = tol[0].is_percent;
196     }
197   } else {
198     tolerance[0] = kDefaultTexelTolerance;
199     tolerance[1] = kDefaultTexelTolerance;
200     tolerance[2] = kDefaultTexelTolerance;
201     tolerance[3] = kDefaultTexelTolerance;
202     is_tolerance_percent[0] = false;
203     is_tolerance_percent[1] = false;
204     is_tolerance_percent[2] = false;
205     is_tolerance_percent[3] = false;
206   }
207 }
208 
209 // Convert data of |texel| into double values based on the
210 // information given in |fmt|.
GetActualValuesFromTexel(const uint8_t * texel,const Format * fmt)211 std::vector<double> GetActualValuesFromTexel(const uint8_t* texel,
212                                              const Format* fmt) {
213   assert(fmt && !fmt->GetSegments().empty());
214 
215   std::vector<double> actual_values(fmt->GetSegments().size());
216   uint32_t bit_offset = 0;
217 
218   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
219     const auto& seg = fmt->GetSegments()[i];
220     if (seg.IsPadding()) {
221       bit_offset += seg.GetNumBits();
222       continue;
223     }
224 
225     uint8_t actual[8] = {0, 0, 0, 0, 0, 0, 0, 0};
226     uint32_t num_bits = seg.GetNumBits();
227     CopyBitsOfMemoryToBuffer(actual, texel, bit_offset, num_bits);
228 
229     FormatMode mode = seg.GetFormatMode();
230     if (type::Type::IsInt8(mode, num_bits)) {
231       int8_t* ptr8 = nullptr;
232       ptr8 = reinterpret_cast<int8_t*>(actual);
233       actual_values[i] = static_cast<double>(*ptr8);
234     } else if (type::Type::IsInt16(mode, num_bits)) {
235       int16_t* ptr16 = nullptr;
236       ptr16 = reinterpret_cast<int16_t*>(actual);
237       actual_values[i] = static_cast<double>(*ptr16);
238     } else if (type::Type::IsInt32(mode, num_bits)) {
239       int32_t* ptr32 = nullptr;
240       ptr32 = reinterpret_cast<int32_t*>(actual);
241       actual_values[i] = static_cast<double>(*ptr32);
242     } else if (type::Type::IsInt64(mode, num_bits)) {
243       int64_t* ptr64 = nullptr;
244       ptr64 = reinterpret_cast<int64_t*>(actual);
245       actual_values[i] = static_cast<double>(*ptr64);
246     } else if (type::Type::IsUint8(mode, num_bits)) {
247       actual_values[i] = static_cast<double>(*actual);
248     } else if (type::Type::IsUint16(mode, num_bits)) {
249       uint16_t* ptr16 = nullptr;
250       ptr16 = reinterpret_cast<uint16_t*>(actual);
251       actual_values[i] = static_cast<double>(*ptr16);
252     } else if (type::Type::IsUint32(mode, num_bits)) {
253       uint32_t* ptr32 = nullptr;
254       ptr32 = reinterpret_cast<uint32_t*>(actual);
255       actual_values[i] = static_cast<double>(*ptr32);
256     } else if (type::Type::IsUint64(mode, num_bits)) {
257       uint64_t* ptr64 = nullptr;
258       ptr64 = reinterpret_cast<uint64_t*>(actual);
259       actual_values[i] = static_cast<double>(*ptr64);
260     } else if (type::Type::IsFloat32(mode, num_bits)) {
261       float* ptr = reinterpret_cast<float*>(actual);
262       actual_values[i] = static_cast<double>(*ptr);
263     } else if (type::Type::IsFloat64(mode, num_bits)) {
264       double* ptr = reinterpret_cast<double*>(actual);
265       actual_values[i] = *ptr;
266     } else if (type::Type::IsFloat(mode) && num_bits < 32) {
267       actual_values[i] = static_cast<double>(
268           float16::HexFloatToFloat(actual, static_cast<uint8_t>(num_bits)));
269     } else {
270       assert(false && "Incorrect number of bits for number.");
271     }
272 
273     bit_offset += num_bits;
274   }
275 
276   return actual_values;
277 }
278 
279 // If component mode of |fmt| is FormatMode::kUNorm or
280 // ::kSNorm or ::kSRGB, scale the corresponding value in |texel|.
281 // Note that we do not scale values with FormatMode::kUInt, ::kSInt,
282 // ::kUFloat, ::kSFloat.
ScaleTexelValuesIfNeeded(std::vector<double> * texel,const Format * fmt)283 void ScaleTexelValuesIfNeeded(std::vector<double>* texel, const Format* fmt) {
284   assert(fmt->GetSegments().size() == texel->size());
285 
286   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
287     const auto& seg = fmt->GetSegments()[i];
288     if (seg.IsPadding())
289       continue;
290 
291     double scaled_value = (*texel)[i];
292     if (seg.GetFormatMode() == FormatMode::kUNorm) {
293       scaled_value /= static_cast<double>((1 << seg.GetNumBits()) - 1);
294     } else if (seg.GetFormatMode() == FormatMode::kSNorm) {
295       scaled_value /= static_cast<double>((1 << (seg.GetNumBits() - 1)) - 1);
296     } else if (seg.GetFormatMode() == FormatMode::kSRGB) {
297       scaled_value /= static_cast<double>((1 << seg.GetNumBits()) - 1);
298       if (seg.GetName() != FormatComponentType::kA)
299         scaled_value = SRGBToLinearValue(scaled_value);
300     } else if (seg.GetFormatMode() == FormatMode::kSScaled ||
301                seg.GetFormatMode() == FormatMode::kUScaled) {
302       assert(false && "UScaled and SScaled are not implemented");
303     }
304 
305     (*texel)[i] = scaled_value;
306   }
307 }
308 
309 /// Check |texel| with |texel_format| is the same with the expected
310 /// RGB(A) values given via |command|. This method allow error
311 /// smaller than |tolerance|. If an element of
312 /// |is_tolerance_percent| is true, we assume that the corresponding
313 /// |tolerance| is relative i.e., percentage allowed error.
IsTexelEqualToExpected(const std::vector<double> & texel,const Format * fmt,const ProbeCommand * command,const double * tolerance,const bool * is_tolerance_percent)314 bool IsTexelEqualToExpected(const std::vector<double>& texel,
315                             const Format* fmt,
316                             const ProbeCommand* command,
317                             const double* tolerance,
318                             const bool* is_tolerance_percent) {
319   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
320     const auto& seg = fmt->GetSegments()[i];
321     if (seg.IsPadding())
322       continue;
323 
324     double texel_for_component = texel[i];
325     double expected = 0;
326     double current_tolerance = 0;
327     bool is_current_tolerance_percent = false;
328     switch (seg.GetName()) {
329       case FormatComponentType::kA:
330         if (!command->IsRGBA())
331           continue;
332 
333         expected = static_cast<double>(command->GetA());
334         current_tolerance = tolerance[3];
335         is_current_tolerance_percent = is_tolerance_percent[3];
336         break;
337       case FormatComponentType::kR:
338         expected = static_cast<double>(command->GetR());
339         current_tolerance = tolerance[0];
340         is_current_tolerance_percent = is_tolerance_percent[0];
341         break;
342       case FormatComponentType::kG:
343         expected = static_cast<double>(command->GetG());
344         current_tolerance = tolerance[1];
345         is_current_tolerance_percent = is_tolerance_percent[1];
346         break;
347       case FormatComponentType::kB:
348         expected = static_cast<double>(command->GetB());
349         current_tolerance = tolerance[2];
350         is_current_tolerance_percent = is_tolerance_percent[2];
351         break;
352       default:
353         continue;
354     }
355 
356     if (!IsEqualWithTolerance(expected, texel_for_component, current_tolerance,
357                               is_current_tolerance_percent)) {
358       return false;
359     }
360   }
361 
362   return true;
363 }
364 
GetTexelInRGBA(const std::vector<double> & texel,const Format * fmt)365 std::vector<double> GetTexelInRGBA(const std::vector<double>& texel,
366                                    const Format* fmt) {
367   std::vector<double> texel_in_rgba(texel.size());
368   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
369     const auto& seg = fmt->GetSegments()[i];
370     if (seg.IsPadding())
371       continue;
372 
373     switch (seg.GetName()) {
374       case FormatComponentType::kR:
375         texel_in_rgba[0] = texel[i];
376         break;
377       case FormatComponentType::kG:
378         texel_in_rgba[1] = texel[i];
379         break;
380       case FormatComponentType::kB:
381         texel_in_rgba[2] = texel[i];
382         break;
383       case FormatComponentType::kA:
384         texel_in_rgba[3] = texel[i];
385         break;
386       default:
387         continue;
388     }
389   }
390   return texel_in_rgba;
391 }
392 
393 }  // namespace
394 
395 Verifier::Verifier() = default;
396 
397 Verifier::~Verifier() = default;
398 
Probe(const ProbeCommand * command,const Format * fmt,uint32_t texel_stride,uint32_t row_stride,uint32_t frame_width,uint32_t frame_height,const void * buf)399 Result Verifier::Probe(const ProbeCommand* command,
400                        const Format* fmt,
401                        uint32_t texel_stride,
402                        uint32_t row_stride,
403                        uint32_t frame_width,
404                        uint32_t frame_height,
405                        const void* buf) {
406   if (!command)
407     return Result("Verifier::Probe given ProbeCommand is nullptr");
408   if (!fmt)
409     return Result("Verifier::Probe given texel's Format is nullptr");
410   if (!buf)
411     return Result("Verifier::Probe given buffer to probe is nullptr");
412 
413   uint32_t x = 0;
414   uint32_t y = 0;
415   uint32_t width = 1;
416   uint32_t height = 1;
417 
418   if (command->IsWholeWindow()) {
419     width = frame_width;
420     height = frame_height;
421   } else if (command->IsRelative()) {
422     x = static_cast<uint32_t>(static_cast<float>(frame_width) *
423                               command->GetX());
424     y = static_cast<uint32_t>(static_cast<float>(frame_height) *
425                               command->GetY());
426     if (command->IsProbeRect()) {
427       width = static_cast<uint32_t>(static_cast<float>(frame_width) *
428                                     command->GetWidth());
429       height = static_cast<uint32_t>(static_cast<float>(frame_height) *
430                                      command->GetHeight());
431     }
432   } else {
433     x = static_cast<uint32_t>(command->GetX());
434     y = static_cast<uint32_t>(command->GetY());
435     width = static_cast<uint32_t>(command->GetWidth());
436     height = static_cast<uint32_t>(command->GetHeight());
437   }
438 
439   if (x + width > frame_width || y + height > frame_height) {
440     return Result(
441         "Line " + std::to_string(command->GetLine()) +
442         ": Verifier::Probe Position(" + std::to_string(x + width - 1) + ", " +
443         std::to_string(y + height - 1) + ") is out of framebuffer scope (" +
444         std::to_string(frame_width) + "," + std::to_string(frame_height) + ")");
445   }
446 
447   if (row_stride < frame_width * texel_stride) {
448     return Result("Line " + std::to_string(command->GetLine()) +
449                   ": Verifier::Probe Row stride of " +
450                   std::to_string(row_stride) + " is too small for " +
451                   std::to_string(frame_width) + " texels of " +
452                   std::to_string(texel_stride) + " bytes each");
453   }
454 
455   double tolerance[4] = {0, 0, 0, 0};
456   bool is_tolerance_percent[4] = {0, 0, 0, 0};
457   SetupToleranceForTexels(command, tolerance, is_tolerance_percent);
458 
459   const uint8_t* ptr = static_cast<const uint8_t*>(buf);
460   uint32_t count_of_invalid_pixels = 0;
461   uint32_t first_invalid_i = 0;
462   uint32_t first_invalid_j = 0;
463   std::vector<double> failure_values;
464   for (uint32_t j = 0; j < height; ++j) {
465     const uint8_t* p = ptr + row_stride * (j + y) + texel_stride * x;
466     for (uint32_t i = 0; i < width; ++i) {
467       auto actual_texel_values =
468           GetActualValuesFromTexel(p + texel_stride * i, fmt);
469       ScaleTexelValuesIfNeeded(&actual_texel_values, fmt);
470       if (!IsTexelEqualToExpected(actual_texel_values, fmt, command, tolerance,
471                                   is_tolerance_percent)) {
472         if (!count_of_invalid_pixels) {
473           failure_values = GetTexelInRGBA(actual_texel_values, fmt);
474           first_invalid_i = i;
475           first_invalid_j = j;
476         }
477         ++count_of_invalid_pixels;
478       }
479     }
480   }
481 
482   if (count_of_invalid_pixels) {
483     float scale = fmt->IsNormalized() ? 255.f : 1.f;
484     std::string reason =
485         "Line " + std::to_string(command->GetLine()) +
486         ": Probe failed at: " + std::to_string(x + first_invalid_i) + ", " +
487         std::to_string(first_invalid_j + y) + "\n" +
488         "  Expected: " + std::to_string(command->GetR() * scale) + ", " +
489         std::to_string(command->GetG() * scale) + ", " +
490         std::to_string(command->GetB() * scale);
491 
492     if (command->IsRGBA()) {
493       reason += ", " + std::to_string(command->GetA() * scale);
494     }
495 
496     reason +=
497         "\n    Actual: " +
498         std::to_string(static_cast<float>(failure_values[0]) * scale) + ", " +
499         std::to_string(static_cast<float>(failure_values[1]) * scale) + ", " +
500         std::to_string(static_cast<float>(failure_values[2]) * scale);
501 
502     if (command->IsRGBA()) {
503       reason +=
504           ", " + std::to_string(static_cast<float>(failure_values[3]) * scale);
505     }
506 
507     reason += "\nProbe failed in " + std::to_string(count_of_invalid_pixels) +
508               " pixels";
509 
510     return Result(reason);
511   }
512 
513   return {};
514 }
515 
ProbeSSBO(const ProbeSSBOCommand * command,uint32_t buffer_element_count,const void * buffer)516 Result Verifier::ProbeSSBO(const ProbeSSBOCommand* command,
517                            uint32_t buffer_element_count,
518                            const void* buffer) {
519   const auto& values = command->GetValues();
520   if (!buffer) {
521     if (values.empty())
522       return {};
523     return Result(
524         "Verifier::ProbeSSBO actual data is empty while expected "
525         "data is not");
526   }
527 
528   auto* fmt = command->GetFormat();
529   size_t elem_count = values.size() / fmt->InputNeededPerElement();
530   size_t offset = static_cast<size_t>(command->GetOffset());
531   size_t size_in_bytes = buffer_element_count * fmt->SizeInBytes();
532   if ((elem_count * fmt->SizeInBytes()) + offset > size_in_bytes) {
533     return Result("Line " + std::to_string(command->GetLine()) +
534                   ": Verifier::ProbeSSBO request to access to byte " +
535                   std::to_string((elem_count * fmt->SizeInBytes()) + offset) +
536                   " would read outside buffer of size " +
537                   std::to_string(size_in_bytes) + " bytes");
538   }
539 
540   if (offset % fmt->SizeInBytes() != 0) {
541     return Result("Line " + std::to_string(command->GetLine()) +
542                   ": Verifier::ProbeSSBO given offset (" +
543                   std::to_string(offset) + ") " +
544                   "is not multiple of element size (" +
545                   std::to_string(fmt->SizeInBytes()) + ")");
546   }
547 
548   auto& segments = fmt->GetSegments();
549 
550   const uint8_t* ptr = static_cast<const uint8_t*>(buffer) + offset;
551   for (size_t i = 0, k = 0; i < values.size(); ++i, ++k) {
552     if (k >= segments.size())
553       k = 0;
554 
555     const auto& value = values[i];
556     auto segment = segments[k];
557     // Skip over any padding bytes.
558     while (segment.IsPadding()) {
559       ptr += segment.PaddingBytes();
560       ++k;
561       if (k >= segments.size())
562         k = 0;
563 
564       segment = segments[k];
565     }
566 
567     Result r;
568     FormatMode mode = segment.GetFormatMode();
569     uint32_t num_bits = segment.GetNumBits();
570     if (type::Type::IsInt8(mode, num_bits)) {
571       r = CheckValue<int8_t>(command, ptr, value);
572     } else if (type::Type::IsUint8(mode, num_bits)) {
573       r = CheckValue<uint8_t>(command, ptr, value);
574     } else if (type::Type::IsInt16(mode, num_bits)) {
575       r = CheckValue<int16_t>(command, ptr, value);
576     } else if (type::Type::IsUint16(mode, num_bits)) {
577       r = CheckValue<uint16_t>(command, ptr, value);
578     } else if (type::Type::IsInt32(mode, num_bits)) {
579       r = CheckValue<int32_t>(command, ptr, value);
580     } else if (type::Type::IsUint32(mode, num_bits)) {
581       r = CheckValue<uint32_t>(command, ptr, value);
582     } else if (type::Type::IsInt64(mode, num_bits)) {
583       r = CheckValue<int64_t>(command, ptr, value);
584     } else if (type::Type::IsUint64(mode, num_bits)) {
585       r = CheckValue<uint64_t>(command, ptr, value);
586     } else if (type::Type::IsFloat16(mode, num_bits)) {
587       r = CheckActualValue<float>(command, float16::HexFloatToFloat(ptr, 16),
588                                   value);
589     } else if (type::Type::IsFloat32(mode, num_bits)) {
590       r = CheckValue<float>(command, ptr, value);
591     } else if (type::Type::IsFloat64(mode, num_bits)) {
592       r = CheckValue<double>(command, ptr, value);
593     } else {
594       return Result("Unknown datum type");
595     }
596 
597     if (!r.IsSuccess()) {
598       return Result("Line " + std::to_string(command->GetLine()) +
599                     ": Verifier failed: " + r.Error() + ", at index " +
600                     std::to_string(i));
601     }
602 
603     ptr += segment.SizeInBytes();
604   }
605 
606   return {};
607 }
608 
609 }  // namespace amber
610