summaryrefslogtreecommitdiffstatshomepage
path: root/py/objfun.c
diff options
context:
space:
mode:
Diffstat (limited to 'py/objfun.c')
-rw-r--r--py/objfun.c219
1 files changed, 156 insertions, 63 deletions
diff --git a/py/objfun.c b/py/objfun.c
index 5c085f883c..d63acc687b 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -236,11 +236,17 @@ arg_error:
fun_pos_args_mismatch(self, self->n_pos_args, n_args);
}
+// With this macro you can tune the maximum number of function state bytes
+// that will be allocated on the stack. Any function that needs more
+// than this will use the heap.
+#define VM_MAX_STATE_ON_STACK (10 * sizeof(machine_uint_t))
+
+// Set this to enable a simple stack overflow check.
+#define VM_DETECT_STACK_OVERFLOW (0)
+
STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
// This function is pretty complicated. It's main aim is to be efficient in speed and RAM
// usage for the common case of positional only args.
- //
- // extra_args layout: def_args, var_arg tuple, kwonly args, var_kw dict
DEBUG_printf("Input n_args: %d, n_kw: %d\n", n_args, n_kw);
DEBUG_printf("Input pos args: ");
@@ -250,9 +256,42 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
mp_obj_fun_bc_t *self = self_in;
DEBUG_printf("Func n_def_args: %d\n", self->n_def_args);
+ const byte *ip = self->bytecode;
+
+ // get code info size, and skip line number table
+ machine_uint_t code_info_size = ip[0] | (ip[1] << 8) | (ip[2] << 16) | (ip[3] << 24);
+ ip += code_info_size;
+
+ // bytecode prelude: state size and exception stack size; 16 bit uints
+ machine_uint_t n_state = ip[0] | (ip[1] << 8);
+ machine_uint_t n_exc_stack = ip[2] | (ip[3] << 8);
+ ip += 4;
+
+#if VM_DETECT_STACK_OVERFLOW
+ n_state += 1;
+#endif
+
+ // allocate state for locals and stack
+ uint state_size = n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t);
+ mp_code_state *code_state;
+ if (state_size > VM_MAX_STATE_ON_STACK) {
+ code_state = m_new_obj_var(mp_code_state, byte, state_size);
+ } else {
+ code_state = alloca(sizeof(mp_code_state) + state_size);
+ }
+
+ code_state->code_info = self->bytecode;
+ code_state->sp = &code_state->state[0] - 1;
+ code_state->exc_sp = (mp_exc_stack_t*)(code_state->state + n_state) - 1;
+ code_state->n_state = n_state;
+
+ // zero out the local stack to begin with
+ memset(code_state->state, 0, n_state * sizeof(*code_state->state));
+
const mp_obj_t *kwargs = args + n_args;
- mp_obj_t *extra_args = self->extra_args + self->n_def_args;
- uint n_extra_args = 0;
+
+ // var_pos_kw_args points to the stack where the var-args tuple, and var-kw dict, should go (if they are needed)
+ mp_obj_t *var_pos_kw_args = &code_state->state[n_state - 1 - self->n_pos_args - self->n_kwonly_args];
// check positional arguments
@@ -262,57 +301,53 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
fun_pos_args_mismatch(self, self->n_pos_args, n_args);
}
// put extra arguments in varargs tuple
- *extra_args = mp_obj_new_tuple(n_args - self->n_pos_args, args + self->n_pos_args);
- n_extra_args = 1;
+ *var_pos_kw_args-- = mp_obj_new_tuple(n_args - self->n_pos_args, args + self->n_pos_args);
n_args = self->n_pos_args;
} else {
if (self->takes_var_args) {
DEBUG_printf("passing empty tuple as *args\n");
- *extra_args = mp_const_empty_tuple;
- n_extra_args = 1;
+ *var_pos_kw_args-- = mp_const_empty_tuple;
}
// Apply processing and check below only if we don't have kwargs,
// otherwise, kw handling code below has own extensive checks.
- if (n_kw == 0) {
+ if (n_kw == 0 && !self->has_def_kw_args) {
if (n_args >= self->n_pos_args - self->n_def_args) {
// given enough arguments, but may need to use some default arguments
- extra_args -= self->n_pos_args - n_args;
- n_extra_args += self->n_pos_args - n_args;
+ for (uint i = n_args; i < self->n_pos_args; i++) {
+ code_state->state[n_state - 1 - i] = self->extra_args[i - (self->n_pos_args - self->n_def_args)];
+ }
} else {
fun_pos_args_mismatch(self, self->n_pos_args - self->n_def_args, n_args);
}
}
}
+ // copy positional args into state
+ for (uint i = 0; i < n_args; i++) {
+ code_state->state[n_state - 1 - i] = args[i];
+ }
+
// check keyword arguments
- if (n_kw != 0) {
- // We cannot use dynamically-sized array here, because GCC indeed
- // deallocates it on leaving defining scope (unlike most static stack allocs).
- // So, we have 2 choices: allocate it unconditionally at the top of function
- // (wastes stack), or use alloca which is guaranteed to dealloc on func exit.
- //mp_obj_t flat_args[self->n_args];
- mp_obj_t *flat_args = alloca((self->n_pos_args + self->n_kwonly_args) * sizeof(mp_obj_t));
- for (int i = self->n_pos_args + self->n_kwonly_args - 1; i >= 0; i--) {
- flat_args[i] = MP_OBJ_NULL;
- }
- memcpy(flat_args, args, sizeof(*args) * n_args);
+ if (n_kw != 0 || self->has_def_kw_args) {
DEBUG_printf("Initial args: ");
- dump_args(flat_args, self->n_pos_args + self->n_kwonly_args);
+ dump_args(code_state->state + n_state - self->n_pos_args - self->n_kwonly_args, self->n_pos_args + self->n_kwonly_args);
mp_obj_t dict = MP_OBJ_NULL;
if (self->takes_kw_args) {
dict = mp_obj_new_dict(n_kw); // TODO: better go conservative with 0?
+ *var_pos_kw_args = dict;
}
+
for (uint i = 0; i < n_kw; i++) {
qstr arg_name = MP_OBJ_QSTR_VALUE(kwargs[2 * i]);
for (uint j = 0; j < self->n_pos_args + self->n_kwonly_args; j++) {
if (arg_name == self->args[j]) {
- if (flat_args[j] != MP_OBJ_NULL) {
+ if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
"function got multiple values for argument '%s'", qstr_str(arg_name)));
}
- flat_args[j] = kwargs[2 * i + 1];
+ code_state->state[n_state - 1 - j] = kwargs[2 * i + 1];
goto continue2;
}
}
@@ -323,43 +358,47 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
mp_obj_dict_store(dict, kwargs[2 * i], kwargs[2 * i + 1]);
continue2:;
}
+
DEBUG_printf("Args with kws flattened: ");
- dump_args(flat_args, self->n_pos_args + self->n_kwonly_args);
+ dump_args(code_state->state + n_state - self->n_pos_args - self->n_kwonly_args, self->n_pos_args + self->n_kwonly_args);
- // Now fill in defaults for positional args
- mp_obj_t *d = &flat_args[self->n_pos_args - 1];
+ // fill in defaults for positional args
+ mp_obj_t *d = &code_state->state[n_state - self->n_pos_args];
mp_obj_t *s = &self->extra_args[self->n_def_args - 1];
- for (int i = self->n_def_args; i > 0; i--, d--, s--) {
+ for (int i = self->n_def_args; i > 0; i--, d++, s--) {
if (*d == MP_OBJ_NULL) {
*d = *s;
}
}
- DEBUG_printf("Args after filling defaults: ");
- dump_args(flat_args, self->n_pos_args + self->n_kwonly_args);
+
+ DEBUG_printf("Args after filling default positional: ");
+ dump_args(code_state->state + n_state - self->n_pos_args - self->n_kwonly_args, self->n_pos_args + self->n_kwonly_args);
// Check that all mandatory positional args are specified
- while (d >= flat_args) {
- if (*d-- == MP_OBJ_NULL) {
+ while (d < &code_state->state[n_state]) {
+ if (*d++ == MP_OBJ_NULL) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
- "function missing required positional argument #%d", d - flat_args));
+ "function missing required positional argument #%d", &code_state->state[n_state] - d));
}
}
// Check that all mandatory keyword args are specified
- for (int i = 0; i < self->n_kwonly_args; i++) {
- if (flat_args[self->n_pos_args + i] == MP_OBJ_NULL) {
- nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
- "function missing required keyword argument '%s'", qstr_str(self->args[self->n_pos_args + i])));
+ // Fill in default kw args if we have them
+ for (uint i = 0; i < self->n_kwonly_args; i++) {
+ if (code_state->state[n_state - 1 - self->n_pos_args - i] == MP_OBJ_NULL) {
+ mp_map_elem_t *elem = NULL;
+ if (self->has_def_kw_args) {
+ 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);
+ }
+ if (elem != NULL) {
+ code_state->state[n_state - 1 - self->n_pos_args - i] = elem->value;
+ } else {
+ nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
+ "function missing required keyword argument '%s'", qstr_str(self->args[self->n_pos_args + i])));
+ }
}
}
- args = flat_args;
- n_args = self->n_pos_args + self->n_kwonly_args;
-
- if (self->takes_kw_args) {
- extra_args[n_extra_args] = dict;
- n_extra_args += 1;
- }
} else {
// no keyword arguments given
if (self->n_kwonly_args != 0) {
@@ -367,20 +406,80 @@ continue2:;
"function missing keyword-only argument"));
}
if (self->takes_kw_args) {
- extra_args[n_extra_args] = mp_obj_new_dict(0);
- n_extra_args += 1;
+ *var_pos_kw_args = mp_obj_new_dict(0);
}
}
+ // bytecode prelude: initialise closed over variables
+ for (uint n_local = *ip++; n_local > 0; n_local--) {
+ uint local_num = *ip++;
+ code_state->state[n_state - 1 - local_num] = mp_obj_new_cell(code_state->state[n_state - 1 - local_num]);
+ }
+
+ // now that we skipped over the prelude, set the ip for the VM
+ code_state->ip = ip;
+
+ DEBUG_printf("Calling: n_pos_args=%d, n_kwonly_args=%d\n", self->n_pos_args, self->n_kwonly_args);
+ dump_args(code_state->state + n_state - self->n_pos_args - self->n_kwonly_args, self->n_pos_args + self->n_kwonly_args);
+ dump_args(code_state->state, n_state);
+
+ // execute the byte code with the correct globals context
mp_obj_dict_t *old_globals = mp_globals_get();
mp_globals_set(self->globals);
- mp_obj_t result;
- DEBUG_printf("Calling: args=%p, n_args=%d, extra_args=%p, n_extra_args=%d\n", args, n_args, extra_args, n_extra_args);
- dump_args(args, n_args);
- dump_args(extra_args, n_extra_args);
- mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(self->bytecode, args, n_args, extra_args, n_extra_args, &result);
+ mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL);
mp_globals_set(old_globals);
+#if VM_DETECT_STACK_OVERFLOW
+ if (vm_return_kind == MP_VM_RETURN_NORMAL) {
+ if (code_state->sp < code_state->state) {
+ printf("VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state);
+ assert(0);
+ }
+ }
+ // We can't check the case when an exception is returned in state[n_state - 1]
+ // and there are no arguments, because in this case our detection slot may have
+ // been overwritten by the returned exception (which is allowed).
+ if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && self->n_pos_args + self->n_kwonly_args == 0)) {
+ // Just check to see that we have at least 1 null object left in the state.
+ bool overflow = true;
+ for (uint i = 0; i < n_state - self->n_pos_args - self->n_kwonly_args; i++) {
+ if (code_state->state[i] == MP_OBJ_NULL) {
+ overflow = false;
+ break;
+ }
+ }
+ if (overflow) {
+ printf("VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state);
+ assert(0);
+ }
+ }
+#endif
+
+ mp_obj_t result;
+ switch (vm_return_kind) {
+ case MP_VM_RETURN_NORMAL:
+ // return value is in *sp
+ result = *code_state->sp;
+ break;
+
+ case MP_VM_RETURN_EXCEPTION:
+ // return value is in state[n_state - 1]
+ result = code_state->state[n_state - 1];
+ break;
+
+ case MP_VM_RETURN_YIELD: // byte-code shouldn't yield
+ default:
+ assert(0);
+ result = mp_const_none;
+ vm_return_kind = MP_VM_RETURN_NORMAL;
+ break;
+ }
+
+ // free the state if it was allocated on the heap
+ if (state_size > VM_MAX_STATE_ON_STACK) {
+ m_del_var(mp_code_state, byte, state_size, code_state);
+ }
+
if (vm_return_kind == MP_VM_RETURN_NORMAL) {
return result;
} else { // MP_VM_RETURN_EXCEPTION
@@ -398,7 +497,7 @@ const mp_obj_type_t mp_type_fun_bc = {
.binary_op = fun_binary_op,
};
-mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args_in, const byte *code) {
+mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code) {
uint n_def_args = 0;
uint n_extra_args = 0;
mp_obj_tuple_t *def_args = def_args_in;
@@ -407,10 +506,7 @@ mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n
n_def_args = def_args->len;
n_extra_args = def_args->len;
}
- if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
- n_extra_args += 1;
- }
- if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) {
+ if (def_kw_args != MP_OBJ_NULL) {
n_extra_args += 1;
}
mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args);
@@ -420,18 +516,15 @@ mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n
o->n_pos_args = n_pos_args;
o->n_kwonly_args = n_kwonly_args;
o->n_def_args = n_def_args;
+ o->has_def_kw_args = def_kw_args != MP_OBJ_NULL;
o->takes_var_args = (scope_flags & MP_SCOPE_FLAG_VARARGS) != 0;
o->takes_kw_args = (scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0;
o->bytecode = code;
- memset(o->extra_args, 0, n_extra_args * sizeof(mp_obj_t));
if (def_args != MP_OBJ_NULL) {
memcpy(o->extra_args, def_args->items, n_def_args * sizeof(mp_obj_t));
}
- if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
- o->extra_args[n_def_args] = MP_OBJ_NULL;
- }
- if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
- o->extra_args[n_extra_args - 1] = MP_OBJ_NULL;
+ if (def_kw_args != MP_OBJ_NULL) {
+ o->extra_args[n_def_args] = def_kw_args;
}
return o;
}