docs(objects): explain wordcode operations and codec boundaries

This commit is contained in:
2026-11-15 17:21:56 +08:00
parent 717eab6d32
commit b793bac407
+85 -13
View File
@@ -1,48 +1,87 @@
//! 编译器与 VM 共享的指令协议:编译器构造逻辑指令,编码器产生双字节代码单元,
//! EndorCode 构造时解码并验证,eval 消费已解码指令;这里不执行指令或维护运行栈。
use crate::{CompareOp, VmError};
/// Endor wordcode, following CPython's opcode/oparg code-unit design.
/// Numeric opcode IDs are Endor's own, not a CPython bytecode ABI.
/// Endor 自有操作码;每个物理代码单元含 opcode/oparg 两字节。
/// 采用类似 CPython 的单元设计,但编号与其字节码 ABI 不兼容。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Opcode {
/// 无操作,无操作数。
Nop = 0,
/// 以操作数索引代码常量表,将借用常量保留为栈根。
LoadConst = 1,
/// 读取 localsplus 局部槽,操作数为槽索引。
LoadFast = 2,
/// 弹出栈顶写局部槽,执行器先处理对象赋值协议。
StoreFast = 3,
/// 清除局部槽并归还其根,未绑定时报错。
DeleteFast = 4,
/// 以 names 索引查全局空间,缺失再查函数捕获的内置空间。
LoadGlobal = 5,
/// 以 names 索引将栈顶写入全局字典并弹出。
StoreGlobal = 6,
/// 以 names 索引删除全局键,缺失时报错。
DeleteGlobal = 7,
/// 丢弃栈顶并释放对应根。
PopTop = 8,
/// 复制距栈顶第 argument 项(从 1 起),对象值新增栈根。
Copy = 9,
/// 交换栈顶与第 argument 项,合法深度至少为 2。
Swap = 10,
/// 压入调用协议专用 Null 标记,不是语言 Nil。
PushNull = 11,
/// 消费函数、接收者或 Null 及 argument 个显式参数,调用后留下结果。
Call = 12,
/// 移出帧结果并清理执行帧,无操作数。
ReturnValue = 13,
/// 从当前逻辑指令末尾向前跳 argument 个代码单元。
JumpForward = 14,
/// 从当前逻辑指令末尾向后跳 argument 个代码单元。
JumpBackward = 15,
/// 弹出条件,假时向前跳 argument 个代码单元。
PopJumpIfFalse = 16,
/// 弹出条件,真时向前跳 argument 个代码单元。
PopJumpIfTrue = 17,
/// 把栈顶经真值协议变为布尔值,无操作数。
ToBool = 18,
/// 对栈顶求真后逻辑取反,无操作数。
UnaryNot = 19,
/// 比较栈顶两个值;操作数采用 compare/compare_operation 的映射。
CompareOp = 20,
/// 读取 localsplus 对应 Cell 内值,空 Cell 报未绑定。
LoadDeref = 21,
/// 把栈顶写入对应 Cell,保持闭包共享槽身份。
StoreDeref = 22,
/// 清空对应 Cell,后续读取视为未绑定。
DeleteDeref = 23,
/// 压入对应 Cell 自身句柄,供创建嵌套闭包使用。
LoadClosure = 24,
/// 以 names 索引绑定栈顶对象属性,以所得值替换 owner 栈项。
LoadAttr = 25,
/// 以 names 索引把栈中的值写入对象属性。
StoreAttr = 26,
/// 消费 argument 对键值,构造插入顺序字典。
BuildMap = 27,
/// 操作数低/高 16 位分别计默认值与捕获槽数量。
MakeFunction = 28,
/// 消费四个构类输入并压入类型结果,无操作数。
BuildClass = 29,
/// 观察栈顶等待对象,Pending 保存暂停位置,Ready 替换为结果。
Await = 30,
/// 操作数指定算术种类,对两个栈值求值并压入结果。
BinaryArithmetic = 31,
/// 对栈顶数值取负,无操作数。
UnaryNegate = 32,
/// 编码器专用高位前缀;逻辑指令构造方不得直接提交。
ExtendedArg = 255,
}
impl Opcode {
/// 物理解码器验证一个操作码字节;未知编号返回 None,不做不安全 enum 转换。
fn decode(byte: u8) -> Option<Self> {
// 显式白名单保证保留编号不会误解为已实现指令。
Some(match byte {
0 => Self::Nop,
1 => Self::LoadConst,
@@ -82,6 +121,7 @@ impl Opcode {
})
}
/// 编解码共用的操作数分类;无操作数指令只能携带零且不得有扩展前缀。
pub(crate) fn takes_argument(self) -> bool {
!matches!(
self,
@@ -98,25 +138,28 @@ impl Opcode {
}
}
/// A decoded logical instruction; EXTENDED_ARG is handled by the codec.
/// 编译器构造的逻辑指令;完整操作数为 u32,扩展前缀由编解码器处理。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Instruction {
/// 逻辑操作,不应手工指定 ExtendedArg。
pub opcode: Opcode,
/// 完整操作数;具体含义由 opcode 决定,构造时尚未做代码布局校验。
pub argument: u32,
}
impl Instruction {
/// 编译器/宿主组装指令;这里只赋值,合法索引和栈效果由 EndorCode 校验。
pub const fn new(opcode: Opcode, argument: u32) -> Self {
Self { opcode, argument }
}
/// 编译器发出无参数指令的便捷入口,把 argument 明确置零。
pub const fn simple(opcode: Opcode) -> Self {
Self::new(opcode, 0)
}
/// Build a function from [defaults..., closure cells..., code] on the stack.
/// The low 16 operand bits count trailing positional defaults; the high
/// 16 bits count cells in the code's free-variable order. No tuples or
/// subsequent mutation of the function object are required.
/// 编译器为栈布局 [默认值..., 捕获 Cell..., code] 生成函数创建指令。
/// 低 16 位计末尾位置参数默认值,高 16 位计按自由变量顺序排列的捕获槽。
/// VM 一次构造函数,不需要额外元组或事后修改函数字段。
pub const fn make_function(defaults: u16, closure: u16) -> Self {
Self::new(
Opcode::MakeFunction,
@@ -124,6 +167,7 @@ impl Instruction {
)
}
/// 编译器将比较运算转成固定操作数,与下方反向解码表配对。
pub const fn compare(operation: CompareOp) -> Self {
Self::new(
Opcode::CompareOp,
@@ -138,29 +182,36 @@ impl Instruction {
)
}
/// Decode a comparison operand; None denotes an invalid operand.
/// VM/校验器解析比较种类;超出六个固定值时返回 None。
pub fn compare_operation(argument: u32) -> Option<CompareOp> {
compare_operation(argument)
}
/// Decode the default-value and closure-cell counts of MAKE_FUNCTION.
/// VM 创建函数及栈校验器拆出默认值数、闭包槽数,不访问运行栈。
pub fn function_counts(argument: u32) -> (usize, usize) {
function_counts(argument)
}
/// Encodes 8-bit operands directly and prefixes up to three EXTENDED_ARG
/// code units for larger operands. Each code unit is [opcode, operand].
/// EndorCode::from_instructions 调用:小操作数直接编码,大操作数最多用三个前缀。
/// 每单元两字节,前缀携带高位;不在这里重算跳转距离或校验常量/槽索引。
pub fn encode_all(instructions: &[Self]) -> Result<Vec<u8>, VmError> {
// 逐条追加;报错时局部缓冲丢弃,不向调用者返回部分编码。
let mut bytes = Vec::new();
// 逻辑列表中每项最终对应一个非前缀单元及零到三个前缀单元。
for instruction in instructions {
// 前缀由完整 u32 操作数推导,拒绝手工插入导致分组歧义。
if instruction.opcode == Opcode::ExtendedArg {
return Err(invalid(0, "EXTENDED_ARG is emitted by the encoder"));
}
// 无参数操作携带非零数据是协议错误,报告当前输出的代码单元位置。
if !instruction.opcode.takes_argument() && instruction.argument != 0 {
return Err(invalid(bytes.len() / 2, "unexpected instruction operand"));
}
// 大端拆分方便先发高位前缀,最低字节始终放在最终 opcode 单元。
let parts = instruction.argument.to_be_bytes();
// 跳过前导零;全零时选最后字节,保证仍发出一个有效单元。
let first = parts.iter().position(|part| *part != 0).unwrap_or(3);
// 只输出所需高位,最长三项,不把最低字节重复编码。
for part in &parts[first..3] {
bytes.extend([Opcode::ExtendedArg as u8, *part]);
}
@@ -172,15 +223,18 @@ impl Instruction {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedInstruction {
/// Offsets count two-byte code units, including EXTENDED_ARG prefixes.
/// 逻辑指令起点(包括首个前缀),单位是双字节代码单元。
pub offset: usize,
/// 最后一个实际 opcode 单元之后的位置,作为相对跳转的基点。
pub next_offset: usize,
/// 已合并前缀的完整逻辑指令。
pub instruction: Instruction,
}
impl DecodedInstruction {
/// Resolve a jump relative to the end of this logical instruction.
/// VM/校验器相对逻辑指令末尾解析跳转;此处不检查目标是否为有效指令边界。
pub fn jump_target(self) -> Result<usize, VmError> {
// 非跳转操作不能借用跳转计算;条件跳转按前向距离解释。
if !matches!(
self.instruction.opcode,
Opcode::JumpForward
@@ -194,37 +248,52 @@ impl DecodedInstruction {
}
}
/// EndorCode::with_layout 调用,将物理单元分组成逻辑指令;后续 verify 检查控制流与栈。
pub(crate) fn decode(bytes: &[u8]) -> Result<Vec<DecodedInstruction>, VmError> {
// 拒绝空代码与半个单元,保证下方 cursor+1 总能访问操作数字节。
if bytes.is_empty() || !bytes.len().is_multiple_of(2) {
return Err(invalid(
0,
"wordcode must contain complete two-byte code units",
));
}
// 输出逻辑指令列表与物理字节游标分开,前缀不会独立进入输出。
let mut instructions = Vec::new();
// cursor 以字节计,保存到 offset/next_offset 时再除以二。
let mut cursor = 0;
// 外层每次完整读一条逻辑指令,内层吸收连续高位前缀。
while cursor < bytes.len() {
// 记住首前缀/实际指令的起点,错误定位与跳转边界均用它。
let start = cursor / 2;
// 从高到低合并最多四个操作数字节。
let mut argument = 0_u32;
// 前缀最多三个,第四个将溢出 u32 编码容量。
let mut prefixes = 0;
// 遇到实际 opcode 才完成本条;流末尾仍只有前缀时报告悬空前缀。
loop {
// 前缀之后必须还有完整单元,不能将截断流视作正常结束。
if cursor >= bytes.len() {
return Err(invalid(start, "dangling EXTENDED_ARG"));
}
// 验证编号后再组合操作数,未知编号的错误位置指向该物理单元。
let opcode = Opcode::decode(bytes[cursor])
.ok_or_else(|| invalid(cursor / 2, "unknown opcode"))?;
argument = (argument << 8) | u32::from(bytes[cursor + 1]);
cursor += 2;
// 前缀只累积数据,不压入逻辑指令列表。
if opcode == Opcode::ExtendedArg {
prefixes += 1;
// 在读取第五个数据字节之前拒绝,防截断高位。
if prefixes > 3 {
return Err(invalid(start, "too many EXTENDED_ARG prefixes"));
}
continue;
}
// 即使扩展数据全零,无参数指令也不允许前缀。
if !opcode.takes_argument() && (argument != 0 || prefixes != 0) {
return Err(invalid(start, "unexpected instruction operand"));
}
// 起点保留首前缀位置,末尾记录已消费单元之后的位置。
instructions.push(DecodedInstruction {
offset: start,
next_offset: cursor / 2,
@@ -236,6 +305,7 @@ pub(crate) fn decode(bytes: &[u8]) -> Result<Vec<DecodedInstruction>, VmError> {
Ok(instructions)
}
/// Instruction 公共查询和代码校验共用的比较编号反向映射。
pub(crate) fn compare_operation(argument: u32) -> Option<CompareOp> {
Some(match argument {
0 => CompareOp::Lt,
@@ -248,10 +318,12 @@ pub(crate) fn compare_operation(argument: u32) -> Option<CompareOp> {
})
}
/// 解出 MAKE_FUNCTION 的两个无符号计数;合法槽布局由 EndorCode/执行器另行检查。
pub(crate) fn function_counts(argument: u32) -> (usize, usize) {
((argument & 0xffff) as usize, (argument >> 16) as usize)
}
/// 编解码与代码校验统一构造带代码单元偏移的错误,不把偏移转成源文件行号。
pub(crate) fn invalid(offset: usize, reason: &'static str) -> VmError {
VmError::InvalidBytecode { offset, reason }
}