作者: 张汉东 / 审校: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 来实现 AsBaseimpl<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.rscompiler/rustc_codegen_ssa/src/glue.rscompiler/rustc_codegen_ssa/src/meth.rscompiler/rustc_codegen_ssa/src/mir/block.rscompiler/rustc_middle/src/query/mod.rscompiler/rustc_middle/src/ty/mod.rscompiler/rustc_mir/src/interpret/traits.rscompiler/rustc_mir/src/monomorphize/collector.rscompiler/rustc_trait_selection/src/traits/mod.rscompiler/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。
#[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。
// 使用的是一个宏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](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html)类型上 的方法,并且内存中的查询缓存也同样被存储在此。在代码中,通常会有一个名为tcx变量,它是 类型上下文上的一个句柄。有同样会见到名为'tcx的生命周期,这意味着有东西被和TyCtxt的 生命周期绑定在了一起(通常它会被存储或者被驻留化)。
[**ty::Ty**](https://rustcrustc.github.io/rustc-dev-guide-zh/overview.html#tyty) 介绍
类型在 Rust 中相当重要,并且形成了许多编译器分析的核心。用于表示类型(在用户程序中)的 主要类型(在编译器中)是 [rustc_middle::ty::Ty](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.Ty.html)。它是如此的重要以至于我们为其 设置了一整章[ty::Ty](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.Ty.html),但是对于现在而言,我们只想提到它存在并且是rustc用来表示类型的方法!
同样注意到rustc_middle::ty模块定义了我们之前提到的TyCtxt结构体。
rustc_codegen_ssa 中的修改
因为 rustc_codegen_ssa 是 后端 codegen 的接口,所以先看这里。
rustc_codegen_ssa 主要入口点: [rustc_codegen_ssa::base::codegen_crate](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/base/fn.codegen_crate.html)
- 它单态化并且产出 LLVM IR给一个代码生成单元。 它之后启动一个后台线程来运行一个之后必须被结合的LLVM。
 - 单态化通过
[FunctionCx::monomorphize](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#method.monomorphize)懒启动以及[rustc_codegen_ssa::base::codegen_instance](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/base/fn.codegen_instance.html) 
在 rust_codgen_ssa 出现之前,生成代码都是由 rust_codgen_llvm 处理。
LLVM codegen的两个最重要的结构是CodegenCx和Builder。它们由多个生命期参数和Value的类型组成。
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.rsrustc_codegen_ssa/src/glue.rsrustc_codegen_ssa/src/mir/block.rs
在 **meth.rs** 中:
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** 中:
// 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** 中:
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相关代码。
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 entryfor (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 entryfor (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。 比如:
trait Convert<Target> {fn convert(&self) -> Target;}
这个trait只有一个方法。它是最简单的。它从(隐含的)Self类型转换到Target类型。如果我们想允许isize和usize之间的转换,我们可以这样实现Convert。
impl Convert<usize> for isize { ... } // isize -> usizeimpl Convert<isize> for usize { ... } // usize -> isize
现在想象一下,有一些像下面这样的代码。
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**中:
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_foundty::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 。
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 相关。
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 entrieslet 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 意思是 单态化,意味着 这个模块用于 泛型单态化。
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来实现的。
