Trait Upcasting 系列 | Part I
作者: 张汉东 / 审校:crlf0710
引子
记录 @crlf0710 的 Trait Upcasting系列 系列 PR 过程。因为后续我也参与其中一个 PR,所以先要理清楚 @crlf0710 的 PR 思路,以便后续我的参与。也借此机会分享出来,希望更多的人能参与到 Rust 语言贡献中。
PR 系列:
- Refactor vtable codegen #86291
- Change vtable memory representation to use tcx allocated allocations.#86475
- Refactor vtable format for upcoming trait_upcasting feature. #86461
- Trait upcasting (part1) #86264
- Trait upcasting (part2)
本文为 第一个 PR 的描述。
前情提要
故事要从 Trait upcasting #60900 这个 PR 讲起 。
Trait upcasting ,是 trait 向上转型的意思。这个 PR 提出,当 Foo: Bar
,即 Foo trait 继承自 Bar trait 时,允许从 dyn Foo
转到 dyn Bar
。
目前 Rust 版本中,不支持此功能。因为目前trait 继承情况下, trait 对象的方法都是存储在同一个虚表中,无法区分哪个函数是属于哪个trait 对象。
社区内有一个通用的解决办法:
trait Base { fn base(&self) { println!("base..."); } } trait AsBase { fn as_base(&self) -> &dyn Base; //返回 Base trait对象 } // blanket implementation // 为所有实现 Base 的 T 来实现 AsBase impl<T: Base> AsBase for T { // 返回 Base trait对象 fn as_base(&self) -> &dyn Base { self } } trait Foo: AsBase { fn foo(&self) { println!("foo.."); } } #[derive(Debug)] struct MyStruct; impl Foo for MyStruct {} impl Base for MyStruct {} fn main() { let s = MyStruct; let foo: &dyn Foo = &s; foo.foo(); let base: &dyn Base = foo.as_base(); // 通过 as_base 来返回 Base trait对象达到 upcasting 的效果 base.base(); }
在 PR #60900 中,作者给出了一些实现,但是因为太大了,需要对这份工作进行重构,然后这个 PR 就被关闭了。关于这个 PR 的相关讨论被记录于 rust-lang.zulipchat.
这份重构的工作,就由 crlf0710 承接起来了,这就是这个系列 PR 的由来。相关提案:Trait Upcasting #98 , 跟踪 issues :Tracking issue for trait upcasting #65991
第一步工作: 重构 vtable 代码生成
状态:这部分工作已经被合并。
相关PR: Refactor vtable codegen #86291 。
修改文件概述
本次修改涉及 十个文件。
compiler/rustc_codegen_cranelift/src/vtable.rs
compiler/rustc_codegen_ssa/src/glue.rs
compiler/rustc_codegen_ssa/src/meth.rs
compiler/rustc_codegen_ssa/src/mir/block.rs
compiler/rustc_middle/src/query/mod.rs
compiler/rustc_middle/src/ty/mod.rs
compiler/rustc_mir/src/interpret/traits.rs
compiler/rustc_mir/src/monomorphize/collector.rs
compiler/rustc_trait_selection/src/traits/mod.rs
compiler/rustc_trait_selection/src/traits/select/confirmation.rs
这十个文件涉及五个 crate:
rustc_codegen_cranelift
,是 基于 cranelift 的编译器后端,专门用于 debug 模式。rustc_codegen_ssa
,截至2021年1月,RustC_Codegen_SSA 为所有后端提供了一个抽象的接口,以允许其他Codegen后端(例如Cranelift)。rustc_middle
,属于 rust 编译器的 main crate ,包含rustc“家族”中的其他crate使用的通用类型定义,包括 HIR/MIR/Types。rustc_mir
,用于操作 MIR 的库。rustc_trait_selection
,该库定义了 trait resolution 相关方法。详细内容:Trait resolution (old-style) 。
rustc_middle 库中的修改
在 compiler/rustc_middle/src/ty/mod.rs
中新增了枚举类型:VtblEntry。
#![allow(unused)] fn main() { #[derive(Clone, Copy, Debug, PartialEq, HashStable)] pub enum VtblEntry<'tcx> { MetadataDropInPlace, MetadataSize, MetadataAlign, Vacant, Method(DefId, SubstsRef<'tcx>), } pub const COMMON_VTABLE_ENTRIES: &[VtblEntry<'_>] = &[VtblEntry::MetadataDropInPlace, VtblEntry::MetadataSize, VtblEntry::MetadataAlign]; pub const COMMON_VTABLE_ENTRIES_DROPINPLACE: usize = 0; pub const COMMON_VTABLE_ENTRIES_SIZE: usize = 1; pub const COMMON_VTABLE_ENTRIES_ALIGN: usize = 2; }
这是为了识别 vtable 中的不同 entry,这样才有可能识别 存储在vtable中的不同 trait 对象。
接下来,在 compiler/rustc_middle/src/query/mod.rs
中把 query vtable_methods
修改为 query vtable_entries
。
#![allow(unused)] fn main() { // 使用的是一个宏 rustc_queries! { // ... query vtable_entries(key: ty::PolyTraitRef<'tcx>) -> &'tcx [ty::VtblEntry<'tcx>] { desc { |tcx| "finding all vtable entries for trait {}", tcx.def_path_str(key.def_id()) } } // ... } }
在 rust_middle 中定义了 rustc 的 query 系统 。Rust 使用查询系统,是为了支持增量编译。参考 编译器概览 。
举个例子。假如有一条查询负责询问某个东西的类型, 而另一条查询负责询问某个函数的优化后的 MIR。这些查询可以相互调用并且由查询系统所跟踪。 查询的结果被缓存于硬盘上,这样我们就可以分辨相较于上次编译,哪些查询的结果改变了,并且仅重做这些查询。 这就是增量编译是如何工作的。
类型上下文(TyCtxt),它是一个相当巨大的结构体, 是所有东西的中心。所有查询都被定义为在
TyCtxt
类型上 的方法,并且内存中的查询缓存也同样被存储在此。在代码中,通常会有一个名为tcx
变量,它是 类型上下文上的一个句柄。有同样会见到名为'tcx
的生命周期,这意味着有东西被和TyCtxt
的 生命周期绑定在了一起(通常它会被存储或者被驻留化)。
ty::Ty
介绍
类型在 Rust 中相当重要,并且形成了许多编译器分析的核心。用于表示类型(在用户程序中)的 主要类型(在编译器中)是 rustc_middle::ty::Ty
。它是如此的重要以至于我们为其 设置了一整章ty::Ty
,但是对于现在而言,我们只想提到它存在并且是rustc
用来表示类型的方法!
同样注意到rustc_middle::ty
模块定义了我们之前提到的TyCtxt
结构体。
rustc_codegen_ssa 中的修改
因为 rustc_codegen_ssa 是 后端 codegen 的接口,所以先看这里。
rustc_codegen_ssa 主要入口点: rustc_codegen_ssa::base::codegen_crate
- 它单态化并且产出 LLVM IR给一个代码生成单元。 它之后启动一个后台线程来运行一个之后必须被结合的LLVM。
- 单态化通过
FunctionCx::monomorphize
懒启动以及rustc_codegen_ssa::base::codegen_instance
在 rust_codgen_ssa 出现之前,生成代码都是由 rust_codgen_llvm 处理。
LLVM codegen的两个最重要的结构是CodegenCx
和Builder
。它们由多个生命期参数和Value的类型组成。
#![allow(unused)] fn main() { struct CodegenCx<'ll, 'tcx> { /* ... */ } struct Builder<'a, 'll, 'tcx> { cx: &'a CodegenCx<'ll, 'tcx>, /* ... */ } }
CodegenCx
是用来编译一个可以包含多个函数的 codegen-unit 的,而Builder
是为了编译一个基本块而创建的。CodegenCx
和Builder
将是实现所有定义后端接口的traits的结构。
这些 trait 被定义在rustc_codegen_ssa/traits
文件夹中,所有与后端无关的代码都以它们为参数。
在 rustc_codegen_ssa 有个关键的 trait :BuilderMethods
,它表示后端实现的构建方法。那么实际上, rustc_codegen_cranelift 目前并没有依赖 rustc_codegen_ssa 的这个 BuilderMethods trait, 而 rustc_codegen_llvm 依赖了。看来目前 rustc_codegen_ssa 并未重构完成。
重构 vtable 的相关工作,主要涉及三个文件:
rustc_codegen_ssa/src/meth.rs
rustc_codegen_ssa/src/glue.rs
rustc_codegen_ssa/src/mir/block.rs
在 meth.rs
中:
#![allow(unused)] fn main() { use rustc_middle::ty::{self, Instance, Ty, VtblEntry, COMMON_VTABLE_ENTRIES}; // 引入 rustc_middle 新加的枚举 VtblEntry 相关 impl<'a, 'tcx> VirtualIndex { pub fn from_index(index: usize) -> Self { VirtualIndex(index as u64) // 修改虚表index ,之前是偏移 3 ,因为之前是没有 vtable Entry 的,所以 DESTRUCTOR(index 0),SIZE(index 1),ALIGN(index 2) 都展开放了,现在则不需要。 } // ... // 修改 pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>( cx: &Cx, ty: Ty<'tcx>, trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, ) -> Cx::Value { // ... // 新增 // 当有 T: Trait 或 SubTrait: ParentTrait 这种形式出现时,就会有 trait_ref // 所以,相当于是 如果是有 trait 继承的情况下,就利用 query vtable_entries 来查询该trait // 并返回 vtable_entries ,否则返回 COMMON_VTABLE_ENTRIES,代表是单一的trait 对象 let vtable_entries = if let Some(trait_ref) = trait_ref { tcx.vtable_entries(trait_ref.with_self_ty(tcx, ty)) } else { COMMON_VTABLE_ENTRIES }; let layout = cx.layout_of(ty); // 新增 // ///////////////////////////////////////////////////////////////////////////////////////////// // If you touch this code, be sure to also make the corresponding changes to // `get_vtable` in `rust_mir/interpret/traits.rs`. // ///////////////////////////////////////////////////////////////////////////////////////////// // 新增 // 迭代处理每个 vtable entry 的元信息:drop/大小/对齐/方法等 let components: Vec<_> = vtable_entries .iter() .map(|entry| match entry { VtblEntry::MetadataDropInPlace => { cx.get_fn_addr(Instance::resolve_drop_in_place(cx.tcx(), ty)) } VtblEntry::MetadataSize => cx.const_usize(layout.size.bytes()), VtblEntry::MetadataAlign => cx.const_usize(layout.align.abi.bytes()), VtblEntry::Vacant => nullptr, VtblEntry::Method(def_id, substs) => cx.get_fn_addr( ty::Instance::resolve_for_vtable( cx.tcx(), ty::ParamEnv::reveal_all(), *def_id, substs, ) .unwrap() .polymorphize(cx.tcx()), ), }) .collect(); // ... } } }
文档:rustc_middle::ty::PolyExistentialTraitRef ,该类型表示 对一个已经擦除了 Self 的 trait 的存在性引用,所以使用 with_self_ty
来提供一个self占位。
在 src/glue.rs
中:
#![allow(unused)] fn main() { // BuilderMethods 是通用后端接口,但目前只有llvm用这个 pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, t: Ty<'tcx>, info: Option<Bx::Value>, ) -> (Bx::Value, Bx::Value) { // ... match t.kind() { ty::Dynamic(..) => { // load size/align from vtable // 新增 let vtable = info.unwrap(); ( meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_SIZE) .get_usize(bx, vtable), meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_ALIGN) .get_usize(bx, vtable), ) } // ... } // ... } }
问题: 既然目前 BuilderMethods 只有 llvm使用而 cranelift没有使用, 为什么 rustc_codegen_cranelift/src/unsize.rs#L131 中对应的 size_and_align_of_dst
函数不做对应修改?
答:因为 rustc_codegen_cranelift 中 vtable 要做相应修改,具体在后面描述。
src/glue.rs
就是一个胶水模块,在生成底层指令的相关模块中,会调用该方法。
在 src/mir/block.rs
中:
#![allow(unused)] fn main() { struct TerminatorCodegenHelper<'tcx> { bb: mir::BasicBlock, terminator: &'tcx mir::Terminator<'tcx>, funclet_bb: Option<mir::BasicBlock>, } impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> { // ... fn codegen_drop_terminator( &mut self, helper: TerminatorCodegenHelper<'tcx>, mut bx: Bx, location: mir::Place<'tcx>, target: mir::BasicBlock, unwind: Option<mir::BasicBlock>, ) { // ... let (drop_fn, fn_abi) = match ty.kind() { // FIXME(eddyb) perhaps move some of this logic into // `Instance::resolve_drop_in_place`? ty::Dynamic(..) => { // ... // 新增 ( meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_DROPINPLACE) .get_fn(&mut bx, vtable, &fn_abi), fn_abi, ) } // ... } // ... } // ... } }
src/mir/block.rs
顾名思义,这个是和 MIR 生成 basicblock 有关。代码中,要生成 drop 相关的终止符,所以需要得到虚表中 COMMON_VTABLE_ENTRIES_DROPINPLACE
相关 index信息。
rustc_codegen_cranelift 库中的修改
在 compiler/rustc_codegen_cranelift/src/vtable.rs
中,定义了一些自由函数,用于定义 trait 对象中 vtable相关。
因为在 rustc_codegen_ssa 做了一些相关修改,而目前 rustc_codegen_cranelift 并没有使用 rustc_codegen_ssa 的统一接口,所以需要修改 rustc_codegen_cranelift vtable相关代码。
#![allow(unused)] fn main() { use ty::VtblEntry; // 新增 pub(crate) fn drop_fn_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value { let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize; fx.bcx.ins().load( pointer_ty(fx.tcx), vtable_memflags(), vtable, (ty::COMMON_VTABLE_ENTRIES_DROPINPLACE * usize_size) as i32, // 新增 ) } pub(crate) fn size_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value { let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize; fx.bcx.ins().load( pointer_ty(fx.tcx), vtable_memflags(), vtable, (ty::COMMON_VTABLE_ENTRIES_SIZE * usize_size) as i32, // 新增 ) } pub(crate) fn min_align_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value { let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize; fx.bcx.ins().load( pointer_ty(fx.tcx), vtable_memflags(), vtable, (ty::COMMON_VTABLE_ENTRIES_SIZE * usize_size) as i32, // 新增 ) } pub(crate) fn get_ptr_and_method_ref<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, arg: CValue<'tcx>, idx: usize, ) -> (Value, Value) { let (ptr, vtable) = if let Abi::ScalarPair(_, _) = arg.layout().abi { arg.load_scalar_pair(fx) } else { let (ptr, vtable) = arg.try_to_ptr().unwrap(); (ptr.get_addr(fx), vtable.unwrap()) }; let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes(); let func_ref = fx.bcx.ins().load( pointer_ty(fx.tcx), vtable_memflags(), vtable, (idx * usize_size as usize) as i32, // 修改,因为 idx 变了,之前是 idx+3 ); (ptr, func_ref) } fn build_vtable<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, layout: TyAndLayout<'tcx>, trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, ) -> DataId { let tcx = fx.tcx; let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize; let drop_in_place_fn = import_function( tcx, fx.module, Instance::resolve_drop_in_place(tcx, layout.ty).polymorphize(fx.tcx), ); // 新增 let vtable_entries = if let Some(trait_ref) = trait_ref { tcx.vtable_entries(trait_ref.with_self_ty(tcx, layout.ty)) } else { ty::COMMON_VTABLE_ENTRIES }; let mut data_ctx = DataContext::new(); let mut data = ::std::iter::repeat(0u8) .take(vtable_entries.len() * usize_size) .collect::<Vec<u8>>() .into_boxed_slice(); // 新增 // 迭代处理 vtable entry for (idx, entry) in vtable_entries.iter().enumerate() { match entry { VtblEntry::MetadataSize => { write_usize(fx.tcx, &mut data, idx, layout.size.bytes()); } VtblEntry::MetadataAlign => { write_usize(fx.tcx, &mut data, idx, layout.align.abi.bytes()); } VtblEntry::MetadataDropInPlace | VtblEntry::Vacant | VtblEntry::Method(_, _) => {} } } data_ctx.define(data); // 迭代处理 vtable entry for (idx, entry) in vtable_entries.iter().enumerate() { match entry { VtblEntry::MetadataDropInPlace => { let func_ref = fx.module.declare_func_in_data(drop_in_place_fn, &mut data_ctx); data_ctx.write_function_addr((idx * usize_size) as u32, func_ref); } VtblEntry::Method(def_id, substs) => { let func_id = import_function( tcx, fx.module, Instance::resolve_for_vtable(tcx, ParamEnv::reveal_all(), *def_id, substs) .unwrap() .polymorphize(fx.tcx), ); let func_ref = fx.module.declare_func_in_data(func_id, &mut data_ctx); data_ctx.write_function_addr((idx * usize_size) as u32, func_ref); } VtblEntry::MetadataSize | VtblEntry::MetadataAlign | VtblEntry::Vacant => {} } } // ... } }
对 vtable 的修改,类似于 rustc_codegen_ssa 相关代码修改,只不过 rustc_codegen_cranelift 没有完全使用 rustc_codegen_ssa 的接口,所以需要另行单独处理。
rustc_trait_selection 中的修改
该库定义了 trait resolution 相关方法。Rust 编译器类型检查,mir层都依赖于该库。
Trait Resolution 主要用于判断该如何选择合理的 trait。 比如:
#![allow(unused)] fn main() { trait Convert<Target> { fn convert(&self) -> Target; } }
这个trait只有一个方法。它是最简单的。它从(隐含的)Self类型转换到Target类型。如果我们想允许isize和usize之间的转换,我们可以这样实现Convert。
#![allow(unused)] fn main() { impl Convert<usize> for isize { ... } // isize -> usize impl Convert<isize> for usize { ... } // usize -> isize }
现在想象一下,有一些像下面这样的代码。
#![allow(unused)] fn main() { let x: isize = .....; let y = x.convert(); }
对convert的调用将为isize生成一个trait reference Convert<$Y>
,其中$Y
是代表y类型的类型变量。在我们可以看到的两个impls中,唯一匹配的是Convert<usize> for isize
。因此,我们可以选择这个函数,这将导致$Y
的类型被统一为usize
。(注意,在组装候选程序时,我们在一个事务中进行初始统一,这样它们就不会相互影响。)
还有其他情况,可以参考 Trait resolution (old-style) 。
既然 vtable 已经修改,那也必须得修改该库中相关代码。
一共修改两个文件:rustc_trait_selection/src/traits/mod.rs
和 rustc_trait_selection/src/traits/select/confirmation.rs
。
在 src/traits/mod.rs
中:
#![allow(unused)] fn main() { use rustc_middle::ty::{ self, GenericParamDefKind, ParamEnv, ToPredicate, Ty, TyCtxt, VtblEntry, WithConstness, COMMON_VTABLE_ENTRIES, }; // 引入 新增的 VtblEntry类型 pub use self::util::{ supertrait_def_ids, supertraits, transitive_bounds, transitive_bounds_that_define_assoc_type, SupertraitDefIds, Supertraits, }; /// Given a trait `trait_ref`, iterates the vtable entries /// that come from `trait_ref`, including its supertraits. // 修改 原方法 fn vtable_entries<'tcx>( tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, ) -> &'tcx [VtblEntry<'tcx>] { debug!("vtable_entries({:?})", trait_ref); let entries = COMMON_VTABLE_ENTRIES.iter().cloned().chain( supertraits(tcx, trait_ref).flat_map(move |trait_ref| { let trait_methods = tcx .associated_items(trait_ref.def_id()) .in_definition_order() .filter(|item| item.kind == ty::AssocKind::Fn); // Now list each method's DefId and InternalSubsts (for within its trait). // If the method can never be called from this object, produce `Vacant`. trait_methods.map(move |trait_method| { debug!("vtable_entries: trait_method={:?}", trait_method); let def_id = trait_method.def_id; // Some methods cannot be called on an object; skip those. if !is_vtable_safe_method(tcx, trait_ref.def_id(), &trait_method) { debug!("vtable_entries: not vtable safe"); return VtblEntry::Vacant; } // The method may have some early-bound lifetimes; add regions for those. let substs = trait_ref.map_bound(|trait_ref| { InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { trait_ref.substs[param.index as usize] } }) }); // The trait type may have higher-ranked lifetimes in it; // erase them if they appear, so that we get the type // at some particular call site. let substs = tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); // It's possible that the method relies on where-clauses that // do not hold for this particular set of type parameters. // Note that this method could then never be called, so we // do not want to try and codegen it, in that case (see #23435). let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); if impossible_predicates(tcx, predicates.predicates) { debug!("vtable_entries: predicates do not hold"); return VtblEntry::Vacant; } VtblEntry::Method(def_id, substs) }) }), ); tcx.arena.alloc_from_iter(entries) } /// Find slot base for trait methods within vtable entries of another trait // 新增 : 查找其他 trait 的VTable条目中的 trait 方法的位置 fn vtable_trait_first_method_offset<'tcx>( tcx: TyCtxt<'tcx>, key: ( ty::PolyTraitRef<'tcx>, // trait_to_be_found ty::PolyTraitRef<'tcx>, // trait_owning_vtable ), ) -> usize { let (trait_to_be_found, trait_owning_vtable) = key; let mut supertraits = util::supertraits(tcx, trait_owning_vtable); // For each of the non-matching predicates that // we pass over, we sum up the set of number of vtable // entries, so that we can compute the offset for the selected // trait. let vtable_base = ty::COMMON_VTABLE_ENTRIES.len() + supertraits .by_ref() .take_while(|t| *t != trait_to_be_found) .map(|t| util::count_own_vtable_entries(tcx, t)) .sum::<usize>(); vtable_base } // 修改 pub fn provide(providers: &mut ty::query::Providers) { object_safety::provide(providers); structural_match::provide(providers); *providers = ty::query::Providers { // ... vtable_entries, // ... }; } }
在 src/traits/select/confirmation.rs
中:
该模块用于确认选中的 trait 。
#![allow(unused)] fn main() { fn confirm_object_candidate( &mut self, obligation: &TraitObligation<'tcx>, index: usize, ) -> Result<ImplSourceObjectData<'tcx, PredicateObligation<'tcx>>, SelectionError<'tcx>> { // ... let unnormalized_upcast_trait_ref = supertraits.nth(index).expect("supertraits iterator no longer has as many elements"); // 修改 // ... let vtable_base = super::super::vtable_trait_first_method_offset( tcx, (unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)), ); // ... } }
rustc_mir 中的修改
在 rust_mir 中涉及两个文件修改: rustc_mir/src/interpret/traits.rs
和 rustc_mir/src/monomorphize/collector.rs
。
在 src/interpret/traits.rs
中:
interpret 是和 mir 转译为 llvm ir 相关。
#![allow(unused)] fn main() { use rustc_middle::ty::{ self, Instance, Ty, VtblEntry, COMMON_VTABLE_ENTRIES, COMMON_VTABLE_ENTRIES_ALIGN, COMMON_VTABLE_ENTRIES_DROPINPLACE, COMMON_VTABLE_ENTRIES_SIZE, }; // 修改,引入 VtblEntry 相关新类型 // 修改原方法 // InterpCx 是 interpret 上下文 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Creates a dynamic vtable for the given type and vtable origin. This is used only for /// objects. /// /// The `trait_ref` encodes the erased self type. Hence, if we are /// making an object `Foo<Trait>` from a value of type `Foo<T>`, then /// `trait_ref` would map `T: Trait`. pub fn get_vtable( &mut self, ty: Ty<'tcx>, poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, ) -> InterpResult<'tcx, Pointer<M::PointerTag>> { // ... // 获取 vtable entries let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref { let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty); let trait_ref = self.tcx.erase_regions(trait_ref); self.tcx.vtable_entries(trait_ref) } else { COMMON_VTABLE_ENTRIES }; // ... // 新增 //////////////////////////////////////////////////////////////////////// // If you touch this code, be sure to also make the corresponding changes to // `get_vtable` in `rust_codegen_llvm/meth.rs`. // ///////////////////////////////////////////////////////////////////// let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap(); // ... // 新增 // No need to do any alignment checks on the memory accesses below, because we know the // allocation is correctly aligned as we created it above. Also we're only offsetting by // multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`. // 迭代处理 vtable entries 中每个虚表的布局 let scalars = vtable_entries .iter() .map(|entry| -> InterpResult<'tcx, _> { match entry { VtblEntry::MetadataDropInPlace => Ok(Some(drop.into())), VtblEntry::MetadataSize => Ok(Some(Scalar::from_uint(size, ptr_size).into())), VtblEntry::MetadataAlign => Ok(Some(Scalar::from_uint(align, ptr_size).into())), VtblEntry::Vacant => Ok(None), VtblEntry::Method(def_id, substs) => { // Prepare the fn ptr we write into the vtable. let instance = ty::Instance::resolve_for_vtable(tcx, self.param_env, *def_id, substs) .ok_or_else(|| err_inval!(TooGeneric))?; let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance)); Ok(Some(fn_ptr.into())) } } }) .collect::<Result<Vec<_>, _>>()?; let mut vtable_alloc = self.memory.get_mut(vtable.into(), vtable_size, ptr_align)?.expect("not a ZST"); for (idx, scalar) in scalars.into_iter().enumerate() { if let Some(scalar) = scalar { let idx: u64 = u64::try_from(idx).unwrap(); vtable_alloc.write_ptr_sized(ptr_size * idx, scalar)?; } } // ... // 修改原方法 /// Resolves the function at the specified slot in the provided /// vtable. Currently an index of '3' (`COMMON_VTABLE_ENTRIES.len()`) /// corresponds to the first method declared in the trait of the provided vtable. pub fn get_vtable_slot( &self, vtable: Scalar<M::PointerTag>, idx: u64, ) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> { let ptr_size = self.pointer_size(); let vtable_slot = vtable.ptr_offset(ptr_size * idx, self)?; // 新增 let vtable_slot = self .memory .get(vtable_slot, ptr_size, self.tcx.data_layout.pointer_align.abi)? .expect("cannot be a ZST"); let fn_ptr = vtable_slot.read_ptr_sized(Size::ZERO)?.check_init()?; self.memory.get_fn(fn_ptr) } /// Returns the drop fn instance as well as the actual dynamic type. pub fn read_drop_type_from_vtable( &self, vtable: Scalar<M::PointerTag>, ) -> InterpResult<'tcx, (ty::Instance<'tcx>, Ty<'tcx>)> { let pointer_size = self.pointer_size(); // We don't care about the pointee type; we just want a pointer. let vtable = self .memory .get( vtable, pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES.len()).unwrap(), self.tcx.data_layout.pointer_align.abi, )? .expect("cannot be a ZST"); let drop_fn = vtable .read_ptr_sized( pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_DROPINPLACE).unwrap(), )? .check_init()?; // .... } // ... // 修改原方法 pub fn read_size_and_align_from_vtable( &self, vtable: Scalar<M::PointerTag>, ) -> InterpResult<'tcx, (Size, Align)> { let pointer_size = self.pointer_size(); // We check for `size = 3 * ptr_size`, which covers the drop fn (unused here), // the size, and the align (which we read below). let vtable = self .memory .get( vtable, pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES.len()).unwrap(), self.tcx.data_layout.pointer_align.abi, )? .expect("cannot be a ZST"); let size = vtable .read_ptr_sized(pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_SIZE).unwrap())? .check_init()?; let size = u64::try_from(self.force_bits(size, pointer_size)?).unwrap(); let align = vtable .read_ptr_sized(pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_ALIGN).unwrap())? .check_init()?; let align = u64::try_from(self.force_bits(align, pointer_size)?).unwrap(); let align = Align::from_bytes(align).map_err(|e| err_ub!(InvalidVtableAlignment(e)))?; if size >= self.tcx.data_layout.obj_size_bound() { throw_ub!(InvalidVtableSize); } Ok((Size::from_bytes(size), align)) } // ... } } }
在 src/monomorphize/collector.rs
中:
monomorphize 意思是 单态化,意味着 这个模块用于 泛型单态化。
#![allow(unused)] fn main() { use rustc_middle::ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable, VtblEntry}; // 引入新的 VtablEntry 类型 /// Creates a `MonoItem` for each method that is referenced by the vtable for /// the given trait/impl pair. fn create_mono_items_for_vtable_methods<'tcx>( tcx: TyCtxt<'tcx>, trait_ty: Ty<'tcx>, impl_ty: Ty<'tcx>, source: Span, output: &mut Vec<Spanned<MonoItem<'tcx>>>, ) { // ... if let ty::Dynamic(ref trait_ty, ..) = trait_ty.kind() { if let Some(principal) = trait_ty.principal() { // ... // Walk all methods of the trait, including those of its supertraits // 走查所有的 trait 方法,包括 supertrait 的 let entries = tcx.vtable_entries(poly_trait_ref); let methods = entries .iter() .filter_map(|entry| match entry { VtblEntry::MetadataDropInPlace | VtblEntry::MetadataSize | VtblEntry::MetadataAlign | VtblEntry::Vacant => None, VtblEntry::Method(def_id, substs) => ty::Instance::resolve_for_vtable( tcx, ty::ParamEnv::reveal_all(), *def_id, substs, ) .filter(|instance| should_codegen_locally(tcx, instance)), }) .map(|item| create_fn_mono_item(tcx, item, source)); output.extend(methods); } } // ... } }
小结
第一步工作,主要是为了改进生成的 vtable结构,能够识别 多个trait 对象。
从 rustc_middle -> rustc_codgen_ssa -> rustc_codegen_cranelift -> rustc_trait_selection -> rustc_mir
这个过程,是从上到下,从抽象类型 到 能转译为 llvm IR 的 MIR。
如果 rustc_codgen_cranelift
能够完全使用 rustc_codgen_ssa
,那么代码修改起来应该更方便了。
后续:看到 rustc_codegen_gcc 就是基于 rustc_codgen_ssa
来实现的。