本文深度讲解在github上收获20.8k Star的爆火项目—RustPython,文章和一般讲解文章不同,旨在让读者更好的理解 RustPython 的整体架构,所以顺序按概述、核心虚拟机、对象系统、标准库、编译管道、项目组织的结构走,单独读本文可能你并不能很好的理解,但是读过本文之后你在去精读 RustPython 你会发现更容易上手。
原文地址:RustPython
1. 概述
在编程语言的世界里,跨语言的融合与创新从未停止。而我们今天的主角RustPython就是一个完全用Rust语言实现的Python 3解释器,这一特性让它在众多Python实现中脱颖而出。RustPython的目标非常明确,它致力于提供一个完全的Python - 3环境,且完全由Rust实现,而非依赖CPython的绑定。同时,它追求一种简洁的实现方式,避免兼容性方面的hack手段。这种设计理念使得RustPython具有独特的优势,它可以作为一个库和shell环境使用,借助Rust的特性,让Python能够被用作Rust应用程序的编程语言,还能立即通过WebAssembly在浏览器中编译运行,让用户可以轻松地在浏览器中运行Python代码。
2. 核心虚拟机
2.1 虚拟机的作用与地位
在RustPython中,核心虚拟机(VM)扮演着至关重要的角色,它是执行Python字节码的核心组件,负责将编译生成的字节码转化为实际的操作并运行。rustpython-vm这个crate承担了运行虚拟机以执行Python指令的重要工作,vm/src目录包含了实现读取和评估循环的代码,这些循环用于获取和分发指令,是虚拟机运作的核心环节。
虚拟机的存在使得Python代码能够脱离源代码,以更高效的字节码形式被执行。对于程序设计人员来说,理解虚拟机的工作原理有助于深入掌握RustPython的运行机制,从而更好地进行代码优化和调试。
2.2 虚拟机的实现结构
从代码组织来看,vm/src目录下的结构清晰地反映了虚拟机的组成部分。其中,vm/src/builtins目录存放了用于表示不同Python对象及其方法的Rust代码,vm/src/stdlib则是用Rust实现的Python标准库模块。
虚拟机目前被实现为一个栈式机器,这意味着它使用一个栈来存储中间结果。这种设计在很多解释型语言的虚拟机中较为常见,栈式结构简单直观,便于实现和理解。
以下是一些与虚拟机相关的代码片段,能帮助我们更好地理解其结构:
在vm/Cargo.toml中,定义了rustpython-vm crate的相关信息和依赖:
[package]
name = "rustpython-vm"
description = "RustPython virtual machine."
include = ["src/**/*.rs", "Cargo.toml", "build.rs", "Lib/**/*.py"]
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[features]
default = ["***piler", "wasmbind", "stdio"]
stdio = []
importlib = []
encodings = ["importlib"]
vm-tracing-logging = []
flame-it = ["flame", "flamer"]
freeze-stdlib = ["encodings"]
jit = ["rustpython-jit"]
threading = ["rustpython-***mon/threading"]
***piler = ["parser", "codegen", "rustpython-***piler"]
ast = ["ruff_python_ast", "ruff_text_size"]
codegen = ["rustpython-codegen", "ast"]
parser = ["ast"]
serde = ["dep:serde"]
wasmbind = ["rustpython-***mon/wasm_js", "chrono/wasmbind", "wasm-bindgen"]
[dependencies]
rustpython-***piler = { workspace = true, optional = true }
rustpython-codegen = { workspace = true, optional = true }
rustpython-***mon = { workspace = true }
rustpython-derive = { workspace = true }
rustpython-jit = { workspace = true, optional = true }
# 其他依赖...
这些配置信息展示了虚拟机所依赖的其他组件,如编译器、代码生成器等,以及一些可选功能,如JIT编译、WebAssembly绑定等,这也体现了虚拟机在整个RustPython项目中的核心地位,需要与多个模块协同工作。
2.3 虚拟机的工作流程
虚拟机的工作流程大致可以分为几个关键步骤:接收字节码、解析字节码指令、执行指令并操作栈、处理异常等,流程如下所示:
当Python代码经过编译生成字节码后,虚拟机会获取这些字节码。然后,虚拟机通过一个循环来逐条读取字节码指令。对于每条指令,虚拟机进行解析,确定需要执行的操作,例如加载变量、执行算术运算、调用函数等。
在执行过程中,栈式机器的特性得到充分体现。例如,当执行加法运算时,虚拟机会从栈顶弹出两个操作数,进行加法运算后,再将结果压入栈中。这种基于栈的操作方式使得指令的实现相对简单,每条指令只需要关注对栈的操作即可。
以下是src/lib.rs中与虚拟机运行相关的代码片段,展示了虚拟机如何处理不同的运行模式:
fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
#[cfg(feature = "flame-it")]
let main_guard = flame::start_guard("RustPython main");
let scope = setup_main_module(vm)?;
if !vm.state.settings.safe_path {
// 处理路径相关设置
vm.run_code_string(
vm.new_scope_with_builtins(),
"import sys; sys.path.insert(0, '')",
"<embedded>".to_owned(),
)?;
}
// 处理site模块导入
let site_result = vm.import("site", 0);
if site_result.is_err() {
warn!(
"Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \
environment variable",
);
}
let is_repl = matches!(run_mode, RunMode::Repl);
if !vm.state.settings.quiet
&& (vm.state.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal()))
{
// 输出欢迎信息
eprintln!(
"Wel***e to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
env!("CARGO_PKG_VERSION")
);
eprintln!(
"RustPython {}.{}.{}",
vm::version::MAJOR,
vm::version::MINOR,
vm::version::MICRO,
);
eprintln!("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.");
}
let res = match run_mode {
RunMode::***mand(***mand) => {
debug!("Running ***mand {***mand}");
vm.run_code_string(scope.clone(), &***mand, "<stdin>".to_owned())
.map(drop)
}
RunMode::Module(module) => {
debug!("Running module {module}");
vm.run_module(&module)
}
RunMode::InstallPip(installer) => install_pip(installer, scope.clone(), vm),
RunMode::Script(script) => {
debug!("Running script {}", &script);
vm.run_script(scope.clone(), &script)
}
RunMode::Repl => Ok(()),
};
if is_repl || vm.state.settings.inspect {
shell::run_shell(vm, scope)?;
} else {
res?;
}
#[cfg(feature = "flame-it")]
{
main_guard.end();
if let Err(e) = write_profile(&vm.state.as_ref().settings) {
error!("Error writing profile information: {}", e);
}
}
Ok(())
}
这段代码展示了虚拟机在不同运行模式(如执行命令、模块、脚本、REPL等)下的处理逻辑。它首先设置主模块,处理路径和模块导入,然后根据不同的运行模式执行相应的操作,最后处理性能分析等后续工作。这清晰地体现了虚拟机作为核心执行组件的角色,能够根据不同的输入形式来执行相应的Python代码。
2.4 虚拟机的关键特性
RustPython的虚拟机具有一些关键特性,使得它能够高效、准确地执行Python代码。
- 它支持多种运行模式,如执行单个命令、运行模块、执行脚本以及交互式的REPL模式,这为用户提供了灵活的使用方式,满足不同场景下的需求。
- 虚拟机具有良好的可扩展性。通过
vm.add_native_module和vm.add_frozen等方法,可以向虚拟机添加自定义的原生模块和冻结的代码,这使得开发者能够根据自己的需求扩展RustPython的功能。
例如,在src/lib.rs的示例代码中:
use rustpython_vm::{pymodule, py_freeze};
fn main() {
rustpython::run(|vm| {
vm.add_native_module("my_mod".to_owned(), Box::new(my_mod::make_module));
vm.add_frozen(py_freeze!(source = "def foo(): pass", module_name = "other_thing"));
});
}
#[pymodule]
mod my_mod {
use rustpython_vm::builtins::PyStrRef;
#[pyfunction]
fn do_thing(x: i32) -> i32 {
x + 1
}
#[pyfunction]
fn other_thing(s: PyStrRef) -> (String, usize) {
let new_string = format!("hello from rust, {}!", s);
let prev_len = s.as_str().len();
(new_string, prev_len)
}
}
这段代码展示了如何向虚拟机添加自定义的原生模块和冻结的代码,体现了虚拟机的可扩展性。开发者可以通过这种方式将自己的Rust代码集成到RustPython中,为Python提供新的功能。
- 虚拟机还具备一些高级特性的支持,如可选的JIT(即时编译)功能。通过启用
jit特性,虚拟机可以尝试对热点代码进行即时编译,将字节码转化为机器码,从而提高代码的执行效率。虽然这一功能目前还处于实验阶段,但它展示了RustPython在性能优化方面的潜力。
2.5 虚拟机与其他组件的交互
虚拟机并非孤立存在,它需要与RustPython的其他组件紧密协作,才能完成Python代码的执行过程。
-
虚拟机与编译器关系密切。编译器负责将Python源代码编译为字节码,而虚拟机则负责执行这些字节码。在
rustpython-vm的依赖中,rustpython-***piler是一个可选依赖,当启用***piler特性时,虚拟机可以利用编译器来处理代码的编译过程。 -
虚拟机还与解析器(parser)和代码生成器(codegen)存在交互。解析器将Python源代码转换为抽象语法树(AST),代码生成器则将AST转换为字节码,虚拟机则接收并执行这些字节码。这种协作关系确保了从源代码到最终执行结果的完整流程。
-
虚拟机还需要与标准库和内置对象进行交互。
vm/src/stdlib目录下的标准库模块和vm/src/builtins目录下的内置对象为虚拟机提供了丰富的功能支持,使得Python代码能够调用各种标准函数和操作各种内置类型。
例如,当Python代码中调用print函数时,虚拟机会找到对应的内置函数实现,并执行相应的操作,这背后就涉及到虚拟机与内置对象模块的交互。
3. 对象系统
3.1 对象系统的核心概念
在Python中,有一个核心的理念:“一切皆对象”。RustPython完全遵循这一理念,其对象系统是实现这一理念的基础。在RustPython中,所有的数据类型,无论是基本类型(如整数、字符串)还是复杂类型(如列表、字典、函数等),都被表示为对象。
对象系统的核心作用是统一管理和操作这些不同类型的数据,使得它们能够在虚拟机中被一致地处理。对于程序设计人员来说,理解RustPython的对象系统有助于理解Python中各种数据类型的行为和交互方式。
3.2 对象的核心实现
RustPython中对象的核心实现位于vm/src/object/core.rs文件中。在这个文件中,定义了Python对象的基本结构和行为,为所有具体的对象类型提供了统一的接口。
在Rust中,由于其强类型和所有权特性,实现一个灵活的对象系统需要一些特殊的设计。RustPython通过定义PyObject trait或类似的结构来实现对象的多态性,使得不同类型的对象能够被统一处理。
以下是对对象核心实现的一些关键方面的分析:
-
对象的表示:在RustPython中,对象通常被表示为一种引用类型,可能使用
Rc(引用计数)或Arc(原子引用计数)来管理内存,确保对象在适当的时候被释放。这种内存管理方式与Python的垃圾回收机制类似,通过引用计数来跟踪对象的使用情况。 -
对象的类型信息:每个对象都包含类型信息,这使得虚拟机能够知道对象的类型,并调用相应的方法。类型信息通常包括对象的名称、方法列表、属性等。
-
通用方法:核心对象定义了一些通用的方法,如
__repr__、__str__、__add__等,这些方法为对象提供了基本的行为。具体的对象类型可以重写这些方法,以实现特定的功能。
3.3 内置对象的实现
vm/src/builtins目录包含了各种Python内置对象及其方法的Rust实现。这些内置对象包括整数(int)、字符串(str)、列表(list)、字典(dict)、函数(function)等,它们是Python编程的基础。
每种内置对象都有其特定的实现方式,以满足Python语言规范对该类型的要求。
以整数对象为例,在RustPython中,整数对象需要支持各种算术运算、比较操作等。其实现可能基于Rust的整数类型或大整数库,以支持任意精度的整数运算,符合Python中整数的特性。
以下是一个假设的整数对象方法实现的示例(基于RustPython的设计理念):
// 假设的整数对象结构
struct PyInt {
value: i64, // 或者使用大整数类型
}
impl PyInt {
// 加法运算
fn __add__(&self, other: &dyn PyObject) -> Result<Box<dyn PyObject>, Error> {
if let Some(other_int) = other.downcast_ref::<PyInt>() {
Ok(Box::new(PyInt {
value: self.value + other_int.value,
}))
} else {
Err(Error::TypeError("Unsupported operand type for +".to_string()))
}
}
// 其他方法...
}
这个示例展示了整数对象的加法方法如何实现,它首先检查另一个操作数是否也是整数对象,如果是则进行加法运算,否则返回类型错误。这符合Python中对整数加法的行为定义。
对于字符串对象,其实现需要支持字符串的拼接、切片、查找等操作。Rust的字符串处理能力为实现这些功能提供了良好的基础,但需要适配Python的字符串语义,如Unicode支持、不可变性等。
列表对象则需要支持动态添加、删除元素,切片操作等。其内部可能基于Rust的Vec数据结构来实现,以高效地管理元素的存储和访问。
3.4 对象的方法调用机制
在RustPython中,对象的方法调用是一个复杂但关键的过程。当调用一个对象的方法时,虚拟机会经历以下几个步骤:
-
查找方法:虚拟机会根据方法名在对象的类型信息中查找对应的方法。如果找不到,则会沿着继承链向上查找,直到找到为止或确定方法不存在。
-
参数处理:方法通常需要接收参数,虚拟机会对传入的参数进行处理,包括类型检查、参数数量匹配等,以确保方法能够正确接收和处理参数。
-
执行方法:找到方法并处理好参数后,虚拟机会执行方法的代码。方法的代码可能是用Rust实现的内置方法,也可能是用Python编写的字节码。
-
返回结果:方法执行完成后,会返回一个结果对象,虚拟机会将这个结果对象压入栈中,供后续操作使用。
例如,当调用列表的append方法时,虚拟机会在列表对象的类型信息中找到append方法,检查传入的参数是否符合要求,然后执行append方法的代码,将元素添加到列表中,并返回None对象。
3.5 对象的内存管理
RustPython的对象系统采用了引用计数的内存管理方式,这与CPython类似。每个对象都有一个引用计数,当对象被创建或被引用时,引用计数增加;当对象的引用被释放时,引用计数减少。当引用计数变为零时,对象所占用的内存会被释放。
这种内存管理方式简单有效,能够及时释放不再使用的对象,避免内存泄漏。但它也存在一些局限性,例如无法处理循环引用的情况。为了解决这个问题,RustPython可能会在未来引入垃圾回收机制,以补充引用计数的不足。
在Rust中实现引用计数可以使用Rc或Arc类型。Rc适用于单线程环境,而Arc适用于多线程环境。RustPython根据具体的使用场景选择合适的引用计数类型,以确保内存管理的效率和正确性。
3.6 自定义对象
除了内置对象,RustPython也支持用户自定义对象,这通过类(class)来实现。当用户定义一个类时,RustPython会创建一个对应的类型对象,该类型对象包含了类的属性和方法。当创建类的实例时,实例对象会包含该类的属性,并可以调用类中定义的方法。
自定义对象的实现基于RustPython的对象系统框架,遵循与内置对象相同的接口和行为规范。这使得自定义对象能够与内置对象无缝交互,共同构成Python丰富的数据类型系统。
例如,用户定义一个Person类:
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"
在RustPython中,这个类会被表示为一个类型对象,Person类的实例则是该类型对象的实例。__init__方法用于初始化实例的属性,greet方法则是实例的一个方法,可以被调用以返回问候语。
虚拟机会为Person类创建相应的类型信息,包含__init__和greet等方法。当创建Person实例时,会分配相应的内存,并调用__init__方法进行初始化。当调用greet方法时,虚拟机会按照方法调用机制查找并执行该方法。
综上所述,RustPython的对象系统是其核心组成部分,它实现了Python中“一切皆对象”的理念,为各种数据类型提供了统一的管理和操作方式。从内置对象到自定义对象,从方法调用到内存管理,对象系统的设计和实现确保了RustPython能够正确、高效地执行Python代码,为用户提供与标准Python相似的编程体验。同时,借助Rust的特性,RustPython的对象系统在安全性和性能方面也具有一定的优势。
4. 标准库:兼容与扩展的平衡之道
Python的强大之处不仅在于其简洁的语法,更在于其丰富的标准库。RustPython作为一个兼容Python 3的解释器,在标准库的实现上采取了"拿来主义"与"原生实现"相结合的策略,既保证了兼容性,又利用Rust的特性提升了性能和安全性。
4.1 标准库的双重结构
RustPython的标准库由两个主要部分组成:从CPython移植的纯Python代码和用Rust原生实现的核心模块。这种混合架构既最大化了兼容性,又充分发挥了Rust的优势。
4.1.1 从CPython移植的纯Python代码(Lib目录)
Lib目录是RustPython兼容性的关键,它直接复制了CPython标准库中的纯Python代码。这一做法的优势显而易见:无需重新实现大量成熟的代码,就能快速获得完整的标准库支持。
# 来自 .github/copilot-instructions.md
- `Lib/` - CPython's standard library in Python (copied from CPython).
**IMPORTANT**: Do not edit this directory directly; The only allowed operation is copying files from CPython.
这一目录的代码完全来自CPython,并且有严格的规定:不允许直接修改,只能从CPython复制。这确保了代码的兼容性和稳定性。例如,Lib/test目录包含了大量的Python测试用例,这些测试用例可以直接用于验证RustPython的兼容性。
但这种"拿来主义"也带来了挑战。由于RustPython的实现细节与CPython存在差异,部分从CPython移植的代码可能无法直接运行。为此,RustPython团队采取了一些临时措施:
# 来自 .github/copilot-instructions.md
- Tests in `Lib/test` often use one of the following markers:
- Add a `# TODO: RUSTPYTHON` ***ment when modifications are made
- `unittest.skip("TODO: RustPython <reason>")`
- `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` ***ment
这些标记帮助开发者跟踪哪些测试用例需要适配RustPython,随着项目的发展,这些标记会逐渐被移除,以实现更完整的兼容性。
4.1.2 Rust原生实现的标准库模块
除了移植的纯Python代码,RustPython还有一部分标准库是用Rust原生实现的。这些模块主要分为两类:核心必需模块和扩展模块。
- 核心必需模块(vm/src/stdlib)
这些模块是Python运行所必需的,使用Rust实现可以提高性能和可靠性。
# 来自 DEVELOPMENT.md
The `vm/src` directory also contains the implementation of the
Python Standard Library modules in Rust (`vm/src/stdlib`).
例如,vm/src/stdlib目录下实现了sys、os等核心模块。这些模块与Python的运行时紧密相关,用Rust实现可以更好地与虚拟机集成,提升性能。
- 非核心扩展模块(stdlib目录)
这些模块是对标准库的补充,虽然不是运行Python所必需的,但能提供丰富的功能。
# 来自 .github/copilot-instructions.md
- `stdlib/` - Non-essential Python standard library modules implemented in Rust
(useful but not required for core functionality)
这些模块包括一些工具类库和扩展功能,用Rust实现可以利用Rust的内存安全和并发特性,提供更可靠的功能。
4.2 标准库的实现细节
RustPython的标准库实现不仅仅是简单的功能移植,还需要考虑与Rust生态的集成和性能优化。
4.2.1 与Rust类型系统的桥接
Python的动态类型系统与Rust的静态类型系统存在显著差异,标准库的实现需要处理这种差异。RustPython通过vm/src/convert.rs中的类型转换机制,实现了Python对象和Rust类型之间的无缝转换。
// 类型转换相关的核心trait(简化版)
pub trait TryFromObject: Sized {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self>;
}
pub trait TryFromBorrowedObject: Sized {
fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<Self>;
}
这些trait允许Rust类型定义如何从Python对象转换而来,使得标准库函数可以方便地接收Python传递的参数,并返回Python可以理解的结果。
4.2.2 模块注册机制
Rust实现的标准库模块需要注册到虚拟机中,才能被Python代码导入和使用。RustPython提供了一套宏系统来简化这一过程。
// 来自 vm/src/lib.rs
pub use rustpython_derive::*;
rustpython_derive crate提供了#[pymodule]等宏,允许开发者用声明式的方式定义Python模块。例如:
#[pymodule]
mod sys {
use rustpython_vm::prelude::*;
#[pyfunction]
fn getsizeof(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
Ok(vm.size_of(&obj))
}
// 其他函数和变量...
}
通过这种方式,Rust函数可以被注册为Python模块的函数,实现了Rust代码向Python的暴露。
4.2.3 标准库的初始化
在虚拟机启动时,需要初始化标准库。这一过程在InterpreterConfig中完成:
// 来自 src/lib.rs
let mut config = InterpreterConfig::new().settings(settings);
#[cfg(feature = "stdlib")]
{
config = config.init_stdlib();
}
init_stdlib方法会注册所有Rust实现的标准库模块,并确保Lib目录下的纯Python模块可以被正确导入。
4.2.4 标准库的测试
为了确保标准库的正确性,RustPython采用了多层次的测试策略:
- Rust单元测试:测试Rust实现的内部逻辑。
- Python测试用例:运行
Lib/test目录下的测试用例,验证兼容性。 - 集成测试:在
extra_tests目录下提供额外的集成测试。
# 来自 .github/copilot-instructions.md
# Run Python snippets tests
cd extra_tests
pytest -v
# Run the Python test module
cargo run --release -- -m test ${TEST_MODULE}
cargo run --release -- -m test test_unicode # to test test_unicode.py
这些测试确保了标准库的实现既符合Python的规范,又能在RustPython环境下正确运行。
4.3 标准库的挑战与解决方案
在实现标准库的过程中,RustPython团队面临着诸多挑战,其中最主要的是兼容性和性能之间的平衡。
4.3.1 兼容性挑战
Python标准库的某些部分依赖于CPython的内部实现细节,这给RustPython的兼容带来了困难。例如,某些模块可能使用了CPython的C API,而这些API在RustPython中并不存在。
为了解决这一问题,RustPython采取了以下策略:
- 模拟CPython的行为:对于依赖CPython内部实现的功能,RustPython会尽可能模拟其行为,即使内部实现方式不同。
- 提供替代实现:对于无法直接模拟的功能,RustPython会提供功能相当的替代实现。
- 逐步适配:随着项目的发展,逐步提高兼容性,移除临时的适配代码。
4.3.2 性能优化
虽然Rust本身比C有更好的内存安全特性,但解释器的性能仍然是一个挑战。RustPython在标准库中采用了多种优化策略:
- 利用Rust的静态分发:对于热点函数,使用Rust的静态分发特性,减少动态调度的开销。
- 内存池:对于频繁创建和销毁的对象(如小整数、短字符串),使用内存池减少内存分配的开销。
- 惰性初始化:某些资源密集型的功能采用惰性初始化,只有在首次使用时才分配资源。
4.3.3 扩展与定制
RustPython的标准库设计允许用户方便地扩展和定制。通过add_native_module方法,用户可以向虚拟机添加自己的Rust实现的模块:
// 来自 src/lib.rs 的示例
vm.add_native_module("my_mod".to_owned(), Box::new(my_mod::make_module));
这种灵活性使得RustPython可以作为一个嵌入式解释器,为Rust应用程序提供Python脚本支持,同时通过自定义模块实现Rust和Python的无缝交互。
5. 编译管道:从源代码到执行的旅程
编译管道是解释器的核心部分,它负责将Python源代码转换为可执行的字节码,并最终在虚拟机中执行。RustPython的编译管道借鉴了CPython的设计,但采用Rust重新实现,带来了更好的安全性和可维护性。
5.1 编译管道的整体架构
RustPython的编译管道可以分为四个主要阶段:词法分析(Lexing)、语法分析(Parsing)、语义分析与字节码生成(Code Generation),以及字节码执行(Execution)。
# 来自 DEVELOPMENT.md
Together, these crates provide the functions of a programming language and
enable a line of code to go through a series of steps:
- parse the line of source code into tokens
- determine if the tokens are valid syntax
- create an Abstract Syntax Tree (AST)
- ***pile the AST into bytecode
- execute the bytecode in the virtual machine (VM).
这一流程与大多数编译器的工作流程相似,但针对Python的动态特性做了特殊处理。
5.2 词法分析:将源代码转换为令牌
词法分析是编译过程的第一步,它将原始的源代码字符串转换为有意义的令牌(tokens)。
5.2.1 词法分析器的实现
RustPython的词法分析器由rustpython-parser crate实现,位于***piler/parser/src目录下。
# 来自 DEVELOPMENT.md
- `rustpython-parser` (implementation in `***piler/parser/src`)
词法分析器的核心任务是识别Python的关键字、标识符、字面量、运算符等。例如,对于代码x = 42 + y,词法分析器会生成以下令牌序列:Identifier(x), Assign, Integer(42), Plus, Identifier(y)。
5.2.2 处理Python的特殊语法
Python的语法有一些特殊之处,如缩进敏感、多行语句等,这给词法分析带来了挑战。
RustPython的词法分析器通过跟踪缩进级别来处理缩进敏感的语法。它会将缩进的变化转换为Indent和Dedent令牌,使得后续的语法分析可以正确理解代码块的结构。
对于多行语句,词法分析器会处理反斜杠\和括号来确定语句的结束位置,确保多行语句被正确解析为一个整体。
5.3 语法分析:构建抽象语法树(AST)
语法分析器(Parser)接收词法分析器生成的令牌序列,根据Python的语法规则构建抽象语法树(AST)。AST是源代码的结构化表示,它捕获了代码的语法结构和语义信息。
5.3.1 AST的结构
RustPython的AST定义在***piler/ast/src目录下,使用Rust的结构体和枚举来表示不同的Python语法结构。
# 来自 DEVELOPMENT.md
- `***piler/src`: python ***pilation to bytecode
- `core/src`: python bytecode representation in rust structures
- `parser/src`: python lexing, parsing and ast
例如,表达式、语句、函数定义等都有对应的AST节点类型:
// 简化的AST节点示例
enum Expr {
Constant(Constant),
Name(Name),
BinOp(BinOp),
// 其他表达式类型...
}
enum Stmt {
Assign(Assign),
FunctionDef(FunctionDef),
If(If),
// 其他语句类型...
}
这些AST节点不仅包含了语法信息,还包含了位置信息(行号、列号),这对于错误提示和调试非常有用。
5.3.2 语法错误处理
语法分析器在遇到不符合Python语法规则的令牌序列时,会生成语法错误。RustPython的语法分析器会尽可能提供有用的错误信息,包括错误位置和可能的修复建议。
例如,对于代码if x = 5:,语法分析器会检测到条件表达式中使用了赋值运算符=而不是比较运算符==,并生成相应的错误提示。
5.3.3 代码生成辅助
除了构建AST,语法分析器还会进行一些初步的语义分析,为后续的字节码生成做准备。例如,它会检查变量的使用是否符合Python的作用域规则,函数调用的参数数量是否正确等。
5.4 语义分析与字节码生成
语义分析与字节码生成是编译管道的核心阶段,它将AST转换为虚拟机可以执行的字节码。这一阶段由rustpython-***piler crate实现。
# 来自 DEVELOPMENT.md
The `rustpython-***piler` crate's purpose is to transform the AST (Abstract Syntax
Tree) to bytecode. The implementation of the ***piler is found in the
`***piler/src` directory.
5.4.1 符号表与作用域管理
在生成字节码之前,编译器需要构建符号表(Symbol Table)来跟踪变量的作用域和绑定信息。这对于正确生成访问变量的字节码至关重要。
Python的作用域规则相对复杂,包括全局作用域、局部作用域、嵌套作用域等。RustPython的编译器会为每个函数和类创建一个符号表,记录变量的声明和使用情况。
例如,对于嵌套函数:
def outer():
x = 10
def inner():
print(x)
inner()
编译器会识别出inner函数中的x是来自outer函数的非局部变量,并生成相应的字节码来正确访问这个变量。
5.4.2 字节码的表示
Python的字节码是一种抽象的机器指令,它由操作码(opcode)和操作数(operand)组成。RustPython在***piler/core/src/bytecode.rs中定义了字节码的表示。
# 来自 DEVELOPMENT.md
Implementation of bytecode structure in Rust is found in the `***piler/core/src`
directory. `***piler/core/src/bytecode.rs` contains the representation of
instructions and operations in Rust.
字节码指令的定义类似于:
// 简化的字节码指令示例
#[derive(Debug, Clone, PartialEq)]
pub enum OpCode {
LOAD_CONST(usize), // 加载常量,操作数是常量池索引
LOAD_FAST(usize), // 加载局部变量,操作数是局部变量索引
STORE_FAST(usize), // 存储局部变量,操作数是局部变量索引
BINARY_ADD, // 加法运算
// 其他指令...
}
每个指令都有特定的语义,虚拟机通过执行这些指令来完成相应的操作。
5.4.3 从AST到字节码的转换
编译器遍历AST,为每个节点生成相应的字节码序列。这一过程需要考虑Python的各种语言特性,如动态类型、异常处理、生成器等。
以简单的加法表达式a + b为例,编译器会生成以下字节码:
LOAD_FAST a # 加载变量a到栈顶
LOAD_FAST b # 加载变量b到栈顶
BINARY_ADD # 弹出栈顶的两个值,相加后将结果压入栈顶
对于更复杂的结构,如条件语句:
if x > 5:
print("x is large")
else:
print("x is small")
编译器会生成包含条件跳转的字节码:
LOAD_FAST x # 加载x
LOAD_CONST 5 # 加载常量5
***PARE_OP GT # 比较x > 5,结果压栈
POP_JUMP_IF_FALSE else_label # 如果结果为假,跳转到else分支
LOAD_GLOBAL print # 加载print函数
LOAD_CONST "x is large"
CALL_FUNCTION 1 # 调用print函数
JUMP end_label # 跳转到结束
else_label:
LOAD_GLOBAL print
LOAD_CONST "x is small"
CALL_FUNCTION 1
end_label:
LOAD_CONST None
RETURN_VALUE
这种字节码生成过程需要深入理解Python的语义,确保生成的字节码能够正确实现源代码的逻辑。
5.4.4 字节码优化
为了提高执行效率,RustPython的编译器会对生成的字节码进行一些优化。虽然目前的优化相对简单,但为未来更复杂的优化奠定了基础。
常见的优化包括:
- 常量折叠:在编译时计算常量表达式的值,如
2 + 3会被直接替换为5。 - 空操作消除:移除无效的空操作,如连续的
NOP指令。 - 局部变量优化:优化局部变量的访问,减少栈操作。
这些优化虽然简单,但能显著提高某些代码的执行效率。
5.5 字节码执行
字节码的执行是编译管道的最后阶段,由RustPython的虚拟机负责。虚拟机是一个栈式机器,它通过操作栈来执行字节码指令。
5.5.1 虚拟机的执行循环
虚拟机的核心是一个执行循环,它不断地从字节码中取出指令并执行:
// 简化的虚拟机执行循环
fn run_code(&self, code: &CodeObject, frame: &mut Frame) -> PyResult {
let mut pc = 0; // 程序计数器,指向当前要执行的指令
while pc < code.bytecode.len() {
let opcode = &code.bytecode[pc];
pc += 1; // 前进到下一条指令
self.execute_opcode(opcode, frame, &mut pc)?;
}
Ok(())
}
execute_opcode方法根据指令类型执行相应的操作,如加载变量、执行运算、调用函数等。
5.5.2 栈操作
栈式虚拟机的大部分操作都围绕着操作栈进行。例如,LOAD_FAST指令将变量的值压入栈顶,BINARY_ADD指令弹出栈顶的两个值,执行加法后将结果压回栈顶。
操作栈的实现通常使用一个动态数组,在RustPython中,栈的定义类似于:
// 简化的栈定义
pub struct Frame {
stack: Vec<PyObjectRef>,
// 其他帧信息...
}
impl Frame {
pub fn push(&mut self, value: PyObjectRef) {
self.stack.push(value);
}
pub fn pop(&mut self) -> PyObjectRef {
self.stack.pop().expect("Stack underflow")
}
// 其他栈操作方法...
}
栈操作的效率直接影响虚拟机的性能,Rust的Vec类型提供了高效的栈操作支持。
5.5.3 异常处理
Python的异常处理机制在字节码层面通过SETUP_EXCEPT、RAISE_VARARGS等指令实现。当发生异常时,虚拟机会查找当前调用栈中最近的异常处理块,并跳转到相应的代码执行。
RustPython的虚拟机在执行过程中会维护一个异常状态,当执行RAISE_VARARGS指令时,会设置这个状态,并开始查找异常处理块。
// 简化的异常处理示例
fn execute_raise(&mut self, frame: &mut Frame) -> PyResult {
let exc = frame.pop();
// 设置当前异常
self.set_exception(exc);
// 查找异常处理块
self.find_exception_handler(frame)
}
这种异常处理机制确保了Python的try...except...finally语句能够正确工作。
5.5.4 函数调用与栈帧
函数调用是通过CALL_FUNCTION指令实现的。当执行函数调用时,虚拟机会创建一个新的栈帧(Frame),用于存储函数的局部变量、操作栈等信息。
// 简化的函数调用示例
fn execute_call_function(&mut self, frame: &mut Frame, arg_count: usize) -> PyResult {
// 从栈中弹出参数和函数对象
let args = (0..arg_count).map(|_| frame.pop()).rev().collect();
let func = frame.pop();
// 创建新的栈帧
let new_frame = self.create_frame(&func, args);
// 执行函数
let result = self.run_frame(new_frame);
// 将结果压入调用者的栈
frame.push(result?);
Ok(())
}
栈帧的管理是虚拟机的核心功能之一,它确保了函数调用的正确嵌套和变量作用域的隔离。
5.6 即时编译(JIT)的实验性支持
为了进一步提高性能,RustPython正在实验性地支持即时编译(JIT)技术。JIT编译器可以将频繁执行的字节码转换为机器码,从而显著提高执行速度。
# 来自 .github/copilot-instructions.md
- `jit/` - Experimental JIT ***piler implementation
RustPython的JIT实现基于Cranelift代码生成器,这是一个由Mozilla开发的轻量级代码生成库。JIT模块的Cargo.toml文件显示了相关依赖:
# 来自 jit/Cargo.toml
cranelift = "0.119"
cranelift-jit = "0.119"
cranelift-module = "0.119"
目前,JIT支持还处于实验阶段,只实现了部分功能。但这一方向为RustPython未来的性能提升提供了可能。
6. 项目组织:模块化与可扩展性的工程实践
一个大型开源项目的组织架构直接影响其可维护性和可扩展性。RustPython采用了模块化的设计理念,将整个项目分为多个功能明确的子 crate,每个 crate 负责特定的功能。这种组织结构使得代码更易于理解、测试和扩展。
6.1 项目整体结构
RustPython的代码库包含多个子目录,每个目录对应一个或多个 crate,负责解释器的不同部分。
# 来自 .github/copilot-instructions.md
## Repository Structure
- `src/` - Top-level code for the RustPython binary
- `vm/` - The Python virtual machine implementation
- `builtins/` - Python built-in types and functions
- `stdlib/` - Essential standard library modules implemented in Rust, required to run the Python core
- `***piler/` - Python ***piler ***ponents
- `parser/` - Parser for converting Python source to AST
- `core/` - Bytecode representation in Rust structures
- `codegen/` - AST to bytecode ***piler
- `Lib/` - CPython's standard library in Python (copied from CPython)
- `derive/` - Rust macros for RustPython
- `***mon/` - ***mon utilities
- `extra_tests/` - Integration tests
这一结构清晰地划分了项目的各个组成部分,使得开发者可以快速定位到自己感兴趣的模块。
6.2 核心 crate 解析
6.2.1 rustpython-vm:虚拟机核心
vm/目录包含了RustPython的虚拟机实现,是整个项目的核心。
# 来自 DEVELOPMENT.md
The `rustpython-vm` crate has the important job of running the virtual machine that
executes Python's instructions. The `vm/src` directory contains code to
implement the read and evaluation loop that fetches and dispatches
instructions.
vm/src/lib.rs是虚拟机的入口点,定义了VirtualMachine、Interpreter等核心结构:
// 来自 vm/src/lib.rs
pub use self::vm::{Context, Interpreter, Settings, VirtualMachine};
虚拟机的主要功能包括:
- 执行字节码指令
- 管理对象和内存
- 处理异常
- 实现Python的内置类型和函数(
vm/src/builtins) - 提供核心标准库模块(
vm/src/stdlib)
6.2.2 编译器相关 crate
编译器相关的功能被分为多个 crate,协同工作完成从源代码到字节码的转换。
- rustpython-parser:负责词法分析和语法分析,生成AST。
# 来自 DEVELOPMENT.md
- `rustpython-parser` (implementation in `***piler/parser/src`)
- rustpython-***piler-core:定义字节码的表示形式。
# 来自 ***piler/codegen/Cargo.toml
rustpython-***piler-core = { workspace = true }
- rustpython-codegen:将AST转换为字节码。
# 来自 .github/copilot-instructions.md
- `***piler/` - Python ***piler ***ponents
- `codegen/` - AST to bytecode ***piler
- rustpython-***piler:整合上述组件,提供完整的编译功能。
# 来自 DEVELOPMENT.md
- `rustpython-***piler` (implementation in `***piler/src`)
这些 crate 之间通过清晰的接口交互,例如rustpython-codegen依赖rustpython-***piler-core来使用字节码的定义,rustpython-***piler则依赖rustpython-parser和rustpython-codegen来完成整个编译过程。
6.2.3 辅助 crate
除了核心的虚拟机和编译器,RustPython还有多个辅助 crate,提供各种支持功能。
- rustpython-derive:提供宏定义,简化Python模块和函数的注册。
# 来自 vm/src/lib.rs
#[macro_use]
extern crate rustpython_derive;
- rustpython-***mon:提供通用工具函数和类型。
# 来自 vm/src/lib.rs
pub use rustpython_***mon as ***mon;
- rustpython-literal:处理Python字面量的解析和转换。
# 来自 ***piler/codegen/Cargo.toml
rustpython-literal = {workspace = true }
这些辅助 crate 提取了通用功能,避免了代码重复,提高了代码的可维护性。
6.2.4 应用与测试相关目录
-
src/:包含RustPython的命令行入口点,负责解析命令行参数、设置环境等。
// 来自 src/lib.rs
/// The main cli of the `rustpython` interpreter.
pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
// 解析命令行参数、初始化虚拟机、执行相应操作
}
-
Lib/:包含从CPython移植的纯Python标准库。 -
extra_tests/:包含额外的集成测试和代码片段。
# 来自 architecture/architecture.md
## Additional packages
### extra_tests
Integration and snippets that test for additional edge-cases, implementation specific functionality and bug report snippets.
-
wasm/:提供WebAssembly支持,使得RustPython可以在浏览器中运行。
# 来自 README.md
## ***piling to WebAssembly
[See this doc](wasm/README.md)
-
example_projects/:提供示例项目,展示如何在Rust应用中嵌入RustPython。
# 来自 example_projects/aheui-rust.md
This crate shows you how to embed an entire Python project into bytecode and ship it.
6.3 跨平台支持
RustPython的项目组织也考虑了跨平台支持,特别是WebAssembly平台。
6.3.1 WebAssembly支持
wasm/目录包含了WebAssembly相关的代码和配置,使得RustPython可以被编译为WebAssembly,在浏览器中运行。
# 来自 wasm/notebook/README.md
The Notebook loads python in your browser (so you don't have to install it) then let yous play with those languages.
RustPython提供了两个WebAssembly演示:
- 在线演示页面
- RustPython Notebook
这些演示展示了RustPython在Web环境中的应用潜力。
6.3.2 条件编译
Rust的条件编译特性被广泛用于处理不同平台之间的差异。例如,在vm/src/lib.rs中:
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
pub mod ospath;
#[cfg(windows)]
pub mod windows;
这种条件编译确保了特定平台的代码只会在相应的平台上被编译,提高了代码的可移植性。
6.4 构建系统与依赖管理
RustPython使用Cargo作为构建系统和依赖管理器,通过Cargo.toml和Cargo.lock文件管理项目的依赖和构建配置。
6.4.1 工作区配置
RustPython采用Cargo工作区(workspace)来管理多个相关的crate。根目录下的Cargo.toml定义了工作区的成员:
# 根目录 Cargo.toml(简化)
[workspace]
members = [
"vm",
"***piler",
"***piler/parser",
"***piler/codegen",
"derive",
"***mon",
"stdlib",
"jit",
# 其他成员...
]
这种配置使得可以一次性构建所有相关的crate,简化了开发流程。
6.4.2 特性标志(Feature Flags)
RustPython使用Cargo的特性标志来控制条件编译,允许用户根据需求选择不同的功能组合。
例如,vm/Cargo.toml中定义了多个特性:
# 来自 vm/Cargo.toml
[features]
default = ["***piler", "wasmbind", "stdio"]
stdio = []
importlib = []
encodings = ["importlib"]
vm-tracing-logging = []
flame-it = ["flame", "flamer"]
freeze-stdlib = ["encodings"]
jit = ["rustpython-jit"]
threading = ["rustpython-***mon/threading"]
***piler = ["parser", "codegen", "rustpython-***piler"]
# 其他特性...
这些特性允许用户启用或禁用某些功能,如JIT支持、线程支持、标准库冻结等,使得RustPython可以根据不同的应用场景进行定制。
6.4.3 依赖管理
RustPython严格管理依赖项,确保项目的稳定性和安全性。所有依赖项的版本都在Cargo.toml中明确指定,并且通过Cargo.lock文件锁定具体的版本,避免因依赖项更新而导致的兼容性问题。
同时,RustPython优先使用稳定的依赖项版本,并定期更新依赖项以获取安全补丁和性能改进。
6.5 开发与贡献支持
RustPython的项目组织也考虑了开发者的体验,提供了丰富的文档和工具支持,方便开发者贡献代码。
6.5.1 文档
项目提供了详细的文档,帮助开发者了解项目的架构和开发流程:
# 来自 .github/copilot-instructions.md
## Documentation
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
- Generate documentation with `cargo doc --no-deps --all`
- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/)
这些文档涵盖了从项目概述到详细开发指南的各个方面,为新开发者提供了入门帮助。
6.5.2 测试基础设施
RustPython拥有完善的测试基础设施,包括单元测试、集成测试和Python兼容性测试:
# 来自 .github/copilot-instructions.md
# Run Rust unit tests
cargo test --workspace --exclude rustpython_wasm
# Run Python snippets tests
cd extra_tests
pytest -v
# Run the Python test module
cargo run --release -- -m test ${TEST_MODULE}
这些测试确保了代码的质量和兼容性,也为开发者提供了验证自己修改的方法。
6.5.3 代码风格与规范
为了保持代码的一致性,RustPython采用了Rust社区的代码风格,并使用rustfmt和clippy等工具进行代码格式化和 lint 检查。
项目的DEVELOPMENT.md文档中也包含了代码风格的指导,帮助开发者写出符合项目规范的代码。
6.6 项目的可扩展性
RustPython的模块化设计使其具有良好的可扩展性。开发者可以通过以下方式扩展RustPython的功能:
- 添加新的标准库模块:可以在
stdlib/目录下实现新的标准库模块,或在vm/src/stdlib中添加核心模块。 - 扩展编译器:可以修改
***piler/目录下的代码,添加新的优化或支持新的语法特性。 - 增强虚拟机:可以在
vm/目录下添加新的内置类型或函数,或优化虚拟机的执行效率。 - 集成新的功能:如JIT编译器的进一步开发,或添加新的平台支持。
这种可扩展性使得RustPython能够不断发展,逐步实现对Python的完全兼容,并在性能和功能上不断提升。
总结
RustPython作为一个用Rust编写的Python解释器,在标准库实现、编译管道设计和项目组织方面都展现了出色的工程实践。通过混合使用从CPython移植的纯Python代码和Rust原生实现,RustPython在保证兼容性的同时,充分发挥了Rust的性能和安全优势。其编译管道借鉴了传统编译器的设计,将源代码逐步转换为可执行的字节码,并通过实验性的JIT支持探索进一步的性能提升。项目的模块化组织结构则确保了代码的可维护性和可扩展性,为未来的发展奠定了坚实的基础。随着RustPython的不断发展,我们有理由相信它将成为Python生态系统中一个重要的补充,为Python带来更好的性能、安全性和跨平台支持,同时也为Rust的应用开辟新的领域。如果你看到这里忍不住想要体验一下这款全新的python解释器,那你可以点击文章开头的链接,进入仓库的主页,拉去源码进行试验,也祝你有一个全新的体验,同时如果你感觉本篇文章不错的话别忘了一键三连哦。
想了解更多关于Rust语言的知识及应用,可前往华为开放原子旋武开源社区(https://xuanwu.openatom.***/),了解更多资讯~