• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
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 
16 // Implements debug logging for numbers by converting them into strings and then
17 // calling the main DebugLog(char*) function. These are separated into a
18 // different file so that platforms can just implement the string output version
19 // of DebugLog() and then get the numerical variations without requiring any
20 // more code.
21 
22 #include "tensorflow/lite/micro/micro_string.h"
23 
24 #include <cstdarg>
25 #include <cstdint>
26 #include <cstring>
27 
28 namespace {
29 
30 // Int formats can need up to 10 bytes for the value plus a single byte for the
31 // sign.
32 constexpr int kMaxIntCharsNeeded = 10 + 1;
33 // Hex formats can need up to 8 bytes for the value plus two bytes for the "0x".
34 constexpr int kMaxHexCharsNeeded = 8 + 2;
35 
36 // Float formats can need up to 7 bytes for the fraction plus 3 bytes for "x2^"
37 // plus 3 bytes for the exponent and a single sign bit.
38 constexpr float kMaxFloatCharsNeeded = 7 + 3 + 3 + 1;
39 
40 // All input buffers to the number conversion functions must be this long.
41 const int kFastToBufferSize = 48;
42 
43 // Reverses a zero-terminated string in-place.
ReverseStringInPlace(char * start,char * end)44 char* ReverseStringInPlace(char* start, char* end) {
45   char* p1 = start;
46   char* p2 = end - 1;
47   while (p1 < p2) {
48     char tmp = *p1;
49     *p1++ = *p2;
50     *p2-- = tmp;
51   }
52   return start;
53 }
54 
55 // Appends a string to a string, in-place. You need to pass in the maximum
56 // string length as the second argument.
StrCatStr(char * main,int main_max_length,const char * to_append)57 char* StrCatStr(char* main, int main_max_length, const char* to_append) {
58   char* current = main;
59   while (*current != 0) {
60     ++current;
61   }
62   char* current_end = main + (main_max_length - 1);
63   while ((*to_append != 0) && (current < current_end)) {
64     *current = *to_append;
65     ++current;
66     ++to_append;
67   }
68   *current = 0;
69   return current;
70 }
71 
72 // Populates the provided buffer with an ASCII representation of the number.
FastUInt32ToBufferLeft(uint32_t i,char * buffer,int base)73 char* FastUInt32ToBufferLeft(uint32_t i, char* buffer, int base) {
74   char* start = buffer;
75   do {
76     int32_t digit = i % base;
77     char character;
78     if (digit < 10) {
79       character = '0' + digit;
80     } else {
81       character = 'a' + (digit - 10);
82     }
83     *buffer++ = character;
84     i /= base;
85   } while (i > 0);
86   *buffer = 0;
87   ReverseStringInPlace(start, buffer);
88   return buffer;
89 }
90 
91 // Populates the provided buffer with an ASCII representation of the number.
FastInt32ToBufferLeft(int32_t i,char * buffer)92 char* FastInt32ToBufferLeft(int32_t i, char* buffer) {
93   uint32_t u = i;
94   if (i < 0) {
95     *buffer++ = '-';
96     u = -u;
97   }
98   return FastUInt32ToBufferLeft(u, buffer, 10);
99 }
100 
101 // Converts a number to a string and appends it to another.
StrCatInt32(char * main,int main_max_length,int32_t number)102 char* StrCatInt32(char* main, int main_max_length, int32_t number) {
103   char number_string[kFastToBufferSize];
104   FastInt32ToBufferLeft(number, number_string);
105   return StrCatStr(main, main_max_length, number_string);
106 }
107 
108 // Converts a number to a string and appends it to another.
StrCatUInt32(char * main,int main_max_length,uint32_t number,int base)109 char* StrCatUInt32(char* main, int main_max_length, uint32_t number, int base) {
110   char number_string[kFastToBufferSize];
111   FastUInt32ToBufferLeft(number, number_string, base);
112   return StrCatStr(main, main_max_length, number_string);
113 }
114 
115 // Populates the provided buffer with ASCII representation of the float number.
116 // Avoids the use of any floating point instructions (since these aren't
117 // supported on many microcontrollers) and as a consequence prints values with
118 // power-of-two exponents.
FastFloatToBufferLeft(float f,char * buffer)119 char* FastFloatToBufferLeft(float f, char* buffer) {
120   char* current = buffer;
121   char* current_end = buffer + (kFastToBufferSize - 1);
122   // Access the bit fields of the floating point value to avoid requiring any
123   // float instructions. These constants are derived from IEEE 754.
124   const uint32_t sign_mask = 0x80000000;
125   const uint32_t exponent_mask = 0x7f800000;
126   const int32_t exponent_shift = 23;
127   const int32_t exponent_bias = 127;
128   const uint32_t fraction_mask = 0x007fffff;
129   uint32_t u;
130   memcpy(&u, &f, sizeof(int32_t));
131   const int32_t exponent =
132       ((u & exponent_mask) >> exponent_shift) - exponent_bias;
133   const uint32_t fraction = (u & fraction_mask);
134   // Expect ~0x2B1B9D3 for fraction.
135   if (u & sign_mask) {
136     *current = '-';
137     current += 1;
138   }
139   *current = 0;
140   // These are special cases for infinities and not-a-numbers.
141   if (exponent == 128) {
142     if (fraction == 0) {
143       current = StrCatStr(current, (current_end - current), "Inf");
144       return current;
145     } else {
146       current = StrCatStr(current, (current_end - current), "NaN");
147       return current;
148     }
149   }
150   // 0x007fffff (8388607) represents 0.99... for the fraction, so to print the
151   // correct decimal digits we need to scale our value before passing it to the
152   // conversion function. This scale should be 10000000/8388608 = 1.1920928955.
153   // We can approximate this using multiply-adds and right-shifts using the
154   // values in this array. The 1. portion of the number string is printed out
155   // in a fixed way before the fraction, below.
156   const int32_t scale_shifts_size = 13;
157   const int8_t scale_shifts[13] = {3,  4,  8,  11, 13, 14, 17,
158                                    18, 19, 20, 21, 22, 23};
159   uint32_t scaled_fraction = fraction;
160   for (int i = 0; i < scale_shifts_size; ++i) {
161     scaled_fraction += (fraction >> scale_shifts[i]);
162   }
163   *current = '1';
164   current += 1;
165   *current = '.';
166   current += 1;
167   *current = 0;
168 
169   // Prepend leading zeros to fill in all 7 bytes of the fraction. Truncate
170   // zeros off the end of the fraction. Every fractional value takes 7 bytes.
171   // For example, 2500 would be written into the buffer as 0002500 since it
172   // represents .00025.
173   constexpr int kMaxFractionalDigits = 7;
174 
175   // Abort early if there is not enough space in the buffer.
176   if (current_end - current <= kMaxFractionalDigits) {
177     return current;
178   }
179 
180   // Pre-fill buffer with zeros to ensure zero-truncation works properly.
181   for (int i = 1; i < kMaxFractionalDigits; i++) {
182     *(current + i) = '0';
183   }
184 
185   // Track how large the fraction is to add leading zeros.
186   char* previous = current;
187   current = StrCatUInt32(current, (current_end - current), scaled_fraction, 10);
188   int fraction_digits = current - previous;
189   int leading_zeros = kMaxFractionalDigits - fraction_digits;
190 
191   // Overwrite the null terminator from StrCatUInt32 to ensure zero-trunctaion
192   // works properly.
193   *current = '0';
194 
195   // Shift fraction values and prepend zeros if necessary.
196   if (leading_zeros != 0) {
197     for (int i = 0; i < fraction_digits; i++) {
198       current--;
199       *(current + leading_zeros) = *current;
200       *current = '0';
201     }
202     current += kMaxFractionalDigits;
203   }
204 
205   // Truncate trailing zeros for cleaner logs. Ensure we leave at least one
206   // fractional character for the case when scaled_fraction is 0.
207   while (*(current - 1) == '0' && (current - 1) > previous) {
208     current--;
209   }
210   *current = 0;
211   current = StrCatStr(current, (current_end - current), "*2^");
212   current = StrCatInt32(current, (current_end - current), exponent);
213   return current;
214 }
215 
FormatInt32(char * output,int32_t i)216 int FormatInt32(char* output, int32_t i) {
217   return static_cast<int>(FastInt32ToBufferLeft(i, output) - output);
218 }
219 
FormatUInt32(char * output,uint32_t i)220 int FormatUInt32(char* output, uint32_t i) {
221   return static_cast<int>(FastUInt32ToBufferLeft(i, output, 10) - output);
222 }
223 
FormatHex(char * output,uint32_t i)224 int FormatHex(char* output, uint32_t i) {
225   return static_cast<int>(FastUInt32ToBufferLeft(i, output, 16) - output);
226 }
227 
FormatFloat(char * output,float i)228 int FormatFloat(char* output, float i) {
229   return static_cast<int>(FastFloatToBufferLeft(i, output) - output);
230 }
231 
232 }  // namespace
233 
MicroVsnprintf(char * output,int len,const char * format,va_list args)234 extern "C" int MicroVsnprintf(char* output, int len, const char* format,
235                               va_list args) {
236   int output_index = 0;
237   const char* current = format;
238   // One extra character must be left for the null terminator.
239   const int usable_length = len - 1;
240   while (*current != '\0' && output_index < usable_length) {
241     if (*current == '%') {
242       current++;
243       switch (*current) {
244         case 'd':
245           // Cut off log message if format could exceed log buffer length.
246           if (usable_length - output_index < kMaxIntCharsNeeded) {
247             output[output_index++] = '\0';
248             return output_index;
249           }
250           output_index +=
251               FormatInt32(&output[output_index], va_arg(args, int32_t));
252           current++;
253           break;
254         case 'u':
255           if (usable_length - output_index < kMaxIntCharsNeeded) {
256             output[output_index++] = '\0';
257             return output_index;
258           }
259           output_index +=
260               FormatUInt32(&output[output_index], va_arg(args, uint32_t));
261           current++;
262           break;
263         case 'x':
264           if (usable_length - output_index < kMaxHexCharsNeeded) {
265             output[output_index++] = '\0';
266             return output_index;
267           }
268           output[output_index++] = '0';
269           output[output_index++] = 'x';
270           output_index +=
271               FormatHex(&output[output_index], va_arg(args, uint32_t));
272           current++;
273           break;
274         case 'f':
275           if (usable_length - output_index < kMaxFloatCharsNeeded) {
276             output[output_index++] = '\0';
277             return output_index;
278           }
279           output_index +=
280               FormatFloat(&output[output_index], va_arg(args, double));
281           current++;
282           break;
283         case '%':
284           output[output_index++] = *current++;
285           break;
286         case 's':
287           char* string = va_arg(args, char*);
288           int string_idx = 0;
289           while (string_idx + output_index < usable_length &&
290                  string[string_idx] != '\0') {
291             output[output_index++] = string[string_idx++];
292           }
293           current++;
294       }
295     } else {
296       output[output_index++] = *current++;
297     }
298   }
299   output[output_index++] = '\0';
300   return output_index;
301 }
302 
MicroSnprintf(char * output,int len,const char * format,...)303 extern "C" int MicroSnprintf(char* output, int len, const char* format, ...) {
304   va_list args;
305   va_start(args, format);
306   int bytes_written = MicroVsnprintf(output, len, format, args);
307   va_end(args);
308   return bytes_written;
309 }
310