blueloveTH 2 rokov pred
rodič
commit
b87cd6ed52
12 zmenil súbory, kde vykonal 285 pridanie a 92 odobranie
  1. 10 11
      docs/features/differences.md
  2. 60 18
      src/ceval.h
  3. 3 2
      src/common.h
  4. 24 8
      src/compiler.h
  5. 8 0
      src/dict.h
  6. 45 29
      src/expr.h
  7. 1 0
      src/memory.h
  8. 18 1
      src/obj.h
  9. 8 3
      src/opcodes.h
  10. 20 2
      src/str.h
  11. 62 17
      src/vm.h
  12. 26 1
      tests/21_functions.py

+ 10 - 11
docs/features/differences.md

@@ -20,17 +20,16 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp
 
 ## Unimplemented features
 
-1. `**kwargs` in function definition.
-2. `__getattr__` and `__setattr__`.
-3. Descriptor protocol `__get__` and `__set__`. However, `@property` is implemented.
-4. `__slots__` in class definition.
-5. One element tuple. `(1,)` is not supported.
-6. Unpacking in `list` and `dict` literals, e.g. `[1, 2, *a]`.
-7. Access the exception object in try..except.
-8.  `else` clause in try..except.
-9.  Inplace methods like `__iadd__` and `__imul__`.
-10. `__del__` in class definition.
-11. Multiple inheritance.
+1. `__getattr__` and `__setattr__`.
+2. Descriptor protocol `__get__` and `__set__`. However, `@property` is implemented.
+3. `__slots__` in class definition.
+4. One element tuple. `(1,)` is not supported.
+5. Unpacking in `list` and `dict` literals, e.g. `[1, 2, *a]`.
+6. Access the exception object in try..except.
+7.  `else` clause in try..except.
+8.  Inplace methods like `__iadd__` and `__imul__`.
+9. `__del__` in class definition.
+10. Multiple inheritance.
 
 ## Different behaviors
 

+ 60 - 18
src/ceval.h

@@ -91,7 +91,7 @@ __NEXT_STEP:;
     TARGET(LOAD_ELLIPSIS) PUSH(Ellipsis); DISPATCH();
     TARGET(LOAD_FUNCTION) {
         FuncDecl_ decl = co->func_decls[byte.arg];
-        bool is_simple = decl->starred_arg==-1 && decl->kwargs.size()==0 && !decl->code->is_generator;
+        bool is_simple = decl->starred_kwarg==-1 && decl->starred_arg==-1 && decl->kwargs.size()==0 && !decl->code->is_generator;
         int argc = decl->args.size();
         PyObject* obj;
         if(decl->nested){
@@ -236,6 +236,11 @@ __NEXT_STEP:;
         }
         DISPATCH();
     /*****************************************/
+    TARGET(BUILD_TUPLE)
+        _0 = VAR(STACK_VIEW(byte.arg).to_tuple());
+        STACK_SHRINK(byte.arg);
+        PUSH(_0);
+        DISPATCH();
     TARGET(BUILD_LIST)
         _0 = VAR(STACK_VIEW(byte.arg).to_list());
         STACK_SHRINK(byte.arg);
@@ -263,11 +268,6 @@ __NEXT_STEP:;
         _0 = POPX();    // start
         PUSH(VAR(Slice(_0, _1, _2)));
         DISPATCH();
-    TARGET(BUILD_TUPLE)
-        _0 = VAR(STACK_VIEW(byte.arg).to_tuple());
-        STACK_SHRINK(byte.arg);
-        PUSH(_0);
-        DISPATCH();
     TARGET(BUILD_STRING) {
         std::stringstream ss;
         ArgsView view = STACK_VIEW(byte.arg);
@@ -276,6 +276,40 @@ __NEXT_STEP:;
         PUSH(VAR(ss.str()));
     } DISPATCH();
     /*****************************************/
+    TARGET(BUILD_TUPLE_UNPACK) {
+        auto _lock = heap.gc_scope_lock();
+        List list;
+        _unpack_as_list(STACK_VIEW(byte.arg), list);
+        STACK_SHRINK(byte.arg);
+        _0 = VAR(Tuple(std::move(list)));
+        PUSH(_0);
+    } DISPATCH();
+    TARGET(BUILD_LIST_UNPACK) {
+        auto _lock = heap.gc_scope_lock();
+        List list;
+        _unpack_as_list(STACK_VIEW(byte.arg), list);
+        STACK_SHRINK(byte.arg);
+        _0 = VAR(std::move(list));
+        PUSH(_0);
+    } DISPATCH();
+    TARGET(BUILD_DICT_UNPACK) {
+        auto _lock = heap.gc_scope_lock();
+        Dict dict(this);
+        _unpack_as_dict(STACK_VIEW(byte.arg), dict);
+        STACK_SHRINK(byte.arg);
+        _0 = VAR(std::move(dict));
+        PUSH(_0);
+    } DISPATCH();
+    TARGET(BUILD_SET_UNPACK) {
+        auto _lock = heap.gc_scope_lock();
+        List list;
+        _unpack_as_list(STACK_VIEW(byte.arg), list);
+        STACK_SHRINK(byte.arg);
+        _0 = VAR(std::move(list));
+        _0 = call(builtins->attr(set), _0);
+        PUSH(_0);
+    } DISPATCH();
+    /*****************************************/
 #define PREDICT_INT_OP(op)                              \
     if(is_both_int(TOP(), SECOND())){                   \
         _1 = POPX();                                    \
@@ -426,9 +460,6 @@ __NEXT_STEP:;
         frame->jump_abs_break(index);
     } DISPATCH();
     /*****************************************/
-    TARGET(BEGIN_CALL)
-        PUSH(PY_BEGIN_CALL);
-        DISPATCH();
     TARGET(CALL)
         _0 = vectorcall(
             byte.arg & 0xFFFF,          // ARGC
@@ -438,6 +469,23 @@ __NEXT_STEP:;
         if(_0 == PY_OP_CALL) DISPATCH_OP_CALL();
         PUSH(_0);
         DISPATCH();
+    TARGET(CALL_TP)
+        // [callable, <self>, args: tuple, kwargs: dict]
+        _2 = POPX();
+        _1 = POPX();
+        for(PyObject* obj: _CAST(Tuple&, _1)) PUSH(obj);
+        _CAST(Dict&, _2).apply([this](PyObject* k, PyObject* v){
+            PUSH(VAR(StrName(CAST(Str&, k)).index));
+            PUSH(v);
+        });
+        _0 = vectorcall(
+            _CAST(Tuple&, _1).size(),   // ARGC
+            _CAST(Dict&, _2).size(),    // KWARGC
+            true
+        );
+        if(_0 == PY_OP_CALL) DISPATCH_OP_CALL();
+        PUSH(_0);
+        DISPATCH();
     TARGET(RETURN_VALUE)
         _0 = POPX();
         _pop_frame();
@@ -471,6 +519,9 @@ __NEXT_STEP:;
     TARGET(UNARY_NOT)
         TOP() = VAR(!py_bool(TOP()));
         DISPATCH();
+    TARGET(UNARY_STAR)
+        TOP() = VAR(StarWrapper(byte.arg, TOP()));
+        DISPATCH();
     /*****************************************/
     TARGET(GET_ITER)
         TOP() = py_iter(TOP());
@@ -528,15 +579,6 @@ __NEXT_STEP:;
         }
         PUSH(VAR(extras));
     } DISPATCH();
-    TARGET(UNPACK_UNLIMITED) {
-        auto _lock = heap.gc_scope_lock();  // lock the gc via RAII!!
-        _0 = py_iter(POPX());
-        _1 = py_next(_0);
-        while(_1 != StopIteration){
-            PUSH(_1);
-            _1 = py_next(_0);
-        }
-    } DISPATCH();
     /*****************************************/
     TARGET(BEGIN_CLASS)
         _name = StrName(byte.arg);

+ 3 - 2
src/common.h

@@ -29,7 +29,7 @@
 #include <variant>
 #include <type_traits>
 
-#define PK_VERSION				"1.0.1"
+#define PK_VERSION				"1.0.2"
 
 // debug macros
 #define DEBUG_NO_BUILTIN_MODULES    0
@@ -153,6 +153,8 @@ struct Type {
 #define FATAL_ERROR() throw std::runtime_error( __FILE__ + std::string(":") + std::to_string(__LINE__) + " FATAL_ERROR()!");
 #endif
 
+#define PK_ASSERT(x) if(!(x)) FATAL_ERROR();
+
 inline const float kInstAttrLoadFactor = 0.67f;
 inline const float kTypeAttrLoadFactor = 0.5f;
 
@@ -173,7 +175,6 @@ inline bool is_both_int(PyObject* a, PyObject* b) noexcept {
 
 // special singals, is_tagged() for them is true
 inline PyObject* const PY_NULL = (PyObject*)0b000011;		// tagged null
-inline PyObject* const PY_BEGIN_CALL = (PyObject*)0b010011;
 inline PyObject* const PY_OP_CALL = (PyObject*)0b100011;
 inline PyObject* const PY_OP_YIELD = (PyObject*)0b110011;
 

+ 24 - 8
src/compiler.h

@@ -90,7 +90,7 @@ class Compiler {
         rules[TK("*")] =        { METHOD(exprUnaryOp),   METHOD(exprBinaryOp),       PREC_FACTOR };
         rules[TK("/")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_FACTOR };
         rules[TK("//")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_FACTOR };
-        rules[TK("**")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_EXPONENT };
+        rules[TK("**")] =       { METHOD(exprUnaryOp),   METHOD(exprBinaryOp),       PREC_EXPONENT };
         rules[TK(">")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("<")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("==")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
@@ -280,7 +280,10 @@ class Compiler {
                 ctx()->s_expr.push(make_expr<NegatedExpr>(ctx()->s_expr.popx()));
                 break;
             case TK("*"):
-                ctx()->s_expr.push(make_expr<StarredExpr>(ctx()->s_expr.popx()));
+                ctx()->s_expr.push(make_expr<StarredExpr>(1, ctx()->s_expr.popx()));
+                break;
+            case TK("**"):
+                ctx()->s_expr.push(make_expr<StarredExpr>(2, ctx()->s_expr.popx()));
                 break;
             default: FATAL_ERROR();
         }
@@ -387,9 +390,15 @@ class Compiler {
                 EXPR();
                 e->kwargs.push_back({key, ctx()->s_expr.popx()});
             } else{
-                if(!e->kwargs.empty()) SyntaxError("positional argument follows keyword argument");
                 EXPR();
-                e->args.push_back(ctx()->s_expr.popx());
+                if(ctx()->s_expr.top()->star_level() == 2){
+                    // **kwargs
+                    e->kwargs.push_back({"**", ctx()->s_expr.popx()});
+                }else{
+                    // positional argument
+                    if(!e->kwargs.empty()) SyntaxError("positional argument follows keyword argument");
+                    e->args.push_back(ctx()->s_expr.popx());
+                }
             }
             match_newlines_repl();
         } while (match(TK(",")));
@@ -876,6 +885,7 @@ __SUBSCR_END:
     void _compile_f_args(FuncDecl_ decl, bool enable_type_hints){
         int state = 0;      // 0 for args, 1 for *args, 2 for k=v, 3 for **kwargs
         do {
+            if(state > 3) SyntaxError();
             if(state == 3) SyntaxError("**kwargs should be the last argument");
             match_newlines();
             if(match(TK("*"))){
@@ -894,14 +904,17 @@ __SUBSCR_END:
                     SyntaxError("duplicate argument name");
                 }
             }
-            if(decl->starred_arg!=-1 && decl->code->varnames[decl->starred_arg] == name){
-                SyntaxError("duplicate argument name");
-            }
             for(auto& kv: decl->kwargs){
                 if(decl->code->varnames[kv.key] == name){
                     SyntaxError("duplicate argument name");
                 }
             }
+            if(decl->starred_arg!=-1 && decl->code->varnames[decl->starred_arg] == name){
+                SyntaxError("duplicate argument name");
+            }
+            if(decl->starred_kwarg!=-1 && decl->code->varnames[decl->starred_kwarg] == name){
+                SyntaxError("duplicate argument name");
+            }
 
             // eat type hints
             if(enable_type_hints && match(TK(":"))) consume_type_hints();
@@ -924,7 +937,10 @@ __SUBSCR_END:
                     }
                     decl->kwargs.push_back(FuncDecl::KwArg{index, value});
                 } break;
-                case 3: SyntaxError("**kwargs is not supported yet"); break;
+                case 3:
+                    decl->starred_kwarg = index;
+                    state+=1;
+                    break;
             }
         } while (match(TK(",")));
     }

+ 8 - 0
src/dict.h

@@ -124,6 +124,14 @@ struct Dict{
         return v;
     }
 
+    template<typename __Func>
+    void apply(__Func f) const {
+        for(int i=0; i<_capacity; i++){
+            if(_items[i].first == nullptr) continue;
+            f(_items[i].first, _items[i].second);
+        }
+    }
+
     void clear(){
         memset(_items, 0, _capacity * sizeof(Item));
         _size = 0;

+ 45 - 29
src/expr.h

@@ -19,11 +19,12 @@ struct Expr{
     virtual void emit(CodeEmitContext* ctx) = 0;
     virtual std::string str() const = 0;
 
-    virtual bool is_starred() const { return false; }
     virtual bool is_literal() const { return false; }
     virtual bool is_json_object() const { return false; }
     virtual bool is_attrib() const { return false; }
     virtual bool is_compare() const { return false; }
+    virtual int star_level() const { return 0; }
+    bool is_starred() const { return star_level() > 0; }
 
     // for OP_DELETE_XXX
     [[nodiscard]] virtual bool emit_del(CodeEmitContext* ctx) { return false; }
@@ -183,24 +184,25 @@ struct NameExpr: Expr{
 };
 
 struct StarredExpr: Expr{
+    int level;
     Expr_ child;
-    StarredExpr(Expr_&& child): child(std::move(child)) {}
-    std::string str() const override { return "Starred()"; }
+    StarredExpr(int level, Expr_&& child): level(level), child(std::move(child)) {}
+    std::string str() const override { return fmt("Starred(level=", level, ")"); }
 
-    bool is_starred() const override { return true; }
+    int star_level() const override { return level; }
 
     void emit(CodeEmitContext* ctx) override {
         child->emit(ctx);
-        ctx->emit(OP_UNPACK_UNLIMITED, BC_NOARG, line);
+        ctx->emit(OP_UNARY_STAR, level, line);
     }
 
     bool emit_store(CodeEmitContext* ctx) override {
+        if(level != 1) return false;
         // simply proxy to child
         return child->emit_store(ctx);
     }
 };
 
-
 struct NotExpr: Expr{
     Expr_ child;
     NotExpr(Expr_&& child): child(std::move(child)) {}
@@ -265,16 +267,13 @@ struct LiteralExpr: Expr{
         if(std::holds_alternative<i64>(value)){
             return std::to_string(std::get<i64>(value));
         }
-
         if(std::holds_alternative<f64>(value)){
             return std::to_string(std::get<f64>(value));
         }
-
         if(std::holds_alternative<Str>(value)){
             Str s = std::get<Str>(value).escape();
             return s.str();
         }
-
         FATAL_ERROR();
     }
 
@@ -644,37 +643,54 @@ struct AttribExpr: Expr{
 struct CallExpr: Expr{
     Expr_ callable;
     std::vector<Expr_> args;
+    // **a will be interpreted as a special keyword argument: {"**": a}
     std::vector<std::pair<Str, Expr_>> kwargs;
     std::string str() const override { return "Call()"; }
 
-    bool need_unpack() const {
-        for(auto& item: args) if(item->is_starred()) return true;
-        return false;
-    }
-
     void emit(CodeEmitContext* ctx) override {
-        VM* vm = ctx->vm;
-        if(need_unpack()) ctx->emit(OP_BEGIN_CALL, BC_NOARG, line);
+        bool vargs = false;
+        bool vkwargs = false;
+        for(auto& arg: args) if(arg->is_starred()) vargs = true;
+        for(auto& item: kwargs) if(item.second->is_starred()) vkwargs = true;
+
         // if callable is a AttrExpr, we should try to use `fast_call` instead of use `boundmethod` proxy
         if(callable->is_attrib()){
             auto p = static_cast<AttribExpr*>(callable.get());
-            p->emit_method(ctx);
+            p->emit_method(ctx);    // OP_LOAD_METHOD
         }else{
             callable->emit(ctx);
             ctx->emit(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE);
         }
-        // emit args
-        for(auto& item: args) item->emit(ctx);
-        // emit kwargs
-        for(auto& item: kwargs){
-            int index = StrName::get(item.first.sv()).index;
-            ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(index)), line);
-            item.second->emit(ctx);
-        }
-        int KWARGC = (int)kwargs.size();
-        int ARGC = (int)args.size();
-        if(need_unpack()) ARGC = 0xFFFF;
-        ctx->emit(OP_CALL, (KWARGC<<16)|ARGC, line);
+
+        if(vargs || vkwargs){
+            for(auto& item: args) item->emit(ctx);
+            ctx->emit(OP_BUILD_TUPLE_UNPACK, (int)args.size(), line);
+
+            for(auto& item: kwargs){
+                item.second->emit(ctx);
+                if(item.second->is_starred()){
+                    if(item.second->star_level() != 2) FATAL_ERROR();
+                }else{
+                    // k=v
+                    int index = ctx->add_const(py_var(ctx->vm, item.first));
+                    ctx->emit(OP_LOAD_CONST, index, line);
+                    ctx->emit(OP_BUILD_TUPLE, 2, line);
+                }
+            }
+            ctx->emit(OP_BUILD_DICT_UNPACK, (int)kwargs.size(), line);
+            ctx->emit(OP_CALL_TP, BC_NOARG, line);
+        }else{
+            // vectorcall protocal
+            for(auto& item: args) item->emit(ctx);
+            for(auto& item: kwargs){
+                int index = StrName(item.first.sv()).index;
+                ctx->emit(OP_LOAD_INTEGER, index, line);
+                item.second->emit(ctx);
+            }
+            int KWARGC = (int)kwargs.size();
+            int ARGC = (int)args.size();
+            ctx->emit(OP_CALL, (KWARGC<<16)|ARGC, line);
+        }
     }
 };
 

+ 1 - 0
src/memory.h

@@ -238,6 +238,7 @@ struct MemoryPool{
     }
 };
 
+// TODO: make them thread-safe
 inline MemoryPool<64> pool64;
 inline MemoryPool<128> pool128;
 

+ 18 - 1
src/obj.h

@@ -60,8 +60,9 @@ struct FuncDecl {
     };
     CodeObject_ code;           // code object of this function
     pod_vector<int> args;       // indices in co->varnames
-    int starred_arg = -1;       // index in co->varnames, -1 if no *arg
     pod_vector<KwArg> kwargs;   // indices in co->varnames
+    int starred_arg = -1;       // index in co->varnames, -1 if no *arg
+    int starred_kwarg = -1;     // index in co->varnames, -1 if no **kwarg
     bool nested = false;        // whether this function is nested
     void _gc_mark() const;
 };
@@ -101,6 +102,12 @@ struct Range {
     i64 step = 1;
 };
 
+struct StarWrapper{
+    int level;      // either 1 or 2
+    PyObject* obj;
+    StarWrapper(int level, PyObject* obj) : level(level), obj(obj) {}
+};
+
 struct Bytes{
     std::vector<char> _data;
     bool _ok;
@@ -335,6 +342,16 @@ struct Py_<BoundMethod> final: PyObject {
     }
 };
 
+template<>
+struct Py_<StarWrapper> final: PyObject {
+    StarWrapper _value;
+    void* value() override { return &_value; }
+    Py_(Type type, StarWrapper val): PyObject(type), _value(val) {}
+    void _obj_gc_mark() override {
+        OBJ_MARK(_value.obj);
+    }
+};
+
 template<>
 struct Py_<Property> final: PyObject {
     Property _value;

+ 8 - 3
src/opcodes.h

@@ -38,13 +38,18 @@ OPCODE(DELETE_GLOBAL)
 OPCODE(DELETE_ATTR)
 OPCODE(DELETE_SUBSCR)
 /**************************/
+OPCODE(BUILD_TUPLE)
 OPCODE(BUILD_LIST)
 OPCODE(BUILD_DICT)
 OPCODE(BUILD_SET)
 OPCODE(BUILD_SLICE)
-OPCODE(BUILD_TUPLE)
 OPCODE(BUILD_STRING)
 /**************************/
+OPCODE(BUILD_TUPLE_UNPACK)
+OPCODE(BUILD_LIST_UNPACK)
+OPCODE(BUILD_DICT_UNPACK)
+OPCODE(BUILD_SET_UNPACK)
+/**************************/
 OPCODE(BINARY_TRUEDIV)
 OPCODE(BINARY_POW)
 
@@ -81,8 +86,8 @@ OPCODE(LOOP_CONTINUE)
 OPCODE(LOOP_BREAK)
 OPCODE(GOTO)
 /**************************/
-OPCODE(BEGIN_CALL)
 OPCODE(CALL)
+OPCODE(CALL_TP)
 OPCODE(RETURN_VALUE)
 OPCODE(YIELD_VALUE)
 /**************************/
@@ -92,6 +97,7 @@ OPCODE(SET_ADD)
 /**************************/
 OPCODE(UNARY_NEGATIVE)
 OPCODE(UNARY_NOT)
+OPCODE(UNARY_STAR)
 /**************************/
 OPCODE(GET_ITER)
 OPCODE(FOR_ITER)
@@ -102,7 +108,6 @@ OPCODE(IMPORT_STAR)
 /**************************/
 OPCODE(UNPACK_SEQUENCE)
 OPCODE(UNPACK_EX)
-OPCODE(UNPACK_UNLIMITED)
 /**************************/
 OPCODE(BEGIN_CLASS)
 OPCODE(END_CLASS)

+ 20 - 2
src/str.h

@@ -124,19 +124,37 @@ struct Str{
         return memcmp(data, other.data, size) != 0;
     }
 
+    bool operator==(const std::string_view other) const {
+        if(size != (int)other.size()) return false;
+        return memcmp(data, other.data(), size) == 0;
+    }
+
+    bool operator!=(const std::string_view other) const {
+        if(size != (int)other.size()) return true;
+        return memcmp(data, other.data(), size) != 0;
+    }
+
+    bool operator==(const char* p) const {
+        return *this == std::string_view(p);
+    }
+
+    bool operator!=(const char* p) const {
+        return *this != std::string_view(p);
+    }
+
     bool operator<(const Str& other) const {
         int ret = strncmp(data, other.data, std::min(size, other.size));
         if(ret != 0) return ret < 0;
         return size < other.size;
     }
 
-    bool operator<(const std::string_view& other) const {
+    bool operator<(const std::string_view other) const {
         int ret = strncmp(data, other.data(), std::min(size, (int)other.size()));
         if(ret != 0) return ret < 0;
         return size < (int)other.size();
     }
 
-    friend bool operator<(const std::string_view& other, const Str& str){
+    friend bool operator<(const std::string_view other, const Str& str){
         return str > other;
     }
 

+ 62 - 17
src/vm.h

@@ -134,7 +134,7 @@ public:
     Type tp_function, tp_native_func, tp_bound_method;
     Type tp_slice, tp_range, tp_module;
     Type tp_super, tp_exception, tp_bytes, tp_mappingproxy;
-    Type tp_dict, tp_property;
+    Type tp_dict, tp_property, tp_star_wrapper;
 
     const bool enable_os;
 
@@ -637,6 +637,8 @@ public:
 #if DEBUG_CEVAL_STEP
     void _log_s_data(const char* title = nullptr);
 #endif
+    void _unpack_as_list(ArgsView args, List& list);
+    void _unpack_as_dict(ArgsView args, Dict& dict);
     PyObject* vectorcall(int ARGC, int KWARGC=0, bool op_call=false);
     CodeObject_ compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope=false);
     PyObject* py_negate(PyObject* obj);
@@ -686,6 +688,7 @@ DEF_NATIVE_2(Bytes, tp_bytes)
 DEF_NATIVE_2(MappingProxy, tp_mappingproxy)
 DEF_NATIVE_2(Dict, tp_dict)
 DEF_NATIVE_2(Property, tp_property)
+DEF_NATIVE_2(StarWrapper, tp_star_wrapper)
 
 #undef DEF_NATIVE_2
 
@@ -1063,7 +1066,6 @@ inline void VM::_log_s_data(const char* title) {
         if(sp_bases[p] > 0) ss << " ";
         PyObject* obj = *p;
         if(obj == nullptr) ss << "(nil)";
-        else if(obj == PY_BEGIN_CALL) ss << "BEGIN_CALL";
         else if(obj == PY_NULL) ss << "NULL";
         else if(is_int(obj)) ss << CAST(i64, obj);
         else if(is_float(obj)) ss << CAST(f64, obj);
@@ -1121,6 +1123,7 @@ inline void VM::init_builtin_types(){
     tp_mappingproxy = _new_type_object("mappingproxy");
     tp_dict = _new_type_object("dict");
     tp_property = _new_type_object("property");
+    tp_star_wrapper = _new_type_object("_star_wrapper");
 
     this->None = heap._new<Dummy>(_new_type_object("NoneType"), {});
     this->Ellipsis = heap._new<Dummy>(_new_type_object("ellipsis"), {});
@@ -1154,21 +1157,47 @@ inline void VM::init_builtin_types(){
     this->_main = new_module("__main__");
 }
 
+// `heap.gc_scope_lock();` needed before calling this function
+inline void VM::_unpack_as_list(ArgsView args, List& list){
+    for(PyObject* obj: args){
+        if(is_non_tagged_type(obj, tp_star_wrapper)){
+            const StarWrapper& w = _CAST(StarWrapper&, obj);
+            // maybe this check should be done in the compile time
+            if(w.level != 1) TypeError("expected level 1 star wrapper");
+            PyObject* _0 = py_iter(w.obj);
+            PyObject* _1 = py_next(_0);
+            while(_1 != StopIteration){
+                list.push_back(_1);
+                _1 = py_next(_0);
+            }
+        }else{
+            list.push_back(obj);
+        }
+    }
+}
+
+// `heap.gc_scope_lock();` needed before calling this function
+inline void VM::_unpack_as_dict(ArgsView args, Dict& dict){
+    for(PyObject* obj: args){
+        if(is_non_tagged_type(obj, tp_star_wrapper)){
+            const StarWrapper& w = _CAST(StarWrapper&, obj);
+            // maybe this check should be done in the compile time
+            if(w.level != 2) TypeError("expected level 2 star wrapper");
+            const Dict& other = CAST(Dict&, w.obj);
+            dict.update(other);
+        }else{
+            const Tuple& t = CAST(Tuple&, obj);
+            if(t.size() != 2) TypeError("expected tuple of length 2");
+            dict.set(t[0], t[1]);
+        }
+    }
+}
+
 inline PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
-    bool is_varargs = ARGC == 0xFFFF;
-    PyObject** p0;
     PyObject** p1 = s_data._sp - KWARGC*2;
-    if(is_varargs){
-        p0 = p1 - 1;
-        while(*p0 != PY_BEGIN_CALL) p0--;
-        // [BEGIN_CALL, callable, <self>, args..., kwargs...]
-        //      ^p0                                ^p1      ^_sp
-        ARGC = p1 - (p0 + 3);
-    }else{
-        p0 = p1 - ARGC - 2 - (int)is_varargs;
-        // [callable, <self>, args..., kwargs...]
-        //      ^p0                    ^p1      ^_sp
-    }
+    PyObject** p0 = p1 - ARGC - 2;
+    // [callable, <self>, args..., kwargs...]
+    //      ^p0                    ^p1      ^_sp
     PyObject* callable = p1[-(ARGC + 2)];
     bool method_call = p1[-(ARGC + 1)] != PY_NULL;
 
@@ -1249,11 +1278,27 @@ inline PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
             if(i < args.size()) TypeError(fmt("too many arguments", " (", fn.decl->code->name, ')'));
         }
         
+        PyObject* vkwargs;
+        if(fn.decl->starred_kwarg != -1){
+            vkwargs = VAR(Dict(this));
+            buffer[fn.decl->starred_kwarg] = vkwargs;
+        }else{
+            vkwargs = nullptr;
+        }
+
         for(int i=0; i<kwargs.size(); i+=2){
             StrName key = CAST(int, kwargs[i]);
             int index = co->varnames_inv.try_get(key);
-            if(index<0) TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
-            buffer[index] = kwargs[i+1];
+            if(index < 0){
+                if(vkwargs == nullptr){
+                    TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
+                }else{
+                    Dict& dict = _CAST(Dict&, vkwargs);
+                    dict.set(VAR(key.sv()), kwargs[i+1]);
+                }
+            }else{
+                buffer[index] = kwargs[i+1];
+            }
         }
         
         if(co->is_generator){

+ 26 - 1
tests/21_functions.py

@@ -47,6 +47,9 @@ assert f(10, 1, 2, 3) == 18
 def f(a, b, *c, d=2, e=5):
     return a + b + d + e + sum(c)
 
+def g(*args, **kwargs):
+    return f(*args, **kwargs)
+
 assert f(1, 2, 3, 4) == 17
 assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) == 62
 assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, d=1, e=2) == 58
@@ -56,6 +59,13 @@ assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=1) == 58
 assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) == 217
 assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, d=1, e=2) == 213
 
+assert g(1, 2, 3, 4) == 17
+assert g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) == 62
+assert g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, d=1, e=2) == 58
+assert g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=1, d=2) == 58
+assert g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, d=1) == 61
+assert g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=1) == 58
+
 a = 1
 b = 2
 
@@ -66,4 +76,19 @@ def f():
 
 f()
 assert a == 3
-assert b == 4
+assert b == 4
+
+def g(a, b, *args, c=1, d=2, **kwargs):
+    S = a + b + c + d + sum(args)
+    return S, kwargs
+
+S, kwargs = g(1, 2, 3, 4, 5, c=4, e=5, f=6)
+# a = 1
+# b = 2
+# c = 4
+# d = 2
+# sum(args) = 3 + 4 + 5 = 12
+# S = 1 + 2 + 4 + 2 + 12 = 21
+
+assert S == 21
+assert kwargs == {'e': 5, 'f': 6}