Extract DebugLog calls to a single location in MicroErrorReporter. Expose MicroSnprintf method for use elsewhere in the micro codebase.

PiperOrigin-RevId: 297743314
Change-Id: I235d065d0ee754c108108c14b3b0f632b8d78614
This commit is contained in:
Nat Jeffries 2020-02-27 18:57:26 -08:00 committed by TensorFlower Gardener
parent 3be1f72f52
commit 02fac26517
8 changed files with 319 additions and 97 deletions

View File

@ -26,7 +26,6 @@ cc_library(
name = "micro_framework",
srcs = [
"debug_log.cc",
"debug_log_numbers.cc",
"memory_helpers.cc",
"micro_allocator.cc",
"micro_error_reporter.cc",
@ -37,7 +36,6 @@ cc_library(
],
hdrs = [
"debug_log.h",
"debug_log_numbers.h",
"memory_helpers.h",
"micro_allocator.h",
"micro_error_reporter.h",
@ -55,6 +53,7 @@ cc_library(
],
deps = [
":micro_compatibility",
":micro_string",
":micro_utils",
"//tensorflow/lite:type_to_tflitetype",
"//tensorflow/lite/c:common",
@ -65,6 +64,23 @@ cc_library(
],
)
cc_library(
name = "micro_string",
srcs = [
"micro_string.cc",
],
hdrs = [
"micro_string.h",
],
build_for_embedded = True,
copts = micro_copts(),
deps = [
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/api",
"//tensorflow/lite/kernels/internal:compatibility",
],
)
cc_library(
name = "micro_utils",
srcs = [
@ -168,3 +184,14 @@ tflite_micro_cc_test(
"//tensorflow/lite/micro/testing:micro_test",
],
)
tflite_micro_cc_test(
name = "micro_string_test",
srcs = [
"micro_string_test.cc",
],
deps = [
":micro_string",
"//tensorflow/lite/micro/testing:micro_test",
],
)

View File

@ -1,28 +0,0 @@
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_
#define TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_
#include <cstdint>
// Output numbers to the debug logging stream.
extern "C" {
void DebugLogInt32(int32_t i);
void DebugLogUInt32(uint32_t i);
void DebugLogHex(uint32_t i);
void DebugLogFloat(float i);
}
#endif // TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_

View File

@ -15,53 +15,21 @@ limitations under the License.
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_string.h"
namespace tflite {
namespace {
void DebugLogPrintf(const char* format, va_list args) {
const int output_cache_size = 64;
char output_cache[output_cache_size + 1];
int output_cache_index = 0;
const char* current = format;
while (*current != 0) {
if (*current == '%') {
const char next = *(current + 1);
if ((next == 'd') || (next == 's') || (next == 'f')) {
current += 1;
if (output_cache_index > 0) {
output_cache[output_cache_index] = 0;
DebugLog(output_cache);
output_cache_index = 0;
}
if (next == 'd') {
DebugLogInt32(va_arg(args, int));
} else if (next == 's') {
DebugLog(va_arg(args, char*));
} else if (next == 'f') {
DebugLogFloat(va_arg(args, double));
}
}
} else {
output_cache[output_cache_index] = *current;
output_cache_index += 1;
}
if (output_cache_index >= output_cache_size) {
output_cache[output_cache_index] = 0;
DebugLog(output_cache);
output_cache_index = 0;
}
current += 1;
}
if (output_cache_index > 0) {
output_cache[output_cache_index] = 0;
DebugLog(output_cache);
output_cache_index = 0;
}
DebugLog("\r\n");
}
constexpr int kMaxLogLen = 256;
} // namespace
int MicroErrorReporter::Report(const char* format, va_list args) {
DebugLogPrintf(format, args);
char log_buffer[kMaxLogLen];
MicroVsnprintf(log_buffer, kMaxLogLen, format, args);
DebugLog(log_buffer);
DebugLog("\r\n");
return 0;
}

View File

@ -18,7 +18,6 @@ limitations under the License.
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/micro/compatibility.h"
#include "tensorflow/lite/micro/debug_log.h"
#include "tensorflow/lite/micro/debug_log_numbers.h"
namespace tflite {

View File

@ -19,14 +19,22 @@ limitations under the License.
// of DebugLog() and then get the numerical variations without requiring any
// more code.
#include "tensorflow/lite/micro/debug_log_numbers.h"
#include "tensorflow/lite/micro/debug_log.h"
#include "tensorflow/lite/micro/micro_string.h"
namespace {
// Int formats can need up to 10 bytes for the value plus a single byte for the
// sign.
constexpr int kMaxIntCharsNeeded = 10 + 1;
// Hex formats can need up to 8 bytes for the value plus two bytes for the "0x".
constexpr int kMaxHexCharsNeeded = 8 + 2;
// Float formats can need up to 7 bytes for the fraction plus 3 bytes for "x2^"
// plus 3 bytes for the exponent and a single sign bit.
constexpr float kMaxFloatCharsNeeded = 7 + 3 + 3 + 1;
// All input buffers to the number conversion functions must be this long.
static const int kFastToBufferSize = 48;
const int kFastToBufferSize = 48;
// Reverses a zero-terminated string in-place.
char* ReverseStringInPlace(char* start, char* end) {
@ -158,28 +166,97 @@ char* FastFloatToBufferLeft(float f, char* buffer) {
return current;
}
int FormatInt32(char* output, int32_t i) {
return static_cast<int>(FastInt32ToBufferLeft(i, output) - output);
}
int FormatUInt32(char* output, uint32_t i) {
return static_cast<int>(FastUInt32ToBufferLeft(i, output, 10) - output);
}
int FormatHex(char* output, uint32_t i) {
return static_cast<int>(FastUInt32ToBufferLeft(i, output, 16) - output);
}
int FormatFloat(char* output, float i) {
return static_cast<int>(FastFloatToBufferLeft(i, output) - output);
}
} // namespace
extern "C" void DebugLogInt32(int32_t i) {
char number_string[kFastToBufferSize];
FastInt32ToBufferLeft(i, number_string);
DebugLog(number_string);
extern "C" int MicroVsnprintf(char* output, int len, const char* format,
va_list args) {
int output_index = 0;
const char* current = format;
// One extra character must be left for the null terminator.
const int usable_length = len - 1;
while (*current != '\0' && output_index < usable_length) {
if (*current == '%') {
current++;
switch (*current) {
case 'd':
// Cut off log message if format could exceed log buffer length.
if (usable_length - output_index < kMaxIntCharsNeeded) {
output[output_index++] = '\0';
return output_index;
}
output_index +=
FormatInt32(&output[output_index], va_arg(args, int32_t));
current++;
break;
case 'u':
if (usable_length - output_index < kMaxIntCharsNeeded) {
output[output_index++] = '\0';
return output_index;
}
output_index +=
FormatUInt32(&output[output_index], va_arg(args, uint32_t));
current++;
break;
case 'x':
if (usable_length - output_index < kMaxHexCharsNeeded) {
output[output_index++] = '\0';
return output_index;
}
output[output_index++] = '0';
output[output_index++] = 'x';
output_index +=
FormatHex(&output[output_index], va_arg(args, uint32_t));
current++;
break;
case 'f':
if (usable_length - output_index < kMaxFloatCharsNeeded) {
output[output_index++] = '\0';
return output_index;
}
output_index +=
FormatFloat(&output[output_index], va_arg(args, double));
current++;
break;
case '%':
output[output_index++] = *current++;
break;
case 's':
char* string = va_arg(args, char*);
int string_idx = 0;
while (string_idx + output_index < usable_length &&
string[string_idx] != '\0') {
output[output_index++] = string[string_idx++];
}
current++;
}
} else {
output[output_index++] = *current++;
}
}
output[output_index++] = '\0';
return output_index;
}
extern "C" void DebugLogUInt32(uint32_t i) {
char number_string[kFastToBufferSize];
FastUInt32ToBufferLeft(i, number_string, 10);
DebugLog(number_string);
}
extern "C" void DebugLogHex(uint32_t i) {
char number_string[kFastToBufferSize];
FastUInt32ToBufferLeft(i, number_string, 16);
DebugLog(number_string);
}
extern "C" void DebugLogFloat(float i) {
char number_string[kFastToBufferSize];
FastFloatToBufferLeft(i, number_string);
DebugLog(number_string);
extern "C" int MicroSnprintf(char* output, int len, const char* format, ...) {
va_list args;
va_start(args, format);
int bytes_written = MicroVsnprintf(output, len, format, args);
va_end(args);
return bytes_written;
}

View File

@ -0,0 +1,36 @@
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_MICRO_MICRO_STRING_H_
#define TENSORFLOW_LITE_MICRO_MICRO_STRING_H_
#include <cstdint>
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/micro/micro_string.h"
// Implements simple string formatting for numeric types. Returns the number of
// bytes written to output.
extern "C" {
// Functionally equivalent to vsnprintf, trimmed down for TFLite Micro.
// MicroSnprintf() is implemented using MicroVsnprintf().
int MicroVsnprintf(char* output, int len, const char* format, va_list args);
// Functionally equavalent to snprintf, trimmed down for TFLite Micro.
// For example, MicroSnprintf(buffer, 10, "int %d", 10) will put the string
// "int 10" in the buffer.
// Floating point values are logged in exponent notation (1.XXX*2^N).
int MicroSnprintf(char* output, int len, const char* format, ...);
}
#endif // TENSORFLOW_LITE_MICRO_MICRO_STRING_H_

View File

@ -0,0 +1,132 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/micro/micro_string.h"
#include "tensorflow/lite/micro/testing/micro_test.h"
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(FormatPositiveIntShouldMatchExpected) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "Int: 55";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Int: %d", 55);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(FormatNegativeIntShouldMatchExpected) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "Int: -55";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Int: %d", -55);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(FormatUnsignedIntShouldMatchExpected) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "UInt: 12345";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "UInt: %u", 12345);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(FormatHexShouldMatchExpected) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "Hex: 0x12345";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Hex: %x", 0x12345);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(FormatFloatShouldMatchExpected) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "Float: 1.0*2^4";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Float: %f", 16.f);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(BadlyFormattedStringShouldProduceReasonableString) {
const int kBufferLen = 32;
char buffer[kBufferLen];
const char golden[] = "Test Badly % formated % string";
int bytes_written =
MicroSnprintf(buffer, kBufferLen, "Test Badly %% formated %% string%");
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(IntFormatOverrunShouldTruncate) {
const int kBufferLen = 8;
char buffer[kBufferLen];
const char golden[] = "Int: ";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Int: %d", 12345);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(UnsignedIntFormatOverrunShouldTruncate) {
const int kBufferLen = 8;
char buffer[kBufferLen];
const char golden[] = "UInt: ";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "UInt: %u", 12345);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(HexFormatOverrunShouldTruncate) {
const int kBufferLen = 8;
char buffer[kBufferLen];
const char golden[] = "Hex: ";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Hex: %x", 0x12345);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(FloatFormatOverrunShouldTruncate) {
const int kBufferLen = 12;
char buffer[kBufferLen];
const char golden[] = "Float: ";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "Float: %x", 12345.f);
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(StringFormatOverrunShouldTruncate) {
const int kBufferLen = 10;
char buffer[kBufferLen];
const char golden[] = "String: h";
int bytes_written =
MicroSnprintf(buffer, kBufferLen, "String: %s", "hello world");
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TEST(StringFormatWithExactOutputSizeOverrunShouldTruncate) {
const int kBufferLen = 10;
char buffer[kBufferLen];
const char golden[] = "format st";
int bytes_written = MicroSnprintf(buffer, kBufferLen, "format str");
TF_LITE_MICRO_EXPECT_EQ(sizeof(golden), bytes_written);
TF_LITE_MICRO_EXPECT_STRING_EQ(golden, buffer);
}
TF_LITE_MICRO_TESTS_END

View File

@ -213,4 +213,15 @@ extern tflite::ErrorReporter* reporter;
micro_test::did_test_fail = true; \
} while (false)
#define TF_LITE_MICRO_EXPECT_STRING_EQ(string1, string2) \
do { \
for (int i = 0; string1[i] != '\0' && string2[i] != '\0'; i++) { \
if (string1[i] != string2[i]) { \
micro_test::reporter->Report("FAIL: %s did not match %s", string1, \
string2, __FILE__, __LINE__); \
micro_test::did_test_fail = true; \
} \
} \
} while (false)
#endif // TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_