Implement default function arguments (for Python functions).

TODO: Decide if we really need separate bytecode for creating functions
with default arguments - we would need same for closures, then there're
keywords arguments too. Having all combinations is a small exponential
explosion, likely we need just 2 cases - simplest (no defaults, no kw),
and full - defaults & kw.
diff --git a/py/bc0.h b/py/bc0.h
index 569477f..7aab555 100644
--- a/py/bc0.h
+++ b/py/bc0.h
@@ -94,7 +94,9 @@
 #define MP_BC_CALL_METHOD_VAR    (0x97) // uint
 #define MP_BC_CALL_METHOD_KW     (0x98) // uint
 #define MP_BC_CALL_METHOD_VAR_KW (0x99) // uint
+#define MP_BC_MAKE_FUNCTION_DEFARGS  (0x9a) // uint
 
 #define MP_BC_IMPORT_NAME        (0xe0) // qstr
 #define MP_BC_IMPORT_FROM        (0xe1) // qstr
 #define MP_BC_IMPORT_STAR        (0xe2)
+
diff --git a/py/compile.c b/py/compile.c
index c1a7955..c4b582e 100644
--- a/py/compile.c
+++ b/py/compile.c
@@ -3212,7 +3212,7 @@
         return mp_const_true;
 #else
         // return function that executes the outer module
-        return rt_make_function_from_id(unique_code_id);
+        return rt_make_function_from_id(unique_code_id, MP_OBJ_NULL);
 #endif
     }
 }
diff --git a/py/emitbc.c b/py/emitbc.c
index d74b065..10a93d5 100644
--- a/py/emitbc.c
+++ b/py/emitbc.c
@@ -664,9 +664,15 @@
 }
 
 static void emit_bc_make_function(emit_t *emit, scope_t *scope, int n_dict_params, int n_default_params) {
-    assert(n_default_params == 0 && n_dict_params == 0);
-    emit_pre(emit, 1);
-    emit_write_byte_code_byte_uint(emit, MP_BC_MAKE_FUNCTION, scope->unique_code_id);
+    assert(n_dict_params == 0);
+    if (n_default_params != 0) {
+        emit_bc_build_tuple(emit, n_default_params);
+        emit_pre(emit, 0);
+        emit_write_byte_code_byte_uint(emit, MP_BC_MAKE_FUNCTION_DEFARGS, scope->unique_code_id);
+    } else {
+        emit_pre(emit, 1);
+        emit_write_byte_code_byte_uint(emit, MP_BC_MAKE_FUNCTION, scope->unique_code_id);
+    }
 }
 
 static void emit_bc_make_closure(emit_t *emit, scope_t *scope, int n_dict_params, int n_default_params) {
diff --git a/py/obj.h b/py/obj.h
index 56e4d96..0680e6f 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -221,7 +221,7 @@
 mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
 mp_obj_t mp_obj_new_range(int start, int stop, int step);
 mp_obj_t mp_obj_new_range_iterator(int cur, int stop, int step);
-mp_obj_t mp_obj_new_fun_bc(int n_args, uint n_state, const byte *code);
+mp_obj_t mp_obj_new_fun_bc(int n_args, mp_obj_t def_args, uint n_state, const byte *code);
 mp_obj_t mp_obj_new_fun_asm(uint n_args, void *fun);
 mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun);
 mp_obj_t mp_obj_new_gen_instance(const byte *bytecode, uint n_state, int n_args, const mp_obj_t *args);
diff --git a/py/objfun.c b/py/objfun.c
index fe4f494..c60800b 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -8,6 +8,7 @@
 #include "mpconfig.h"
 #include "qstr.h"
 #include "obj.h"
+#include "objtuple.h"
 #include "map.h"
 #include "runtime.h"
 #include "bc.h"
@@ -136,21 +137,32 @@
 typedef struct _mp_obj_fun_bc_t {
     mp_obj_base_t base;
     mp_map_t *globals;      // the context within which this function was defined
-    int n_args;             // number of arguments this function takes
+    short n_args;           // number of arguments this function takes
+    short n_def_args;       // number of default arguments
     uint n_state;           // total state size for the executing function (incl args, locals, stack)
     const byte *bytecode;   // bytecode for the function
+    mp_obj_t def_args[];    // values of default args, if any
 } mp_obj_fun_bc_t;
 
 mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     mp_obj_fun_bc_t *self = self_in;
 
-    if (n_args != self->n_args) {
+    if (n_args < self->n_args - self->n_def_args || n_args > self->n_args) {
         nlr_jump(mp_obj_new_exception_msg_2_args(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", (const char*)(machine_int_t)self->n_args, (const char*)(machine_int_t)n_args));
     }
     if (n_kw != 0) {
         nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
     }
 
+    mp_obj_t full_args[n_args];
+    if (n_args < self->n_args) {
+        memcpy(full_args, args, n_args * sizeof(*args));
+        int use_def_args = self->n_args - n_args;
+        memcpy(full_args + n_args, self->def_args + self->n_def_args - use_def_args, use_def_args * sizeof(*args));
+        args = full_args;
+        n_args = self->n_args;
+    }
+
     // optimisation: allow the compiler to optimise this tail call for
     // the common case when the globals don't need to be changed
     mp_map_t *old_globals = rt_globals_get();
@@ -170,13 +182,22 @@
     .call = fun_bc_call,
 };
 
-mp_obj_t mp_obj_new_fun_bc(int n_args, uint n_state, const byte *code) {
-    mp_obj_fun_bc_t *o = m_new_obj(mp_obj_fun_bc_t);
+mp_obj_t mp_obj_new_fun_bc(int n_args, mp_obj_t def_args_in, uint n_state, const byte *code) {
+    int n_def_args = 0;
+    mp_obj_tuple_t *def_args = def_args_in;
+    if (def_args != MP_OBJ_NULL) {
+        n_def_args = def_args->len;
+    }
+    mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_def_args);
     o->base.type = &fun_bc_type;
     o->globals = rt_globals_get();
     o->n_args = n_args;
+    o->n_def_args = n_def_args;
     o->n_state = n_state;
     o->bytecode = code;
+    if (def_args != MP_OBJ_NULL) {
+        memcpy(o->def_args, def_args->items, n_def_args * sizeof(*o->def_args));
+    }
     return o;
 }
 
diff --git a/py/runtime.c b/py/runtime.c
index ee8d720..77e596c 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -674,7 +674,7 @@
     return mp_const_none;
 }
 
-mp_obj_t rt_make_function_from_id(int unique_code_id) {
+mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args) {
     DEBUG_OP_printf("make_function_from_id %d\n", unique_code_id);
     if (unique_code_id < 1 || unique_code_id >= next_unique_code_id) {
         // illegal code id
@@ -686,7 +686,7 @@
     mp_obj_t fun;
     switch (c->kind) {
         case MP_CODE_BYTE:
-            fun = mp_obj_new_fun_bc(c->n_args, c->n_state, c->u_byte.code);
+            fun = mp_obj_new_fun_bc(c->n_args, def_args, c->n_state, c->u_byte.code);
             break;
         case MP_CODE_NATIVE:
             fun = rt_make_function_n(c->n_args, c->u_native.fun);
@@ -710,7 +710,7 @@
 mp_obj_t rt_make_closure_from_id(int unique_code_id, mp_obj_t closure_tuple) {
     DEBUG_OP_printf("make_closure_from_id %d\n", unique_code_id);
     // make function object
-    mp_obj_t ffun = rt_make_function_from_id(unique_code_id);
+    mp_obj_t ffun = rt_make_function_from_id(unique_code_id, MP_OBJ_NULL);
     // wrap function in closure object
     return mp_obj_new_closure(ffun, closure_tuple);
 }
diff --git a/py/runtime.h b/py/runtime.h
index c8113f4..10c262a 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -12,7 +12,7 @@
 void rt_store_global(qstr qstr, mp_obj_t obj);
 mp_obj_t rt_unary_op(int op, mp_obj_t arg);
 mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs);
-mp_obj_t rt_make_function_from_id(int unique_code_id);
+mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args);
 mp_obj_t rt_make_function_n(int n_args, void *fun); // fun must have the correct signature for n_args fixed arguments
 mp_obj_t rt_make_function_var(int n_args_min, mp_fun_var_t fun);
 mp_obj_t rt_make_function_var_between(int n_args_min, int n_args_max, mp_fun_var_t fun); // min and max are inclusive
diff --git a/py/showbc.c b/py/showbc.c
index 8a12302..53a1826 100644
--- a/py/showbc.c
+++ b/py/showbc.c
@@ -339,6 +339,11 @@
                 printf("MAKE_FUNCTION " UINT_FMT, unum);
                 break;
 
+            case MP_BC_MAKE_FUNCTION_DEFARGS:
+                DECODE_UINT;
+                printf("MAKE_FUNCTION_DEFARGS " UINT_FMT, unum);
+                break;
+
             case MP_BC_MAKE_CLOSURE:
                 DECODE_UINT;
                 printf("MAKE_CLOSURE " UINT_FMT, unum);
diff --git a/py/vm.c b/py/vm.c
index f61fe48..68a7ffc 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -476,7 +476,12 @@
 
                     case MP_BC_MAKE_FUNCTION:
                         DECODE_UINT;
-                        PUSH(rt_make_function_from_id(unum));
+                        PUSH(rt_make_function_from_id(unum, MP_OBJ_NULL));
+                        break;
+
+                    case MP_BC_MAKE_FUNCTION_DEFARGS:
+                        DECODE_UINT;
+                        SET_TOP(rt_make_function_from_id(unum, TOP()));
                         break;
 
                     case MP_BC_MAKE_CLOSURE:
diff --git a/tests/basics/fun-defargs.py b/tests/basics/fun-defargs.py
new file mode 100644
index 0000000..0666b8c
--- /dev/null
+++ b/tests/basics/fun-defargs.py
@@ -0,0 +1,20 @@
+def fun1(val=5):
+    print(5)
+
+fun1()
+fun1(10)
+
+def fun2(p1, p2=100, p3="foo"):
+    print(p1, p2, p3)
+
+fun2(1)
+fun2(1, None)
+fun2(0, "bar", 200)
+try:
+    fun2()
+except TypeError:
+    print("TypeError")
+try:
+    fun2(1, 2, 3, 4)
+except TypeError:
+    print("TypeError")