Skip to content

Commit de5a9d7

Browse files
committed
Compress all translated strings with Huffman coding.
This saves code space in builds which use link-time optimization. The optimization drops the untranslated strings and replaces them with a compressed_string_t struct. It can then be decompressed to a c string. Builds without LTO work as well but include both untranslated strings and compressed strings. This work could be expanded to include QSTRs and loaded strings if a compress method is added to C. Its tracked in micropython#531.
1 parent 92ed5d7 commit de5a9d7

File tree

32 files changed

+375
-115
lines changed

32 files changed

+375
-115
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,6 @@
8080
path = lib/tinyusb
8181
url = https://github.com/hathach/tinyusb.git
8282
branch = develop
83+
[submodule "tools/huffman"]
84+
path = tools/huffman
85+
url = https://github.com/tannewt/huffman.git

extmod/machine_mem.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
STATIC uintptr_t machine_mem_get_addr(mp_obj_t addr_o, uint align) {
4343
uintptr_t addr = mp_obj_int_get_truncated(addr_o);
4444
if ((addr & (align - 1)) != 0) {
45-
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "address %08x is not aligned to %d bytes", addr, align));
45+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, translate("address %08x is not aligned to %d bytes"), addr, align));
4646
}
4747
return addr;
4848
}

extmod/modframebuf.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ STATIC mp_obj_t framebuf_make_new(const mp_obj_type_t *type, size_t n_args, size
296296
case FRAMEBUF_GS8:
297297
break;
298298
default:
299-
mp_raise_ValueError("invalid format");
299+
mp_raise_ValueError(translate("invalid format"));
300300
}
301301

302302
return MP_OBJ_FROM_PTR(o);

extmod/modubinascii.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
static void check_not_unicode(const mp_obj_t arg) {
3636
#if MICROPY_CPYTHON_COMPAT
3737
if (MP_OBJ_IS_STR(arg)) {
38-
mp_raise_TypeError("a bytes-like object is required");
38+
mp_raise_TypeError(translate("a bytes-like object is required"));
3939
}
4040
#endif
4141
}
@@ -87,7 +87,7 @@ mp_obj_t mod_binascii_unhexlify(mp_obj_t data) {
8787
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
8888

8989
if ((bufinfo.len & 1) != 0) {
90-
mp_raise_ValueError("odd-length string");
90+
mp_raise_ValueError(translate("odd-length string"));
9191
}
9292
vstr_t vstr;
9393
vstr_init_len(&vstr, bufinfo.len / 2);
@@ -98,7 +98,7 @@ mp_obj_t mod_binascii_unhexlify(mp_obj_t data) {
9898
if (unichar_isxdigit(hex_ch)) {
9999
hex_byte += unichar_xdigit_value(hex_ch);
100100
} else {
101-
mp_raise_ValueError("non-hex digit found");
101+
mp_raise_ValueError(translate("non-hex digit found"));
102102
}
103103
if (i & 1) {
104104
hex_byte <<= 4;
@@ -166,7 +166,7 @@ mp_obj_t mod_binascii_a2b_base64(mp_obj_t data) {
166166
}
167167

168168
if (nbits) {
169-
mp_raise_ValueError("incorrect padding");
169+
mp_raise_ValueError(translate("incorrect padding"));
170170
}
171171

172172
return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);

extmod/modure.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ STATIC mp_obj_t re_split(size_t n_args, const mp_obj_t *args) {
158158
mp_obj_t s = mp_obj_new_str_of_type(str_type, (const byte*)subj.begin, caps[0] - subj.begin);
159159
mp_obj_list_append(retval, s);
160160
if (self->re.sub > 0) {
161-
mp_raise_NotImplementedError("Splitting with sub-captures");
161+
mp_raise_NotImplementedError(translate("Splitting with sub-captures"));
162162
}
163163
subj.begin = caps[1];
164164
if (maxsplit > 0 && --maxsplit == 0) {
@@ -204,7 +204,7 @@ STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) {
204204
int error = re1_5_compilecode(&o->re, re_str);
205205
if (error != 0) {
206206
error:
207-
mp_raise_ValueError("Error in regex");
207+
mp_raise_ValueError(translate("Error in regex"));
208208
}
209209
if (flags & FLAG_DEBUG) {
210210
re1_5_dumpcode(&o->re);

main.c

+26-17
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,22 @@ const char* first_existing_file_in_list(const char ** filenames) {
128128
return NULL;
129129
}
130130

131+
void write_compressed(const compressed_string_t* compressed) {
132+
char decompressed[compressed->length];
133+
decompress(compressed, decompressed);
134+
serial_write(decompressed);
135+
}
136+
131137
bool maybe_run_list(const char ** filenames, pyexec_result_t* exec_result) {
132138
const char* filename = first_existing_file_in_list(filenames);
133139
if (filename == NULL) {
134140
return false;
135141
}
136142
mp_hal_stdout_tx_str(filename);
137-
mp_hal_stdout_tx_str(translate(" output:\n"));
143+
const compressed_string_t* compressed = translate(" output:\n");
144+
char decompressed[compressed->length];
145+
decompress(compressed, decompressed);
146+
mp_hal_stdout_tx_str(decompressed);
138147
pyexec_file(filename, exec_result);
139148
return true;
140149
}
@@ -145,11 +154,11 @@ bool run_code_py(safe_mode_t safe_mode) {
145154
if (serial_connected_at_start) {
146155
serial_write("\n");
147156
if (autoreload_is_enabled()) {
148-
serial_write(translate("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\n"));
157+
write_compressed(translate("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\n"));
149158
} else if (safe_mode != NO_SAFE_MODE) {
150-
serial_write(translate("Running in safe mode! Auto-reload is off.\n"));
159+
write_compressed(translate("Running in safe mode! Auto-reload is off.\n"));
151160
} else if (!autoreload_is_enabled()) {
152-
serial_write(translate("Auto-reload is off.\n"));
161+
write_compressed(translate("Auto-reload is off.\n"));
153162
}
154163
}
155164
#endif
@@ -163,7 +172,7 @@ bool run_code_py(safe_mode_t safe_mode) {
163172
bool found_main = false;
164173

165174
if (safe_mode != NO_SAFE_MODE) {
166-
serial_write(translate("Running in safe mode! Not running saved code.\n"));
175+
write_compressed(translate("Running in safe mode! Not running saved code.\n"));
167176
} else {
168177
new_status_color(MAIN_RUNNING);
169178

@@ -179,7 +188,7 @@ bool run_code_py(safe_mode_t safe_mode) {
179188
if (!found_main){
180189
found_main = maybe_run_list(double_extension_filenames, &result);
181190
if (found_main) {
182-
serial_write(translate("WARNING: Your code filename has two extensions\n"));
191+
write_compressed(translate("WARNING: Your code filename has two extensions\n"));
183192
}
184193
}
185194
stop_mp();
@@ -218,37 +227,37 @@ bool run_code_py(safe_mode_t safe_mode) {
218227

219228
if (!serial_connected_at_start) {
220229
if (autoreload_is_enabled()) {
221-
serial_write(translate("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\n"));
230+
write_compressed(translate("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\n"));
222231
} else {
223-
serial_write(translate("Auto-reload is off.\n"));
232+
write_compressed(translate("Auto-reload is off.\n"));
224233
}
225234
}
226235
// Output a user safe mode string if its set.
227236
#ifdef BOARD_USER_SAFE_MODE
228237
if (safe_mode == USER_SAFE_MODE) {
229238
serial_write("\n");
230-
serial_write(translate("You requested starting safe mode by "));
239+
write_compressed(translate("You requested starting safe mode by "));
231240
serial_write(BOARD_USER_SAFE_MODE_ACTION);
232241
serial_write("\n");
233-
serial_write(translate("To exit, please reset the board without "));
242+
write_compressed(translate("To exit, please reset the board without "));
234243
serial_write(BOARD_USER_SAFE_MODE_ACTION);
235244
serial_write("\n");
236245
} else
237246
#endif
238247
if (safe_mode != NO_SAFE_MODE) {
239248
serial_write("\n");
240-
serial_write(translate("You are running in safe mode which means something really bad happened.\n"));
249+
write_compressed(translate("You are running in safe mode which means something really bad happened.\n"));
241250
if (safe_mode == HARD_CRASH) {
242-
serial_write(translate("Looks like our core CircuitPython code crashed hard. Whoops!\n"));
243-
serial_write(translate("Please file an issue here with the contents of your CIRCUITPY drive:\n"));
251+
write_compressed(translate("Looks like our core CircuitPython code crashed hard. Whoops!\n"));
252+
write_compressed(translate("Please file an issue here with the contents of your CIRCUITPY drive:\n"));
244253
serial_write("https://github.com/adafruit/circuitpython/issues\n");
245254
} else if (safe_mode == BROWNOUT) {
246-
serial_write(translate("The microcontroller's power dipped. Please make sure your power supply provides\n"));
247-
serial_write(translate("enough power for the whole circuit and press reset (after ejecting CIRCUITPY).\n"));
255+
write_compressed(translate("The microcontroller's power dipped. Please make sure your power supply provides\n"));
256+
write_compressed(translate("enough power for the whole circuit and press reset (after ejecting CIRCUITPY).\n"));
248257
}
249258
}
250259
serial_write("\n");
251-
serial_write(translate("Press any key to enter the REPL. Use CTRL-D to reload."));
260+
write_compressed(translate("Press any key to enter the REPL. Use CTRL-D to reload."));
252261
}
253262
if (serial_connected_before_animation && !serial_connected()) {
254263
serial_connected_at_start = false;
@@ -403,7 +412,7 @@ int __attribute__((used)) main(void) {
403412
}
404413
if (exit_code == PYEXEC_FORCED_EXIT) {
405414
if (!first_run) {
406-
serial_write(translate("soft reboot\n"));
415+
write_compressed(translate("soft reboot\n"));
407416
}
408417
first_run = false;
409418
skip_repl = run_code_py(safe_mode);

ports/atmel-samd/audio_dma.c

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "shared-bindings/audioio/WaveFile.h"
3434

3535
#include "py/mpstate.h"
36+
#include "py/runtime.h"
3637

3738
static audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT];
3839

@@ -279,6 +280,10 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
279280
// We're likely double buffering so set up the block interrupts.
280281
turn_on_event_system();
281282
dma->event_channel = find_sync_event_channel();
283+
284+
if (dma->event_channel >= EVSYS_SYNCH_NUM) {
285+
mp_raise_RuntimeError(translate("All sync event channels in use"));
286+
}
282287
init_event_channel_interrupt(dma->event_channel, CORE_GCLK, EVSYS_ID_GEN_DMAC_CH_0 + dma_channel);
283288

284289
// We keep the audio_dma_t for internal use and the sample as a root pointer because it

ports/atmel-samd/bindings/samd/Clock.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ STATIC mp_obj_t samd_clock_set_calibration(mp_obj_t self_in, mp_obj_t calibratio
132132
samd_clock_obj_t *self = MP_OBJ_TO_PTR(self_in);
133133
int ret = clock_set_calibration(self->type, self->index, mp_obj_get_int(calibration));
134134
if (ret == -2)
135-
mp_raise_AttributeError("calibration is read only");
135+
mp_raise_AttributeError(translate("calibration is read only"));
136136
if (ret == -1)
137-
mp_raise_ValueError("calibration is out of range");
137+
mp_raise_ValueError(translate("calibration is out of range"));
138138
return mp_const_none;
139139
}
140140

ports/atmel-samd/common-hal/audiobusio/PDMIn.c

+3
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* se
357357
uint16_t* output_buffer, uint32_t output_buffer_length) {
358358
uint8_t dma_channel = find_free_audio_dma_channel();
359359
uint8_t event_channel = find_sync_event_channel();
360+
if (event_channel >= EVSYS_SYNCH_NUM) {
361+
mp_raise_RuntimeError(translate("All sync event channels in use"));
362+
}
360363

361364
// We allocate two buffers on the stack to use for double buffering.
362365
const uint8_t samples_per_buffer = SAMPLES_PER_BUFFER;

ports/atmel-samd/common-hal/audioio/AudioOut.c

+4
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ void common_hal_audioio_audioout_construct(audioio_audioout_obj_t* self,
211211
// Find a free event channel. We start at the highest channels because we only need and async
212212
// path.
213213
uint8_t channel = find_async_event_channel();
214+
if (channel >= EVSYS_CHANNELS) {
215+
mp_raise_RuntimeError(translate("All event channels in use"));
216+
}
217+
214218
#ifdef SAMD51
215219
connect_event_user_to_channel(EVSYS_ID_USER_DAC_START_1, channel);
216220
#define EVSYS_ID_USER_DAC_START EVSYS_ID_USER_DAC_START_0

ports/atmel-samd/common-hal/busio/SPI.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
129129
}
130130
#endif
131131
if (sercom == NULL) {
132-
mp_raise_ValueError("Invalid pins");
132+
mp_raise_ValueError(translate("Invalid pins"));
133133
}
134134

135135
// Set up SPI clocks on SERCOM.

py/compile.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ STATIC void compile_error_set_line(compiler_t *comp, mp_parse_node_t pn) {
156156
}
157157
}
158158

159-
STATIC void compile_syntax_error(compiler_t *comp, mp_parse_node_t pn, const char *msg) {
159+
STATIC void compile_syntax_error(compiler_t *comp, mp_parse_node_t pn, const compressed_string_t *msg) {
160160
// only register the error if there has been no other error
161161
if (comp->compile_error == MP_OBJ_NULL) {
162162
comp->compile_error = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg);
@@ -949,7 +949,7 @@ STATIC void compile_del_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) {
949949

950950
STATIC void compile_break_cont_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) {
951951
uint16_t label;
952-
const char *error_msg;
952+
const compressed_string_t *error_msg;
953953
if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_break_stmt) {
954954
label = comp->break_label;
955955
error_msg = translate("'break' outside loop");

0 commit comments

Comments
 (0)