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