首页
/ Deno op2 宏深度解析:Rust 与 V8 之间的高性能桥梁(Fastcall、Async 与 CppGC 对象)

Deno op2 宏深度解析:Rust 与 V8 之间的高性能桥梁(Fastcall、Async 与 CppGC 对象)

2026-09-04 14:07:25作者:宣聪麟

#[op2] 是 Deno 运行时中连接 Rust 侧扩展(extensions)与 V8 引擎的核心 proc-macro。本文基于仓库内 libs/ops/op2/README.md 的完整内容展开,并结合 libs/ops/op2/mod.rslibs/ops/op2/config.rs 等源码实现,系统讲解 op2 的字符串拷贝语义、可失败 op、异步 op 的三种模式(eager / lazy / deferred)、V8 fastcall 的声明与校验规则、参数与返回值的类型转换体系,以及基于 V8 CppGC 的原生 JavaScript 类定义与继承机制。读完后你应当能够:正确声明并注册一个 op、为其选择合适的参数类型与 fastcall 策略、编写带错误处理的异步 op,以及定义支持 JS extends 的 CppGC 对象。

一、op2 的定位:从 #[op] 到标准接口层

libs/ops/op2/README.md 开篇即说明:#[op2]#[op] 的替代实现(in-progress replacement for #[op])。在当前的仓库代码中,op2 事实上已经成为 Deno 扩展开发的标准 op 声明方式——它由 deno_ops proc-macro crate 定义并在 libs/ops/lib.rs 中导出:

/// A macro designed to provide an extremely fast V8->Rust interface layer.
#[doc = include_str!("op2/README.md")]
#[proc_macro_attribute]
pub fn op2(attr: TokenStream, item: TokenStream) -> TokenStream {
  op2_macro(attr, item)
}

注意第 24 行的 #[doc = include_str!("op2/README.md")]:本文所依据的 README 实际上被直接编译进了 op2 宏的 rustdoc,也就是说这份文档同时是宏的官方 API 文档。随后 deno_core 将其再导出给所有扩展 crate 使用(见 libs/core/lib.rs):

pub use deno_ops::CppgcBase;
pub use deno_ops::CppgcInherits;
pub use deno_ops::FromV8;
pub use deno_ops::ToV8;
pub use deno_ops::WebIDL;
pub use deno_ops::op2;

一个最小的完整示例(来自 libs/ops/README.mdlibs/ops/op2/test_cases/sync/add.rs):

use deno_core::{op2, extension};

// 声明一个 op。
#[op2(fast)]
pub fn op_add(a: i32, b: i32) -> i32 {
  a + b
}

// 注册到 extension。
extension!(
  math,
  ops = [op_add]
)

从源码结构看,op2 宏的入口函数 op2() 会先把输入按 ItemFn(普通函数 op)解析;若解析失败则尝试按 syn::ItemImplimpl 块)解析,后者交给 libs/ops/op2/object_wrap.rs 处理——这正是后文 CppGC 对象包装的入口。对于函数 op,宏会:

  1. 解析 #[op2(...)] 属性中的配置项,生成 MacroConfig(见 libs/ops/op2/config.rs);
  2. 解析函数签名为 op 参数/返回值模型(parse_signature / RetVal);
  3. 同时生成慢路径(slow)快路径(fast) 两套 dispatch 代码,最终产出一个实现 deno_core::_ops::Op trait 的零大小结构体,其 DECL: OpDecl 常量携带 nameis_asyncarg_countslow_fnfast_fn 等元数据(见 libs/ops/op2/mod.rs)。

MacroConfig 中的字段一一对应了 README 中出现的各种属性标记:fast / nofast / fast_alternative(fastcall 三态)、async_lazy / async_deferred / fake_async(异步模式)、reentrantno_side_effectsstack_tracerequiredrenamesymbolpromise_idconstructableconstructorgetter / setter / static_membermethod 等(libs/ops/op2/config.rs)。宏还强制这些 flag 按字母序书写,并拒绝非法组合,例如 fastnofast 同时出现、no_side_effectsreentrant 同时出现都会直接编译报错(libs/ops/op2/config.rs)。

二、字符串:为什么 String 参数总有一次拷贝

README 的 "Strings" 一节给出了 op2 处理字符串的底层约束:

  • Rust 中的 String 永远是 UTF-8;
  • V8 中的字符串要么是两字节 UTF-16,要么是一字节 Latin-1;
  • 一字节 Latin-1 与 UTF-8 不字节兼容:索引 128–255 的字符在 UTF-8 中需要两个字节编码。

因此 op 中的 String 参数至少需要一次拷贝,以避免把 Latin-1 数据误当作 UTF-8 传给下游方法。目前无法完全避免这次拷贝,但 op 代码会尽可能利用**栈缓冲区(stack buffer)**来避免堆分配——这一点在参数表的 #[string] &str 说明中也有体现("Will create an owned String copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall")。

基于这一约束,op2 提供了梯度不同的字符串参数类型(详见第四节参数表):#[string] String 总是产生一次堆分配的 UTF-8 拷贝;#[string] &str#[string] Cow<str> 优先走栈缓冲、放不下才分配;而 #[string(onebyte)] Cow<[u8]> 是最快的字符串类型——它要求字符串必须是 Latin-1,否则抛出 TypeError,完全不产生转换拷贝。

三、可失败的 op(Fallible ops)

op 函数可以通过返回 Result 声明自己是可失败的:错误类型必须实现 deno_error::JsErrorClass。当函数返回 Err 时,op2 生成的 dispatch 代码会在 JS 侧抛出对应的异常(exception)。也就是说 Deno 中常见的 NotSupportedNotFoundPermissionDenied 等错误类别,在 op 层就是普通 Rust Result 的错误变体,由 JsErrorClass 决定 JS 侧捕获到的异常类名。

四、异步 op:两种声明形式与三种调度模式

4.1 声明形式:从签名完全推断

异步调用完全从函数定义中推断,支持两种等价形式:

async fn op_xyz(/* ... */) -> X {}
fn op_xyz(/* ... */) -> impl Future<Output = X> {}

4.2 语法糖的真相:隐藏的 promise_idOption<X>

上述两种写法最终会被 desugar 成如下形态(README 原文示例):

fn op_xyz(promise_id: i32 /* ... */) -> Option<X> {}

即:函数被加上了一个隐藏的 promise_id 参数,返回值变为 Option<X>。Deno 会立即(eagerly)轮询这个 future:

  • 如果 op 立刻 ready,函数直接返回 Some(X),结果同步送达 JS,延迟最低;
  • 如果 op 未 ready,函数返回 None,该 future 交由 Deno 的 pending op 系统(libs/ops/op2/dispatch_async.rs 生成的异步 dispatch)接管,待其完成后按 promise_id 唤醒 JS 侧的 promise。

宏在 libs/ops/op2/mod.rs 中根据 signature.ret_val.is_async()fake_async 标志选择生成 generate_dispatch_async(异步)或 generate_dispatch_slow(同步)路径。

4.3 三种异步调度模式

模式 声明 行为 适用场景
eager(默认) 直接写 async fn 调用时立即轮询 future;若首次轮询即 ready 则同步返回 绝大多数异步 op,延迟最低
async(lazy) #[op2(async(lazy))] 允许 runtime 推迟到稍后才轮询该 op。提交(submit)开销可能更快,但对"首次轮询即 ready"的 op,其结果延迟会更高 可能用于提高吞吐,必须配合仔细的 benchmarking 使用;它可能走 fastcall,但结果解析仍在慢路径
async(deferred) #[op2(async(deferred))] runtime 立即轮询 op,但即使结果已经 ready,也会推迟到事件循环的下一轮才交付 README 明确警告:"This is almost certainly not what you want to use and should only be used if you really know what you are doing."

README 同时强调:lazy / deferred 的异步调用可能是 fastcall(提交阶段),但结果的解析(resolution)仍发生在慢路径。从 libs/ops/op2/config.rsAsyncMode 枚举(Deferred / Fake / Lazy)可以看到这些模式在宏层面的落地,fake_async 则用于 Deno 内部的确定性/模拟时钟场景(is_fake_async 传入 GeneratorState,见 libs/ops/op2/mod.rs)。

五、Fastcall:op2 的性能核心

V8 fastcall 允许 JavaScript 调用不经过标准的 JS 参数对象转换,直接把标量/简单引用以 C ABI 风格传入 Rust,显著降低调用开销。op2 对 fastcall 的规则:

  1. 必须显式声明。op2 要求 fastcall 兼容的 op 标注 fast;若你不想为某个 op 生成 fastcall(这种情况很少),可以改用 nofast。这不是可选建议,而是编译期强制:宏在 libs/ops/op2/mod.rs 中会先尝试 generate_dispatch_fast——

    • 若函数 fastcall 兼容,但未标 fast(也未标 nofast/fast(...)/getter/setter),直接报 ShouldBeFast 错误("This op is fast-compatible and should be marked as (fast)");
    • 若函数不兼容却被标了 fastnofast,则报 ShouldNotBeFast
  2. fastcall 备选函数 fast(op_XYZ)。可以为一个慢路径函数指定另一个 Rust 函数作为其 fastcall 实现:

    #[op2(fast(op_xyz_fast))]
    pub fn op_xyz(/* 接受任意 buffer 类型 */) -> ... { ... }
    
    #[op2(fast)]
    pub fn op_xyz_fast(/* 只接受高速类型化的 u8 buffer */) -> ... { ... }
    

    被指定的 fast 函数必须标注 #[op2(fast)],且不需要在 extension 中注册。当 V8 将慢函数优化为 fastcall、且参数类型兼容时,会切换到这个 fast 实现。README 给出的典型场景:慢路径接受任意 buffer 类型的函数,希望 fast 路径使用极快的类型化 u8 buffer。

  3. 属性解析层面,fast(...) 的参数是一个 Rust 类型(Type),见 libs/ops/op2/config.rsFlags::Fast(Option<String>) 的解析逻辑。

仓库中大量真实扩展都使用了 #[op2(fast)],例如 ext/fs/ops.rsext/net/lib.rsext/canvas/lib.rsext/crypto/lib.rs 等(ext/* 目录下广泛出现 #[op2(fast)] 标注),可以作为实际用法参考。

六、参数与返回值的类型转换

6.1 参数转换:默认 FromV8Scopeless,可选 #[scoped]

非 fast op 的参数默认使用 deno_core::convert::FromV8Scopeless trait 进行转换。该 trait 的转换过程不需要 v8 scope,因此对很多类型更高效。如果需要转换期间访问 v8 scope,则对该参数添加 #[scoped] 属性,改用 FromV8 trait:

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

#[scoped] 参数不走 fastcall,且可能较慢(README 参数表中标注 "⚠️ May be slow")。

6.2 返回值转换:默认 ToV8

返回类型在非 fast op 中默认使用 ToV8 trait:任何实现了 deno_core::convert::ToV8 的类型都可以不带任何属性直接作为返回值。

6.3 参数类型支持表(Parameters)

以下表格完整继承自 libs/ops/op2/README.md 中由宏测试自动生成的参数表("Fastcall" 列的 ✅ 表示该类型可走 fastcall;"v8" 列为可接受的 JS 值类型):

Rust Fastcall v8 说明
bool Bool
i8 Uint32, Int32, Number, BigInt
u8 Uint32, Int32, Number, BigInt
i16 Uint32, Int32, Number, BigInt
u16 Uint32, Int32, Number, BigInt
i32 Uint32, Int32, Number, BigInt
u32 Uint32, Int32, Number, BigInt
#[smi] ResourceId Uint32, Int32, Number, BigInt SMI 内部以有符号整数表示,但无符号的 #[smi] 类型在 Rust 侧会按位转换为无符号值;JavaScript 侧仍看到有符号整数
#[bigint] i64 Uint32, Int32, Number, BigInt
#[bigint] u64 Uint32, Int32, Number, BigInt
#[bigint] isize Uint32, Int32, Number, BigInt
#[bigint] usize Uint32, Int32, Number, BigInt
f32 Uint32, Int32, Number, BigInt
f64 Uint32, Int32, Number, BigInt
#[string] String String 仅当字符串为 Latin-1 时可走 fastcall;总是产生一次堆分配的 UTF-8 拷贝
#[string] &str String 仅 Latin-1 可走 fastcall;放不进栈缓冲时产生 owned String 拷贝;fastcall 中绝不分配,但会做 Latin-1 → UTF-8 转换
#[string] Cow<str> String 仅 Latin-1 可走 fastcall;放不进栈缓冲时产生 Cow::Owned;fastcall 中总是 Cow::Borrowed,但会做 Latin-1 → UTF-8 转换
#[string(onebyte)] Cow<[u8]> String 最快的 String 类型;字符串非 Latin-1 时抛 TypeError
&v8::Value any
&v8::String String
&v8::Object Object
&v8::Function Function
&v8::... ... 任意其他 v8 类型
v8::Local<v8::Value> any
v8::Local<v8::String> String
v8::Local<v8::Object> Object
v8::Local<v8::Function> Function
v8::Local<v8::...> ...
FromV8Scopeless any 任何实现 deno_core::convert::FromV8Scopeless 的类型
#[scoped] FromV8Type any 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢
#[scoped] (Tuple, Tuple) any 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢
#[serde] SerdeType any ⚠️ 可能较慢。遗留方案,不推荐,请改用 FromV8 trait 与宏
#[serde] (Tuple, Tuple) any ⚠️ 可能较慢。遗留方案,不推荐,请改用 FromV8 trait 与宏
#[arraybuffer] &mut [u8] ArrayBuffer (resizable=true,false) ⚠️ 若 V8 被重入调用,JS 可能修改 slice 内容
#[arraybuffer] &[u8] ArrayBuffer (resizable=true,false) ⚠️ 同上
#[arraybuffer] *mut u8 ArrayBuffer (resizable=true,false) ⚠️ 同上;由于 V8 对空数组的处理,fastcall 中空数组总是以 null 传入
#[arraybuffer] *const u8 ArrayBuffer (resizable=true,false) ⚠️ 同上;空数组同样传 null
#[arraybuffer(copy)] Vec<u8> ArrayBuffer (resizable=true,false) 安全,但强制拷贝
#[arraybuffer(copy)] Box<[u8]> ArrayBuffer (resizable=true,false) 安全,但强制拷贝
#[arraybuffer(copy)] bytes::Bytes ArrayBuffer (resizable=true,false) 安全,但强制拷贝
#[buffer(copy)] Vec<u8> UInt8Array (resizable=true,false) 安全,但强制拷贝
#[buffer(copy)] Box<[u8]> UInt8Array (resizable=true,false) 安全,但强制拷贝
#[buffer(copy)] bytes::Bytes UInt8Array (resizable=true,false) 安全,但强制拷贝
#[buffer] &mut [u32] UInt32Array (resizable=true,false) ⚠️ 重入时 JS 可能修改 slice
#[buffer] &[u32] UInt32Array (resizable=true,false) ⚠️ 重入时 JS 可能修改 slice
#[buffer(copy)] Vec<u32>, #[buffer(copy)] Box<[u32]> UInt32Array (resizable=true,false) 安全,但强制拷贝
#[buffer(detach)] JsBuffer ArrayBufferView (resizable=true,false) 安全
*const std::ffi::c_void External
*mut std::ffi::c_void External
&OpState
&mut OpState
Rc<RefCell<OpState>>
&JsRuntimeState 只能在 deno_core 内部使用

使用建议可直接从表中读出:优先选标量(i32/u32/f64/bool)以吃满 fastcall;字符串按性能需求在 #[string] &str#[string(onebyte)] Cow<[u8]> 之间选择;buffer 场景若不希望 JS 侧重入修改数据,用 (copy) 变体或 detach 变体换取安全。

6.4 返回值类型支持表(Return Values)

同样继承自 libs/ops/op2/README.md 的返回类型表:

Rust Fastcall v8 / 说明
bool, i8, u8, i16, u16, i32, u32 直接映射为对应 JS 数值
#[smi] ResourceId SMI 说明同参数表(Rust 侧无符号位转换,JS 侧仍为有符号)
#[bigint] i64, #[bigint] u64, #[bigint] isize, #[bigint] usize 映射为 BigInt
#[number] i64, #[number] u64, #[number] isize, #[number] usize 结果必须落在 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 之间
f32, f64 直接映射为 Number
#[string] String, #[string] &str, #[string] Cow<str>, #[string(onebyte)] Cow<[u8]> 映射为 JS String
#[arraybuffer] V8Slice<u8>, #[arraybuffer] Vec<u8>, #[arraybuffer] Box<[u8]>, #[arraybuffer] bytes::BytesMut 映射为 ArrayBuffer
#[buffer] V8Slice<u8>, #[buffer] Vec<u8>, #[buffer] Box<[u8]>, #[buffer] bytes::BytesMut 映射为 TypedArray
#[buffer] V8Slice<u32> 映射为 UInt32Array
*const std::ffi::c_void, *mut std::ffi::c_void 映射为 External
v8::Local<v8::Value>, v8::Local<v8::String>, v8::Local<v8::Object>, v8::Local<v8::Function>, v8::Local<v8::...> 返回预构建好的 v8 句柄
ToV8Type 任何实现 deno_core::convert::ToV8 的类型
(ToV8Type, ToV8Type) 二元组,两元素均实现 ToV8(映射为 JS 二元组)
#[serde] SerdeType, #[serde] (SerdeType, SerdeType) ⚠️ 遗留方案,不推荐,请改用 ToV8 trait 与宏

七、CppGC 对象:Rust 背板的原生 JavaScript 类

op2 支持基于 V8 CppGC(C++ 垃圾回收)定义由 Rust 类型背板的原生 JavaScript 类。这些对象存放在 V8 堆上,自动被 GC 回收——不需要像传统 Rc<RefCell<...>> 资源那样手动管理生命周期。

7.1 基本用法

定义一个实现 GarbageCollected 的结构体,然后在 impl 块上加 #[op2] 定义其 JS API(完整示例继承自 README):

use deno_core::GarbageCollected;
use deno_core::v8::cppgc::GcCell;

#[repr(C)]
pub struct MyObject {
  value: GcCell<f64>,
}

unsafe impl GarbageCollected for MyObject {
  fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
  fn get_name(&self) -> &'static std::ffi::CStr {
    c"MyObject"
  }
}

#[op2]
impl MyObject {
  #[constructor]
  #[cppgc]
  fn new(value: f64) -> MyObject {
    MyObject {
      value: GcCell::new(value),
    }
  }

  #[getter]
  fn value(&self, isolate: &v8::Isolate) -> f64 {
    *self.value.get(isolate)
  }

  #[setter]
  fn value(&self, isolate: &mut v8::Isolate, value: f64) {
    self.value.set(isolate, value);
  }

  #[fast]
  fn double_value(&self, isolate: &v8::Isolate) -> f64 {
    *self.value.get(isolate) * 2.0
  }

  #[static_method]
  #[cppgc]
  fn create(value: f64) -> MyObject {
    MyObject {
      value: GcCell::new(value),
    }
  }
}

在 extension 中注册对象:

deno_core::extension!(
  my_ext,
  objects = [MyObject],
  // ...
);

注册后 JavaScript 侧即可使用:

import { MyObject } from "ext:core/ops";

const obj = new MyObject(42);
console.log(obj.value); // 42
console.log(obj.doubleValue()); // 84
obj.value = 10;

7.2 支持的对象成员类型

  • #[constructor] — JS 构造函数。必须返回该结构体类型(可以包在 Result 中);用 #[cppgc] 标记表示返回的是 CppGC 对象;
  • #[getter] / #[setter] — 属性访问器。同一属性的 getter 与 setter 应使用相同的函数名
  • #[static_method] — 类上的静态方法(如 MyObject.create());
  • #[fast] — 普通实例方法。使用 &self 作为首参以接收原生对象。

在宏实现中,这些成员属性会改写函数名(setter 前缀 __set_、静态方法前缀 __static_,见 libs/ops/op2/mod.rs),并设置 MacroConfig 中对应的 constructor / getter / setter / static_member 标志,getter/setter 最终映射为 deno_core::AccessorTypelibs/ops/op2/mod.rs)。impl 块的解析与成员分发逻辑位于 libs/ops/op2/object_wrap.rs

7.3 继承:base 与 derived 类

CppGC 对象支持镜像 JavaScript 类继承的原型继承模型:可以在 Rust 中定义基类,派生类像 class Child extends Parent 一样继承其方法与属性。

定义基类:结构体标记 #[derive(CppgcBase)]impl 块标记 #[op2(base)]

use deno_core::CppgcBase;

#[derive(CppgcBase)]
#[repr(C)]
pub struct Shape {
  sides: GcCell<u32>,
}

unsafe impl GarbageCollected for Shape {
  fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
  fn get_name(&self) -> &'static std::ffi::CStr {
    c"Shape"
  }
}

#[op2(base)]
impl Shape {
  #[constructor]
  #[cppgc]
  fn new(sides: u32) -> Shape {
    Shape {
      sides: GcCell::new(sides),
    }
  }

  #[getter]
  fn sides(&self, isolate: &v8::Isolate) -> u32 {
    *self.sides.get(isolate)
  }
}

base 属性告诉 op2 在访问 &self 时使用多态 unwrap(polymorphic unwrap),使得 Shape 上的方法可以在任何继承自它的类型上被调用。在 libs/ops/op2/mod.rs 中,这对应 try_unwrap_cppgc 在 base 模式下选用 try_unwrap_cppgc_base_object,否则选用 try_unwrap_cppgc_object

定义派生类:结构体标记 #[derive(CppgcInherits)],将基类型作为第一个字段impl 块使用 #[op2(inherit = BaseType)]

use deno_core::CppgcInherits;

#[derive(CppgcInherits)]
#[cppgc_inherits_from(Shape)]
#[repr(C)]
pub struct Rectangle {
  base: Shape, // 必须是第一个字段
  width: GcCell<f64>,
  height: GcCell<f64>,
}

unsafe impl GarbageCollected for Rectangle {
  fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
  fn get_name(&self) -> &'static std::ffi::CStr {
    c"Rectangle"
  }
}

#[op2(inherit = Shape)]
impl Rectangle {
  #[constructor]
  #[cppgc]
  fn new(width: f64, height: f64) -> Rectangle {
    Rectangle {
      base: Shape {
        sides: GcCell::new(4),
      },
      width: GcCell::new(width),
      height: GcCell::new(height),
    }
  }

  #[fast]
  fn area(&self, isolate: &v8::Isolate) -> f64 {
    *self.width.get(isolate) * *self.height.get(isolate)
  }
}

JavaScript 侧 Rectangle 继承自 Shape

const rect = new Rectangle(3, 4);
console.log(rect.sides); // 4(继承自 Shape)
console.log(rect.area()); // 12
console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Shape); // true

JavaScript 类也可以继续扩展这些原生类:

class Square extends Rectangle {
  constructor(size) {
    super(size, size);
  }
}
const sq = new Square(5);
console.log(sq.area()); // 25
console.log(sq.sides); // 4

多级继承:如果一个派生类本身还要被继承,它必须同时是基类——即同时 derive CppgcInheritsCppgcBase,并使用 #[op2(base, inherit = ParentType)]

// Rectangle 既是 Shape 的子类,又是进一步派生的基类。
#[derive(CppgcInherits, CppgcBase)]
#[cppgc_inherits_from(Shape)]
#[repr(C)]
pub struct Rectangle {
  base: Shape,
  width: GcCell<f64>,
  height: GcCell<f64>,
}

#[op2(base, inherit = Shape)]
impl Rectangle {
  // ... methods ...
}

// Square 继承自 Rectangle
#[derive(CppgcInherits)]
#[cppgc_inherits_from(Rectangle)]
#[repr(C)]
pub struct Square {
  base: Rectangle,
}

#[op2(inherit = Rectangle)]
impl Square {
  // ... methods ...
}

硬性要求(README "Requirements" 一节):

  • 继承链上的所有类型必须使用 #[repr(C)]
  • 基类型必须是派生结构体的第一个字段,且位于 offset 0;
  • 叶子类型(不再被继承)只需 #[derive(CppgcInherits)];根基类只需 #[derive(CppgcBase)]
  • 继承链中间的类型需要 #[derive(CppgcInherits, CppgcBase)] 两者兼有;
  • 所有类型都必须注册进 extension 的 objects = [...] 列表,且基类型必须列在派生类型之前

CppgcBase / CppgcInherits 这两个 derive 宏由 deno_ops crate 提供(libs/ops/lib.rs),并经 libs/core/lib.rsdeno_core 再导出;GarbageCollected trait 与 GcCell 等实现位于 libs/core/cppgc.rs

八、工程化保证:文档表与宏行为如何保持一致

值得了解的是,README 中的两张类型支持表不是手写的,而是由宏的测试用例自动生成并做一致性断言的。在 libs/ops/op2/mod.rs 中:

  • test_valid_args_md 从源表 libs/ops/op2/valid_args.md 读取每一行类型声明,真正构造 fn op_test(x: T) 并调用 generate_op2 验证宏确实能生成该 op,再把生成结果与 README.md<!-- START ARGS --> ... <!-- END ARGS --> 之间的 HTML 表格逐字节比对;
  • test_valid_retvals_mdlibs/ops/op2/valid_retvals.md<!-- START RV --> ... <!-- END RV --> 区段做同样的事,并对支持异步的类型额外验证 async fn 形式;
  • 修改类型支持后,需要设置环境变量 UPDATE_EXPECTED=1 重跑测试来重新生成 README 中的表格。

此外,宏的所有展开行为都有 fixture 测试覆盖:libs/ops/op2/test_cases/sync/(40+ 个同步用例,如 add.rsbuffers.rsfast_alternative.rssmi.rsstring_onebyte.rs 等)与 libs/ops/op2/test_cases/async/async_lazy.rsasync_deferred.rsasync_result_impl.rs 等),每个用例的展开结果以 .out 期望文件做快照比对,失败时同样可用 UPDATE_EXPECTED=1 刷新。这意味着本文所述行为(如 fast/nofast 强制校验、promise_id 注入、Option<X> 返回)均有可执行的回归测试背书。

九、速查总结

  • 声明与注册#[op2(...)] 标注函数(或 impl 块),通过 extension!(name, ops = [...], objects = [...]) 注册;deno_core 已再导出 op2CppgcBaseCppgcInheritsFromV8ToV8WebIDL
  • 字符串:总有一次拷贝不可避(Latin-1/UTF-8 不兼容),用栈缓冲规避分配;最快路径是 #[string(onebyte)] Cow<[u8]>(非 Latin-1 抛 TypeError);
  • 错误:返回 Result,错误类型实现 JsErrorClassErr 时向 JS 抛异常;
  • 异步async fn-> impl Future,desugar 成 promise_id + Option<X>,默认 eager 轮询;async(lazy) 换取提交速度、async(deferred) 推迟结果交付(慎用);
  • fastcall:兼容必须标 fast,否则编译报错;可用 fast(op_XYZ) 指定类型更严格的 fast 实现;#[scoped]/#[serde] 参数不走 fastcall;
  • CppGC 对象GarbageCollected + #[op2] impl 定义原生类,支持 constructor/getter/setter/static_method/fast 成员;继承链要求 #[repr(C)]、基类为 offset 0 的首字段、按基类在前顺序注册。

本文所有结论均可在仓库中核验:文档主体为 libs/ops/op2/README.md,宏实现位于 libs/ops/op2/(入口 libs/ops/op2/mod.rs、属性解析 libs/ops/op2/config.rs、slow/fast/async 三套 dispatch 生成器 libs/ops/op2/dispatch_slow.rs / libs/ops/op2/dispatch_fast.rs / libs/ops/op2/dispatch_async.rs、对象包装 libs/ops/op2/object_wrap.rs)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
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
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384