@@ -19,8 +19,8 @@ mod vm_ops;
1919use crate :: {
2020 AsObject , Py , PyObject , PyObjectRef , PyPayload , PyRef , PyResult ,
2121 builtins:: {
22- PyBaseExceptionRef , PyDictRef , PyInt , PyList , PyModule , PyStr , PyStrInterned , PyStrRef ,
23- PyTypeRef , code:: PyCode , pystr:: AsPyStr , tuple:: PyTuple ,
22+ PyBaseExceptionRef , PyDict , PyDictRef , PyInt , PyList , PyModule , PyStr , PyStrInterned ,
23+ PyStrRef , PyTypeRef , code:: PyCode , pystr:: AsPyStr , tuple:: PyTuple ,
2424 } ,
2525 codecs:: CodecsRegistry ,
2626 common:: { hash:: HashSecret , lock:: PyMutex , rc:: PyRc } ,
@@ -460,6 +460,42 @@ impl VirtualMachine {
460460 self . signal_rx = Some ( signal_rx) ;
461461 }
462462
463+ /// Execute Python bytecode (`.pyc`) from an in-memory buffer.
464+ ///
465+ /// When the RustPython CLI is available, `.pyc` files are normally executed by
466+ /// invoking `rustpython <input>.pyc`. This method provides an alternative for
467+ /// environments where the binary is unavailable or file I/O is restricted
468+ /// (e.g. WASM).
469+ ///
470+ /// ## Preparing a `.pyc` file
471+ ///
472+ /// First, compile a Python source file into bytecode:
473+ ///
474+ /// ```sh
475+ /// # Generate a .pyc file
476+ /// $ rustpython -m py_compile <input>.py
477+ /// ```
478+ ///
479+ /// ## Running the bytecode
480+ ///
481+ /// Load the resulting `.pyc` file into memory and execute it using the VM:
482+ ///
483+ /// ```no_run
484+ /// use rustpython_vm::Interpreter;
485+ /// Interpreter::without_stdlib(Default::default()).enter(|vm| {
486+ /// let bytes = std::fs::read("__pycache__/<input>.rustpython-313.pyc").unwrap();
487+ /// let main_scope = vm.new_scope_with_main().unwrap();
488+ /// vm.run_pyc_bytes(&bytes, main_scope);
489+ /// });
490+ /// ```
491+ pub fn run_pyc_bytes ( & self , pyc_bytes : & [ u8 ] , scope : Scope ) -> PyResult < ( ) > {
492+ let code = PyCode :: from_pyc ( pyc_bytes, Some ( "<pyc_bytes>" ) , None , None , self ) ?;
493+ self . with_simple_run ( "<source>" , |_module_dict| {
494+ self . run_code_obj ( code, scope) ?;
495+ Ok ( ( ) )
496+ } )
497+ }
498+
463499 pub fn run_code_obj ( & self , code : PyRef < PyCode > , scope : Scope ) -> PyResult {
464500 use crate :: builtins:: PyFunction ;
465501
@@ -500,6 +536,52 @@ impl VirtualMachine {
500536 }
501537 }
502538
539+ /// Run `run` with main scope.
540+ fn with_simple_run (
541+ & self ,
542+ path : & str ,
543+ run : impl FnOnce ( & Py < PyDict > ) -> PyResult < ( ) > ,
544+ ) -> PyResult < ( ) > {
545+ let sys_modules = self . sys_module . get_attr ( identifier ! ( self , modules) , self ) ?;
546+ let main_module = sys_modules. get_item ( identifier ! ( self , __main__) , self ) ?;
547+ let module_dict = main_module. dict ( ) . expect ( "main module must have __dict__" ) ;
548+
549+ // Track whether we set __file__ (for cleanup)
550+ let set_file_name = !module_dict. contains_key ( identifier ! ( self , __file__) , self ) ;
551+ if set_file_name {
552+ module_dict. set_item (
553+ identifier ! ( self , __file__) ,
554+ self . ctx . new_str ( path) . into ( ) ,
555+ self ,
556+ ) ?;
557+ module_dict. set_item ( identifier ! ( self , __cached__) , self . ctx . none ( ) , self ) ?;
558+ }
559+
560+ let result = run ( & module_dict) ;
561+
562+ self . flush_io ( ) ;
563+
564+ // Cleanup __file__ and __cached__ after execution
565+ if set_file_name {
566+ let _ = module_dict. del_item ( identifier ! ( self , __file__) , self ) ;
567+ let _ = module_dict. del_item ( identifier ! ( self , __cached__) , self ) ;
568+ }
569+
570+ result
571+ }
572+
573+ /// flush_io
574+ ///
575+ /// Flush stdout and stderr. Errors are silently ignored.
576+ fn flush_io ( & self ) {
577+ if let Ok ( stdout) = self . sys_module . get_attr ( "stdout" , self ) {
578+ let _ = self . call_method ( & stdout, identifier ! ( self , flush) . as_str ( ) , ( ) ) ;
579+ }
580+ if let Ok ( stderr) = self . sys_module . get_attr ( "stderr" , self ) {
581+ let _ = self . call_method ( & stderr, identifier ! ( self , flush) . as_str ( ) , ( ) ) ;
582+ }
583+ }
584+
503585 pub fn current_recursion_depth ( & self ) -> usize {
504586 self . recursion_depth . get ( )
505587 }
0 commit comments