blueloveTH 2 år sedan
förälder
incheckning
51cf6d42e8
13 ändrade filer med 522 tillägg och 515 borttagningar
  1. 118 118
      .github/workflows/main.yml
  2. 1 5
      benchmarks/recursive.py
  3. 44 103
      src/ceval.h
  4. 23 23
      src/cffi.h
  5. 2 2
      src/common.h
  6. 29 13
      src/frame.h
  7. 14 14
      src/io.h
  8. 1 1
      src/main.cpp
  9. 9 2
      src/obj.h
  10. 0 3
      src/opcodes.h
  11. 82 83
      src/pocketpy.h
  12. 2 15
      src/tuplelist.h
  13. 197 133
      src/vm.h

+ 118 - 118
.github/workflows/main.yml

@@ -1,118 +1,118 @@
-name: build
-on: [push, pull_request]
-jobs:
-  build_win:
-    runs-on: windows-latest
-    steps:
-    - uses: actions/checkout@v3
-    - uses: ilammy/msvc-dev-cmd@v1
-    - name: Compile
-      shell: bash
-      run: |
-        python3 build.py windows
-        python3 build.py windows -lib
-        mkdir -p output/windows/x86_64
-        cp pocketpy.exe output/windows/x86_64
-        cp pocketpy.dll output/windows/x86_64
-    - uses: actions/upload-artifact@v3
-      with:
-        path: output
-    - name: Unit Test
-      run: python3 scripts/run_tests.py
-    - name: Benchmark
-      run: python3 scripts/run_tests.py benchmark
-  build_linux:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v3
-    - name: Setup Clang
-      uses: egor-tensin/setup-clang@v1
-      with:
-        version: 15
-        platform: x64
-    - name: Install libc++
-      run: sudo apt install -y libc++-15-dev libc++1-15 libc++abi-15-dev libc++abi1-15
-    - name: Compile
-      run: |
-        python3 build.py linux
-        python3 build.py linux -lib
-        mkdir -p output/linux/x86_64
-        cp pocketpy output/linux/x86_64
-        cp pocketpy.so output/linux/x86_64
-    - uses: actions/upload-artifact@v3
-      with:
-        path: output
-    - name: Unit Test
-      run: python3 scripts/run_tests.py
-    - name: Benchmark
-      run: python3 scripts/run_tests.py benchmark
-  build_macos:
-      runs-on: macos-latest
-      steps:
-      - uses: actions/checkout@v3
-      - run: |
-          python3 amalgamate.py
-          cd plugins/macos/pocketpy
-          mkdir -p output/macos
-          xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
-          cp -r build/Release/pocketpy.bundle output/macos
-      - uses: actions/upload-artifact@v3
-        with:
-          path: plugins/macos/pocketpy/output
-  build_android:
-      runs-on: ubuntu-latest
-      steps:
-      - uses: actions/checkout@v3
-      - uses: subosito/flutter-action@v2
-        with:
-          flutter-version: '3.3.0'
-          channel: 'stable'
-          cache: true
-      - run: flutter --version
-      - name: Compile
-        run: |
-          python3 amalgamate.py
-          cd plugins/flutter/example
-          flutter build apk --split-debug-info=.debug-info --split-per-abi
-          cd build/app/outputs/flutter-apk
-          mkdir -p output/android/arm64-v8a
-          mkdir -p output/android/armeabi-v7a
-          mkdir -p output/android/x86_64
-          unzip -q app-arm64-v8a-release.apk -d tmp
-          mv tmp/lib/arm64-v8a/libpocketpy.so output/android/arm64-v8a/libpocketpy.so
-          rm -rf tmp
-          unzip -q app-armeabi-v7a-release.apk -d tmp
-          mv tmp/lib/armeabi-v7a/libpocketpy.so output/android/armeabi-v7a/libpocketpy.so
-          rm -rf tmp
-          unzip -q app-x86_64-release.apk -d tmp
-          mv tmp/lib/x86_64/libpocketpy.so output/android/x86_64/libpocketpy.so
-          rm -rf tmp
-      - uses: actions/upload-artifact@v3
-        with:
-          path: plugins/flutter/example/build/app/outputs/flutter-apk/output
-  build_web:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v3
-    - name: Setup emsdk
-      uses: mymindstorm/setup-emsdk@v12
-      with:
-        version: 3.1.25
-        actions-cache-folder: 'emsdk-cache'
-    - name: Verify emsdk
-      run: emcc -v
-    - name: Compile
-      run: |
-        mkdir -p output/web/lib
-        python3 build.py web
-        cp web/lib/* output/web/lib
-    - uses: crazy-max/ghaction-github-pages@v3
-      with:
-        target_branch: gh-pages
-        build_dir: web
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      if: github.event_name == 'push' && github.ref == 'refs/heads/main'
-    - uses: actions/upload-artifact@v3
-      with:
-        path: output
+# name: build
+# on: [push, pull_request]
+# jobs:
+#   build_win:
+#     runs-on: windows-latest
+#     steps:
+#     - uses: actions/checkout@v3
+#     - uses: ilammy/msvc-dev-cmd@v1
+#     - name: Compile
+#       shell: bash
+#       run: |
+#         python3 build.py windows
+#         python3 build.py windows -lib
+#         mkdir -p output/windows/x86_64
+#         cp pocketpy.exe output/windows/x86_64
+#         cp pocketpy.dll output/windows/x86_64
+#     - uses: actions/upload-artifact@v3
+#       with:
+#         path: output
+#     - name: Unit Test
+#       run: python3 scripts/run_tests.py
+#     - name: Benchmark
+#       run: python3 scripts/run_tests.py benchmark
+#   build_linux:
+#     runs-on: ubuntu-latest
+#     steps:
+#     - uses: actions/checkout@v3
+#     - name: Setup Clang
+#       uses: egor-tensin/setup-clang@v1
+#       with:
+#         version: 15
+#         platform: x64
+#     - name: Install libc++
+#       run: sudo apt install -y libc++-15-dev libc++1-15 libc++abi-15-dev libc++abi1-15
+#     - name: Compile
+#       run: |
+#         python3 build.py linux
+#         python3 build.py linux -lib
+#         mkdir -p output/linux/x86_64
+#         cp pocketpy output/linux/x86_64
+#         cp pocketpy.so output/linux/x86_64
+#     - uses: actions/upload-artifact@v3
+#       with:
+#         path: output
+#     - name: Unit Test
+#       run: python3 scripts/run_tests.py
+#     - name: Benchmark
+#       run: python3 scripts/run_tests.py benchmark
+#   build_macos:
+#       runs-on: macos-latest
+#       steps:
+#       - uses: actions/checkout@v3
+#       - run: |
+#           python3 amalgamate.py
+#           cd plugins/macos/pocketpy
+#           mkdir -p output/macos
+#           xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
+#           cp -r build/Release/pocketpy.bundle output/macos
+#       - uses: actions/upload-artifact@v3
+#         with:
+#           path: plugins/macos/pocketpy/output
+#   build_android:
+#       runs-on: ubuntu-latest
+#       steps:
+#       - uses: actions/checkout@v3
+#       - uses: subosito/flutter-action@v2
+#         with:
+#           flutter-version: '3.3.0'
+#           channel: 'stable'
+#           cache: true
+#       - run: flutter --version
+#       - name: Compile
+#         run: |
+#           python3 amalgamate.py
+#           cd plugins/flutter/example
+#           flutter build apk --split-debug-info=.debug-info --split-per-abi
+#           cd build/app/outputs/flutter-apk
+#           mkdir -p output/android/arm64-v8a
+#           mkdir -p output/android/armeabi-v7a
+#           mkdir -p output/android/x86_64
+#           unzip -q app-arm64-v8a-release.apk -d tmp
+#           mv tmp/lib/arm64-v8a/libpocketpy.so output/android/arm64-v8a/libpocketpy.so
+#           rm -rf tmp
+#           unzip -q app-armeabi-v7a-release.apk -d tmp
+#           mv tmp/lib/armeabi-v7a/libpocketpy.so output/android/armeabi-v7a/libpocketpy.so
+#           rm -rf tmp
+#           unzip -q app-x86_64-release.apk -d tmp
+#           mv tmp/lib/x86_64/libpocketpy.so output/android/x86_64/libpocketpy.so
+#           rm -rf tmp
+#       - uses: actions/upload-artifact@v3
+#         with:
+#           path: plugins/flutter/example/build/app/outputs/flutter-apk/output
+#   build_web:
+#     runs-on: ubuntu-latest
+#     steps:
+#     - uses: actions/checkout@v3
+#     - name: Setup emsdk
+#       uses: mymindstorm/setup-emsdk@v12
+#       with:
+#         version: 3.1.25
+#         actions-cache-folder: 'emsdk-cache'
+#     - name: Verify emsdk
+#       run: emcc -v
+#     - name: Compile
+#       run: |
+#         mkdir -p output/web/lib
+#         python3 build.py web
+#         cp web/lib/* output/web/lib
+#     - uses: crazy-max/ghaction-github-pages@v3
+#       with:
+#         target_branch: gh-pages
+#         build_dir: web
+#       env:
+#         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+#       if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+#     - uses: actions/upload-artifact@v3
+#       with:
+#         path: output

+ 1 - 5
benchmarks/recursive.py

@@ -1,9 +1,5 @@
-import sys
-
-sys.setrecursionlimit(5000)
-
 def f(n):
-    if n == 4000:
+    if n == 900:
         return -1
     return f(n + 1)
 

+ 44 - 103
src/ceval.h

@@ -25,18 +25,6 @@ inline PyObject* VM::_run_top_frame(){
  * `Args` containing strong references is safe if it is passed to `call` or `fast_call`
  */
 {
-
-/* Stack manipulation macros */
-// https://github.com/python/cpython/blob/3.9/Python/ceval.c#L1123
-#define TOP()             (s_data.top())
-#define SECOND()          (s_data.second())
-#define PEEK(n)           (s_data.peek(n))
-#define STACK_SHRINK(n)   (s_data.shrink(n))
-#define PUSH(v)           (s_data.push(v))
-#define POP()             (s_data.pop())
-#define POPX()            (s_data.popx())
-#define STACK_VIEW(n)     (s_data.view(n))
-
 #define DISPATCH_OP_CALL() { frame = top_frame(); goto __NEXT_FRAME; }
 __NEXT_FRAME:
     Bytecode byte = frame->next_bytecode();
@@ -157,10 +145,7 @@ __NEXT_STEP:;
         PUSH(self);
     } DISPATCH();
     TARGET(LOAD_SUBSCR) {
-        Args args(2);
-        args[1] = POPX();    // b
-        args[0] = TOP();     // a
-        TOP() = fast_call(__getitem__, std::move(args));
+        TOP() = fast_call_method(SECOND(), __getitem__, 2);
     } DISPATCH();
     TARGET(STORE_FAST)
         frame->_locals[byte.arg] = POPX();
@@ -186,13 +171,12 @@ __NEXT_STEP:;
         setattr(a, name, val);
         STACK_SHRINK(2);
     } DISPATCH();
-    TARGET(STORE_SUBSCR) {
-        Args args(3);
-        args[1] = POPX();    // b
-        args[0] = POPX();    // a
-        args[2] = POPX();    // val
-        fast_call(__setitem__, std::move(args));
-    } DISPATCH();
+    TARGET(STORE_SUBSCR)
+        // val a b -> a b val
+        std::swap(SECOND(), THIRD());
+        std::swap(TOP(), SECOND());
+        fast_call_method(THIRD(), __setitem__, 3);
+        DISPATCH();
     TARGET(DELETE_FAST) {
         PyObject* val = frame->_locals[byte.arg];
         if(val == nullptr) vm->NameError(co->varnames[byte.arg]);
@@ -223,11 +207,9 @@ __NEXT_STEP:;
         if(!a->attr().contains(name)) AttributeError(a, name);
         a->attr().erase(name);
     } DISPATCH();
-    TARGET(DELETE_SUBSCR) {
-        PyObject* b = POPX();
-        PyObject* a = POPX();
-        fast_call(__delitem__, Args{a, b});
-    } DISPATCH();
+    TARGET(DELETE_SUBSCR)
+        fast_call_method(SECOND(), __delitem__, 2);
+        DISPATCH();
     /*****************************************/
     TARGET(BUILD_LIST) {
         PyObject* obj = VAR(STACK_VIEW(byte.arg).to_list());
@@ -236,13 +218,13 @@ __NEXT_STEP:;
     } DISPATCH();
     TARGET(BUILD_DICT) {
         PyObject* t = VAR(STACK_VIEW(byte.arg).to_tuple());
-        PyObject* obj = call(builtins->attr(m_dict), Args{t});
+        PyObject* obj = call_(builtins->attr(m_dict), t);
         STACK_SHRINK(byte.arg);
         PUSH(obj);
     } DISPATCH();
     TARGET(BUILD_SET) {
         PyObject* t = VAR(STACK_VIEW(byte.arg).to_tuple());
-        PyObject* obj = call(builtins->attr(m_set), Args{t});
+        PyObject* obj = call_(builtins->attr(m_set), t);
         STACK_SHRINK(byte.arg);
         PUSH(obj);
     } DISPATCH();
@@ -269,24 +251,18 @@ __NEXT_STEP:;
         PUSH(VAR(ss.str()));
     } DISPATCH();
     /*****************************************/
-    TARGET(BINARY_OP) {
-        Args args(2);
-        args[1] = POPX();    // lhs
-        args[0] = TOP();     // rhs
-        TOP() = fast_call(BINARY_SPECIAL_METHODS[byte.arg], std::move(args));
-    } DISPATCH();
+    TARGET(BINARY_OP)
+        TOP() = fast_call_method(SECOND(), BINARY_SPECIAL_METHODS[byte.arg], 2);
+        DISPATCH();
 
-#define INT_BINARY_OP(op, func) \
-        if(is_both_int(TOP(), SECOND())){               \
-            i64 b = _CAST(i64, TOP());                  \
-            i64 a = _CAST(i64, SECOND());               \
-            POP();                                      \
-            TOP() = VAR(a op b);                        \
-        }else{                                          \
-            Args args(2);                               \
-            args[1] = POPX();                    \
-            args[0] = TOP();                            \
-            TOP() = fast_call(func, std::move(args));   \
+#define INT_BINARY_OP(op, func)                             \
+        if(is_both_int(TOP(), SECOND())){                   \
+            i64 b = _CAST(i64, TOP());                      \
+            i64 a = _CAST(i64, SECOND());                   \
+            POP();                                          \
+            TOP() = VAR(a op b);                            \
+        }else{                                              \
+            TOP() = fast_call_method(SECOND(), func, 2);    \
         }
 
     TARGET(BINARY_ADD)
@@ -346,10 +322,9 @@ __NEXT_STEP:;
         TOP() = VAR(ret_c);
     } DISPATCH();
     TARGET(CONTAINS_OP) {
-        Args args(2);
-        args[0] = POPX();
-        args[1] = TOP();
-        PyObject* ret = fast_call(__contains__, std::move(args));
+        // a in b -> b __contains__ a
+        std::swap(TOP(), SECOND());
+        PyObject* ret = fast_call_method(SECOND(), __contains__, 2);
         bool ret_c = CAST(bool, ret);
         if(byte.arg == 1) ret_c = !ret_c;
         TOP() = VAR(ret_c);
@@ -382,66 +357,28 @@ __NEXT_STEP:;
         frame->jump_abs_break(index);
     } DISPATCH();
     /*****************************************/
-    TARGET(CALL)
-    TARGET(CALL_UNPACK) {
-        int ARGC = byte.arg;
-        PyObject* callable = PEEK(ARGC+2);
-        bool method_call = PEEK(ARGC+1) != _py_null;
-
-        // fast path
-        if(byte.op==OP_CALL && is_type(callable, tp_function)){
-            PyObject* ret = _py_call(callable, STACK_VIEW(ARGC + int(method_call)), {});
-            STACK_SHRINK(ARGC + 2);
-            // TODO: _sp_base is incorrect
-            top_frame()->_sp_base = s_data._sp;
-            if(ret == nullptr) { DISPATCH_OP_CALL(); }
-            else PUSH(ret);      // a generator
-            DISPATCH();
-        }
-        Args args = STACK_VIEW(ARGC + int(method_call)).to_tuple();
-        if(byte.op == OP_CALL_UNPACK) unpack_args(args);
-        PyObject* ret = call(callable, std::move(args), no_arg(), true);
-        STACK_SHRINK(ARGC + 2);
-        if(ret == _py_op_call) { DISPATCH_OP_CALL(); }
-        PUSH(ret);
-    } DISPATCH();
-    TARGET(CALL_KWARGS)
-    TARGET(CALL_KWARGS_UNPACK) {
-        // TODO: poor performance, refactor needed
+    TARGET(CALL) {
         int ARGC = byte.arg & 0xFFFF;
         int KWARGC = (byte.arg >> 16) & 0xFFFF;
-        Args kwargs = STACK_VIEW(KWARGC*2).to_tuple();
-        STACK_SHRINK(KWARGC*2);
-
-        bool method_call = PEEK(ARGC+1) != _py_null;
-        if(method_call) ARGC++;         // add self into args
-        Args args = STACK_VIEW(ARGC).to_tuple();
-        STACK_SHRINK(ARGC);
-        if(!method_call) POP();
-
-        if(byte.op == OP_CALL_KWARGS_UNPACK) unpack_args(args);
-        PyObject* callable = POPX();
-        PyObject* ret = call(callable, std::move(args), kwargs, true);
+        PyObject* ret = _vectorcall(ARGC, KWARGC, true);
         if(ret == _py_op_call) { DISPATCH_OP_CALL(); }
         PUSH(ret);
-    } DISPATCH();
+        DISPATCH();
+    }
     TARGET(RETURN_VALUE) {
-#if DEBUG_EXTRA_CHECK
-        if(frame->stack_size() != 1) FATAL_ERROR();
-#endif
+        PyObject* __ret = POPX();
+        // cleanup the stack on return
+        callstack.pop();
+        s_data.reset(frame->_sp_base);
         if(frame.index == base_id){       // [ frameBase<- ]
-            callstack.pop();
-            return POPX();
+            return __ret;
         }else{
-            callstack.pop();
             frame = top_frame();
+            PUSH(__ret);
             goto __NEXT_FRAME;
         }
     }
     TARGET(YIELD_VALUE)
-#if DEBUG_EXTRA_CHECK
-        if(frame->stack_size() != 1) FATAL_ERROR();
-#endif
         return _py_op_yield;
     /*****************************************/
     TARGET(LIST_APPEND) {
@@ -451,12 +388,16 @@ __NEXT_STEP:;
     } DISPATCH();
     TARGET(DICT_ADD) {
         PyObject* kv = POPX();
-        Tuple& t = CAST(Tuple& ,kv);
-        fast_call(__setitem__, Args{SECOND(), t[0], t[1]});
+        Tuple& t = CAST(Tuple&, kv);
+        PyObject* self;
+        PyObject* callable = get_unbound_method(SECOND(), __setitem__, &self);
+        call_method(self, callable, t[0], t[1]);
     } DISPATCH();
     TARGET(SET_ADD) {
-        PyObject* obj = POPX();
-        fast_call(m_add, Args{SECOND(), obj});
+        PyObject* val = POPX();
+        PyObject* self;
+        PyObject* callable = get_unbound_method(SECOND(), m_add, &self);
+        call_method(self, callable, val);
     } DISPATCH();
     /*****************************************/
     TARGET(UNARY_NEGATIVE)

+ 23 - 23
src/cffi.h

@@ -12,7 +12,7 @@ struct NativeProxyFunc {
     _Fp func;
     NativeProxyFunc(_Fp func) : func(func) {}
 
-    PyObject* operator()(VM* vm, Args& args) {
+    PyObject* operator()(VM* vm, ArgsView args) {
         if (args.size() != N) {
             vm->TypeError("expected " + std::to_string(N) + " arguments, but got " + std::to_string(args.size()));
         }
@@ -20,13 +20,13 @@ struct NativeProxyFunc {
     }
 
     template<typename __Ret, size_t... Is>
-    std::enable_if_t<std::is_void_v<__Ret>, PyObject*> call(VM* vm, Args& args, std::index_sequence<Is...>) {
+    std::enable_if_t<std::is_void_v<__Ret>, PyObject*> call(VM* vm, ArgsView args, std::index_sequence<Is...>) {
         func(py_cast<Params>(vm, args[Is])...);
         return vm->None;
     }
 
     template<typename __Ret, size_t... Is>
-    std::enable_if_t<!std::is_void_v<__Ret>, PyObject*> call(VM* vm, Args& args, std::index_sequence<Is...>) {
+    std::enable_if_t<!std::is_void_v<__Ret>, PyObject*> call(VM* vm, ArgsView args, std::index_sequence<Is...>) {
         __Ret ret = func(py_cast<Params>(vm, args[Is])...);
         return VAR(std::move(ret));
     }
@@ -39,7 +39,7 @@ struct NativeProxyMethod {
     _Fp func;
     NativeProxyMethod(_Fp func) : func(func) {}
 
-    PyObject* operator()(VM* vm, Args& args) {
+    PyObject* operator()(VM* vm, ArgsView args) {
         int actual_size = args.size() - 1;
         if (actual_size != N) {
             vm->TypeError("expected " + std::to_string(N) + " arguments, but got " + std::to_string(actual_size));
@@ -48,14 +48,14 @@ struct NativeProxyMethod {
     }
 
     template<typename __Ret, size_t... Is>
-    std::enable_if_t<std::is_void_v<__Ret>, PyObject*> call(VM* vm, Args& args, std::index_sequence<Is...>) {
+    std::enable_if_t<std::is_void_v<__Ret>, PyObject*> call(VM* vm, ArgsView args, std::index_sequence<Is...>) {
         T& self = py_cast<T&>(vm, args[0]);
         (self.*func)(py_cast<Params>(vm, args[Is+1])...);
         return vm->None;
     }
 
     template<typename __Ret, size_t... Is>
-    std::enable_if_t<!std::is_void_v<__Ret>, PyObject*> call(VM* vm, Args& args, std::index_sequence<Is...>) {
+    std::enable_if_t<!std::is_void_v<__Ret>, PyObject*> call(VM* vm, ArgsView args, std::index_sequence<Is...>) {
         T& self = py_cast<T&>(vm, args[0]);
         __Ret ret = (self.*func)(py_cast<Params>(vm, args[Is+1])...);
         return VAR(std::move(ret));
@@ -201,7 +201,7 @@ struct Pointer{
     static void _register(VM* vm, PyObject* mod, PyObject* type){
         vm->bind_static_method<-1>(type, "__new__", CPP_NOT_IMPLEMENTED());
 
-        vm->bind_method<0>(type, "__repr__", [](VM* vm, Args& args) {
+        vm->bind_method<0>(type, "__repr__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             std::stringstream ss;
             ss << "<" << self.ctype->name;
@@ -210,53 +210,53 @@ struct Pointer{
             return VAR(ss.str());
         });
 
-        vm->bind_method<1>(type, "__add__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__add__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             return VAR_T(Pointer, self + CAST(i64, args[1]));
         });
 
-        vm->bind_method<1>(type, "__sub__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__sub__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             return VAR_T(Pointer, self - CAST(i64, args[1]));
         });
 
-        vm->bind_method<1>(type, "__eq__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__eq__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             Pointer& other = CAST(Pointer&, args[1]);
             return VAR(self.ptr == other.ptr);
         });
 
-        vm->bind_method<1>(type, "__ne__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__ne__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             Pointer& other = CAST(Pointer&, args[1]);
             return VAR(self.ptr != other.ptr);
         });
 
-        vm->bind_method<1>(type, "__getitem__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__getitem__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             i64 index = CAST(i64, args[1]);
             return (self+index).get(vm);
         });
 
-        vm->bind_method<2>(type, "__setitem__", [](VM* vm, Args& args) {
+        vm->bind_method<2>(type, "__setitem__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             i64 index = CAST(i64, args[1]);
             (self+index).set(vm, args[2]);
             return vm->None;
         });
 
-        vm->bind_method<1>(type, "__getattr__", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "__getattr__", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             const Str& name = CAST(Str&, args[1]);
             return VAR_T(Pointer, self._to(vm, name));
         });
 
-        vm->bind_method<0>(type, "get", [](VM* vm, Args& args) {
+        vm->bind_method<0>(type, "get", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             return self.get(vm);
         });
 
-        vm->bind_method<1>(type, "set", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "set", [](VM* vm, ArgsView args) {
             Pointer& self = CAST(Pointer&, args[0]);
             self.set(vm, args[1]);
             return vm->None;
@@ -336,7 +336,7 @@ struct CType{
     CType(const TypeInfo* type) : type(type) {}
 
     static void _register(VM* vm, PyObject* mod, PyObject* type){
-        vm->bind_static_method<1>(type, "__new__", [](VM* vm, Args& args) {
+        vm->bind_static_method<1>(type, "__new__", [](VM* vm, ArgsView args) {
             const Str& name = CAST(Str&, args[0]);
             const TypeInfo* type = _type_db.get(name);
             if(type == nullptr) vm->TypeError("unknown type: " + name.escape());
@@ -352,18 +352,18 @@ inline void add_module_c(VM* vm){
 
     vm->setattr(mod, "nullptr", VAR_T(Pointer));
 
-    vm->bind_func<1>(mod, "malloc", [](VM* vm, Args& args) {
+    vm->bind_func<1>(mod, "malloc", [](VM* vm, ArgsView args) {
         i64 size = CAST(i64, args[0]);
         return VAR_T(Pointer, _type_db.get<void>(), (char*)malloc(size));
     });
 
-    vm->bind_func<1>(mod, "free", [](VM* vm, Args& args) {
+    vm->bind_func<1>(mod, "free", [](VM* vm, ArgsView args) {
         Pointer& self = CAST(Pointer&, args[0]);
         free(self.ptr);
         return vm->None;
     });
 
-    vm->bind_func<3>(mod, "memcpy", [](VM* vm, Args& args) {
+    vm->bind_func<3>(mod, "memcpy", [](VM* vm, ArgsView args) {
         Pointer& dst = CAST(Pointer&, args[0]);
         Pointer& src = CAST(Pointer&, args[1]);
         i64 size = CAST(i64, args[2]);
@@ -371,7 +371,7 @@ inline void add_module_c(VM* vm){
         return vm->None;
     });
 
-    vm->bind_func<2>(mod, "cast", [](VM* vm, Args& args) {
+    vm->bind_func<2>(mod, "cast", [](VM* vm, ArgsView args) {
         Pointer& self = CAST(Pointer&, args[0]);
         const Str& name = CAST(Str&, args[1]);
         int level = 0;
@@ -386,7 +386,7 @@ inline void add_module_c(VM* vm){
         return VAR_T(Pointer, type, level, self.ptr);
     });
 
-    vm->bind_func<1>(mod, "sizeof", [](VM* vm, Args& args) {
+    vm->bind_func<1>(mod, "sizeof", [](VM* vm, ArgsView args) {
         const Str& name = CAST(Str&, args[0]);
         if(name.index("*") != -1) return VAR(sizeof(void*));
         const TypeInfo* type = _type_db.get(name);
@@ -394,7 +394,7 @@ inline void add_module_c(VM* vm){
         return VAR(type->size);
     });
 
-    vm->bind_func<3>(mod, "memset", [](VM* vm, Args& args) {
+    vm->bind_func<3>(mod, "memset", [](VM* vm, ArgsView args) {
         Pointer& dst = CAST(Pointer&, args[0]);
         i64 val = CAST(i64, args[1]);
         i64 size = CAST(i64, args[2]);

+ 2 - 2
src/common.h

@@ -88,8 +88,8 @@ struct Type {
 };
 
 #define THREAD_LOCAL	// thread_local
-#define CPP_LAMBDA(x) ([](VM* vm, Args& args) { return x; })
-#define CPP_NOT_IMPLEMENTED() ([](VM* vm, Args& args) { vm->NotImplementedError(); return vm->None; })
+#define CPP_LAMBDA(x) ([](VM* vm, ArgsView args) { return x; })
+#define CPP_NOT_IMPLEMENTED() ([](VM* vm, ArgsView args) { vm->NotImplementedError(); return vm->None; })
 
 #ifdef POCKETPY_H
 #define FATAL_ERROR() throw std::runtime_error( "L" + std::to_string(__LINE__) + " FATAL_ERROR()!");

+ 29 - 13
src/frame.h

@@ -48,14 +48,18 @@ struct FastLocals{
         a[index] = nullptr;
     }
 
-    bool try_set(StrName name, PyObject* value){
-        if(!is_valid()) return false;
+    bool _try_set(StrName name, PyObject* value){
         int index = varnames_inv->try_get(name);
         if(index == -1) return false;
         a[index] = value;
         return true;
     }
 
+    bool try_set(StrName name, PyObject* value){
+        if(!is_valid()) return false;
+        return _try_set(name, value);
+    }
+
     FastLocals(const FastLocals& other){
         varnames_inv = other.varnames_inv;
         a = other.a;
@@ -126,8 +130,9 @@ template<> inline void gc_mark<Function>(Function& t){
 }
 
 struct ValueStack {
-    static const int MAX_SIZE = 32768;
-    PyObject* _begin[MAX_SIZE];
+    static const size_t MAX_SIZE = 32768;
+    // We allocate 512 more bytes to keep `_sp` valid when `is_overflow() == true`.
+    PyObject* _begin[MAX_SIZE + 512];
     PyObject** _sp;
 
     ValueStack(): _sp(_begin) {}
@@ -136,6 +141,8 @@ struct ValueStack {
     PyObject* top() const { return _sp[-1]; }
     PyObject*& second(){ return _sp[-2]; }
     PyObject* second() const { return _sp[-2]; }
+    PyObject*& third(){ return _sp[-3]; }
+    PyObject* third() const { return _sp[-3]; }
     PyObject*& peek(int n){ return _sp[-n]; }
     PyObject* peek(int n) const { return _sp[-n]; }
     void push(PyObject* v){ *_sp++ = v; }
@@ -143,13 +150,22 @@ struct ValueStack {
     PyObject* popx(){ return *--_sp; }
     ArgsView view(int n){ return ArgsView(_sp-n, _sp); }
     void shrink(int n){ _sp -= n; }
-    // int size() const { return _sp - _begin; }
-    // bool empty() const { return _sp == _begin; }
+    int size() const { return _sp - _begin; }
+    bool empty() const { return _sp == _begin; }
     PyObject** begin() { return _begin; }
     PyObject** end() { return _sp; }
     void reset(PyObject** sp) { _sp = sp; }
-    // void resize(int n) { _sp = _begin + n; }
+    void clear() { _sp = _begin; }
     bool is_overflow() const { return _sp >= _begin + MAX_SIZE; }
+    
+    void dup_top_n(int n) {
+        // [a, b, c]
+        // ^p0     ^p1
+        PyObject** p0 = _sp - n;
+        PyObject** p1 = _sp;
+        memcpy(p1, p0, n * sizeof(void*));
+        _sp += n;
+    }
 
     ValueStack(const ValueStack&) = delete;
     ValueStack(ValueStack&&) = delete;
@@ -170,14 +186,14 @@ struct Frame {
 
     NameDict& f_globals() noexcept { return _module->attr(); }
 
-    Frame(ValueStack* _s, const CodeObject* co, PyObject* _module, FastLocals&& _locals, const FastLocals& _closure)
-            : _s(_s), _sp_base(_s->_sp), co(co), _module(_module), _locals(std::move(_locals)), _closure(_closure) { }
+    Frame(ValueStack* _s, PyObject** _sp_base, const CodeObject* co, PyObject* _module, FastLocals&& _locals, const FastLocals& _closure)
+            : _s(_s), _sp_base(_sp_base), co(co), _module(_module), _locals(std::move(_locals)), _closure(_closure) { }
 
-    Frame(ValueStack* _s, const CodeObject* co, PyObject* _module, const FastLocals& _locals, const FastLocals& _closure)
-            : _s(_s), _sp_base(_s->_sp), co(co), _module(_module), _locals(_locals), _closure(_closure) { }
+    Frame(ValueStack* _s, PyObject** _sp_base, const CodeObject* co, PyObject* _module, const FastLocals& _locals, const FastLocals& _closure)
+            : _s(_s), _sp_base(_sp_base), co(co), _module(_module), _locals(_locals), _closure(_closure) { }
 
-    Frame(ValueStack* _s, const CodeObject_& co, PyObject* _module)
-            : _s(_s), _sp_base(_s->_sp), co(co.get()), _module(_module), _locals(), _closure() { }
+    Frame(ValueStack* _s, PyObject** _sp_base, const CodeObject_& co, PyObject* _module)
+            : _s(_s), _sp_base(_sp_base), co(co.get()), _module(_module), _locals(), _closure() { }
 
     Frame(const Frame& other) = delete;
     Frame& operator=(const Frame& other) = delete;

+ 14 - 14
src/io.h

@@ -43,32 +43,32 @@ struct FileIO {
     }
 
     static void _register(VM* vm, PyObject* mod, PyObject* type){
-        vm->bind_static_method<2>(type, "__new__", [](VM* vm, Args& args){
+        vm->bind_static_method<2>(type, "__new__", [](VM* vm, ArgsView args){
             return VAR_T(FileIO, 
                 vm, CAST(Str, args[0]), CAST(Str, args[1])
             );
         });
 
-        vm->bind_method<0>(type, "read", [](VM* vm, Args& args){
+        vm->bind_method<0>(type, "read", [](VM* vm, ArgsView args){
             FileIO& io = CAST(FileIO&, args[0]);
             std::string buffer;
             io._fs >> buffer;
             return VAR(buffer);
         });
 
-        vm->bind_method<1>(type, "write", [](VM* vm, Args& args){
+        vm->bind_method<1>(type, "write", [](VM* vm, ArgsView args){
             FileIO& io = CAST(FileIO&, args[0]);
             io._fs << CAST(Str&, args[1]);
             return vm->None;
         });
 
-        vm->bind_method<0>(type, "close", [](VM* vm, Args& args){
+        vm->bind_method<0>(type, "close", [](VM* vm, ArgsView args){
             FileIO& io = CAST(FileIO&, args[0]);
             io._fs.close();
             return vm->None;
         });
 
-        vm->bind_method<0>(type, "__exit__", [](VM* vm, Args& args){
+        vm->bind_method<0>(type, "__exit__", [](VM* vm, ArgsView args){
             FileIO& io = CAST(FileIO&, args[0]);
             io._fs.close();
             return vm->None;
@@ -81,7 +81,7 @@ struct FileIO {
 inline void add_module_io(VM* vm){
     PyObject* mod = vm->new_module("io");
     PyObject* type = FileIO::register_class(vm, mod);
-    vm->bind_builtin_func<2>("open", [type](VM* vm, const Args& args){
+    vm->bind_builtin_func<2>("open", [type](VM* vm, ArgsView args){
         return vm->call(type, args);
     });
 }
@@ -89,17 +89,17 @@ inline void add_module_io(VM* vm){
 inline void add_module_os(VM* vm){
     PyObject* mod = vm->new_module("os");
     // Working directory is shared by all VMs!!
-    vm->bind_func<0>(mod, "getcwd", [](VM* vm, const Args& args){
+    vm->bind_func<0>(mod, "getcwd", [](VM* vm, ArgsView args){
         return VAR(std::filesystem::current_path().string());
     });
 
-    vm->bind_func<1>(mod, "chdir", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "chdir", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         std::filesystem::current_path(path);
         return vm->None;
     });
 
-    vm->bind_func<1>(mod, "listdir", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "listdir", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         std::filesystem::directory_iterator di;
         try{
@@ -115,28 +115,28 @@ inline void add_module_os(VM* vm){
         return VAR(ret);
     });
 
-    vm->bind_func<1>(mod, "remove", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "remove", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         bool ok = std::filesystem::remove(path);
         if(!ok) vm->IOError("operation failed");
         return vm->None;
     });
 
-    vm->bind_func<1>(mod, "mkdir", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "mkdir", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         bool ok = std::filesystem::create_directory(path);
         if(!ok) vm->IOError("operation failed");
         return vm->None;
     });
 
-    vm->bind_func<1>(mod, "rmdir", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "rmdir", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         bool ok = std::filesystem::remove(path);
         if(!ok) vm->IOError("operation failed");
         return vm->None;
     });
 
-    vm->bind_func<-1>(mod, "path_join", [](VM* vm, const Args& args){
+    vm->bind_func<-1>(mod, "path_join", [](VM* vm, ArgsView args){
         std::filesystem::path path;
         for(int i=0; i<args.size(); i++){
             path /= CAST(Str&, args[i]).c_str();
@@ -144,7 +144,7 @@ inline void add_module_os(VM* vm){
         return VAR(path.string());
     });
 
-    vm->bind_func<1>(mod, "path_exists", [](VM* vm, const Args& args){
+    vm->bind_func<1>(mod, "path_exists", [](VM* vm, ArgsView args){
         std::filesystem::path path(CAST(Str&, args[0]).c_str());
         bool exists = std::filesystem::exists(path);
         return VAR(exists);

+ 1 - 1
src/main.cpp

@@ -7,7 +7,7 @@
 
 int main(int argc, char** argv){
     pkpy::VM* vm = pkpy_new_vm(true);
-    vm->bind_builtin_func<0>("input", [](pkpy::VM* vm, pkpy::Args& args){
+    vm->bind_builtin_func<0>("input", [](pkpy::VM* vm, pkpy::ArgsView args){
         return VAR(pkpy::getline());
     });
     if(argc == 1){

+ 9 - 2
src/obj.h

@@ -11,7 +11,7 @@ struct Frame;
 struct Function;
 class VM;
 
-typedef std::function<PyObject*(VM*, Args&)> NativeFuncRaw;
+typedef std::function<PyObject*(VM*, ArgsView)> NativeFuncRaw;
 typedef shared_ptr<CodeObject> CodeObject_;
 
 struct NativeFunc {
@@ -20,7 +20,7 @@ struct NativeFunc {
     bool method;
     
     NativeFunc(NativeFuncRaw f, int argc, bool method) : f(f), argc(argc), method(method) {}
-    PyObject* operator()(VM* vm, Args& args) const;
+    PyObject* operator()(VM* vm, ArgsView args) const;
 };
 
 struct FuncDecl {
@@ -167,6 +167,13 @@ inline bool is_type(PyObject* obj, Type type) {
     }
 }
 
+inline bool is_non_tagged_type(PyObject* obj, Type type) {
+#if DEBUG_EXTRA_CHECK
+    if(obj == nullptr) throw std::runtime_error("is_non_tagged_type() called with nullptr");
+#endif
+    return !is_tagged(obj) && obj->type == type;
+}
+
 #define PY_CLASS(T, mod, name) \
     static Type _type(VM* vm) {  \
         static const StrName __x0(#mod);      \

+ 0 - 3
src/opcodes.h

@@ -77,9 +77,6 @@ OPCODE(LOOP_BREAK)
 OPCODE(GOTO)
 /**************************/
 OPCODE(CALL)
-OPCODE(CALL_UNPACK)
-OPCODE(CALL_KWARGS)
-OPCODE(CALL_KWARGS_UNPACK)
 OPCODE(RETURN_VALUE)
 OPCODE(YIELD_VALUE)
 /**************************/

+ 82 - 83
src/pocketpy.h

@@ -25,7 +25,7 @@ inline CodeObject_ VM::compile(Str source, Str filename, CompileMode mode, bool
 }
 
 #define BIND_NUM_ARITH_OPT(name, op)                                                                    \
-    _vm->_bind_methods<1>({"int","float"}, #name, [](VM* vm, Args& args){                               \
+    _vm->_bind_methods<1>({"int","float"}, #name, [](VM* vm, ArgsView args){                               \
         if(is_both_int(args[0], args[1])){                                                              \
             return VAR(_CAST(i64, args[0]) op _CAST(i64, args[1]));                                     \
         }else{                                                                                          \
@@ -34,7 +34,7 @@ inline CodeObject_ VM::compile(Str source, Str filename, CompileMode mode, bool
     });
 
 #define BIND_NUM_LOGICAL_OPT(name, op, is_eq)                                                           \
-    _vm->_bind_methods<1>({"int","float"}, #name, [](VM* vm, Args& args){                               \
+    _vm->_bind_methods<1>({"int","float"}, #name, [](VM* vm, ArgsView args){                               \
         if(is_both_int(args[0], args[1]))                                                               \
             return VAR(_CAST(i64, args[0]) op _CAST(i64, args[1]));                                     \
         if(!is_both_int_or_float(args[0], args[1])){                                                    \
@@ -60,12 +60,12 @@ inline void init_builtins(VM* _vm) {
 #undef BIND_NUM_ARITH_OPT
 #undef BIND_NUM_LOGICAL_OPT
 
-    _vm->bind_builtin_func<1>("__sys_stdout_write", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("__sys_stdout_write", [](VM* vm, ArgsView args) {
         (*vm->_stdout) << CAST(Str&, args[0]);
         return vm->None;
     });
 
-    _vm->bind_builtin_func<2>("super", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<2>("super", [](VM* vm, ArgsView args) {
         vm->check_type(args[0], vm->tp_type);
         Type type = OBJ_GET(Type, args[0]);
         if(!vm->isinstance(args[1], type)){
@@ -77,39 +77,39 @@ inline void init_builtins(VM* _vm) {
         return vm->heap.gcnew(vm->tp_super, Super(args[1], base));
     });
 
-    _vm->bind_builtin_func<2>("isinstance", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<2>("isinstance", [](VM* vm, ArgsView args) {
         vm->check_type(args[1], vm->tp_type);
         Type type = OBJ_GET(Type, args[1]);
         return VAR(vm->isinstance(args[0], type));
     });
 
-    _vm->bind_builtin_func<1>("id", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("id", [](VM* vm, ArgsView args) {
         PyObject* obj = args[0];
         if(is_tagged(obj)) return VAR((i64)0);
         return VAR(BITS(obj));
     });
 
-    _vm->bind_builtin_func<2>("divmod", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<2>("divmod", [](VM* vm, ArgsView args) {
         i64 lhs = CAST(i64, args[0]);
         i64 rhs = CAST(i64, args[1]);
         if(rhs == 0) vm->ZeroDivisionError();
         return VAR(Tuple({VAR(lhs/rhs), VAR(lhs%rhs)}));
     });
 
-    _vm->bind_builtin_func<1>("eval", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("eval", [](VM* vm, ArgsView args) {
         CodeObject_ code = vm->compile(CAST(Str&, args[0]), "<eval>", EVAL_MODE, true);
         FrameId frame = vm->top_frame();
         return vm->_exec(code.get(), frame->_module, frame->_locals, nullptr);
     });
 
-    _vm->bind_builtin_func<1>("exec", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("exec", [](VM* vm, ArgsView args) {
         CodeObject_ code = vm->compile(CAST(Str&, args[0]), "<exec>", EXEC_MODE, true);
         FrameId frame = vm->top_frame();
         vm->_exec(code.get(), frame->_module, frame->_locals, nullptr);
         return vm->None;
     });
 
-    _vm->bind_builtin_func<-1>("exit", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<-1>("exit", [](VM* vm, ArgsView args) {
         if(args.size() == 0) std::exit(0);
         else if(args.size() == 1) std::exit(CAST(int, args[0]));
         else vm->TypeError("exit() takes at most 1 argument");
@@ -117,51 +117,55 @@ inline void init_builtins(VM* _vm) {
     });
 
     _vm->bind_builtin_func<1>("repr", CPP_LAMBDA(vm->asRepr(args[0])));
-    _vm->bind_builtin_func<1>("len", CPP_LAMBDA(vm->fast_call(__len__, Args{args[0]})));
+    _vm->bind_builtin_func<1>("len", [](VM* vm, ArgsView args){
+        PyObject* self;
+        PyObject* len_f = vm->get_unbound_method(args[0], __len__, &self);
+        return vm->call_method(self, len_f);
+    });
 
-    _vm->bind_builtin_func<1>("hash", [](VM* vm, Args& args){
+    _vm->bind_builtin_func<1>("hash", [](VM* vm, ArgsView args){
         i64 value = vm->hash(args[0]);
         if(((value << 2) >> 2) != value) value >>= 2;
         return VAR(value);
     });
 
-    _vm->bind_builtin_func<1>("chr", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("chr", [](VM* vm, ArgsView args) {
         i64 i = CAST(i64, args[0]);
         if (i < 0 || i > 128) vm->ValueError("chr() arg not in range(128)");
         return VAR(std::string(1, (char)i));
     });
 
-    _vm->bind_builtin_func<1>("ord", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("ord", [](VM* vm, ArgsView args) {
         const Str& s = CAST(Str&, args[0]);
         if (s.length()!=1) vm->TypeError("ord() expected an ASCII character");
         return VAR((i64)(s[0]));
     });
 
-    _vm->bind_builtin_func<2>("hasattr", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<2>("hasattr", [](VM* vm, ArgsView args) {
         return VAR(vm->getattr(args[0], CAST(Str&, args[1]), false) != nullptr);
     });
 
-    _vm->bind_builtin_func<3>("setattr", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<3>("setattr", [](VM* vm, ArgsView args) {
         vm->setattr(args[0], CAST(Str&, args[1]), args[2]);
         return vm->None;
     });
 
-    _vm->bind_builtin_func<2>("getattr", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<2>("getattr", [](VM* vm, ArgsView args) {
         const Str& name = CAST(Str&, args[1]);
         return vm->getattr(args[0], name);
     });
 
-    _vm->bind_builtin_func<1>("hex", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("hex", [](VM* vm, ArgsView args) {
         std::stringstream ss;
         ss << std::hex << CAST(i64, args[0]);
         return VAR("0x" + ss.str());
     });
 
-    _vm->bind_builtin_func<1>("iter", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("iter", [](VM* vm, ArgsView args) {
         return vm->asIter(args[0]);
     });
 
-    _vm->bind_builtin_func<1>("dir", [](VM* vm, Args& args) {
+    _vm->bind_builtin_func<1>("dir", [](VM* vm, ArgsView args) {
         std::set<StrName> names;
         if(args[0]->is_attr_valid()){
             std::vector<StrName> keys = args[0]->attr().keys();
@@ -175,7 +179,7 @@ inline void init_builtins(VM* _vm) {
         return VAR(std::move(ret));
     });
 
-    _vm->bind_method<0>("object", "__repr__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("object", "__repr__", [](VM* vm, ArgsView args) {
         PyObject* self = args[0];
         if(is_tagged(self)) self = nullptr;
         std::stringstream ss;
@@ -187,7 +191,7 @@ inline void init_builtins(VM* _vm) {
     _vm->bind_method<1>("object", "__ne__", CPP_LAMBDA(VAR(args[0] != args[1])));
 
     _vm->bind_static_method<1>("type", "__new__", CPP_LAMBDA(vm->_t(args[0])));
-    _vm->bind_static_method<-1>("range", "__new__", [](VM* vm, Args& args) {
+    _vm->bind_static_method<-1>("range", "__new__", [](VM* vm, ArgsView args) {
         Range r;
         switch (args.size()) {
             case 1: r.stop = CAST(i64, args[0]); break;
@@ -205,13 +209,13 @@ inline void init_builtins(VM* _vm) {
     _vm->bind_method<0>("NoneType", "__repr__", CPP_LAMBDA(VAR("None")));
     _vm->bind_method<0>("NoneType", "__json__", CPP_LAMBDA(VAR("null")));
 
-    _vm->_bind_methods<1>({"int", "float"}, "__truediv__", [](VM* vm, Args& args) {
+    _vm->_bind_methods<1>({"int", "float"}, "__truediv__", [](VM* vm, ArgsView args) {
         f64 rhs = vm->num_to_float(args[1]);
         if (rhs == 0) vm->ZeroDivisionError();
         return VAR(vm->num_to_float(args[0]) / rhs);
     });
 
-    _vm->_bind_methods<1>({"int", "float"}, "__pow__", [](VM* vm, Args& args) {
+    _vm->_bind_methods<1>({"int", "float"}, "__pow__", [](VM* vm, ArgsView args) {
         if(is_both_int(args[0], args[1])){
             i64 lhs = _CAST(i64, args[0]);
             i64 rhs = _CAST(i64, args[1]);
@@ -231,7 +235,7 @@ inline void init_builtins(VM* _vm) {
     });
 
     /************ PyInt ************/
-    _vm->bind_static_method<1>("int", "__new__", [](VM* vm, Args& args) {
+    _vm->bind_static_method<1>("int", "__new__", [](VM* vm, ArgsView args) {
         if (is_type(args[0], vm->tp_int)) return args[0];
         if (is_type(args[0], vm->tp_float)) return VAR((i64)CAST(f64, args[0]));
         if (is_type(args[0], vm->tp_bool)) return VAR(_CAST(bool, args[0]) ? 1 : 0);
@@ -250,13 +254,13 @@ inline void init_builtins(VM* _vm) {
         return vm->None;
     });
 
-    _vm->bind_method<1>("int", "__floordiv__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("int", "__floordiv__", [](VM* vm, ArgsView args) {
         i64 rhs = CAST(i64, args[1]);
         if(rhs == 0) vm->ZeroDivisionError();
         return VAR(CAST(i64, args[0]) / rhs);
     });
 
-    _vm->bind_method<1>("int", "__mod__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("int", "__mod__", [](VM* vm, ArgsView args) {
         i64 rhs = CAST(i64, args[1]);
         if(rhs == 0) vm->ZeroDivisionError();
         return VAR(CAST(i64, args[0]) % rhs);
@@ -277,7 +281,7 @@ inline void init_builtins(VM* _vm) {
 #undef INT_BITWISE_OP
 
     /************ PyFloat ************/
-    _vm->bind_static_method<1>("float", "__new__", [](VM* vm, Args& args) {
+    _vm->bind_static_method<1>("float", "__new__", [](VM* vm, ArgsView args) {
         if (is_type(args[0], vm->tp_int)) return VAR((f64)CAST(i64, args[0]));
         if (is_type(args[0], vm->tp_float)) return args[0];
         if (is_type(args[0], vm->tp_bool)) return VAR(_CAST(bool, args[0]) ? 1.0 : 0.0);
@@ -296,7 +300,7 @@ inline void init_builtins(VM* _vm) {
         return vm->None;
     });
 
-    _vm->bind_method<0>("float", "__repr__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("float", "__repr__", [](VM* vm, ArgsView args) {
         f64 val = CAST(f64, args[0]);
         if(std::isinf(val) || std::isnan(val)) return VAR(std::to_string(val));
         std::stringstream ss;
@@ -306,7 +310,7 @@ inline void init_builtins(VM* _vm) {
         return VAR(s);
     });
 
-    _vm->bind_method<0>("float", "__json__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("float", "__json__", [](VM* vm, ArgsView args) {
         f64 val = CAST(f64, args[0]);
         if(std::isinf(val) || std::isnan(val)) vm->ValueError("cannot jsonify 'nan' or 'inf'");
         return VAR(std::to_string(val));
@@ -315,18 +319,18 @@ inline void init_builtins(VM* _vm) {
     /************ PyString ************/
     _vm->bind_static_method<1>("str", "__new__", CPP_LAMBDA(vm->asStr(args[0])));
 
-    _vm->bind_method<1>("str", "__add__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__add__", [](VM* vm, ArgsView args) {
         const Str& lhs = CAST(Str&, args[0]);
         const Str& rhs = CAST(Str&, args[1]);
         return VAR(lhs + rhs);
     });
 
-    _vm->bind_method<0>("str", "__len__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("str", "__len__", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         return VAR(self.u8_length());
     });
 
-    _vm->bind_method<1>("str", "__contains__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__contains__", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         const Str& other = CAST(Str&, args[1]);
         return VAR(self.index(other) != -1);
@@ -335,29 +339,29 @@ inline void init_builtins(VM* _vm) {
     _vm->bind_method<0>("str", "__str__", CPP_LAMBDA(args[0]));
     _vm->bind_method<0>("str", "__iter__", CPP_LAMBDA(vm->PyIter(StringIter(vm, args[0]))));
 
-    _vm->bind_method<0>("str", "__repr__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("str", "__repr__", [](VM* vm, ArgsView args) {
         const Str& _self = CAST(Str&, args[0]);
         return VAR(_self.escape());
     });
 
-    _vm->bind_method<0>("str", "__json__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("str", "__json__", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         return VAR(self.escape(false));
     });
 
-    _vm->bind_method<1>("str", "__eq__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__eq__", [](VM* vm, ArgsView args) {
         if(is_type(args[0], vm->tp_str) && is_type(args[1], vm->tp_str))
             return VAR(CAST(Str&, args[0]) == CAST(Str&, args[1]));
         return VAR(args[0] == args[1]);
     });
 
-    _vm->bind_method<1>("str", "__ne__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__ne__", [](VM* vm, ArgsView args) {
         if(is_type(args[0], vm->tp_str) && is_type(args[1], vm->tp_str))
             return VAR(CAST(Str&, args[0]) != CAST(Str&, args[1]));
         return VAR(args[0] != args[1]);
     });
 
-    _vm->bind_method<1>("str", "__getitem__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__getitem__", [](VM* vm, ArgsView args) {
         const Str& self (CAST(Str&, args[0]));
 
         if(is_type(args[1], vm->tp_slice)){
@@ -371,32 +375,32 @@ inline void init_builtins(VM* _vm) {
         return VAR(self.u8_getitem(index));
     });
 
-    _vm->bind_method<1>("str", "__gt__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__gt__", [](VM* vm, ArgsView args) {
         const Str& self (CAST(Str&, args[0]));
         const Str& obj (CAST(Str&, args[1]));
         return VAR(self > obj);
     });
 
-    _vm->bind_method<1>("str", "__lt__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "__lt__", [](VM* vm, ArgsView args) {
         const Str& self (CAST(Str&, args[0]));
         const Str& obj (CAST(Str&, args[1]));
         return VAR(self < obj);
     });
 
-    _vm->bind_method<2>("str", "replace", [](VM* vm, Args& args) {
+    _vm->bind_method<2>("str", "replace", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         const Str& old = CAST(Str&, args[1]);
         const Str& new_ = CAST(Str&, args[2]);
         return VAR(self.replace(old, new_));
     });
 
-    _vm->bind_method<1>("str", "startswith", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "startswith", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         const Str& prefix = CAST(Str&, args[1]);
         return VAR(self.index(prefix) == 0);
     });
 
-    _vm->bind_method<1>("str", "endswith", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "endswith", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         const Str& suffix = CAST(Str&, args[1]);
         int offset = self.length() - suffix.length();
@@ -405,7 +409,7 @@ inline void init_builtins(VM* _vm) {
         return VAR(ok);
     });
 
-    _vm->bind_method<1>("str", "join", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("str", "join", [](VM* vm, ArgsView args) {
         const Str& self = CAST(Str&, args[0]);
         FastStrStream ss;
         PyObject* obj = vm->asList(args[1]);
@@ -418,13 +422,13 @@ inline void init_builtins(VM* _vm) {
     });
 
     /************ PyList ************/
-    _vm->bind_method<1>("list", "append", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "append", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         self.push_back(args[1]);
         return vm->None;
     });
 
-    _vm->bind_method<1>("list", "extend", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "extend", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         PyObject* obj = vm->asList(args[1]);
         const List& list = CAST(List&, obj);
@@ -432,13 +436,13 @@ inline void init_builtins(VM* _vm) {
         return vm->None;
     });
 
-    _vm->bind_method<0>("list", "reverse", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("list", "reverse", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         std::reverse(self.begin(), self.end());
         return vm->None;
     });
 
-    _vm->bind_method<1>("list", "__mul__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "__mul__", [](VM* vm, ArgsView args) {
         const List& self = CAST(List&, args[0]);
         int n = CAST(int, args[1]);
         List result;
@@ -447,7 +451,7 @@ inline void init_builtins(VM* _vm) {
         return VAR(std::move(result));
     });
 
-    _vm->bind_method<2>("list", "insert", [](VM* vm, Args& args) {
+    _vm->bind_method<2>("list", "insert", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         int index = CAST(int, args[1]);
         if(index < 0) index += self.size();
@@ -457,14 +461,14 @@ inline void init_builtins(VM* _vm) {
         return vm->None;
     });
 
-    _vm->bind_method<0>("list", "clear", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("list", "clear", [](VM* vm, ArgsView args) {
         CAST(List&, args[0]).clear();
         return vm->None;
     });
 
     _vm->bind_method<0>("list", "copy", CPP_LAMBDA(VAR(CAST(List, args[0]))));
 
-    _vm->bind_method<1>("list", "__add__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "__add__", [](VM* vm, ArgsView args) {
         const List& self = CAST(List&, args[0]);
         const List& other = CAST(List&, args[1]);
         List new_list(self);    // copy construct
@@ -472,16 +476,16 @@ inline void init_builtins(VM* _vm) {
         return VAR(std::move(new_list));
     });
 
-    _vm->bind_method<0>("list", "__len__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("list", "__len__", [](VM* vm, ArgsView args) {
         const List& self = CAST(List&, args[0]);
         return VAR(self.size());
     });
 
-    _vm->bind_method<0>("list", "__iter__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("list", "__iter__", [](VM* vm, ArgsView args) {
         return vm->PyIter(ArrayIter<List>(vm, args[0]));
     });
 
-    _vm->bind_method<1>("list", "__getitem__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "__getitem__", [](VM* vm, ArgsView args) {
         const List& self = CAST(List&, args[0]);
 
         if(is_type(args[1], vm->tp_slice)){
@@ -497,7 +501,7 @@ inline void init_builtins(VM* _vm) {
         return self[index];
     });
 
-    _vm->bind_method<2>("list", "__setitem__", [](VM* vm, Args& args) {
+    _vm->bind_method<2>("list", "__setitem__", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         int index = CAST(int, args[1]);
         index = vm->normalized_index(index, self.size());
@@ -505,7 +509,7 @@ inline void init_builtins(VM* _vm) {
         return vm->None;
     });
 
-    _vm->bind_method<1>("list", "__delitem__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("list", "__delitem__", [](VM* vm, ArgsView args) {
         List& self = CAST(List&, args[0]);
         int index = CAST(int, args[1]);
         index = vm->normalized_index(index, self.size());
@@ -514,16 +518,16 @@ inline void init_builtins(VM* _vm) {
     });
 
     /************ PyTuple ************/
-    _vm->bind_static_method<1>("tuple", "__new__", [](VM* vm, Args& args) {
+    _vm->bind_static_method<1>("tuple", "__new__", [](VM* vm, ArgsView args) {
         List list = CAST(List, vm->asList(args[0]));
         return VAR(Tuple(std::move(list)));
     });
 
-    _vm->bind_method<0>("tuple", "__iter__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("tuple", "__iter__", [](VM* vm, ArgsView args) {
         return vm->PyIter(ArrayIter<Args>(vm, args[0]));
     });
 
-    _vm->bind_method<1>("tuple", "__getitem__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("tuple", "__getitem__", [](VM* vm, ArgsView args) {
         const Tuple& self = CAST(Tuple&, args[0]);
 
         if(is_type(args[1], vm->tp_slice)){
@@ -539,7 +543,7 @@ inline void init_builtins(VM* _vm) {
         return self[index];
     });
 
-    _vm->bind_method<0>("tuple", "__len__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("tuple", "__len__", [](VM* vm, ArgsView args) {
         const Tuple& self = CAST(Tuple&, args[0]);
         return VAR(self.size());
     });
@@ -547,17 +551,17 @@ inline void init_builtins(VM* _vm) {
     /************ PyBool ************/
     _vm->bind_static_method<1>("bool", "__new__", CPP_LAMBDA(VAR(vm->asBool(args[0]))));
 
-    _vm->bind_method<0>("bool", "__repr__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("bool", "__repr__", [](VM* vm, ArgsView args) {
         bool val = CAST(bool, args[0]);
         return VAR(val ? "True" : "False");
     });
 
-    _vm->bind_method<0>("bool", "__json__", [](VM* vm, Args& args) {
+    _vm->bind_method<0>("bool", "__json__", [](VM* vm, ArgsView args) {
         bool val = CAST(bool, args[0]);
         return VAR(val ? "true" : "false");
     });
 
-    _vm->bind_method<1>("bool", "__xor__", [](VM* vm, Args& args) {
+    _vm->bind_method<1>("bool", "__xor__", [](VM* vm, ArgsView args) {
         bool self = CAST(bool, args[0]);
         bool other = CAST(bool, args[1]);
         return VAR(self ^ other);
@@ -579,7 +583,7 @@ inline void init_builtins(VM* _vm) {
 
 inline void add_module_time(VM* vm){
     PyObject* mod = vm->new_module("time");
-    vm->bind_func<0>(mod, "time", [](VM* vm, Args& args) {
+    vm->bind_func<0>(mod, "time", [](VM* vm, ArgsView args) {
         auto now = std::chrono::high_resolution_clock::now();
         return VAR(std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count() / 1000000.0);
     });
@@ -588,28 +592,23 @@ inline void add_module_time(VM* vm){
 inline void add_module_sys(VM* vm){
     PyObject* mod = vm->new_module("sys");
     vm->setattr(mod, "version", VAR(PK_VERSION));
-    vm->bind_func<0>(mod, "getrecursionlimit", CPP_LAMBDA(VAR(vm->recursionlimit)));
-    vm->bind_func<1>(mod, "setrecursionlimit", [](VM* vm, Args& args) {
-        vm->recursionlimit = CAST(int, args[0]);
-        return vm->None;
-    });
 }
 
 inline void add_module_json(VM* vm){
     PyObject* mod = vm->new_module("json");
-    vm->bind_func<1>(mod, "loads", [](VM* vm, Args& args) {
+    vm->bind_func<1>(mod, "loads", [](VM* vm, ArgsView args) {
         const Str& expr = CAST(Str&, args[0]);
         CodeObject_ code = vm->compile(expr, "<json>", JSON_MODE);
         return vm->_exec(code, vm->top_frame()->_module);
     });
 
-    vm->bind_func<1>(mod, "dumps", CPP_LAMBDA(vm->fast_call(__json__, Args{args[0]})));
+    vm->bind_func<1>(mod, "dumps", CPP_LAMBDA(vm->call_method(args[0], __json__)));
 }
 
 inline void add_module_math(VM* vm){
     PyObject* mod = vm->new_module("math");
-    vm->setattr(mod, "pi", VAR(3.1415926535897932384));
-    vm->setattr(mod, "e" , VAR(2.7182818284590452354));
+    mod->attr().set("pi", VAR(3.1415926535897932384));
+    mod->attr().set("e" , VAR(2.7182818284590452354));
 
     vm->bind_func<1>(mod, "log", CPP_LAMBDA(VAR(std::log(vm->num_to_float(args[0])))));
     vm->bind_func<1>(mod, "log10", CPP_LAMBDA(VAR(std::log10(vm->num_to_float(args[0])))));
@@ -627,7 +626,7 @@ inline void add_module_math(VM* vm){
 
 inline void add_module_dis(VM* vm){
     PyObject* mod = vm->new_module("dis");
-    vm->bind_func<1>(mod, "dis", [](VM* vm, Args& args) {
+    vm->bind_func<1>(mod, "dis", [](VM* vm, ArgsView args) {
         PyObject* f = args[0];
         if(is_type(f, vm->tp_bound_method)) f = CAST(BoundMethod, args[0]).method;
         CodeObject_ code = CAST(Function&, f).decl->code;
@@ -649,12 +648,12 @@ struct ReMatch {
         vm->bind_method<0>(type, "start", CPP_LAMBDA(VAR(CAST(ReMatch&, args[0]).start)));
         vm->bind_method<0>(type, "end", CPP_LAMBDA(VAR(CAST(ReMatch&, args[0]).end)));
 
-        vm->bind_method<0>(type, "span", [](VM* vm, Args& args) {
+        vm->bind_method<0>(type, "span", [](VM* vm, ArgsView args) {
             auto& self = CAST(ReMatch&, args[0]);
             return VAR(Tuple({VAR(self.start), VAR(self.end)}));
         });
 
-        vm->bind_method<1>(type, "group", [](VM* vm, Args& args) {
+        vm->bind_method<1>(type, "group", [](VM* vm, ArgsView args) {
             auto& self = CAST(ReMatch&, args[0]);
             int index = CAST(int, args[1]);
             index = vm->normalized_index(index, self.m.size());
@@ -679,19 +678,19 @@ inline void add_module_re(VM* vm){
     PyObject* mod = vm->new_module("re");
     ReMatch::register_class(vm, mod);
 
-    vm->bind_func<2>(mod, "match", [](VM* vm, Args& args) {
+    vm->bind_func<2>(mod, "match", [](VM* vm, ArgsView args) {
         const Str& pattern = CAST(Str&, args[0]);
         const Str& string = CAST(Str&, args[1]);
         return _regex_search(pattern, string, true, vm);
     });
 
-    vm->bind_func<2>(mod, "search", [](VM* vm, Args& args) {
+    vm->bind_func<2>(mod, "search", [](VM* vm, ArgsView args) {
         const Str& pattern = CAST(Str&, args[0]);
         const Str& string = CAST(Str&, args[1]);
         return _regex_search(pattern, string, false, vm);
     });
 
-    vm->bind_func<3>(mod, "sub", [](VM* vm, Args& args) {
+    vm->bind_func<3>(mod, "sub", [](VM* vm, ArgsView args) {
         const Str& pattern = CAST(Str&, args[0]);
         const Str& repl = CAST(Str&, args[1]);
         const Str& string = CAST(Str&, args[2]);
@@ -699,7 +698,7 @@ inline void add_module_re(VM* vm){
         return VAR(std::regex_replace(string.str(), re, repl.str()));
     });
 
-    vm->bind_func<2>(mod, "split", [](VM* vm, Args& args) {
+    vm->bind_func<2>(mod, "split", [](VM* vm, ArgsView args) {
         const Str& pattern = CAST(Str&, args[0]);
         const Str& string = CAST(Str&, args[1]);
         std::regex re(pattern.begin(), pattern.end());
@@ -789,11 +788,11 @@ inline void VM::post_init(){
 
     // property is defined in builtins.py so we need to add it after builtins is loaded
     _t(tp_object)->attr().set(__class__, property(CPP_LAMBDA(vm->_t(args[0]))));
-    _t(tp_type)->attr().set(__base__, property([](VM* vm, Args& args){
+    _t(tp_type)->attr().set(__base__, property([](VM* vm, ArgsView args){
         const PyTypeInfo& info = vm->_all_types[OBJ_GET(Type, args[0])];
         return info.base.index == -1 ? vm->None : vm->_all_types[info.base].obj;
     }));
-    _t(tp_type)->attr().set(__name__, property([](VM* vm, Args& args){
+    _t(tp_type)->attr().set(__name__, property([](VM* vm, ArgsView args){
         const PyTypeInfo& info = vm->_all_types[OBJ_GET(Type, args[0])];
         return VAR(info.name);
     }));
@@ -964,7 +963,7 @@ extern "C" {
             ss << f_header;
             for(int i=0; i<args.size(); i++){
                 ss << ' ';
-                pkpy::PyObject* x = vm->fast_call(pkpy::__json__, pkpy::Args{args[i]});
+                pkpy::PyObject* x = vm->call_method(args[i], pkpy::__json__);
                 ss << pkpy::CAST(pkpy::Str&, x);
             }
             char* packet = strdup(ss.str().c_str());

+ 2 - 15
src/tuplelist.h

@@ -63,24 +63,9 @@ public:
     PyObject** begin() const { return _args; }
     PyObject** end() const { return _args + _size; }
 
-    void extend_self(PyObject* self){
-        PyObject** old_args = _args;
-        int old_size = _size;
-        _alloc(old_size+1);
-        _args[0] = self;
-        for(int i=0; i<old_size; i++) _args[i+1] = old_args[i];
-        if(old_args!=nullptr) pool64.dealloc(old_args);
-    }
-
     ~Tuple(){ if(_args!=nullptr) pool64.dealloc(_args); }
 };
 
-using Args = Tuple;
-inline const Args& no_arg() {
-    static const Args _zero(0);
-    return _zero;
-}
-
 // a lightweight view for function args, it does not own the memory
 struct ArgsView{
     PyObject** _begin;
@@ -109,4 +94,6 @@ struct ArgsView{
     }
 };
 
+using Args = ArgsView;
+
 }   // namespace pkpy

+ 197 - 133
src/vm.h

@@ -8,9 +8,23 @@
 #include "memory.h"
 #include "obj.h"
 #include "str.h"
+#include "tuplelist.h"
+#include <tuple>
 
 namespace pkpy{
 
+/* Stack manipulation macros */
+// https://github.com/python/cpython/blob/3.9/Python/ceval.c#L1123
+#define TOP()             (s_data.top())
+#define SECOND()          (s_data.second())
+#define THIRD()           (s_data.third())
+#define PEEK(n)           (s_data.peek(n))
+#define STACK_SHRINK(n)   (s_data.shrink(n))
+#define PUSH(v)           (s_data.push(v))
+#define POP()             (s_data.pop())
+#define POPX()            (s_data.popx())
+#define STACK_VIEW(n)     (s_data.view(n))
+
 Str _read_file_cwd(const Str& name, bool* ok);
 
 #define DEF_NATIVE_2(ctype, ptype)                                      \
@@ -68,9 +82,10 @@ public:
     NameDict _modules;                                  // loaded modules
     std::map<StrName, Str> _lazy_modules;               // lazy loaded modules
 
+    PyObject* _py_null;
+    PyObject* _py_begin_call;
     PyObject* _py_op_call;
     PyObject* _py_op_yield;
-    PyObject* _py_null;
     PyObject* None;
     PyObject* True;
     PyObject* False;
@@ -82,7 +97,6 @@ public:
     std::stringstream _stderr_buffer;
     std::ostream* _stdout;
     std::ostream* _stderr;
-    int recursionlimit = 1000;
 
     // for quick access
     Type tp_object, tp_type, tp_int, tp_float, tp_bool, tp_str;
@@ -111,7 +125,7 @@ public:
     PyObject* asStr(PyObject* obj){
         PyObject* self;
         PyObject* f = get_unbound_method(obj, __str__, &self, false);
-        if(self != _py_null) return call(f, Args{self});
+        if(self != _py_null) return call_method(self, f);
         return asRepr(obj);
     }
 
@@ -119,14 +133,14 @@ public:
         if(is_type(obj, tp_iterator)) return obj;
         PyObject* self;
         PyObject* iter_f = get_unbound_method(obj, __iter__, &self, false);
-        if(self != _py_null) return call(iter_f, Args{self});
+        if(self != _py_null) return call_method(self, iter_f);
         TypeError(OBJ_NAME(_t(obj)).escape() + " object is not iterable");
         return nullptr;
     }
 
-    PyObject* asList(PyObject* iterable){
-        if(is_type(iterable, tp_list)) return iterable;
-        return call(_t(tp_list), Args{iterable});
+    PyObject* asList(PyObject* it){
+        if(is_non_tagged_type(it, tp_list)) return it;
+        return call_(_t(tp_list), it);
     }
 
     PyObject* find_name_in_mro(PyObject* cls, StrName name){
@@ -153,17 +167,18 @@ public:
         return false;
     }
 
-    PyObject* fast_call(StrName name, Args&& args){
-        PyObject* val = find_name_in_mro(_t(args[0]), name);
-        if(val != nullptr) return call(val, std::move(args));
-        AttributeError(args[0], name);
-        return nullptr;
-    }
-
-    template<typename ArgT>
-    std::enable_if_t<std::is_same_v<std::decay_t<ArgT>, Args>, PyObject*>
-    call(PyObject* callable, ArgT&& args){
-        return call(callable, std::forward<ArgT>(args), no_arg(), false);
+    PyObject* fast_call_method(PyObject* obj, StrName name, int ARGC){
+        PyObject* callable = find_name_in_mro(_t(obj), name);
+        if(callable == nullptr) AttributeError(obj, name);
+        // [a, b]
+        // [......., a, b]
+        // [unbound, a, b]
+        //  ^^^^^^^
+        s_data._sp++;
+        PyObject** t = s_data._sp;
+        for(; t>s_data._sp-ARGC; t--) *t = t[-1];
+        *t = obj;
+        return _vectorcall(ARGC-1);
     }
 
     PyObject* exec(Str source, Str filename, CompileMode mode, PyObject* _module=nullptr){
@@ -185,34 +200,54 @@ public:
         }
 #endif
         callstack.clear();
+        s_data.clear();
         return nullptr;
     }
 
     template<typename ...Args>
-    void _push_new_frame(Args&&... args){
-        if(callstack.size() > recursionlimit){
-            _error("RecursionError", "maximum recursion depth exceeded");
-        }
-        callstack.emplace(&s_data, std::forward<Args>(args)...);
+    PyObject* _exec(Args&&... args){
+        callstack.emplace(&s_data, s_data._sp, std::forward<Args>(args)...);
+        return _run_top_frame();
     }
 
-    void _push_new_frame(Frame&& frame){
-        if(callstack.size() > recursionlimit){
-            _error("RecursionError", "maximum recursion depth exceeded");
+    void _push_varargs(int n, ...){
+        va_list args;
+        va_start(args, n);
+        for(int i=0; i<n; i++){
+            PyObject* obj = va_arg(args, PyObject*);
+            PUSH(obj);
         }
-        callstack.emplace(std::move(frame));
+        va_end(args);
     }
 
-    template<typename ...Args>
-    PyObject* _exec(Args&&... args){
-        _push_new_frame(std::forward<Args>(args)...);
-        return _run_top_frame();
+    template<typename... Args>
+    PyObject* call_(PyObject* callable, Args&&... args){
+        PUSH(callable);
+        PUSH(_py_null);
+        int ARGC = sizeof...(args);
+        _push_varargs(ARGC, args...);
+        return _vectorcall(ARGC);
+    }
+
+    template<typename... Args>
+    PyObject* call_method(PyObject* self, PyObject* callable, Args&&... args){
+        PUSH(callable);
+        PUSH(self);
+        int ARGC = sizeof...(args);
+        _push_varargs(ARGC, args...);
+        return _vectorcall(ARGC);
+    }
+
+    template<typename... Args>
+    PyObject* call_method(PyObject* self, StrName name, Args&&... args){
+        PyObject* callable = get_unbound_method(self, name, &self);
+        return call_method(self, callable, args...);
     }
 
     PyObject* property(NativeFuncRaw fget){
         PyObject* p = builtins->attr("property");
         PyObject* method = heap.gcnew(tp_native_function, NativeFunc(fget, 1, false));
-        return call(p, Args{method});
+        return call_(p, method);
     }
 
     PyObject* new_type_object(PyObject* mod, StrName name, Type base){
@@ -297,6 +332,7 @@ public:
         else throw UnhandledException();
     }
 
+    void RecursionError() { _error("RecursionError", "maximum recursion depth exceeded"); }
     void IOError(const Str& msg) { _error("IOError", msg); }
     void NotImplementedError(){ _error("NotImplementedError", ""); }
     void TypeError(const Str& msg){ _error("TypeError", msg); }
@@ -334,6 +370,8 @@ public:
         _lazy_modules.clear();
     }
 
+    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* num_negated(PyObject* obj);
     f64 num_to_float(PyObject* obj);
@@ -343,13 +381,10 @@ public:
     PyObject* new_module(StrName name);
     Str disassemble(CodeObject_ co);
     void init_builtin_types();
-    PyObject* call(PyObject* callable, Args args, const Args& kwargs, bool opCall);
-    PyObject* _py_call(PyObject* callable, ArgsView args, ArgsView kwargs);
-    void unpack_args(Args& args);
+    PyObject* _py_call(PyObject** sp_base, PyObject* callable, ArgsView args, ArgsView kwargs);
     PyObject* getattr(PyObject* obj, StrName name, bool throw_err=true);
     PyObject* get_unbound_method(PyObject* obj, StrName name, PyObject** self, bool throw_err=true, bool fallback=false);
-    template<typename T>
-    void setattr(PyObject* obj, StrName name, T&& value);
+    void setattr(PyObject* obj, StrName name, PyObject* value);
     template<int ARGC>
     void bind_method(PyObject*, Str, NativeFuncRaw);
     template<int ARGC>
@@ -359,7 +394,7 @@ public:
     void post_init();
 };
 
-inline PyObject* NativeFunc::operator()(VM* vm, Args& args) const{
+inline PyObject* NativeFunc::operator()(VM* vm, ArgsView args) const{
     int args_size = args.size() - (int)method;  // remove self
     if(argc != -1 && args_size != argc) {
         vm->TypeError(fmt("expected ", argc, " arguments, but got ", args_size));
@@ -512,23 +547,25 @@ inline f64 VM::num_to_float(PyObject* obj){
 }
 
 inline bool VM::asBool(PyObject* obj){
-    if(is_type(obj, tp_bool)) return obj == True;
+    if(is_non_tagged_type(obj, tp_bool)) return obj == True;
     if(obj == None) return false;
-    if(is_type(obj, tp_int)) return CAST(i64, obj) != 0;
-    if(is_type(obj, tp_float)) return CAST(f64, obj) != 0.0;
+    if(is_int(obj)) return CAST(i64, obj) != 0;
+    if(is_float(obj)) return CAST(f64, obj) != 0.0;
     PyObject* self;
     PyObject* len_f = get_unbound_method(obj, __len__, &self, false);
     if(self != _py_null){
-        PyObject* ret = call(len_f, Args{self});
+        PUSH(len_f);
+        PUSH(self);
+        PyObject* ret = _vectorcall(0);
         return CAST(i64, ret) > 0;
     }
     return true;
 }
 
 inline i64 VM::hash(PyObject* obj){
-    if (is_type(obj, tp_str)) return CAST(Str&, obj).hash();
+    if (is_non_tagged_type(obj, tp_str)) return CAST(Str&, obj).hash();
     if (is_int(obj)) return CAST(i64, obj);
-    if (is_type(obj, tp_tuple)) {
+    if (is_non_tagged_type(obj, tp_tuple)) {
         i64 x = 1000003;
         const Tuple& items = CAST(Tuple&, obj);
         for (int i=0; i<items.size(); i++) {
@@ -538,8 +575,8 @@ inline i64 VM::hash(PyObject* obj){
         }
         return x;
     }
-    if (is_type(obj, tp_type)) return BITS(obj);
-    if (is_type(obj, tp_bool)) return _CAST(bool, obj) ? 1 : 0;
+    if (is_non_tagged_type(obj, tp_type)) return BITS(obj);
+    if (is_non_tagged_type(obj, tp_bool)) return _CAST(bool, obj) ? 1 : 0;
     if (is_float(obj)){
         f64 val = CAST(f64, obj);
         return (i64)std::hash<f64>()(val);
@@ -550,7 +587,7 @@ inline i64 VM::hash(PyObject* obj){
 
 inline PyObject* VM::asRepr(PyObject* obj){
     // TODO: fastcall does not take care of super() proxy!
-    return fast_call(__repr__, Args{obj});
+    return fast_call_method(obj, __repr__, 0);
 }
 
 inline PyObject* VM::new_module(StrName name) {
@@ -657,9 +694,12 @@ inline void VM::init_builtin_types(){
     this->Ellipsis = heap._new<Dummy>(_new_type_object("ellipsis"), {});
     this->True = heap._new<Dummy>(tp_bool, {});
     this->False = heap._new<Dummy>(tp_bool, {});
-    this->_py_null = heap._new<Dummy>(_new_type_object("_py_null"), {});
-    this->_py_op_call = heap._new<Dummy>(_new_type_object("_py_op_call"), {});
-    this->_py_op_yield = heap._new<Dummy>(_new_type_object("_py_op_yield"), {});
+
+    Type _internal_type = _new_type_object("_internal");
+    this->_py_null = heap._new<Dummy>(_internal_type, {});
+    this->_py_begin_call = heap._new<Dummy>(_internal_type, {});
+    this->_py_op_call = heap._new<Dummy>(_internal_type, {});
+    this->_py_op_yield = heap._new<Dummy>(_internal_type, {});
 
     this->builtins = new_module("builtins");
     this->_main = new_module("__main__");
@@ -682,8 +722,102 @@ inline void VM::init_builtin_types(){
     for(auto [k, v]: _modules.items()) v->attr()._try_perfect_rehash();
 }
 
-inline PyObject* VM::_py_call(PyObject* callable, ArgsView args, ArgsView kwargs){
-    // callable is a `function` object
+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* callable = p1[-(ARGC + 2)];
+    bool method_call = p1[-(ARGC + 1)] != _py_null;
+
+    ArgsView args(p1 - ARGC - int(method_call), p1);
+
+    // handle boundmethod, do a patch
+    if(is_non_tagged_type(callable, tp_bound_method)){
+        if(method_call) FATAL_ERROR();
+        auto& bm = CAST(BoundMethod&, callable);
+        callable = bm.method;      // get unbound method
+        p1[-(ARGC + 2)] = bm.method;
+        p1[-(ARGC + 1)] = bm.obj;
+        // [unbound, self, args..., kwargs...]
+    }
+
+    if(is_non_tagged_type(callable, tp_native_function)){
+        const auto& f = OBJ_GET(NativeFunc, callable);
+        if(KWARGC != 0) TypeError("native_function does not accept keyword arguments");
+        PyObject* ret = f(this, args);
+        s_data.reset(p0);
+        return ret;
+    }
+
+    ArgsView kwargs(p1, s_data._sp);
+
+    if(is_non_tagged_type(callable, tp_function)){
+        // ret is nullptr or a generator
+        PyObject* ret = _py_call(p0, callable, args, kwargs);
+        // stack resetting is handled by _py_call
+        if(ret != nullptr) return ret;
+        if(op_call) return _py_op_call;
+        return _run_top_frame();
+    }
+
+    if(is_non_tagged_type(callable, tp_type)){
+        if(method_call) FATAL_ERROR();
+        // [type, NULL, args..., kwargs...]
+
+        // TODO: derived __new__ ?
+        PyObject* new_f = callable->attr().try_get(__new__);
+        PyObject* obj;
+        if(new_f != nullptr){
+            PUSH(new_f);
+            s_data.dup_top_n(1 + ARGC + KWARGC*2);
+            obj = _vectorcall(ARGC, KWARGC, false);
+            if(!isinstance(obj, OBJ_GET(Type, callable))) return obj;
+        }else{
+            obj = heap.gcnew<DummyInstance>(OBJ_GET(Type, callable), {});
+        }
+        PyObject* self;
+        callable = get_unbound_method(obj, __init__, &self, false);
+        if (self != _py_null) {
+            // replace `NULL` with `self`
+            p1[-(ARGC + 2)] = callable;
+            p1[-(ARGC + 1)] = self;
+            // [init_f, self, args..., kwargs...]
+            _vectorcall(ARGC, KWARGC, false);
+            // We just discard the return value of `__init__`
+            // in cpython it raises a TypeError if the return value is not None
+        }else{
+            // manually reset the stack
+            s_data.reset(p0);
+        }
+        return obj;
+    }
+
+    // handle `__call__` overload
+    PyObject* self;
+    PyObject* call_f = get_unbound_method(callable, __call__, &self, false);
+    if(self != _py_null){
+        p1[-(ARGC + 2)] = call_f;
+        p1[-(ARGC + 1)] = self;
+        // [call_f, self, args..., kwargs...]
+        return _vectorcall(ARGC, KWARGC, false);
+    }
+    TypeError(OBJ_NAME(_t(callable)).escape() + " object is not callable");
+    return nullptr;
+}
+
+inline PyObject* VM::_py_call(PyObject** sp_base, PyObject* callable, ArgsView args, ArgsView kwargs){
+    // callable must be a `function` object
     const Function& fn = CAST(Function&, callable);
     const CodeObject* co = fn.decl->code.get();
     FastLocals locals(co);
@@ -723,85 +857,17 @@ inline PyObject* VM::_py_call(PyObject* callable, ArgsView args, ArgsView kwargs
     
     for(int i=0; i<kwargs.size(); i+=2){
         StrName key = CAST(int, kwargs[i]);
-        // try_set has nullptr check
-        // TODO: optimize this
-        bool ok = locals.try_set(key, kwargs[i+1]);
-        if(!ok){
-            TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
-        }
+        bool ok = locals._try_set(key, kwargs[i+1]);
+        if(!ok) TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
     }
     PyObject* _module = fn._module != nullptr ? fn._module : top_frame()->_module;
-    if(co->is_generator){
-        return PyIter(Generator(this, Frame(&s_data, co, _module, std::move(locals), fn._closure)));
-    }
-    _push_new_frame(co, _module, std::move(locals), fn._closure);
+    if(co->is_generator) return PyIter(Generator(this, Frame(
+        &s_data, sp_base, co, _module, std::move(locals), fn._closure
+    )));
+    callstack.emplace(&s_data, sp_base, co, _module, std::move(locals), fn._closure);
     return nullptr;
 }
 
-// TODO: callable/args here may be garbage collected accidentally
-inline PyObject* VM::call(PyObject* callable, Args args, const Args& kwargs, bool opCall){
-    if(is_type(callable, tp_bound_method)){
-        auto& bm = CAST(BoundMethod&, callable);
-        callable = bm.method;      // get unbound method
-        args.extend_self(bm.obj);
-    }
-    
-    if(is_type(callable, tp_native_function)){
-        const auto& f = OBJ_GET(NativeFunc, callable);
-        if(kwargs.size() != 0) TypeError("native_function does not accept keyword arguments");
-        return f(this, args);
-    } else if(is_type(callable, tp_function)){
-        // ret is nullptr or a generator
-        PyObject* ret = _py_call(callable, args, kwargs);
-        if(ret != nullptr) return ret;
-        if(opCall) return _py_op_call;
-        return _run_top_frame();
-    }
-
-    if(is_type(callable, tp_type)){
-        // TODO: derived __new__ ?
-        PyObject* new_f = callable->attr().try_get(__new__);
-        PyObject* obj;
-        if(new_f != nullptr){
-            // should not use std::move here, since we will reuse args in possible __init__
-            obj = call(new_f, args, kwargs, false);
-            if(!isinstance(obj, OBJ_GET(Type, callable))) return obj;
-        }else{
-            obj = heap.gcnew<DummyInstance>(OBJ_GET(Type, callable), {});
-        }
-        PyObject* self;
-        PyObject* init_f = get_unbound_method(obj, __init__, &self, false);
-        if (self != _py_null) {
-            args.extend_self(self);
-            call(init_f, std::move(args), kwargs, false);
-        }
-        return obj;
-    }
-
-    PyObject* self;
-    PyObject* call_f = get_unbound_method(callable, __call__, &self, false);
-    if(self != _py_null){
-        args.extend_self(self);
-        return call(call_f, std::move(args), kwargs, false);
-    }
-    TypeError(OBJ_NAME(_t(callable)).escape() + " object is not callable");
-    return None;
-}
-
-inline void VM::unpack_args(Args& args){
-    List unpacked;
-    for(int i=0; i<args.size(); i++){
-        if(is_type(args[i], tp_star_wrapper)){
-            auto& star = _CAST(StarWrapper&, args[i]);
-            List& list = CAST(List&, asList(star.obj));
-            unpacked.extend(list);
-        }else{
-            unpacked.push_back(args[i]);
-        }
-    }
-    args = Args(std::move(unpacked));
-}
-
 // https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance
 inline PyObject* VM::getattr(PyObject* obj, StrName name, bool throw_err){
     PyObject* objtype = _t(obj);
@@ -815,7 +881,7 @@ inline PyObject* VM::getattr(PyObject* obj, StrName name, bool throw_err){
     if(cls_var != nullptr){
         // handle descriptor
         PyObject* descr_get = _t(cls_var)->attr().try_get(__get__);
-        if(descr_get != nullptr) return call(descr_get, Args{cls_var, obj});
+        if(descr_get != nullptr) return call_method(cls_var, descr_get, obj);
     }
     // handle instance __dict__
     if(!is_tagged(obj) && obj->is_attr_valid()){
@@ -850,7 +916,7 @@ inline PyObject* VM::get_unbound_method(PyObject* obj, StrName name, PyObject**
         if(cls_var != nullptr){
             // handle descriptor
             PyObject* descr_get = _t(cls_var)->attr().try_get(__get__);
-            if(descr_get != nullptr) return call(descr_get, Args{cls_var, obj});
+            if(descr_get != nullptr) return call_method(cls_var, descr_get, obj);
         }
         // handle instance __dict__
         if(!is_tagged(obj) && obj->is_attr_valid()){
@@ -869,9 +935,7 @@ inline PyObject* VM::get_unbound_method(PyObject* obj, StrName name, PyObject**
     return nullptr;
 }
 
-template<typename T>
-inline void VM::setattr(PyObject* obj, StrName name, T&& value){
-    static_assert(std::is_same_v<std::decay_t<T>, PyObject*>);
+inline void VM::setattr(PyObject* obj, StrName name, PyObject* value){
     PyObject* objtype = _t(obj);
     // handle super() proxy
     if(is_type(obj, tp_super)){
@@ -886,7 +950,7 @@ inline void VM::setattr(PyObject* obj, StrName name, T&& value){
         if(cls_var_t->attr().contains(__get__)){
             PyObject* descr_set = cls_var_t->attr().try_get(__set__);
             if(descr_set != nullptr){
-                call(descr_set, Args{cls_var, obj, std::forward<T>(value)});
+                call_method(cls_var, descr_set, obj, value);
             }else{
                 TypeError(fmt("readonly attribute: ", name.escape()));
             }
@@ -895,7 +959,7 @@ inline void VM::setattr(PyObject* obj, StrName name, T&& value){
     }
     // handle instance __dict__
     if(is_tagged(obj) || !obj->is_attr_valid()) TypeError("cannot set attribute");
-    obj->attr().set(name, std::forward<T>(value));
+    obj->attr().set(name, value);
 }
 
 template<int ARGC>