Trait Upcasting 系列 | Part I

作者: 张汉东 / 审校:crlf0710


引子

记录 @crlf0710 的 Trait Upcasting系列 系列 PR 过程。因为后续我也参与其中一个 PR,所以先要理清楚 @crlf0710 的 PR 思路,以便后续我的参与。也借此机会分享出来,希望更多的人能参与到 Rust 语言贡献中。

PR 系列:

  1. Refactor vtable codegen #86291
  2. Change vtable memory representation to use tcx allocated allocations.#86475
  3. Refactor vtable format for upcoming trait_upcasting feature. #86461
  4. Trait upcasting (part1) #86264
  5. 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

修改文件概述

本次修改涉及 十个文件。

  1. compiler/rustc_codegen_cranelift/src/vtable.rs
  2. compiler/rustc_codegen_ssa/src/glue.rs
  3. compiler/rustc_codegen_ssa/src/meth.rs
  4. compiler/rustc_codegen_ssa/src/mir/block.rs
  5. compiler/rustc_middle/src/query/mod.rs
  6. compiler/rustc_middle/src/ty/mod.rs
  7. compiler/rustc_mir/src/interpret/traits.rs
  8. compiler/rustc_mir/src/monomorphize/collector.rs
  9. compiler/rustc_trait_selection/src/traits/mod.rs
  10. compiler/rustc_trait_selection/src/traits/select/confirmation.rs

这十个文件涉及五个 crate:

  1. rustc_codegen_cranelift,是 基于 cranelift 的编译器后端,专门用于 debug 模式。
  2. rustc_codegen_ssa,截至2021年1月,RustC_Codegen_SSA 为所有后端提供了一个抽象的接口,以允许其他Codegen后端(例如Cranelift)。
  3. rustc_middle,属于 rust 编译器的 main crate ,包含rustc“家族”中的其他crate使用的通用类型定义,包括 HIR/MIR/Types。
  4. rustc_mir,用于操作 MIR 的库。
  5. 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

在 rust_codgen_ssa 出现之前,生成代码都是由 rust_codgen_llvm 处理。

LLVM codegen的两个最重要的结构是CodegenCxBuilder。它们由多个生命期参数和Value的类型组成。


#![allow(unused)]

fn main() {
struct CodegenCx<'ll, 'tcx> {
  /* ... */
}

struct Builder<'a, 'll, 'tcx> {
  cx: &'a CodegenCx<'ll, 'tcx>,
  /* ... */
}
}

CodegenCx是用来编译一个可以包含多个函数的 codegen-unit 的,而Builder 是为了编译一个基本块而创建的。CodegenCxBuilder将是实现所有定义后端接口的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 的相关工作,主要涉及三个文件:

  1. rustc_codegen_ssa/src/meth.rs
  2. rustc_codegen_ssa/src/glue.rs
  3. 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.rsrustc_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来实现的。