作者: 张汉东 / 审校: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 对象。

社区内有一个通用的解决办法:

  1. trait Base {
  2. fn base(&self) {
  3. println!("base...");
  4. }
  5. }
  6. trait AsBase {
  7. fn as_base(&self) -> &dyn Base; //返回 Base trait对象
  8. }
  9. // blanket implementation
  10. // 为所有实现 Base 的 T 来实现 AsBase
  11. impl<T: Base> AsBase for T {
  12. // 返回 Base trait对象
  13. fn as_base(&self) -> &dyn Base {
  14. self
  15. }
  16. }
  17. trait Foo: AsBase {
  18. fn foo(&self) {
  19. println!("foo..");
  20. }
  21. }
  22. #[derive(Debug)]
  23. struct MyStruct;
  24. impl Foo for MyStruct {}
  25. impl Base for MyStruct {}
  26. fn main() {
  27. let s = MyStruct;
  28. let foo: &dyn Foo = &s;
  29. foo.foo();
  30. let base: &dyn Base = foo.as_base(); // 通过 as_base 来返回 Base trait对象达到 upcasting 的效果
  31. base.base();
  32. }

在 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。

  1. #[derive(Clone, Copy, Debug, PartialEq, HashStable)]
  2. pub enum VtblEntry<'tcx> {
  3. MetadataDropInPlace,
  4. MetadataSize,
  5. MetadataAlign,
  6. Vacant,
  7. Method(DefId, SubstsRef<'tcx>),
  8. }
  9. pub const COMMON_VTABLE_ENTRIES: &[VtblEntry<'_>] =
  10. &[VtblEntry::MetadataDropInPlace, VtblEntry::MetadataSize, VtblEntry::MetadataAlign];
  11. pub const COMMON_VTABLE_ENTRIES_DROPINPLACE: usize = 0;
  12. pub const COMMON_VTABLE_ENTRIES_SIZE: usize = 1;
  13. 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

  1. // 使用的是一个宏
  2. rustc_queries! {
  3. // ...
  4. query vtable_entries(key: ty::PolyTraitRef<'tcx>)
  5. -> &'tcx [ty::VtblEntry<'tcx>] {
  6. desc { |tcx| "finding all vtable entries for trait {}", tcx.def_path_str(key.def_id()) }
  7. }
  8. // ...
  9. }

在 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的两个最重要的结构是CodegenCxBuilder。它们由多个生命期参数和Value的类型组成。

  1. struct CodegenCx<'ll, 'tcx> {
  2. /* ... */
  3. }
  4. struct Builder<'a, 'll, 'tcx> {
  5. cx: &'a CodegenCx<'ll, 'tcx>,
  6. /* ... */
  7. }

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** 中:

  1. use rustc_middle::ty::{self, Instance, Ty, VtblEntry, COMMON_VTABLE_ENTRIES}; // 引入 rustc_middle 新加的枚举 VtblEntry 相关
  2. impl<'a, 'tcx> VirtualIndex {
  3. pub fn from_index(index: usize) -> Self {
  4. VirtualIndex(index as u64) // 修改虚表index ,之前是偏移 3 ,因为之前是没有 vtable Entry 的,所以 DESTRUCTOR(index 0),SIZE(index 1),ALIGN(index 2) 都展开放了,现在则不需要。
  5. }
  6. // ...
  7. // 修改
  8. pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>(
  9. cx: &Cx,
  10. ty: Ty<'tcx>,
  11. trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
  12. ) -> Cx::Value {
  13. // ...
  14. // 新增
  15. // 当有 T: Trait 或 SubTrait: ParentTrait 这种形式出现时,就会有 trait_ref
  16. // 所以,相当于是 如果是有 trait 继承的情况下,就利用 query vtable_entries 来查询该trait
  17. // 并返回 vtable_entries ,否则返回 COMMON_VTABLE_ENTRIES,代表是单一的trait 对象
  18. let vtable_entries = if let Some(trait_ref) = trait_ref {
  19. tcx.vtable_entries(trait_ref.with_self_ty(tcx, ty))
  20. } else {
  21. COMMON_VTABLE_ENTRIES
  22. };
  23. let layout = cx.layout_of(ty); // 新增
  24. // /////////////////////////////////////////////////////////////////////////////////////////////
  25. // If you touch this code, be sure to also make the corresponding changes to
  26. // `get_vtable` in `rust_mir/interpret/traits.rs`.
  27. // /////////////////////////////////////////////////////////////////////////////////////////////
  28. // 新增
  29. // 迭代处理每个 vtable entry 的元信息:drop/大小/对齐/方法等
  30. let components: Vec<_> = vtable_entries
  31. .iter()
  32. .map(|entry| match entry {
  33. VtblEntry::MetadataDropInPlace => {
  34. cx.get_fn_addr(Instance::resolve_drop_in_place(cx.tcx(), ty))
  35. }
  36. VtblEntry::MetadataSize => cx.const_usize(layout.size.bytes()),
  37. VtblEntry::MetadataAlign => cx.const_usize(layout.align.abi.bytes()),
  38. VtblEntry::Vacant => nullptr,
  39. VtblEntry::Method(def_id, substs) => cx.get_fn_addr(
  40. ty::Instance::resolve_for_vtable(
  41. cx.tcx(),
  42. ty::ParamEnv::reveal_all(),
  43. *def_id,
  44. substs,
  45. )
  46. .unwrap()
  47. .polymorphize(cx.tcx()),
  48. ),
  49. })
  50. .collect();
  51. // ...
  52. }
  53. }

文档:rustc_middle::ty::PolyExistentialTraitRef ,该类型表示 对一个已经擦除了 Self 的 trait 的存在性引用,所以使用 with_self_ty 来提供一个self占位。

**src/glue.rs** 中:

  1. // BuilderMethods 是通用后端接口,但目前只有llvm用这个
  2. pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
  3. bx: &mut Bx,
  4. t: Ty<'tcx>,
  5. info: Option<Bx::Value>,
  6. ) -> (Bx::Value, Bx::Value) {
  7. // ...
  8. match t.kind() {
  9. ty::Dynamic(..) => {
  10. // load size/align from vtable
  11. // 新增
  12. let vtable = info.unwrap();
  13. (
  14. meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_SIZE)
  15. .get_usize(bx, vtable),
  16. meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_ALIGN)
  17. .get_usize(bx, vtable),
  18. )
  19. }
  20. // ...
  21. }
  22. // ...
  23. }

问题: 既然目前 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** 中:

  1. struct TerminatorCodegenHelper<'tcx> {
  2. bb: mir::BasicBlock,
  3. terminator: &'tcx mir::Terminator<'tcx>,
  4. funclet_bb: Option<mir::BasicBlock>,
  5. }
  6. impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
  7. // ...
  8. fn codegen_drop_terminator(
  9. &mut self,
  10. helper: TerminatorCodegenHelper<'tcx>,
  11. mut bx: Bx,
  12. location: mir::Place<'tcx>,
  13. target: mir::BasicBlock,
  14. unwind: Option<mir::BasicBlock>,
  15. ) {
  16. // ...
  17. let (drop_fn, fn_abi) = match ty.kind() {
  18. // FIXME(eddyb) perhaps move some of this logic into
  19. // `Instance::resolve_drop_in_place`?
  20. ty::Dynamic(..) => {
  21. // ...
  22. // 新增
  23. (
  24. meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_DROPINPLACE)
  25. .get_fn(&mut bx, vtable, &fn_abi),
  26. fn_abi,
  27. )
  28. }
  29. // ...
  30. }
  31. // ...
  32. }
  33. // ...
  34. }

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相关代码。

  1. use ty::VtblEntry; // 新增
  2. pub(crate) fn drop_fn_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value {
  3. let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize;
  4. fx.bcx.ins().load(
  5. pointer_ty(fx.tcx),
  6. vtable_memflags(),
  7. vtable,
  8. (ty::COMMON_VTABLE_ENTRIES_DROPINPLACE * usize_size) as i32, // 新增
  9. )
  10. }
  11. pub(crate) fn size_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value {
  12. let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize;
  13. fx.bcx.ins().load(
  14. pointer_ty(fx.tcx),
  15. vtable_memflags(),
  16. vtable,
  17. (ty::COMMON_VTABLE_ENTRIES_SIZE * usize_size) as i32, // 新增
  18. )
  19. }
  20. pub(crate) fn min_align_of_obj(fx: &mut FunctionCx<'_, '_, '_>, vtable: Value) -> Value {
  21. let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize;
  22. fx.bcx.ins().load(
  23. pointer_ty(fx.tcx),
  24. vtable_memflags(),
  25. vtable,
  26. (ty::COMMON_VTABLE_ENTRIES_SIZE * usize_size) as i32, // 新增
  27. )
  28. }
  29. pub(crate) fn get_ptr_and_method_ref<'tcx>(
  30. fx: &mut FunctionCx<'_, '_, 'tcx>,
  31. arg: CValue<'tcx>,
  32. idx: usize,
  33. ) -> (Value, Value) {
  34. let (ptr, vtable) = if let Abi::ScalarPair(_, _) = arg.layout().abi {
  35. arg.load_scalar_pair(fx)
  36. } else {
  37. let (ptr, vtable) = arg.try_to_ptr().unwrap();
  38. (ptr.get_addr(fx), vtable.unwrap())
  39. };
  40. let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes();
  41. let func_ref = fx.bcx.ins().load(
  42. pointer_ty(fx.tcx),
  43. vtable_memflags(),
  44. vtable,
  45. (idx * usize_size as usize) as i32, // 修改,因为 idx 变了,之前是 idx+3
  46. );
  47. (ptr, func_ref)
  48. }
  49. fn build_vtable<'tcx>(
  50. fx: &mut FunctionCx<'_, '_, 'tcx>,
  51. layout: TyAndLayout<'tcx>,
  52. trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
  53. ) -> DataId {
  54. let tcx = fx.tcx;
  55. let usize_size = fx.layout_of(fx.tcx.types.usize).size.bytes() as usize;
  56. let drop_in_place_fn = import_function(
  57. tcx,
  58. fx.module,
  59. Instance::resolve_drop_in_place(tcx, layout.ty).polymorphize(fx.tcx),
  60. );
  61. // 新增
  62. let vtable_entries = if let Some(trait_ref) = trait_ref {
  63. tcx.vtable_entries(trait_ref.with_self_ty(tcx, layout.ty))
  64. } else {
  65. ty::COMMON_VTABLE_ENTRIES
  66. };
  67. let mut data_ctx = DataContext::new();
  68. let mut data = ::std::iter::repeat(0u8)
  69. .take(vtable_entries.len() * usize_size)
  70. .collect::<Vec<u8>>()
  71. .into_boxed_slice();
  72. // 新增
  73. // 迭代处理 vtable entry
  74. for (idx, entry) in vtable_entries.iter().enumerate() {
  75. match entry {
  76. VtblEntry::MetadataSize => {
  77. write_usize(fx.tcx, &mut data, idx, layout.size.bytes());
  78. }
  79. VtblEntry::MetadataAlign => {
  80. write_usize(fx.tcx, &mut data, idx, layout.align.abi.bytes());
  81. }
  82. VtblEntry::MetadataDropInPlace | VtblEntry::Vacant | VtblEntry::Method(_, _) => {}
  83. }
  84. }
  85. data_ctx.define(data);
  86. // 迭代处理 vtable entry
  87. for (idx, entry) in vtable_entries.iter().enumerate() {
  88. match entry {
  89. VtblEntry::MetadataDropInPlace => {
  90. let func_ref = fx.module.declare_func_in_data(drop_in_place_fn, &mut data_ctx);
  91. data_ctx.write_function_addr((idx * usize_size) as u32, func_ref);
  92. }
  93. VtblEntry::Method(def_id, substs) => {
  94. let func_id = import_function(
  95. tcx,
  96. fx.module,
  97. Instance::resolve_for_vtable(tcx, ParamEnv::reveal_all(), *def_id, substs)
  98. .unwrap()
  99. .polymorphize(fx.tcx),
  100. );
  101. let func_ref = fx.module.declare_func_in_data(func_id, &mut data_ctx);
  102. data_ctx.write_function_addr((idx * usize_size) as u32, func_ref);
  103. }
  104. VtblEntry::MetadataSize | VtblEntry::MetadataAlign | VtblEntry::Vacant => {}
  105. }
  106. }
  107. // ...
  108. }

对 vtable 的修改,类似于 rustc_codegen_ssa 相关代码修改,只不过 rustc_codegen_cranelift 没有完全使用 rustc_codegen_ssa 的接口,所以需要另行单独处理。

rustc_trait_selection 中的修改

该库定义了 trait resolution 相关方法。Rust 编译器类型检查,mir层都依赖于该库。

Trait Resolution 主要用于判断该如何选择合理的 trait。 比如:

  1. trait Convert<Target> {
  2. fn convert(&self) -> Target;
  3. }

这个trait只有一个方法。它是最简单的。它从(隐含的)Self类型转换到Target类型。如果我们想允许isize和usize之间的转换,我们可以这样实现Convert。

  1. impl Convert<usize> for isize { ... } // isize -> usize
  2. impl Convert<isize> for usize { ... } // usize -> isize

现在想象一下,有一些像下面这样的代码。

  1. let x: isize = .....;
  2. 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**中:

  1. use rustc_middle::ty::{
  2. self, GenericParamDefKind, ParamEnv, ToPredicate, Ty, TyCtxt, VtblEntry, WithConstness,
  3. COMMON_VTABLE_ENTRIES,
  4. }; // 引入 新增的 VtblEntry类型
  5. pub use self::util::{
  6. supertrait_def_ids, supertraits, transitive_bounds, transitive_bounds_that_define_assoc_type,
  7. SupertraitDefIds, Supertraits,
  8. };
  9. /// Given a trait `trait_ref`, iterates the vtable entries
  10. /// that come from `trait_ref`, including its supertraits.
  11. // 修改 原方法
  12. fn vtable_entries<'tcx>(
  13. tcx: TyCtxt<'tcx>,
  14. trait_ref: ty::PolyTraitRef<'tcx>,
  15. ) -> &'tcx [VtblEntry<'tcx>] {
  16. debug!("vtable_entries({:?})", trait_ref);
  17. let entries = COMMON_VTABLE_ENTRIES.iter().cloned().chain(
  18. supertraits(tcx, trait_ref).flat_map(move |trait_ref| {
  19. let trait_methods = tcx
  20. .associated_items(trait_ref.def_id())
  21. .in_definition_order()
  22. .filter(|item| item.kind == ty::AssocKind::Fn);
  23. // Now list each method's DefId and InternalSubsts (for within its trait).
  24. // If the method can never be called from this object, produce `Vacant`.
  25. trait_methods.map(move |trait_method| {
  26. debug!("vtable_entries: trait_method={:?}", trait_method);
  27. let def_id = trait_method.def_id;
  28. // Some methods cannot be called on an object; skip those.
  29. if !is_vtable_safe_method(tcx, trait_ref.def_id(), &trait_method) {
  30. debug!("vtable_entries: not vtable safe");
  31. return VtblEntry::Vacant;
  32. }
  33. // The method may have some early-bound lifetimes; add regions for those.
  34. let substs = trait_ref.map_bound(|trait_ref| {
  35. InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind {
  36. GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
  37. GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
  38. trait_ref.substs[param.index as usize]
  39. }
  40. })
  41. });
  42. // The trait type may have higher-ranked lifetimes in it;
  43. // erase them if they appear, so that we get the type
  44. // at some particular call site.
  45. let substs =
  46. tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs);
  47. // It's possible that the method relies on where-clauses that
  48. // do not hold for this particular set of type parameters.
  49. // Note that this method could then never be called, so we
  50. // do not want to try and codegen it, in that case (see #23435).
  51. let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs);
  52. if impossible_predicates(tcx, predicates.predicates) {
  53. debug!("vtable_entries: predicates do not hold");
  54. return VtblEntry::Vacant;
  55. }
  56. VtblEntry::Method(def_id, substs)
  57. })
  58. }),
  59. );
  60. tcx.arena.alloc_from_iter(entries)
  61. }
  62. /// Find slot base for trait methods within vtable entries of another trait
  63. // 新增 : 查找其他 trait 的VTable条目中的 trait 方法的位置
  64. fn vtable_trait_first_method_offset<'tcx>(
  65. tcx: TyCtxt<'tcx>,
  66. key: (
  67. ty::PolyTraitRef<'tcx>, // trait_to_be_found
  68. ty::PolyTraitRef<'tcx>, // trait_owning_vtable
  69. ),
  70. ) -> usize {
  71. let (trait_to_be_found, trait_owning_vtable) = key;
  72. let mut supertraits = util::supertraits(tcx, trait_owning_vtable);
  73. // For each of the non-matching predicates that
  74. // we pass over, we sum up the set of number of vtable
  75. // entries, so that we can compute the offset for the selected
  76. // trait.
  77. let vtable_base = ty::COMMON_VTABLE_ENTRIES.len()
  78. + supertraits
  79. .by_ref()
  80. .take_while(|t| *t != trait_to_be_found)
  81. .map(|t| util::count_own_vtable_entries(tcx, t))
  82. .sum::<usize>();
  83. vtable_base
  84. }
  85. // 修改
  86. pub fn provide(providers: &mut ty::query::Providers) {
  87. object_safety::provide(providers);
  88. structural_match::provide(providers);
  89. *providers = ty::query::Providers {
  90. // ...
  91. vtable_entries,
  92. // ...
  93. };
  94. }

**src/traits/select/confirmation.rs** 中:

该模块用于确认选中的 trait 。

  1. fn confirm_object_candidate(
  2. &mut self,
  3. obligation: &TraitObligation<'tcx>,
  4. index: usize,
  5. ) -> Result<ImplSourceObjectData<'tcx, PredicateObligation<'tcx>>, SelectionError<'tcx>> {
  6. // ...
  7. let unnormalized_upcast_trait_ref =
  8. supertraits.nth(index).expect("supertraits iterator no longer has as many elements"); // 修改
  9. // ...
  10. let vtable_base = super::super::vtable_trait_first_method_offset(
  11. tcx,
  12. (unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)),
  13. );
  14. // ...
  15. }

rustc_mir 中的修改

在 rust_mir 中涉及两个文件修改: rustc_mir/src/interpret/traits.rsrustc_mir/src/monomorphize/collector.rs

**src/interpret/traits.rs** 中:

interpret 是和 mir 转译为 llvm ir 相关。

  1. use rustc_middle::ty::{
  2. self, Instance, Ty, VtblEntry, COMMON_VTABLE_ENTRIES, COMMON_VTABLE_ENTRIES_ALIGN,
  3. COMMON_VTABLE_ENTRIES_DROPINPLACE, COMMON_VTABLE_ENTRIES_SIZE,
  4. }; // 修改,引入 VtblEntry 相关新类型
  5. // 修改原方法
  6. // InterpCx 是 interpret 上下文
  7. impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
  8. /// Creates a dynamic vtable for the given type and vtable origin. This is used only for
  9. /// objects.
  10. ///
  11. /// The `trait_ref` encodes the erased self type. Hence, if we are
  12. /// making an object `Foo<Trait>` from a value of type `Foo<T>`, then
  13. /// `trait_ref` would map `T: Trait`.
  14. pub fn get_vtable(
  15. &mut self,
  16. ty: Ty<'tcx>,
  17. poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
  18. ) -> InterpResult<'tcx, Pointer<M::PointerTag>> {
  19. // ...
  20. // 获取 vtable entries
  21. let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref {
  22. let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty);
  23. let trait_ref = self.tcx.erase_regions(trait_ref);
  24. self.tcx.vtable_entries(trait_ref)
  25. } else {
  26. COMMON_VTABLE_ENTRIES
  27. };
  28. // ...
  29. // 新增
  30. ////////////////////////////////////////////////////////////////////////
  31. // If you touch this code, be sure to also make the corresponding changes to
  32. // `get_vtable` in `rust_codegen_llvm/meth.rs`.
  33. // /////////////////////////////////////////////////////////////////////
  34. let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
  35. // ...
  36. // 新增
  37. // No need to do any alignment checks on the memory accesses below, because we know the
  38. // allocation is correctly aligned as we created it above. Also we're only offsetting by
  39. // multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`.
  40. // 迭代处理 vtable entries 中每个虚表的布局
  41. let scalars = vtable_entries
  42. .iter()
  43. .map(|entry| -> InterpResult<'tcx, _> {
  44. match entry {
  45. VtblEntry::MetadataDropInPlace => Ok(Some(drop.into())),
  46. VtblEntry::MetadataSize => Ok(Some(Scalar::from_uint(size, ptr_size).into())),
  47. VtblEntry::MetadataAlign => Ok(Some(Scalar::from_uint(align, ptr_size).into())),
  48. VtblEntry::Vacant => Ok(None),
  49. VtblEntry::Method(def_id, substs) => {
  50. // Prepare the fn ptr we write into the vtable.
  51. let instance =
  52. ty::Instance::resolve_for_vtable(tcx, self.param_env, *def_id, substs)
  53. .ok_or_else(|| err_inval!(TooGeneric))?;
  54. let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance));
  55. Ok(Some(fn_ptr.into()))
  56. }
  57. }
  58. })
  59. .collect::<Result<Vec<_>, _>>()?;
  60. let mut vtable_alloc =
  61. self.memory.get_mut(vtable.into(), vtable_size, ptr_align)?.expect("not a ZST");
  62. for (idx, scalar) in scalars.into_iter().enumerate() {
  63. if let Some(scalar) = scalar {
  64. let idx: u64 = u64::try_from(idx).unwrap();
  65. vtable_alloc.write_ptr_sized(ptr_size * idx, scalar)?;
  66. }
  67. }
  68. // ...
  69. // 修改原方法
  70. /// Resolves the function at the specified slot in the provided
  71. /// vtable. Currently an index of '3' (`COMMON_VTABLE_ENTRIES.len()`)
  72. /// corresponds to the first method declared in the trait of the provided vtable.
  73. pub fn get_vtable_slot(
  74. &self,
  75. vtable: Scalar<M::PointerTag>,
  76. idx: u64,
  77. ) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
  78. let ptr_size = self.pointer_size();
  79. let vtable_slot = vtable.ptr_offset(ptr_size * idx, self)?; // 新增
  80. let vtable_slot = self
  81. .memory
  82. .get(vtable_slot, ptr_size, self.tcx.data_layout.pointer_align.abi)?
  83. .expect("cannot be a ZST");
  84. let fn_ptr = vtable_slot.read_ptr_sized(Size::ZERO)?.check_init()?;
  85. self.memory.get_fn(fn_ptr)
  86. }
  87. /// Returns the drop fn instance as well as the actual dynamic type.
  88. pub fn read_drop_type_from_vtable(
  89. &self,
  90. vtable: Scalar<M::PointerTag>,
  91. ) -> InterpResult<'tcx, (ty::Instance<'tcx>, Ty<'tcx>)> {
  92. let pointer_size = self.pointer_size();
  93. // We don't care about the pointee type; we just want a pointer.
  94. let vtable = self
  95. .memory
  96. .get(
  97. vtable,
  98. pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES.len()).unwrap(),
  99. self.tcx.data_layout.pointer_align.abi,
  100. )?
  101. .expect("cannot be a ZST");
  102. let drop_fn = vtable
  103. .read_ptr_sized(
  104. pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_DROPINPLACE).unwrap(),
  105. )?
  106. .check_init()?;
  107. // ....
  108. }
  109. // ...
  110. // 修改原方法
  111. pub fn read_size_and_align_from_vtable(
  112. &self,
  113. vtable: Scalar<M::PointerTag>,
  114. ) -> InterpResult<'tcx, (Size, Align)> {
  115. let pointer_size = self.pointer_size();
  116. // We check for `size = 3 * ptr_size`, which covers the drop fn (unused here),
  117. // the size, and the align (which we read below).
  118. let vtable = self
  119. .memory
  120. .get(
  121. vtable,
  122. pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES.len()).unwrap(),
  123. self.tcx.data_layout.pointer_align.abi,
  124. )?
  125. .expect("cannot be a ZST");
  126. let size = vtable
  127. .read_ptr_sized(pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_SIZE).unwrap())?
  128. .check_init()?;
  129. let size = u64::try_from(self.force_bits(size, pointer_size)?).unwrap();
  130. let align = vtable
  131. .read_ptr_sized(pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_ALIGN).unwrap())?
  132. .check_init()?;
  133. let align = u64::try_from(self.force_bits(align, pointer_size)?).unwrap();
  134. let align = Align::from_bytes(align).map_err(|e| err_ub!(InvalidVtableAlignment(e)))?;
  135. if size >= self.tcx.data_layout.obj_size_bound() {
  136. throw_ub!(InvalidVtableSize);
  137. }
  138. Ok((Size::from_bytes(size), align))
  139. }
  140. // ...
  141. }
  142. }

**src/monomorphize/collector.rs**中:

monomorphize 意思是 单态化,意味着 这个模块用于 泛型单态化。

  1. use rustc_middle::ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable, VtblEntry}; // 引入新的 VtablEntry 类型
  2. /// Creates a `MonoItem` for each method that is referenced by the vtable for
  3. /// the given trait/impl pair.
  4. fn create_mono_items_for_vtable_methods<'tcx>(
  5. tcx: TyCtxt<'tcx>,
  6. trait_ty: Ty<'tcx>,
  7. impl_ty: Ty<'tcx>,
  8. source: Span,
  9. output: &mut Vec<Spanned<MonoItem<'tcx>>>,
  10. ) {
  11. // ...
  12. if let ty::Dynamic(ref trait_ty, ..) = trait_ty.kind() {
  13. if let Some(principal) = trait_ty.principal() {
  14. // ...
  15. // Walk all methods of the trait, including those of its supertraits
  16. // 走查所有的 trait 方法,包括 supertrait 的
  17. let entries = tcx.vtable_entries(poly_trait_ref);
  18. let methods = entries
  19. .iter()
  20. .filter_map(|entry| match entry {
  21. VtblEntry::MetadataDropInPlace
  22. | VtblEntry::MetadataSize
  23. | VtblEntry::MetadataAlign
  24. | VtblEntry::Vacant => None,
  25. VtblEntry::Method(def_id, substs) => ty::Instance::resolve_for_vtable(
  26. tcx,
  27. ty::ParamEnv::reveal_all(),
  28. *def_id,
  29. substs,
  30. )
  31. .filter(|instance| should_codegen_locally(tcx, instance)),
  32. })
  33. .map(|item| create_fn_mono_item(tcx, item, source));
  34. output.extend(methods);
  35. }
  36. }
  37. // ...
  38. }

小结

第一步工作,主要是为了改进生成的 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来实现的。