Deno op2 宏深度解析:Rust 与 V8 之间的高性能桥梁(Fastcall、Async 与 CppGC 对象)
Deno op2 宏深度解析Rust 与 V8 之间的高性能桥梁Fastcall、Async 与 CppGC 对象【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno#[op2]是 Deno 运行时中连接 Rust 侧扩展extensions与 V8 引擎的核心 proc-macro。本文基于仓库内 libs/ops/op2/README.md 的完整内容展开并结合 libs/ops/op2/mod.rs、libs/ops/op2/config.rs 等源码实现系统讲解 op2 的字符串拷贝语义、可失败 op、异步 op 的三种模式eager / lazy / deferred、V8 fastcall 的声明与校验规则、参数与返回值的类型转换体系以及基于 V8 CppGC 的原生 JavaScript 类定义与继承机制。读完后你应当能够正确声明并注册一个 op、为其选择合适的参数类型与 fastcall 策略、编写带错误处理的异步 op以及定义支持 JSextends的 CppGC 对象。一、op2 的定位从#[op]到标准接口层libs/ops/op2/README.md 开篇即说明#[op2]是#[op]的替代实现in-progress replacement for#[op]。在当前的仓库代码中op2 事实上已经成为 Deno 扩展开发的标准 op 声明方式——它由deno_opsproc-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.rspub 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.md 与 libs/ops/op2/test_cases/sync/add.rsuse 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宏会解析#[op2(...)]属性中的配置项生成MacroConfig见 libs/ops/op2/config.rs解析函数签名为 op 参数/返回值模型parse_signature/RetVal同时生成慢路径slow与快路径fast两套 dispatch 代码最终产出一个实现deno_core::_ops::Optrait 的零大小结构体其DECL: OpDecl常量携带name、is_async、arg_count、slow_fn、fast_fn等元数据见 libs/ops/op2/mod.rs。MacroConfig中的字段一一对应了 README 中出现的各种属性标记fast/nofast/fast_alternativefastcall 三态、async_lazy/async_deferred/fake_async异步模式、reentrant、no_side_effects、stack_trace、required、rename、symbol、promise_id、constructable、constructor、getter/setter/static_member、method等libs/ops/op2/config.rs。宏还强制这些 flag按字母序书写并拒绝非法组合例如fast与nofast同时出现、no_side_effects与reentrant同时出现都会直接编译报错libs/ops/op2/config.rs。二、字符串为什么String参数总有一次拷贝README 的 Strings 一节给出了 op2 处理字符串的底层约束Rust 中的String永远是 UTF-8V8 中的字符串要么是两字节 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 ownedStringcopy of the String data if it doesnt fit on the stack. Will never allocate in a fastcall。基于这一约束op2 提供了梯度不同的字符串参数类型详见第四节参数表#[string] String总是产生一次堆分配的 UTF-8 拷贝#[string] str与#[string] Cowstr优先走栈缓冲、放不下才分配而#[string(onebyte)] Cow[u8]是最快的字符串类型——它要求字符串必须是 Latin-1否则抛出TypeError完全不产生转换拷贝。三、可失败的 opFallible opsop 函数可以通过返回Result声明自己是可失败的错误类型必须实现deno_error::JsErrorClass。当函数返回Err时op2 生成的 dispatch 代码会在 JS 侧抛出对应的异常exception。也就是说 Deno 中常见的NotSupported、NotFound、PermissionDenied等错误类别在 op 层就是普通 RustResult的错误变体由JsErrorClass决定 JS 侧捕获到的异常类名。四、异步 op两种声明形式与三种调度模式4.1 声明形式从签名完全推断异步调用完全从函数定义中推断支持两种等价形式async fn op_xyz(/* ... */) - X {}fn op_xyz(/* ... */) - impl FutureOutput X {}4.2 语法糖的真相隐藏的promise_id与OptionX上述两种写法最终会被 desugar 成如下形态README 原文示例fn op_xyz(promise_id: i32 /* ... */) - OptionX {}即函数被加上了一个隐藏的promise_id参数返回值变为OptionX。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.rs 的AsyncMode枚举Deferred/Fake/Lazy可以看到这些模式在宏层面的落地fake_async则用于 Deno 内部的确定性/模拟时钟场景is_fake_async传入GeneratorState见 libs/ops/op2/mod.rs。五、Fastcallop2 的性能核心V8 fastcall 允许 JavaScript 调用不经过标准的 JS 参数对象转换直接把标量/简单引用以 C ABI 风格传入 Rust显著降低调用开销。op2 对 fastcall 的规则必须显式声明。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)若函数不兼容却被标了fast或nofast则报ShouldNotBeFast。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 路径使用极快的类型化u8buffer。属性解析层面fast(...)的参数是一个 Rust 类型Type见 libs/ops/op2/config.rs 中Flags::Fast(OptionString)的解析逻辑。仓库中大量真实扩展都使用了#[op2(fast)]例如 ext/fs/ops.rs、ext/net/lib.rs、ext/canvas/lib.rs、ext/crypto/lib.rs 等ext/*目录下广泛出现#[op2(fast)]标注可以作为实际用法参考。六、参数与返回值的类型转换6.1 参数转换默认FromV8Scopeless可选#[scoped]非 fast op 的参数默认使用deno_core::convert::FromV8Scopelesstrait 进行转换。该 trait 的转换过程不需要 v8 scope因此对很多类型更高效。如果需要转换期间访问 v8 scope则对该参数添加#[scoped]属性改用FromV8traitfn op_xyz(#[scoped] arg: MyFromV8Type) - X {}#[scoped]参数不走 fastcall且可能较慢README 参数表中标注 ⚠️ May be slow。6.2 返回值转换默认ToV8返回类型在非 fast op 中默认使用ToV8trait任何实现了deno_core::convert::ToV8的类型都可以不带任何属性直接作为返回值。6.3 参数类型支持表Parameters以下表格完整继承自 libs/ops/op2/README.md 中由宏测试自动生成的参数表Fastcall 列的 ✅ 表示该类型可走 fastcallv8 列为可接受的 JS 值类型RustFastcallv8说明bool✅Booli8✅Uint32, Int32, Number, BigIntu8✅Uint32, Int32, Number, BigInti16✅Uint32, Int32, Number, BigIntu16✅Uint32, Int32, Number, BigInti32✅Uint32, Int32, Number, BigIntu32✅Uint32, Int32, Number, BigInt#[smi] ResourceId✅Uint32, Int32, Number, BigIntSMI 内部以有符号整数表示但无符号的#[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, BigIntf32✅Uint32, Int32, Number, BigIntf64✅Uint32, Int32, Number, BigInt#[string] String✅String仅当字符串为 Latin-1 时可走 fastcall总是产生一次堆分配的 UTF-8 拷贝#[string] str✅String仅 Latin-1 可走 fastcall放不进栈缓冲时产生 ownedString拷贝fastcall 中绝不分配但会做 Latin-1 → UTF-8 转换#[string] Cowstr✅String仅 Latin-1 可走 fastcall放不进栈缓冲时产生Cow::Ownedfastcall 中总是Cow::Borrowed但会做 Latin-1 → UTF-8 转换#[string(onebyte)] Cow[u8]✅String最快的 String 类型字符串非 Latin-1 时抛TypeErrorv8::Value✅anyv8::String✅Stringv8::Object✅Objectv8::Function✅Functionv8::...✅...任意其他 v8 类型v8::Localv8::Value✅anyv8::Localv8::String✅Stringv8::Localv8::Object✅Objectv8::Localv8::Function✅Functionv8::Localv8::...✅...FromV8Scopelessany任何实现deno_core::convert::FromV8Scopeless的类型#[scoped] FromV8Typeany任何实现deno_core::convert::FromV8的类型。⚠️ 可能较慢#[scoped] (Tuple, Tuple)any任何实现deno_core::convert::FromV8的类型。⚠️ 可能较慢#[serde] SerdeTypeany⚠️ 可能较慢。遗留方案不推荐请改用FromV8trait 与宏#[serde] (Tuple, Tuple)any⚠️ 可能较慢。遗留方案不推荐请改用FromV8trait 与宏#[arraybuffer] mut [u8]✅ArrayBuffer (resizabletrue,false)⚠️ 若 V8 被重入调用JS 可能修改 slice 内容#[arraybuffer] [u8]✅ArrayBuffer (resizabletrue,false)⚠️ 同上#[arraybuffer] *mut u8✅ArrayBuffer (resizabletrue,false)⚠️ 同上由于 V8 对空数组的处理fastcall 中空数组总是以 null 传入#[arraybuffer] *const u8✅ArrayBuffer (resizabletrue,false)⚠️ 同上空数组同样传 null#[arraybuffer(copy)] Vecu8✅ArrayBuffer (resizabletrue,false)安全但强制拷贝#[arraybuffer(copy)] Box[u8]✅ArrayBuffer (resizabletrue,false)安全但强制拷贝#[arraybuffer(copy)] bytes::Bytes✅ArrayBuffer (resizabletrue,false)安全但强制拷贝#[buffer(copy)] Vecu8✅UInt8Array (resizabletrue,false)安全但强制拷贝#[buffer(copy)] Box[u8]✅UInt8Array (resizabletrue,false)安全但强制拷贝#[buffer(copy)] bytes::Bytes✅UInt8Array (resizabletrue,false)安全但强制拷贝#[buffer] mut [u32]✅UInt32Array (resizabletrue,false)⚠️ 重入时 JS 可能修改 slice#[buffer] [u32]✅UInt32Array (resizabletrue,false)⚠️ 重入时 JS 可能修改 slice#[buffer(copy)] Vecu32,#[buffer(copy)] Box[u32]✅UInt32Array (resizabletrue,false)安全但强制拷贝#[buffer(detach)] JsBufferArrayBufferView (resizabletrue,false)安全*const std::ffi::c_void✅External*mut std::ffi::c_void✅ExternalOpState✅mut OpState✅RcRefCellOpState✅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 的返回类型表RustFastcallv8 / 说明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_INTEGER与Number.MAX_SAFE_INTEGER之间f32,f64✅直接映射为 Number#[string] String,#[string] str,#[string] Cowstr,#[string(onebyte)] Cow[u8]映射为 JS String#[arraybuffer] V8Sliceu8,#[arraybuffer] Vecu8,#[arraybuffer] Box[u8],#[arraybuffer] bytes::BytesMut映射为 ArrayBuffer#[buffer] V8Sliceu8,#[buffer] Vecu8,#[buffer] Box[u8],#[buffer] bytes::BytesMut映射为 TypedArray#[buffer] V8Sliceu32映射为 UInt32Array*const std::ffi::c_void,*mut std::ffi::c_void✅映射为 Externalv8::Localv8::Value,v8::Localv8::String,v8::Localv8::Object,v8::Localv8::Function,v8::Localv8::...返回预构建好的 v8 句柄ToV8Type任何实现deno_core::convert::ToV8的类型(ToV8Type, ToV8Type)二元组两元素均实现ToV8映射为 JS 二元组#[serde] SerdeType,#[serde] (SerdeType, SerdeType)⚠️ 遗留方案不推荐请改用ToV8trait 与宏七、CppGC 对象Rust 背板的原生 JavaScript 类op2 支持基于 V8 CppGCC 垃圾回收定义由 Rust 类型背板的原生 JavaScript 类。这些对象存放在 V8 堆上自动被 GC 回收——不需要像传统RcRefCell...资源那样手动管理生命周期。7.1 基本用法定义一个实现GarbageCollected的结构体然后在impl块上加#[op2]定义其 JS API完整示例继承自 READMEuse deno_core::GarbageCollected; use deno_core::v8::cppgc::GcCell; #[repr(C)] pub struct MyObject { value: GcCellf64, } unsafe impl GarbageCollected for MyObject { fn trace(self, _visitor: mut v8::cppgc::Visitor) {} fn get_name(self) - static std::ffi::CStr { cMyObject } } #[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: GcCellu32, } unsafe impl GarbageCollected for Shape { fn trace(self, _visitor: mut v8::cppgc::Visitor) {} fn get_name(self) - static std::ffi::CStr { cShape } } #[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时使用多态 unwrappolymorphic 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: GcCellf64, height: GcCellf64, } unsafe impl GarbageCollected for Rectangle { fn trace(self, _visitor: mut v8::cppgc::Visitor) {} fn get_name(self) - static std::ffi::CStr { cRectangle } } #[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继承自Shapeconst 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); // trueJavaScript 类也可以继续扩展这些原生类class Square extends Rectangle { constructor(size) { super(size, size); } } const sq new Square(5); console.log(sq.area()); // 25 console.log(sq.sides); // 4多级继承如果一个派生类本身还要被继承它必须同时是基类——即同时 deriveCppgcInherits与CppgcBase并使用#[op2(base, inherit ParentType)]// Rectangle 既是 Shape 的子类又是进一步派生的基类。 #[derive(CppgcInherits, CppgcBase)] #[cppgc_inherits_from(Shape)] #[repr(C)] pub struct Rectangle { base: Shape, width: GcCellf64, height: GcCellf64, } #[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_opscrate 提供libs/ops/lib.rs并经 libs/core/lib.rs 从deno_core再导出GarbageCollectedtrait 与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_md对 libs/ops/op2/valid_retvals.md 与!-- START RV --...!-- END RV --区段做同样的事并对支持异步的类型额外验证async fn形式修改类型支持后需要设置环境变量UPDATE_EXPECTED1重跑测试来重新生成 README 中的表格。此外宏的所有展开行为都有 fixture 测试覆盖libs/ops/op2/test_cases/sync/40 个同步用例如add.rs、buffers.rs、fast_alternative.rs、smi.rs、string_onebyte.rs等与 libs/ops/op2/test_cases/async/async_lazy.rs、async_deferred.rs、async_result_impl.rs等每个用例的展开结果以.out期望文件做快照比对失败时同样可用UPDATE_EXPECTED1刷新。这意味着本文所述行为如 fast/nofast 强制校验、promise_id注入、OptionX返回均有可执行的回归测试背书。九、速查总结声明与注册#[op2(...)]标注函数或impl块通过extension!(name, ops [...], objects [...])注册deno_core已再导出op2、CppgcBase、CppgcInherits、FromV8、ToV8、WebIDL字符串总有一次拷贝不可避Latin-1/UTF-8 不兼容用栈缓冲规避分配最快路径是#[string(onebyte)] Cow[u8]非 Latin-1 抛 TypeError错误返回Result错误类型实现JsErrorClassErr时向 JS 抛异常异步async fn或- impl Futuredesugar 成promise_id OptionX默认 eager 轮询async(lazy)换取提交速度、async(deferred)推迟结果交付慎用fastcall兼容必须标fast否则编译报错可用fast(op_XYZ)指定类型更严格的 fast 实现#[scoped]/#[serde]参数不走 fastcallCppGC 对象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。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考