Branch data Line data Source code
1 : : /*
2 : : * This file is part of the MicroPython project, http://micropython.org/
3 : : *
4 : : * The MIT License (MIT)
5 : : *
6 : : * Copyright (c) 2013, 2014 Damien P. George
7 : : *
8 : : * Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : * of this software and associated documentation files (the "Software"), to deal
10 : : * in the Software without restriction, including without limitation the rights
11 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : * copies of the Software, and to permit persons to whom the Software is
13 : : * furnished to do so, subject to the following conditions:
14 : : *
15 : : * The above copyright notice and this permission notice shall be included in
16 : : * all copies or substantial portions of the Software.
17 : : *
18 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 : : * THE SOFTWARE.
25 : : */
26 : :
27 : : #include <assert.h>
28 : : #include <string.h>
29 : : #include <stdio.h>
30 : :
31 : : #include "py/mpstate.h"
32 : : #include "py/qstr.h"
33 : : #include "py/gc.h"
34 : : #include "py/runtime.h"
35 : :
36 : : #if MICROPY_DEBUG_VERBOSE // print debugging info
37 : : #define DEBUG_printf DEBUG_printf
38 : : #else // don't print debugging info
39 : : #define DEBUG_printf(...) (void)0
40 : : #endif
41 : :
42 : : // A qstr is an index into the qstr pool.
43 : : // The data for a qstr is \0 terminated (so they can be printed using printf)
44 : :
45 : : #define Q_HASH_MASK ((1 << (8 * MICROPY_QSTR_BYTES_IN_HASH)) - 1)
46 : :
47 : : #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
48 : : #define QSTR_ENTER() mp_thread_mutex_lock(&MP_STATE_VM(qstr_mutex), 1)
49 : : #define QSTR_EXIT() mp_thread_mutex_unlock(&MP_STATE_VM(qstr_mutex))
50 : : #else
51 : : #define QSTR_ENTER()
52 : : #define QSTR_EXIT()
53 : : #endif
54 : :
55 : : // Initial number of entries for qstr pool, set so that the first dynamically
56 : : // allocated pool is twice this size. The value here must be <= MP_QSTRnumber_of.
57 : : #define MICROPY_ALLOC_QSTR_ENTRIES_INIT (10)
58 : :
59 : : // this must match the equivalent function in makeqstrdata.py
60 : 809957 : size_t qstr_compute_hash(const byte *data, size_t len) {
61 : : // djb2 algorithm; see http://www.cse.yorku.ca/~oz/hash.html
62 : 809957 : size_t hash = 5381;
63 [ + + ]: 12406087 : for (const byte *top = data + len; data < top; data++) {
64 : 11596130 : hash = ((hash << 5) + hash) ^ (*data); // hash * 33 ^ data
65 : : }
66 : 809957 : hash &= Q_HASH_MASK;
67 : : // Make sure that valid hash is never zero, zero means "hash not computed"
68 [ + + ]: 809957 : if (hash == 0) {
69 : 13 : hash++;
70 : : }
71 : 809957 : return hash;
72 : : }
73 : :
74 : : // The first pool is the static qstr table. The contents must remain stable as
75 : : // it is part of the .mpy ABI. See the top of py/persistentcode.c and
76 : : // static_qstr_list in makeqstrdata.py. This pool is unsorted (although in a
77 : : // future .mpy version we could re-order them and make it sorted). It also
78 : : // contains additional qstrs that must have IDs <256, see operator_qstr_list
79 : : // in makeqstrdata.py.
80 : : const qstr_hash_t mp_qstr_const_hashes_static[] = {
81 : : #ifndef NO_QSTR
82 : : #define QDEF0(id, hash, len, str) hash,
83 : : #define QDEF1(id, hash, len, str)
84 : : #include "genhdr/qstrdefs.generated.h"
85 : : #undef QDEF0
86 : : #undef QDEF1
87 : : #endif
88 : : };
89 : :
90 : : const qstr_len_t mp_qstr_const_lengths_static[] = {
91 : : #ifndef NO_QSTR
92 : : #define QDEF0(id, hash, len, str) len,
93 : : #define QDEF1(id, hash, len, str)
94 : : #include "genhdr/qstrdefs.generated.h"
95 : : #undef QDEF0
96 : : #undef QDEF1
97 : : #endif
98 : : };
99 : :
100 : : const qstr_pool_t mp_qstr_const_pool_static = {
101 : : NULL, // no previous pool
102 : : 0, // no previous pool
103 : : false, // is_sorted
104 : : MICROPY_ALLOC_QSTR_ENTRIES_INIT,
105 : : MP_QSTRnumber_of_static, // corresponds to number of strings in array just below
106 : : (qstr_hash_t *)mp_qstr_const_hashes_static,
107 : : (qstr_len_t *)mp_qstr_const_lengths_static,
108 : : {
109 : : #ifndef NO_QSTR
110 : : #define QDEF0(id, hash, len, str) str,
111 : : #define QDEF1(id, hash, len, str)
112 : : #include "genhdr/qstrdefs.generated.h"
113 : : #undef QDEF0
114 : : #undef QDEF1
115 : : #endif
116 : : },
117 : : };
118 : :
119 : : // The next pool is the remainder of the qstrs defined in the firmware. This
120 : : // is sorted.
121 : : const qstr_hash_t mp_qstr_const_hashes[] = {
122 : : #ifndef NO_QSTR
123 : : #define QDEF0(id, hash, len, str)
124 : : #define QDEF1(id, hash, len, str) hash,
125 : : #include "genhdr/qstrdefs.generated.h"
126 : : #undef QDEF0
127 : : #undef QDEF1
128 : : #endif
129 : : };
130 : :
131 : : const qstr_len_t mp_qstr_const_lengths[] = {
132 : : #ifndef NO_QSTR
133 : : #define QDEF0(id, hash, len, str)
134 : : #define QDEF1(id, hash, len, str) len,
135 : : #include "genhdr/qstrdefs.generated.h"
136 : : #undef QDEF0
137 : : #undef QDEF1
138 : : #endif
139 : : };
140 : :
141 : : const qstr_pool_t mp_qstr_const_pool = {
142 : : &mp_qstr_const_pool_static,
143 : : MP_QSTRnumber_of_static,
144 : : true, // is_sorted
145 : : MICROPY_ALLOC_QSTR_ENTRIES_INIT,
146 : : MP_QSTRnumber_of - MP_QSTRnumber_of_static, // corresponds to number of strings in array just below
147 : : (qstr_hash_t *)mp_qstr_const_hashes,
148 : : (qstr_len_t *)mp_qstr_const_lengths,
149 : : {
150 : : #ifndef NO_QSTR
151 : : #define QDEF0(id, hash, len, str)
152 : : #define QDEF1(id, hash, len, str) str,
153 : : #include "genhdr/qstrdefs.generated.h"
154 : : #undef QDEF0
155 : : #undef QDEF1
156 : : #endif
157 : : },
158 : : };
159 : :
160 : : // If frozen code is enabled, then there is an additional, sorted, ROM pool
161 : : // containing additional qstrs required by the frozen code.
162 : : #ifdef MICROPY_QSTR_EXTRA_POOL
163 : : extern const qstr_pool_t MICROPY_QSTR_EXTRA_POOL;
164 : : #define CONST_POOL MICROPY_QSTR_EXTRA_POOL
165 : : #else
166 : : #define CONST_POOL mp_qstr_const_pool
167 : : #endif
168 : :
169 : 3281 : void qstr_init(void) {
170 : 3281 : MP_STATE_VM(last_pool) = (qstr_pool_t *)&CONST_POOL; // we won't modify the const_pool since it has no allocated room left
171 : 3281 : MP_STATE_VM(qstr_last_chunk) = NULL;
172 : :
173 : : #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
174 : 3281 : mp_thread_mutex_init(&MP_STATE_VM(qstr_mutex));
175 : : #endif
176 : 3281 : }
177 : :
178 : 3089468 : STATIC const qstr_pool_t *find_qstr(qstr *q) {
179 : : // search pool for this qstr
180 : : // total_prev_len==0 in the final pool, so the loop will always terminate
181 : 3089468 : const qstr_pool_t *pool = MP_STATE_VM(last_pool);
182 [ + + ]: 10653865 : while (*q < pool->total_prev_len) {
183 : 7564397 : pool = pool->prev;
184 : : }
185 : 3089468 : *q -= pool->total_prev_len;
186 [ - + ]: 3089468 : assert(*q < pool->len);
187 : 3089468 : return pool;
188 : : }
189 : :
190 : : // qstr_mutex must be taken while in this function
191 : 34265 : STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) {
192 : 34265 : DEBUG_printf("QSTR: add hash=%d len=%d data=%.*s\n", hash, len, len, q_ptr);
193 : :
194 : : // make sure we have room in the pool for a new qstr
195 [ + + ]: 34265 : if (MP_STATE_VM(last_pool)->len >= MP_STATE_VM(last_pool)->alloc) {
196 : 3615 : size_t new_alloc = MP_STATE_VM(last_pool)->alloc * 2;
197 : : #ifdef MICROPY_QSTR_EXTRA_POOL
198 : : // Put a lower bound on the allocation size in case the extra qstr pool has few entries
199 : 3615 : new_alloc = MAX(MICROPY_ALLOC_QSTR_ENTRIES_INIT, new_alloc);
200 : : #endif
201 : 3615 : mp_uint_t pool_size = sizeof(qstr_pool_t)
202 : 3615 : + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * new_alloc;
203 : 3615 : qstr_pool_t *pool = (qstr_pool_t *)m_malloc_maybe(pool_size);
204 [ - + ]: 3615 : if (pool == NULL) {
205 : : // Keep qstr_last_chunk consistent with qstr_pool_t: qstr_last_chunk is not scanned
206 : : // at garbage collection since it's reachable from a qstr_pool_t. And the caller of
207 : : // this function expects q_ptr to be stored in a qstr_pool_t so it can be reached
208 : : // by the collector. If qstr_pool_t allocation failed, qstr_last_chunk needs to be
209 : : // NULL'd. Otherwise it may become a dangling pointer at the next garbage collection.
210 : 0 : MP_STATE_VM(qstr_last_chunk) = NULL;
211 : 0 : QSTR_EXIT();
212 : 0 : m_malloc_fail(new_alloc);
213 : : }
214 : 3615 : pool->hashes = (qstr_hash_t *)(pool->qstrs + new_alloc);
215 : 3615 : pool->lengths = (qstr_len_t *)(pool->hashes + new_alloc);
216 : 3615 : pool->prev = MP_STATE_VM(last_pool);
217 : 3615 : pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len;
218 : 3615 : pool->alloc = new_alloc;
219 : 3615 : pool->len = 0;
220 : 3615 : MP_STATE_VM(last_pool) = pool;
221 : 34265 : DEBUG_printf("QSTR: allocate new pool of size %d\n", MP_STATE_VM(last_pool)->alloc);
222 : : }
223 : :
224 : : // add the new qstr
225 : 34265 : mp_uint_t at = MP_STATE_VM(last_pool)->len;
226 : 34265 : MP_STATE_VM(last_pool)->hashes[at] = hash;
227 : 34265 : MP_STATE_VM(last_pool)->lengths[at] = len;
228 : 34265 : MP_STATE_VM(last_pool)->qstrs[at] = q_ptr;
229 : 34265 : MP_STATE_VM(last_pool)->len++;
230 : :
231 : : // return id for the newly-added qstr
232 : 34265 : return MP_STATE_VM(last_pool)->total_prev_len + at;
233 : : }
234 : :
235 : 484055 : qstr qstr_find_strn(const char *str, size_t str_len) {
236 [ + + ]: 484055 : if (str_len == 0) {
237 : : // strncmp behaviour is undefined for str==NULL.
238 : : return MP_QSTR_;
239 : : }
240 : :
241 : : // work out hash of str
242 : 482780 : size_t str_hash = qstr_compute_hash((const byte *)str, str_len);
243 : :
244 : : // search pools for the data
245 [ + + ]: 2089454 : for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL; pool = pool->prev) {
246 : 1770662 : size_t low = 0;
247 : 1770662 : size_t high = pool->len - 1;
248 : :
249 : : // binary search inside the pool
250 [ + + ]: 1770662 : if (pool->is_sorted) {
251 [ + + ]: 6235177 : while (high - low > 1) {
252 : 5419294 : size_t mid = (low + high) / 2;
253 : 5419294 : int cmp = strncmp(str, pool->qstrs[mid], str_len);
254 [ + + ]: 5419294 : if (cmp <= 0) {
255 : : high = mid;
256 : : } else {
257 : 2962258 : low = mid;
258 : : }
259 : : }
260 : : }
261 : :
262 : : // sequential search for the remaining strings
263 [ + + ]: 87526295 : for (mp_uint_t at = low; at < high + 1; at++) {
264 [ + + + + ]: 85919621 : if (pool->hashes[at] == str_hash && pool->lengths[at] == str_len
265 [ + + ]: 163999 : && memcmp(pool->qstrs[at], str, str_len) == 0) {
266 : 163988 : return pool->total_prev_len + at;
267 : : }
268 : : }
269 : : }
270 : :
271 : : // not found; return null qstr
272 : : return MP_QSTRnull;
273 : : }
274 : :
275 : 6799 : qstr qstr_from_str(const char *str) {
276 : 6799 : return qstr_from_strn(str, strlen(str));
277 : : }
278 : :
279 : 185115 : qstr qstr_from_strn(const char *str, size_t len) {
280 : 185115 : QSTR_ENTER();
281 : 185115 : qstr q = qstr_find_strn(str, len);
282 [ + + ]: 185115 : if (q == 0) {
283 : : // qstr does not exist in interned pool so need to add it
284 : :
285 : : // check that len is not too big
286 [ + + ]: 34317 : if (len >= (1 << (8 * MICROPY_QSTR_BYTES_IN_LEN))) {
287 : 52 : QSTR_EXIT();
288 : 52 : mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long"));
289 : : }
290 : :
291 : : // compute number of bytes needed to intern this string
292 : 34265 : size_t n_bytes = len + 1;
293 : :
294 [ + + + + ]: 34265 : if (MP_STATE_VM(qstr_last_chunk) != NULL && MP_STATE_VM(qstr_last_used) + n_bytes > MP_STATE_VM(qstr_last_alloc)) {
295 : : // not enough room at end of previously interned string so try to grow
296 : 3761 : char *new_p = m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_alloc) + n_bytes, false);
297 [ + + ]: 3761 : if (new_p == NULL) {
298 : : // could not grow existing memory; shrink it to fit previous
299 : 3063 : (void)m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_used), false);
300 : 3063 : MP_STATE_VM(qstr_last_chunk) = NULL;
301 : : } else {
302 : : // could grow existing memory
303 : 698 : MP_STATE_VM(qstr_last_alloc) += n_bytes;
304 : : }
305 : : }
306 : :
307 [ + + ]: 34265 : if (MP_STATE_VM(qstr_last_chunk) == NULL) {
308 : : // no existing memory for the interned string so allocate a new chunk
309 : 6344 : size_t al = n_bytes;
310 [ + + ]: 6344 : if (al < MICROPY_ALLOC_QSTR_CHUNK_INIT) {
311 : 6326 : al = MICROPY_ALLOC_QSTR_CHUNK_INIT;
312 : : }
313 : 6344 : MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, al);
314 [ - + ]: 6344 : if (MP_STATE_VM(qstr_last_chunk) == NULL) {
315 : : // failed to allocate a large chunk so try with exact size
316 : 0 : MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, n_bytes);
317 [ # # ]: 0 : if (MP_STATE_VM(qstr_last_chunk) == NULL) {
318 : 0 : QSTR_EXIT();
319 : 0 : m_malloc_fail(n_bytes);
320 : : }
321 : : al = n_bytes;
322 : : }
323 : 6344 : MP_STATE_VM(qstr_last_alloc) = al;
324 : 6344 : MP_STATE_VM(qstr_last_used) = 0;
325 : : }
326 : :
327 : : // allocate memory from the chunk for this new interned string's data
328 : 34265 : char *q_ptr = MP_STATE_VM(qstr_last_chunk) + MP_STATE_VM(qstr_last_used);
329 : 34265 : MP_STATE_VM(qstr_last_used) += n_bytes;
330 : :
331 : : // store the interned strings' data
332 : 34265 : size_t hash = qstr_compute_hash((const byte *)str, len);
333 : 34265 : memcpy(q_ptr, str, len);
334 : 34265 : q_ptr[len] = '\0';
335 : 34265 : q = qstr_add(hash, len, q_ptr);
336 : : }
337 : 185063 : QSTR_EXIT();
338 : 185063 : return q;
339 : : }
340 : :
341 : 2412637 : mp_uint_t qstr_hash(qstr q) {
342 : 2412637 : const qstr_pool_t *pool = find_qstr(&q);
343 : 2412633 : return pool->hashes[q];
344 : : }
345 : :
346 : 20194 : size_t qstr_len(qstr q) {
347 : 20194 : const qstr_pool_t *pool = find_qstr(&q);
348 : 20194 : return pool->lengths[q];
349 : : }
350 : :
351 : 5903 : const char *qstr_str(qstr q) {
352 : 5903 : const qstr_pool_t *pool = find_qstr(&q);
353 : 5903 : return pool->qstrs[q];
354 : : }
355 : :
356 : 650735 : const byte *qstr_data(qstr q, size_t *len) {
357 : 650735 : const qstr_pool_t *pool = find_qstr(&q);
358 : 650735 : *len = pool->lengths[q];
359 : 650735 : return (byte *)pool->qstrs[q];
360 : : }
361 : :
362 : 8 : void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, size_t *n_total_bytes) {
363 : 8 : QSTR_ENTER();
364 : 8 : *n_pool = 0;
365 : 8 : *n_qstr = 0;
366 : 8 : *n_str_data_bytes = 0;
367 : 8 : *n_total_bytes = 0;
368 [ + - + + ]: 16 : for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) {
369 : 8 : *n_pool += 1;
370 : 8 : *n_qstr += pool->len;
371 [ + + ]: 40 : for (qstr_len_t *l = pool->lengths, *l_top = pool->lengths + pool->len; l < l_top; l++) {
372 : 32 : *n_str_data_bytes += *l + 1;
373 : : }
374 : : #if MICROPY_ENABLE_GC
375 : 8 : *n_total_bytes += gc_nbytes(pool); // this counts actual bytes used in heap
376 : : #else
377 : : *n_total_bytes += sizeof(qstr_pool_t)
378 : : + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * pool->alloc;
379 : : #endif
380 : : }
381 : 8 : *n_total_bytes += *n_str_data_bytes;
382 : 8 : QSTR_EXIT();
383 : 8 : }
384 : :
385 : : #if MICROPY_PY_MICROPYTHON_MEM_INFO
386 : 4 : void qstr_dump_data(void) {
387 : 4 : QSTR_ENTER();
388 [ + - + + ]: 8 : for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) {
389 [ + + ]: 20 : for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) {
390 : 16 : mp_printf(&mp_plat_print, "Q(%s)\n", *q);
391 : : }
392 : : }
393 : 4 : QSTR_EXIT();
394 : 4 : }
395 : : #endif
396 : :
397 : : #if MICROPY_ROM_TEXT_COMPRESSION
398 : :
399 : : #ifdef NO_QSTR
400 : :
401 : : // If NO_QSTR is set, it means we're doing QSTR extraction.
402 : : // So we won't yet have "genhdr/compressed.data.h"
403 : :
404 : : #else
405 : :
406 : : // Emit the compressed_string_data string.
407 : : #define MP_COMPRESSED_DATA(x) STATIC const char *compressed_string_data = x;
408 : : #define MP_MATCH_COMPRESSED(a, b)
409 : : #include "genhdr/compressed.data.h"
410 : : #undef MP_COMPRESSED_DATA
411 : : #undef MP_MATCH_COMPRESSED
412 : :
413 : : #endif // NO_QSTR
414 : :
415 : : // This implements the "common word" compression scheme (see makecompresseddata.py) where the most
416 : : // common 128 words in error messages are replaced by their index into the list of common words.
417 : :
418 : : // The compressed string data is delimited by setting high bit in the final char of each word.
419 : : // e.g. aaaa<0x80|a>bbbbbb<0x80|b>....
420 : : // This method finds the n'th string.
421 : 6143 : STATIC const byte *find_uncompressed_string(uint8_t n) {
422 : 6143 : const byte *c = (byte *)compressed_string_data;
423 [ + + ]: 194259 : while (n > 0) {
424 [ + + ]: 1099316 : while ((*c & 0x80) == 0) {
425 : 911200 : ++c;
426 : : }
427 : 188116 : ++c;
428 : 188116 : --n;
429 : : }
430 : 6143 : return c;
431 : : }
432 : :
433 : : // Given a compressed string in src, decompresses it into dst.
434 : : // dst must be large enough (use MP_MAX_UNCOMPRESSED_TEXT_LEN+1).
435 : 1433 : void mp_decompress_rom_string(byte *dst, const mp_rom_error_text_t src_chr) {
436 : : // Skip past the 0xff marker.
437 : 1433 : const byte *src = (byte *)src_chr + 1;
438 : : // Need to add spaces around compressed words, except for the first (i.e. transition from 1<->2).
439 : : // 0 = start, 1 = compressed, 2 = regular.
440 : 1433 : int state = 0;
441 [ + + ]: 16655 : while (*src) {
442 [ + + ]: 15222 : if ((byte) * src >= 128) {
443 [ + + ]: 6143 : if (state != 0) {
444 : 4918 : *dst++ = ' ';
445 : : }
446 : 6143 : state = 1;
447 : :
448 : : // High bit set, replace with common word.
449 : 6143 : const byte *word = find_uncompressed_string(*src & 0x7f);
450 : : // The word is terminated by the final char having its high bit set.
451 [ + + ]: 30621 : while ((*word & 0x80) == 0) {
452 : 24478 : *dst++ = *word++;
453 : : }
454 : 6143 : *dst++ = (*word & 0x7f);
455 : : } else {
456 : : // Otherwise just copy one char.
457 [ + + ]: 9079 : if (state == 1) {
458 : 1031 : *dst++ = ' ';
459 : : }
460 : 9079 : state = 2;
461 : :
462 : 9079 : *dst++ = *src;
463 : : }
464 : 15222 : ++src;
465 : : }
466 : : // Add null-terminator.
467 : 1433 : *dst = 0;
468 : 1433 : }
469 : :
470 : : #endif // MICROPY_ROM_TEXT_COMPRESSION
|