Skip to content

Commit 1084b0f

Browse files
committed
py: Store bytecode arg names in bytecode (were in own array).
This saves a lot of RAM for 2 reasons: 1. For functions that don't have default values, var args or var kw args (which is a large number of functions in the general case), the mp_obj_fun_bc_t type now fits in 1 GC block (previously needed 2 because of the extra pointer to point to the arg_names array). So this saves 16 bytes per function (32 bytes on 64-bit machines). 2. Combining separate memory regions generally saves RAM because the unused bytes at the end of the GC block are saved for 1 of the blocks (since that block doesn't exist on its own anymore). So generally this saves 8 bytes per function. Tested by importing lots of modules: - 64-bit Linux gave about an 8% RAM saving for 86k of used RAM. - pyboard gave about a 6% RAM saving for 31k of used RAM.
1 parent fcff466 commit 1084b0f

10 files changed

Lines changed: 58 additions & 33 deletions

File tree

py/bc.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ void mp_setup_code_state(mp_code_state *code_state, mp_obj_t self_in, mp_uint_t
9393
// usage for the common case of positional only args.
9494
mp_obj_fun_bc_t *self = self_in;
9595
mp_uint_t n_state = code_state->n_state;
96-
const byte *ip = code_state->ip;
9796

9897
code_state->code_info = self->bytecode;
9998
code_state->sp = &code_state->state[0] - 1;
@@ -153,13 +152,21 @@ void mp_setup_code_state(mp_code_state *code_state, mp_obj_t self_in, mp_uint_t
153152
*var_pos_kw_args = dict;
154153
}
155154

155+
// get pointer to arg_names array at start of bytecode prelude
156+
const mp_obj_t *arg_names;
157+
{
158+
const byte *code_info = code_state->code_info;
159+
mp_uint_t code_info_size = mp_decode_uint(&code_info);
160+
arg_names = (const mp_obj_t*)(code_state->code_info + code_info_size);
161+
}
162+
156163
for (mp_uint_t i = 0; i < n_kw; i++) {
157-
qstr arg_name = MP_OBJ_QSTR_VALUE(kwargs[2 * i]);
164+
mp_obj_t wanted_arg_name = kwargs[2 * i];
158165
for (mp_uint_t j = 0; j < self->n_pos_args + self->n_kwonly_args; j++) {
159-
if (arg_name == self->args[j]) {
166+
if (wanted_arg_name == arg_names[j]) {
160167
if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) {
161168
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
162-
"function got multiple values for argument '%s'", qstr_str(arg_name)));
169+
"function got multiple values for argument '%s'", qstr_str(MP_OBJ_QSTR_VALUE(wanted_arg_name))));
163170
}
164171
code_state->state[n_state - 1 - j] = kwargs[2 * i + 1];
165172
goto continue2;
@@ -202,13 +209,13 @@ continue2:;
202209
if (code_state->state[n_state - 1 - self->n_pos_args - i] == MP_OBJ_NULL) {
203210
mp_map_elem_t *elem = NULL;
204211
if (self->has_def_kw_args) {
205-
elem = mp_map_lookup(&((mp_obj_dict_t*)self->extra_args[self->n_def_args])->map, MP_OBJ_NEW_QSTR(self->args[self->n_pos_args + i]), MP_MAP_LOOKUP);
212+
elem = mp_map_lookup(&((mp_obj_dict_t*)self->extra_args[self->n_def_args])->map, arg_names[self->n_pos_args + i], MP_MAP_LOOKUP);
206213
}
207214
if (elem != NULL) {
208215
code_state->state[n_state - 1 - self->n_pos_args - i] = elem->value;
209216
} else {
210217
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
211-
"function missing required keyword argument '%s'", qstr_str(self->args[self->n_pos_args + i])));
218+
"function missing required keyword argument '%s'", qstr_str(MP_OBJ_QSTR_VALUE(arg_names[self->n_pos_args + i]))));
212219
}
213220
}
214221
}
@@ -225,6 +232,7 @@ continue2:;
225232
}
226233

227234
// bytecode prelude: initialise closed over variables
235+
const byte *ip = code_state->ip;
228236
for (mp_uint_t n_local = *ip++; n_local > 0; n_local--) {
229237
mp_uint_t local_num = *ip++;
230238
code_state->state[n_state - 1 - local_num] = mp_obj_new_cell(code_state->state[n_state - 1 - local_num]);

py/bc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ mp_uint_t mp_decode_uint(const byte **ptr);
5353

5454
mp_vm_return_kind_t mp_execute_bytecode(mp_code_state *code_state, volatile mp_obj_t inject_exc);
5555
void mp_setup_code_state(mp_code_state *code_state, mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
56-
void mp_bytecode_print(const void *descr, const byte *code, mp_uint_t len);
56+
void mp_bytecode_print(const void *descr, mp_uint_t n_total_args, const byte *code, mp_uint_t len);
5757
void mp_bytecode_print2(const byte *code, mp_uint_t len);
5858

5959
// Helper macros to access pointer with least significant bit holding a flag

py/emitbc.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,13 @@ STATIC void emit_write_bytecode_byte_uint(emit_t* emit, byte b, mp_uint_t val) {
218218
emit_write_uint(emit, emit_get_cur_to_write_bytecode, val);
219219
}
220220

221+
STATIC void emit_write_bytecode_prealigned_ptr(emit_t* emit, void *ptr) {
222+
mp_uint_t *c = (mp_uint_t*)emit_get_cur_to_write_bytecode(emit, sizeof(mp_uint_t));
223+
// Verify thar c is already uint-aligned
224+
assert(c == MP_ALIGN(c, sizeof(mp_uint_t)));
225+
*c = (mp_uint_t)ptr;
226+
}
227+
221228
// aligns the pointer so it is friendly to GC
222229
STATIC void emit_write_bytecode_byte_ptr(emit_t* emit, byte b, void *ptr) {
223230
emit_write_bytecode_byte(emit, b);
@@ -294,7 +301,16 @@ STATIC void emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) {
294301
emit_write_code_info_qstr(emit, scope->simple_name);
295302
emit_write_code_info_qstr(emit, scope->source_file);
296303

297-
// bytecode prelude: local state size and exception stack size; 16 bit uints for now
304+
// bytecode prelude: argument names (needed to resolve positional args passed as keywords)
305+
// we store them as full word-sized objects for efficient access in mp_setup_code_state
306+
// this is the start of the prelude and is guaranteed to be aligned on a word boundary
307+
{
308+
for (int i = 0; i < scope->num_pos_args + scope->num_kwonly_args; i++) {
309+
emit_write_bytecode_prealigned_ptr(emit, MP_OBJ_NEW_QSTR(scope->id_info[i].qst));
310+
}
311+
}
312+
313+
// bytecode prelude: local state size and exception stack size
298314
{
299315
mp_uint_t n_state = scope->num_locals + scope->stack_size;
300316
if (n_state == 0) {
@@ -358,13 +374,9 @@ STATIC void emit_bc_end_pass(emit_t *emit) {
358374
emit->code_base = m_new0(byte, emit->code_info_size + emit->bytecode_size);
359375

360376
} else if (emit->pass == MP_PASS_EMIT) {
361-
qstr *arg_names = m_new(qstr, emit->scope->num_pos_args + emit->scope->num_kwonly_args);
362-
for (int i = 0; i < emit->scope->num_pos_args + emit->scope->num_kwonly_args; i++) {
363-
arg_names[i] = emit->scope->id_info[i].qst;
364-
}
365377
mp_emit_glue_assign_bytecode(emit->scope->raw_code, emit->code_base,
366378
emit->code_info_size + emit->bytecode_size,
367-
emit->scope->num_pos_args, emit->scope->num_kwonly_args, arg_names,
379+
emit->scope->num_pos_args, emit->scope->num_kwonly_args,
368380
emit->scope->scope_flags);
369381
}
370382
}

py/emitglue.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,20 @@ mp_raw_code_t *mp_emit_glue_new_raw_code(void) {
5555
return rc;
5656
}
5757

58-
void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, byte *code, mp_uint_t len, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, qstr *arg_names, mp_uint_t scope_flags) {
58+
void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, byte *code, mp_uint_t len, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_uint_t scope_flags) {
5959
rc->kind = MP_CODE_BYTECODE;
6060
rc->scope_flags = scope_flags;
6161
rc->n_pos_args = n_pos_args;
6262
rc->n_kwonly_args = n_kwonly_args;
63-
rc->arg_names = arg_names;
6463
rc->u_byte.code = code;
6564
rc->u_byte.len = len;
6665

6766
#ifdef DEBUG_PRINT
6867
DEBUG_printf("assign byte code: code=%p len=" UINT_FMT " n_pos_args=" UINT_FMT " n_kwonly_args=" UINT_FMT " flags=%x\n", code, len, n_pos_args, n_kwonly_args, (uint)scope_flags);
69-
DEBUG_printf(" arg names:");
70-
for (int i = 0; i < n_pos_args + n_kwonly_args; i++) {
71-
DEBUG_printf(" %s", qstr_str(arg_names[i]));
72-
}
73-
DEBUG_printf("\n");
7468
#endif
7569
#if MICROPY_DEBUG_PRINTERS
7670
if (mp_verbose_flag > 0) {
77-
mp_bytecode_print(rc, code, len);
71+
mp_bytecode_print(rc, n_pos_args + n_kwonly_args, code, len);
7872
}
7973
#endif
8074
}
@@ -121,7 +115,7 @@ mp_obj_t mp_make_function_from_raw_code(mp_raw_code_t *rc, mp_obj_t def_args, mp
121115
mp_obj_t fun;
122116
switch (rc->kind) {
123117
case MP_CODE_BYTECODE:
124-
fun = mp_obj_new_fun_bc(rc->scope_flags, rc->arg_names, rc->n_pos_args, rc->n_kwonly_args, def_args, def_kw_args, rc->u_byte.code);
118+
fun = mp_obj_new_fun_bc(rc->scope_flags, rc->n_pos_args, rc->n_kwonly_args, def_args, def_kw_args, rc->u_byte.code);
125119
break;
126120
#if MICROPY_EMIT_NATIVE
127121
case MP_CODE_NATIVE_PY:

py/emitglue.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ typedef struct _mp_raw_code_t {
4040
mp_uint_t scope_flags : 7;
4141
mp_uint_t n_pos_args : 11;
4242
mp_uint_t n_kwonly_args : 11;
43-
qstr *arg_names;
4443
union {
4544
struct {
4645
byte *code;
@@ -55,7 +54,7 @@ typedef struct _mp_raw_code_t {
5554

5655
mp_raw_code_t *mp_emit_glue_new_raw_code(void);
5756

58-
void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, byte *code, mp_uint_t len, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, qstr *arg_names, mp_uint_t scope_flags);
57+
void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, byte *code, mp_uint_t len, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_uint_t scope_flags);
5958
void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void *fun_data, mp_uint_t fun_len, mp_uint_t n_args, mp_uint_t type_sig);
6059

6160
mp_obj_t mp_make_function_from_raw_code(mp_raw_code_t *rc, mp_obj_t def_args, mp_obj_t def_kw_args);

py/obj.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ mp_obj_t mp_obj_new_exception_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg);
383383
mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, mp_uint_t n_args, const mp_obj_t *args);
384384
mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg);
385385
mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
386-
mp_obj_t mp_obj_new_fun_bc(mp_uint_t scope_flags, qstr *args, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_obj_t def_args, mp_obj_t def_kw_args, const byte *code);
386+
mp_obj_t mp_obj_new_fun_bc(mp_uint_t scope_flags, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_obj_t def_args, mp_obj_t def_kw_args, const byte *code);
387387
mp_obj_t mp_obj_new_fun_native(mp_uint_t n_args, void *fun_data);
388388
mp_obj_t mp_obj_new_fun_viper(mp_uint_t n_args, void *fun_data, mp_uint_t type_sig);
389389
mp_obj_t mp_obj_new_fun_asm(mp_uint_t n_args, void *fun_data);

py/objfun.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw,
173173
mp_uint_t code_info_size = mp_decode_uint(&code_info);
174174
const byte *ip = self->bytecode + code_info_size;
175175

176-
// bytecode prelude: state size and exception stack size; 16 bit uints
176+
// bytecode prelude: skip arg names
177+
ip += (self->n_pos_args + self->n_kwonly_args) * sizeof(mp_obj_t);
178+
179+
// bytecode prelude: state size and exception stack size
177180
mp_uint_t n_state = mp_decode_uint(&ip);
178181
mp_uint_t n_exc_stack = mp_decode_uint(&ip);
179182

@@ -268,7 +271,7 @@ const mp_obj_type_t mp_type_fun_bc = {
268271
.binary_op = mp_obj_fun_binary_op,
269272
};
270273

271-
mp_obj_t mp_obj_new_fun_bc(mp_uint_t scope_flags, qstr *args, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code) {
274+
mp_obj_t mp_obj_new_fun_bc(mp_uint_t scope_flags, mp_uint_t n_pos_args, mp_uint_t n_kwonly_args, mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code) {
272275
mp_uint_t n_def_args = 0;
273276
mp_uint_t n_extra_args = 0;
274277
mp_obj_tuple_t *def_args = def_args_in;
@@ -283,7 +286,6 @@ mp_obj_t mp_obj_new_fun_bc(mp_uint_t scope_flags, qstr *args, mp_uint_t n_pos_ar
283286
mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args);
284287
o->base.type = &mp_type_fun_bc;
285288
o->globals = mp_globals_get();
286-
o->args = args;
287289
o->n_pos_args = n_pos_args;
288290
o->n_kwonly_args = n_kwonly_args;
289291
o->n_def_args = n_def_args;

py/objfun.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@
2727
typedef struct _mp_obj_fun_bc_t {
2828
mp_obj_base_t base;
2929
mp_obj_dict_t *globals; // the context within which this function was defined
30-
mp_uint_t n_pos_args : 16; // number of arguments this function takes
31-
mp_uint_t n_kwonly_args : 16; // number of arguments this function takes
32-
mp_uint_t n_def_args : 16; // number of default arguments
30+
mp_uint_t n_pos_args : 8; // number of arguments this function takes
31+
mp_uint_t n_kwonly_args : 8; // number of keyword-only arguments this function takes
32+
mp_uint_t n_def_args : 8; // number of default arguments
3333
mp_uint_t has_def_kw_args : 1; // set if this function has default keyword args
3434
mp_uint_t takes_var_args : 1; // set if this function takes variable args
3535
mp_uint_t takes_kw_args : 1; // set if this function takes keyword args
3636
const byte *bytecode; // bytecode for the function
37-
qstr *args; // argument names (needed to resolve positional args passed as keywords)
3837
// the following extra_args array is allocated space to take (in order):
3938
// - values of positional default args (if any)
4039
// - a single slot for default kw args dict (if it has them)

py/objgenerator.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw
6262
mp_uint_t code_info_size = mp_decode_uint(&code_info);
6363
const byte *ip = self_fun->bytecode + code_info_size;
6464

65+
// bytecode prelude: skip arg names
66+
ip += (self_fun->n_pos_args + self_fun->n_kwonly_args) * sizeof(mp_obj_t);
67+
6568
// bytecode prelude: get state size and exception stack size
6669
mp_uint_t n_state = mp_decode_uint(&ip);
6770
mp_uint_t n_exc_stack = mp_decode_uint(&ip);

py/showbc.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
ip += sizeof(mp_uint_t); \
5858
} while (0)
5959

60-
void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len) {
60+
void mp_bytecode_print(const void *descr, mp_uint_t n_total_args, const byte *ip, mp_uint_t len) {
6161
const byte *ip_start = ip;
6262

6363
// get code info size
@@ -80,6 +80,14 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len) {
8080
}
8181
printf("\n");
8282

83+
// bytecode prelude: arg names (as qstr objects)
84+
printf("arg names:");
85+
for (int i = 0; i < n_total_args; i++) {
86+
printf(" %s", qstr_str(MP_OBJ_QSTR_VALUE(*(mp_obj_t*)ip)));
87+
ip += sizeof(mp_obj_t);
88+
}
89+
printf("\n");
90+
8391
// bytecode prelude: state size and exception stack size; 16 bit uints
8492
{
8593
uint n_state = mp_decode_uint(&ip);

0 commit comments

Comments
 (0)