首页
/ Deno op2 参数类型参考:Rust 侧 Op 参数完整支持矩阵与底层校验机制

Deno op2 参数类型参考:Rust 侧 Op 参数完整支持矩阵与底层校验机制

2026-09-04 21:32:48作者:柯茵沙

本文以 Deno 仓库中的 valid_args.md 为核心,系统讲解 #[op2] 宏(op2)支持的 Rust 侧参数类型全集:每类 Rust 类型对应哪些 V8 类型、是否走 fastcall 快速路径、以及各自的语义陷阱(如字符串拷贝、空 buffer 传 null、重入修改风险等),并结合仓库源码揭示这张"支持矩阵"是如何被测试自动维护、与 op2/README.md 保持同步的。读完本文,你能在为 Deno 扩展编写 Rust op 时正确选型参数类型,并理解每行矩阵背后的实现与校验逻辑。

一、op2 与 valid_args.md 的角色定位

op2 是 libs/ops/op2/README.md 中定义的 #[op] 的替代实现("in-progress replacement"),用于把 Rust 函数注册为 JavaScript 可调用原语。在 op2 的参数体系中,一个核心问题是:Rust 函数签名中的某个参数类型,能否被宏识别?它能接受 JavaScript 侧的哪些 V8 值?调用时走 fastcall(V8 直接传参的优化路径)还是慢速路径?

valid_args.md 就是这份"参数支持矩阵"的权威清单。它的表格共五列:

  • Supported:该类型是否已被 op2 生成器测试覆盖(X 表示已验证可编译生成);
  • Rust:op 函数参数侧使用的 Rust 类型(含 #[attr] 属性前缀);
  • Fastcall:该参数是否支持 V8 fastcall 快速路径(X 表示支持);
  • V8:JavaScript 侧允许传入的 V8 值类型;
  • Notes:语义注意事项(拷贝行为、空值行为、性能警告等)。

值得强调的是,这个文件不是人工维护的静态文档——它是 op2 宏测试套件的数据源。mod.rs 中的 test_valid_args_md 测试会把表中每一行都构造成一个真实的 op 函数,调用 generate_op2 实际跑一遍宏展开;同时把表格渲染成 HTML 写入 README 的 <!-- START ARGS --> 区块,两者必须完全一致否则测试失败。也就是说:表中每一行"支持"的类型,都有对应的宏展开证据;README 中的参数表格,则是由本表实时生成的

二、完整参数支持矩阵

以下完整继承 valid_args.md 的全部条目,按类型分组并补充说明。

2.1 数值与布尔类型

支持 Rust 参数类型 Fastcall V8 侧允许的类型 备注
X bool X Bool
X i8 X Uint32, Int32, Number, BigInt
X u8 X Uint32, Int32, Number, BigInt
X i16 X Uint32, Int32, Number, BigInt
X u16 X Uint32, Int32, Number, BigInt
X i32 X Uint32, Int32, Number, BigInt
X u32 X Uint32, Int32, Number, BigInt
X #[smi] ResourceId X Uint32, Int32, Number, BigInt SMI 内部是有符号整数表示,但无符号 #[smi] 类型在 Rust 侧会被按位转换为无符号值;JavaScript 代码看到的仍然是有符号整数
X #[bigint] i64 X Uint32, Int32, Number, BigInt
X #[bigint] u64 X Uint32, Int32, Number, BigInt
X #[bigint] isize X Uint32, Int32, Number, BigInt
X #[bigint] usize X Uint32, Int32, Number, BigInt
X f32 X Uint32, Int32, Number, BigInt
X f64 X Uint32, Int32, Number, BigInt

要点解读:

  • 32 位整数与浮点数均支持 fastcall,且 JS 侧 NumberBigInt 与 typed array 元素类型都可以传入;
  • #[smi] 是 Deno 资源句柄(ResourceId)的专用标注。从源码结构看,signature.rsNumericArg 枚举专门保留了 __SMI__ 占位符用于承载 #[smi] 标注的参数,且注释明确"A placeholder argument for arguments annotated with #[smi]";编译测试 test_cases/sync/smi.rs 则同时验证了有符号/无符号 #[smi] 参数(Int16/Int32/Uint16/Uint32 别名类型)与 Option<#[smi]> 三种形态;
  • 64 位整数必须显式标注 #[bigint],否则 64 位值无法在 JS 侧精确表达。

2.2 字符串类型(四档,性能与安全性逐级递减)

支持 Rust 参数类型 Fastcall V8 备注
X #[string] String X String Fastcall 仅在字符串为 Latin-1 时可用。总是创建一份堆分配的 UTF-8 拷贝
X #[string] &str X String Fastcall 仅在 Latin-1 时可用。若数据放不进栈缓冲区才创建 owned String 拷贝;fastcall 下绝不分配,但会做 Latin-1 → UTF-8 拷贝
X #[string] Cow<str> X String Fastcall 仅在 Latin-1 时可用。放不进栈时创建 Cow::Owned 拷贝;fastcall 下总是 Cow::Borrowed,但仍做 Latin-1 → UTF-8 拷贝
X #[string(onebyte)] Cow<[u8]> X String 最快的 String 类型参数。若字符串不是 Latin-1 则抛出 TypeError

这四档差异的根源在 op2/README.md 的 "Strings" 一节:Rust 的 String 恒为 UTF-8,而 V8 字符串要么是双字节 UTF-16、要么是单字节 Latin-1;单字节 Latin-1 与 UTF-8 不是字节兼容的(码位 128–255 在 UTF-8 中占两个字节)。因此 op 参数中的一切字符串都至少需要一次拷贝来保证不把 Latin-1 数据误当 UTF-8 处理;op2 的优化在于尽可能用栈缓冲区避免堆分配。

选型建议(与矩阵 Notes 一致):

  • 只要一个临时引用、追求最低开销:#[string] &str
  • 有时能借用、有时需持有:#[string] Cow<str>
  • 确实要拿走所有权:#[string] String
  • 热路径上确定输入是 ASCII/Latin-1(例如协议帧、路径名):#[string(onebyte)] Cow<[u8]>,代价是非 Latin-1 输入直接 TypeError。编译测试见 test_cases/sync/string_onebyte.rs
#[op2(fast)]
fn op_string_onebyte(#[string(onebyte)] s: Cow<[u8]>) -> u32 {
  s.len() as _
}

2.3 V8 句柄类型

支持 Rust 参数类型 Fastcall V8
X &v8::Value X any
X &v8::**V8** X V8(泛指 String/Object/Function 等具体 v8 类型)
X v8::Local<v8::Value> X any
X v8::Local<v8::**V8**> X V8

表中 &v8::**V8** / v8::Local<v8::**V8**>通配行,代表任意具体 v8 类型(如 &v8::Stringv8::Local<v8::Object> 等)都被支持。这一机制可以从校验逻辑中确认:mod.rsparse_md 函数在解析时检测到 **V8** 占位符,会将其展开为 StringObjectFunction... 四种具体类型,逐一构造 fn op_test(x: 类型) 跑宏展开验证。

2.4 自定义类型转换(三种 trait 路径)

支持 Rust 参数类型 Fastcall V8 备注
X FromV8Scopeless any 任何实现 deno_core::convert::FromV8Scopeless 的类型
X #[scoped] FromV8Type any 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢
X #[scoped] (Tuple, Tuple) any 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢
X #[serde] SerdeType any ⚠️ 可能较慢。遗留写法,不推荐,请改用 FromV8 trait 与宏
X #[serde] (Tuple, Tuple) any ⚠️ 可能较慢。遗留写法,不推荐

这与 op2/README.md "Argument conversion" 一节的描述对应:非 fast op 的参数默认走 FromV8Scopeless trait——该 trait 不需要 v8 scope 即可完成转换,因此对许多类型更高效;需要访问 scope 的类型显式加 #[scoped] 改走 FromV8

fn op_xyz(#[scoped] arg: MyFromV8Type) -> X {}

注意:三类自定义转换(FromV8Scopeless#[scoped]#[serde])均不支持 fastcall(矩阵 Fastcall 列留空),因为它们无法在 V8 快速路径的扁平参数列表中表达。

2.5 缓冲区类型:anybuffer / arraybuffer / buffer

这是矩阵中最庞大、也最需要注意安全语义的一组。

#[anybuffer](任意 ArrayBuffer 或 ArrayBufferView)——表中未标 "Supported X",即生成器可接受该写法,但尚未列入编译测试覆盖集合:

Rust 参数类型 Fastcall V8 备注
#[anybuffer] &mut [u8] X ArrayBuffer, ArrayBufferView (resizable=true,false) ⚠️ 若 V8 被重入调用,JS 可能修改 slice 内容
#[anybuffer] &[u8] X ArrayBuffer, ArrayBufferView (resizable=true,false) ⚠️ 同上
#[anybuffer] *mut u8 X ArrayBuffer, ArrayBufferView (resizable=true,false) ⚠️ 同上;且 V8 在 fastcall 中对空数组的处理是总是传 null
#[anybuffer] *const u8 X ArrayBuffer, ArrayBufferView (resizable=true,false) ⚠️ 同上;空数组总是传 null

#[arraybuffer](严格限定 ArrayBuffer)

支持 Rust 参数类型 Fastcall V8 备注
X #[arraybuffer] &mut [u8] X ArrayBuffer (resizable=true,false) ⚠️ 重入时 JS 可能修改 slice
X #[arraybuffer] &[u8] X ArrayBuffer (resizable=true,false) ⚠️ 同上
X #[arraybuffer] *mut u8 X ArrayBuffer (resizable=true,false) ⚠️ 同上;空数组在 fastcall 中传 null
X #[arraybuffer] *const u8 X ArrayBuffer (resizable=true,false) ⚠️ 同上;空数组在 fastcall 中传 null
X #[arraybuffer(copy)] Vec<u8> X ArrayBuffer (resizable=true,false) 安全,但强制拷贝
X #[arraybuffer(copy)] Box<[u8]> X ArrayBuffer (resizable=true,false) 安全,但强制拷贝
X #[arraybuffer(copy)] bytes::Bytes X ArrayBuffer (resizable=true,false) 安全,但强制拷贝

#[buffer](严格限定 TypedArray)

支持 Rust 参数类型 Fastcall V8 备注
(未列 X) #[buffer] &mut [u8] X UInt8Array (resizable=true,false) ⚠️ 重入时 JS 可能修改 slice
(未列 X) #[buffer] &[u8] X UInt8Array (resizable=true,false) ⚠️ 同上
(未列 X) #[buffer] *mut u8 X UInt8Array (resizable=true,false) ⚠️ 同上;空数组传 null
(未列 X) #[buffer] *const u8 X UInt8Array (resizable=true,false) ⚠️ 同上;空数组传 null
X #[buffer(copy)] Vec<u8> X UInt8Array (resizable=true,false) 安全,但强制拷贝
X #[buffer(copy)] Box<[u8]> X UInt8Array (resizable=true,false) 安全,但强制拷贝
X #[buffer(copy)] bytes::Bytes X UInt8Array (resizable=true,false) 安全,但强制拷贝
X #[buffer] &mut [u32] X UInt32Array (resizable=true,false) ⚠️ 重入时 JS 可能修改 slice
X #[buffer] &[u32] X UInt32Array (resizable=true,false) ⚠️ 同上
X #[buffer(copy)] Vec<u32> X UInt32Array (resizable=true,false) 安全,但强制拷贝
X #[buffer(copy)] Box<[u32]> X UInt32Array (resizable=true,false) 安全,但强制拷贝
(未列 X) #[buffer] V8Slice X ArrayBufferView (resizable=false) ⚠️ JS 可能修改来自 buffer 的 slice
(未列 X) #[buffer(detach)] V8Slice X ArrayBufferView (resizable=true,false) 安全
(未列 X) #[buffer] V8ResizableSlice X ArrayBufferView (resizable=true) ⚠️ JS 可能修改 slice
(未列 X) #[buffer] JsBuffer X ArrayBufferView (resizable=false) ⚠️ JS 可能修改来自 buffer 的 slice
X #[buffer(detach)] JsBuffer ArrayBufferView (resizable=true,false) 安全
(未列 X) #[buffer(unsafe)] bytes::Bytes X ArrayBufferView (resizable=false) ⚠️ JS 可能修改 buffer 内容
(未列 X) #[buffer(detach)] bytes::Bytes X ArrayBufferView (resizable=true,false) 安全

缓冲区选型核心逻辑:

  1. 借用形式&[u8]&mut [u8]、裸指针、V8Slice#[buffer] JsBuffer)零拷贝,但 Rust 拿到的只是对 JS 堆内存的引用——只要 op 体内可能重新进入 V8(比如触发回调),JS 就可以改写这些数据,矩阵对每一行都标了 ⚠️;
  2. (copy) 形式Vec<u8>/Box<[u8]>/bytes::Bytes)强制拷贝,语义安全且支持 fastcall,代价是一次内存复制;
  3. (detach) 形式在转换时把 JS 侧 ArrayBuffer detach(后续 JS 访问会抛异常),从而保证 Rust 独占数据,矩阵标注为 "Safe";
  4. (unsafe) 形式(如 #[buffer(unsafe)] bytes::Bytes)零拷贝且不 detach,是性能与风险的最极端组合,只在确认 JS 不会重入修改时使用;
  5. 裸指针 + fastcall 组合有一个特殊边界:V8 对空数组的处理导致 fastcall 下总是传 null,Rust 侧必须容错处理空指针。

2.6 外部指针与运行时上下文参数

支持 Rust 参数类型 Fastcall V8 备注
X *const std::ffi::c_void X External
X *mut std::ffi::c_void X External
X &OpState X 从 JS 侧无对应值,由运行时注入
X &mut OpState X 同上
X Rc<RefCell<OpState>> X 同上
X &JsRuntimeState X 仅在 deno_core 内部可用

External 是 V8 用于携带不透明 C 指针值的类型,op2 允许把 JS 传入的 External 直接映射为 c_void 指针;OpState 三个变体则是 op 框架自动注入的运行时状态容器(存放资源表、扩展状态等),不占用 JS 侧参数位。

三、这张矩阵在仓库中如何被维护与校验

理解矩阵的生成与校验机制,是判断"表里写的"与"实际支持的"是否一致的关键。以下事实均可在仓库中定位:

  1. 每行都是可编译的mod.rs 中的 parse_md 函数跳过表头两行后逐行解析:对每行取出 Rust 类型列,若以 #[attr] 开头则拆出属性与裸类型,拼出形如 fn op_test(#[attr] x: Type) {} 的函数体(**V8** 行会展开为 String/Object/Function 等具体类型),随后真实调用 generate_op2 完成一次宏展开——展开失败测试直接报错。也就是说矩阵不是"文档声称支持",而是"宏确实能为它生成代码"。

  2. README 参数表由本文件渲染生成test_valid_args_md 会把 valid_args.md 渲染成 HTML 表格,插入 op2/README.md<!-- START ARGS --><!-- END ARGS --> 分隔符之间,然后断言渲染结果与 README 现存内容逐字节相等;设置环境变量 UPDATE_EXPECTED 后测试会反向把渲染结果写回 README。test_valid_retvals_md 对返回类型表 valid_retvals.md 做同样的事(分隔符为 <!-- START RV -->/<!-- END RV -->,且额外为支持 async 的行生成 async fn op_test() -> Type 再跑一遍)。

  3. 类型枚举与矩阵同源signature.rs 定义了宏解析参数时使用的 NumericArg 枚举(bool/i8f64/isize/usize 外加 __SMI____VOID__ 两个占位符)与 V8Arg 枚举(ValueExternalObjectArrayBuffer、各 TypedArray、BigIntObjectFunction 等),二者分别对应矩阵的 "Rust" 列与 "V8" 列;NumericArg::v8_array_type 还给出了整数/浮点参数与 V8 typed array 的映射(如 u32 → Uint32Arrayu64 → BigUint64Array),这解释了矩阵中 #[buffer] &mut [u32] 对应 UInt32Array 的实现依据。

  4. 行为回归用例齐全test_cases/sync/ 目录下有 smi.rsstring_onebyte.rsstring_ref.rsstring_cow.rsbuffers.rsbuffers_copy.rsbigint.rsfrom_v8.rsserde_v8.rs 等编译测试文件,分别钉住矩阵中各特殊参数的宏展开形态,防止后续重构悄悄改变某一行的语义。

四、实操选型清单

综合矩阵 Notes 列与 op2/README.md 的说明,编写 op 参数时可按下面的决策路径:

  • 数值:32 位整数/浮点直接用,免费获得 fastcall;64 位一律 #[bigint](或返回值侧用 #[number] 限制在安全整数区间,见 valid_retvals.md);资源句柄用 #[smi] ResourceId,注意 JS 侧永远看到有符号值。
  • 字符串:热路径优先 &strCow<str>String 三档按"是否需要持有、能否借用"选择;确定 Latin-1 输入才用 #[string(onebyte)]。牢记任何字符串参数都至少有一次 Latin-1 → UTF-8 拷贝,fastcall 下 &str/Cow<str> 走栈缓冲零分配。
  • 缓冲区:需要 JS 保持可读选 (copy)(detach);追求零拷贝用借用形式,但要接受"重入可被改写"的契约;fastcall 下裸指针形态记得处理空数组 = null 的边界。
  • 自定义结构:优先实现 FromV8Scopeless(免 scope、更快);必须访问 scope 才降级到 #[scoped] FromV8#[serde] 属于遗留路径,矩阵中明确标注 "Legacy & not recommended"。
  • 任意值:需要透传用 &v8::Valuev8::Local<v8::Value>,两者均支持 fastcall。

五、相关文档

  • op2/README.md:op2 宏总览,含字符串拷贝原理、fallible op、async op 的 eager/lazy/deferred 语义、fastcall 注解(fast/nofast/fast(op_XYZ))与 CppGC 对象支持;
  • valid_retvals.md:与本文对应的返回值支持矩阵(注意返回值侧 #[number] 要求结果落在 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 区间内);
  • signature.rs:宏签名解析实现,NumericArg/V8Arg 枚举所在;
  • test_cases/:sync/async 两类编译测试,矩阵每一行语义的行为级佐证。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384