@@ -648,7 +648,7 @@ impl Py<PyFunction> {
648648 /// Skips FuncArgs allocation, prepend_arg, and fill_locals_from_args.
649649 /// Only valid when: no VARARGS, no VARKEYWORDS, no kwonlyargs, not generator/coroutine,
650650 /// and nargs == co_argcount.
651- pub fn invoke_exact_args ( & self , args : & [ PyObjectRef ] , vm : & VirtualMachine ) -> PyResult {
651+ pub fn invoke_exact_args ( & self , mut args : Vec < PyObjectRef > , vm : & VirtualMachine ) -> PyResult {
652652 let code: PyRef < PyCode > = ( * self . code ) . to_owned ( ) ;
653653
654654 debug_assert_eq ! ( args. len( ) , code. arg_count as usize ) ;
@@ -671,11 +671,11 @@ impl Py<PyFunction> {
671671 )
672672 . into_ref ( & vm. ctx ) ;
673673
674- // Copy args directly into fastlocals
674+ // Move args directly into fastlocals (no clone/refcount needed)
675675 {
676676 let fastlocals = unsafe { frame. fastlocals . borrow_mut ( ) } ;
677- for ( i , arg) in args . iter ( ) . enumerate ( ) {
678- fastlocals [ i ] = Some ( arg. clone ( ) ) ;
677+ for ( slot , arg) in fastlocals . iter_mut ( ) . zip ( args . drain ( .. ) ) {
678+ * slot = Some ( arg) ;
679679 }
680680 }
681681
@@ -1253,8 +1253,107 @@ impl PyCell {
12531253 }
12541254}
12551255
1256+ /// Vectorcall implementation for PyFunction (PEP 590).
1257+ /// Takes owned args to avoid cloning when filling fastlocals.
1258+ pub ( crate ) fn vectorcall_function (
1259+ zelf_obj : & PyObject ,
1260+ mut args : Vec < PyObjectRef > ,
1261+ nargs : usize ,
1262+ kwnames : Option < & [ PyObjectRef ] > ,
1263+ vm : & VirtualMachine ,
1264+ ) -> PyResult {
1265+ let zelf: & Py < PyFunction > = zelf_obj. downcast_ref ( ) . unwrap ( ) ;
1266+ let code: & Py < PyCode > = & zelf. code ;
1267+
1268+ let has_kwargs = kwnames. is_some_and ( |kw| !kw. is_empty ( ) ) ;
1269+ let is_simple = !has_kwargs
1270+ && !code. flags . contains ( bytecode:: CodeFlags :: VARARGS )
1271+ && !code. flags . contains ( bytecode:: CodeFlags :: VARKEYWORDS )
1272+ && code. kwonlyarg_count == 0
1273+ && !code
1274+ . flags
1275+ . intersects ( bytecode:: CodeFlags :: GENERATOR | bytecode:: CodeFlags :: COROUTINE ) ;
1276+
1277+ if is_simple && nargs == code. arg_count as usize {
1278+ // FAST PATH: simple positional-only call, exact arg count.
1279+ // Move owned args directly into fastlocals — no clone needed.
1280+ let locals = if code. flags . contains ( bytecode:: CodeFlags :: NEWLOCALS ) {
1281+ ArgMapping :: from_dict_exact ( vm. ctx . new_dict ( ) )
1282+ } else {
1283+ ArgMapping :: from_dict_exact ( zelf. globals . clone ( ) )
1284+ } ;
1285+
1286+ let frame = Frame :: new (
1287+ code. to_owned ( ) ,
1288+ Scope :: new ( Some ( locals) , zelf. globals . clone ( ) ) ,
1289+ zelf. builtins . clone ( ) ,
1290+ zelf. closure . as_ref ( ) . map_or ( & [ ] , |c| c. as_slice ( ) ) ,
1291+ Some ( zelf. to_owned ( ) . into ( ) ) ,
1292+ vm,
1293+ )
1294+ . into_ref ( & vm. ctx ) ;
1295+
1296+ {
1297+ let fastlocals = unsafe { frame. fastlocals . borrow_mut ( ) } ;
1298+ for ( slot, arg) in fastlocals. iter_mut ( ) . zip ( args. drain ( ..nargs) ) {
1299+ * slot = Some ( arg) ;
1300+ }
1301+ }
1302+
1303+ if let Some ( cell2arg) = code. cell2arg . as_deref ( ) {
1304+ let fastlocals = unsafe { frame. fastlocals . borrow_mut ( ) } ;
1305+ for ( cell_idx, arg_idx) in cell2arg. iter ( ) . enumerate ( ) . filter ( |( _, i) | * * i != -1 ) {
1306+ let x = fastlocals[ * arg_idx as usize ] . take ( ) ;
1307+ frame. set_cell_contents ( cell_idx, x) ;
1308+ }
1309+ }
1310+
1311+ return vm. run_frame ( frame) ;
1312+ }
1313+
1314+ // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
1315+ let func_args = if has_kwargs {
1316+ FuncArgs :: from_vectorcall ( & args, nargs, kwnames)
1317+ } else {
1318+ args. truncate ( nargs) ;
1319+ FuncArgs :: from ( args)
1320+ } ;
1321+ zelf. invoke ( func_args, vm)
1322+ }
1323+
1324+ /// Vectorcall implementation for PyBoundMethod (PEP 590).
1325+ fn vectorcall_bound_method (
1326+ zelf_obj : & PyObject ,
1327+ mut args : Vec < PyObjectRef > ,
1328+ nargs : usize ,
1329+ kwnames : Option < & [ PyObjectRef ] > ,
1330+ vm : & VirtualMachine ,
1331+ ) -> PyResult {
1332+ let zelf: & Py < PyBoundMethod > = zelf_obj. downcast_ref ( ) . unwrap ( ) ;
1333+
1334+ // Insert self at front of existing Vec (avoids 2nd allocation).
1335+ // O(n) memmove is cheaper than a 2nd heap alloc+dealloc for typical arg counts.
1336+ args. insert ( 0 , zelf. object . clone ( ) ) ;
1337+ let new_nargs = nargs + 1 ;
1338+ zelf. function . vectorcall ( args, new_nargs, kwnames, vm)
1339+ }
1340+
12561341pub fn init ( context : & ' static Context ) {
12571342 PyFunction :: extend_class ( context, context. types . function_type ) ;
1343+ context
1344+ . types
1345+ . function_type
1346+ . slots
1347+ . vectorcall
1348+ . store ( Some ( vectorcall_function) ) ;
1349+
12581350 PyBoundMethod :: extend_class ( context, context. types . bound_method_type ) ;
1351+ context
1352+ . types
1353+ . bound_method_type
1354+ . slots
1355+ . vectorcall
1356+ . store ( Some ( vectorcall_bound_method) ) ;
1357+
12591358 PyCell :: extend_class ( context, context. types . cell_type ) ;
12601359}
0 commit comments