Arena-allocate hir::Lifetime
.
This shrinks `hir::Ty` from 72 to 48 bytes. `visit_lifetime` is added to the HIR stats collector because these types are now stored in memory on their own, instead of being within other types.
This commit is contained in:
parent
512bd84f51
commit
4314615ff8
6 changed files with 65 additions and 56 deletions
|
@ -1196,7 +1196,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
let lifetime_bound = this.elided_dyn_bound(t.span);
|
let lifetime_bound = this.elided_dyn_bound(t.span);
|
||||||
(bounds, lifetime_bound)
|
(bounds, lifetime_bound)
|
||||||
});
|
});
|
||||||
let kind = hir::TyKind::TraitObject(bounds, lifetime_bound, TraitObjectSyntax::None);
|
let kind = hir::TyKind::TraitObject(bounds, &lifetime_bound, TraitObjectSyntax::None);
|
||||||
return hir::Ty { kind, span: self.lower_span(t.span), hir_id: self.next_id() };
|
return hir::Ty { kind, span: self.lower_span(t.span), hir_id: self.next_id() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1934,8 +1934,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
let res = res.unwrap_or(
|
let res = res.unwrap_or(
|
||||||
self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error),
|
self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error),
|
||||||
);
|
);
|
||||||
let l = self.new_named_lifetime_with_res(id, span, ident, res);
|
hir::GenericArg::Lifetime(self.new_named_lifetime_with_res(id, span, ident, res))
|
||||||
hir::GenericArg::Lifetime(l)
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -2004,7 +2003,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lower_lifetime(&mut self, l: &Lifetime) -> hir::Lifetime {
|
fn lower_lifetime(&mut self, l: &Lifetime) -> &'hir hir::Lifetime {
|
||||||
let span = self.lower_span(l.ident.span);
|
let span = self.lower_span(l.ident.span);
|
||||||
let ident = self.lower_ident(l.ident);
|
let ident = self.lower_ident(l.ident);
|
||||||
self.new_named_lifetime(l.id, l.id, span, ident)
|
self.new_named_lifetime(l.id, l.id, span, ident)
|
||||||
|
@ -2017,7 +2016,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
span: Span,
|
span: Span,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
res: LifetimeRes,
|
res: LifetimeRes,
|
||||||
) -> hir::Lifetime {
|
) -> &'hir hir::Lifetime {
|
||||||
let name = match res {
|
let name = match res {
|
||||||
LifetimeRes::Param { param, .. } => {
|
LifetimeRes::Param { param, .. } => {
|
||||||
let p_name = ParamName::Plain(ident);
|
let p_name = ParamName::Plain(ident);
|
||||||
|
@ -2038,7 +2037,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(?name);
|
debug!(?name);
|
||||||
hir::Lifetime { hir_id: self.lower_node_id(id), span: self.lower_span(span), name }
|
self.arena.alloc(hir::Lifetime {
|
||||||
|
hir_id: self.lower_node_id(id),
|
||||||
|
span: self.lower_span(span),
|
||||||
|
name,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
|
@ -2048,7 +2051,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
new_id: NodeId,
|
new_id: NodeId,
|
||||||
span: Span,
|
span: Span,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
) -> hir::Lifetime {
|
) -> &'hir hir::Lifetime {
|
||||||
let res = self.resolver.get_lifetime_res(id).unwrap_or(LifetimeRes::Error);
|
let res = self.resolver.get_lifetime_res(id).unwrap_or(LifetimeRes::Error);
|
||||||
self.new_named_lifetime_with_res(new_id, span, ident, res)
|
self.new_named_lifetime_with_res(new_id, span, ident, res)
|
||||||
}
|
}
|
||||||
|
@ -2462,14 +2465,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
/// bound, like the bound in `Box<dyn Debug>`. This method is not invoked
|
/// bound, like the bound in `Box<dyn Debug>`. This method is not invoked
|
||||||
/// when the bound is written, even if it is written with `'_` like in
|
/// when the bound is written, even if it is written with `'_` like in
|
||||||
/// `Box<dyn Debug + '_>`. In those cases, `lower_lifetime` is invoked.
|
/// `Box<dyn Debug + '_>`. In those cases, `lower_lifetime` is invoked.
|
||||||
fn elided_dyn_bound(&mut self, span: Span) -> hir::Lifetime {
|
fn elided_dyn_bound(&mut self, span: Span) -> &'hir hir::Lifetime {
|
||||||
let r = hir::Lifetime {
|
let r = hir::Lifetime {
|
||||||
hir_id: self.next_id(),
|
hir_id: self.next_id(),
|
||||||
span: self.lower_span(span),
|
span: self.lower_span(span),
|
||||||
name: hir::LifetimeName::ImplicitObjectLifetimeDefault,
|
name: hir::LifetimeName::ImplicitObjectLifetimeDefault,
|
||||||
};
|
};
|
||||||
debug!("elided_dyn_bound: r={:?}", r);
|
debug!("elided_dyn_bound: r={:?}", r);
|
||||||
r
|
self.arena.alloc(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ impl InferArg {
|
||||||
|
|
||||||
#[derive(Debug, HashStable_Generic)]
|
#[derive(Debug, HashStable_Generic)]
|
||||||
pub enum GenericArg<'hir> {
|
pub enum GenericArg<'hir> {
|
||||||
Lifetime(Lifetime),
|
Lifetime(&'hir Lifetime),
|
||||||
Type(&'hir Ty<'hir>),
|
Type(&'hir Ty<'hir>),
|
||||||
Const(ConstArg),
|
Const(ConstArg),
|
||||||
Infer(InferArg),
|
Infer(InferArg),
|
||||||
|
@ -430,7 +430,7 @@ pub enum GenericBound<'hir> {
|
||||||
Trait(PolyTraitRef<'hir>, TraitBoundModifier),
|
Trait(PolyTraitRef<'hir>, TraitBoundModifier),
|
||||||
// FIXME(davidtwco): Introduce `PolyTraitRef::LangItem`
|
// FIXME(davidtwco): Introduce `PolyTraitRef::LangItem`
|
||||||
LangItemTrait(LangItem, Span, HirId, &'hir GenericArgs<'hir>),
|
LangItemTrait(LangItem, Span, HirId, &'hir GenericArgs<'hir>),
|
||||||
Outlives(Lifetime),
|
Outlives(&'hir Lifetime),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenericBound<'_> {
|
impl GenericBound<'_> {
|
||||||
|
@ -756,7 +756,7 @@ impl<'hir> WhereBoundPredicate<'hir> {
|
||||||
pub struct WhereRegionPredicate<'hir> {
|
pub struct WhereRegionPredicate<'hir> {
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub in_where_clause: bool,
|
pub in_where_clause: bool,
|
||||||
pub lifetime: Lifetime,
|
pub lifetime: &'hir Lifetime,
|
||||||
pub bounds: GenericBounds<'hir>,
|
pub bounds: GenericBounds<'hir>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2499,7 +2499,7 @@ pub enum TyKind<'hir> {
|
||||||
/// A raw pointer (i.e., `*const T` or `*mut T`).
|
/// A raw pointer (i.e., `*const T` or `*mut T`).
|
||||||
Ptr(MutTy<'hir>),
|
Ptr(MutTy<'hir>),
|
||||||
/// A reference (i.e., `&'a T` or `&'a mut T`).
|
/// A reference (i.e., `&'a T` or `&'a mut T`).
|
||||||
Rptr(Lifetime, MutTy<'hir>),
|
Rptr(&'hir Lifetime, MutTy<'hir>),
|
||||||
/// A bare function (e.g., `fn(usize) -> bool`).
|
/// A bare function (e.g., `fn(usize) -> bool`).
|
||||||
BareFn(&'hir BareFnTy<'hir>),
|
BareFn(&'hir BareFnTy<'hir>),
|
||||||
/// The never type (`!`).
|
/// The never type (`!`).
|
||||||
|
@ -2518,7 +2518,7 @@ pub enum TyKind<'hir> {
|
||||||
OpaqueDef(ItemId, &'hir [GenericArg<'hir>]),
|
OpaqueDef(ItemId, &'hir [GenericArg<'hir>]),
|
||||||
/// A trait object type `Bound1 + Bound2 + Bound3`
|
/// A trait object type `Bound1 + Bound2 + Bound3`
|
||||||
/// where `Bound` is a trait or a lifetime.
|
/// where `Bound` is a trait or a lifetime.
|
||||||
TraitObject(&'hir [PolyTraitRef<'hir>], Lifetime, TraitObjectSyntax),
|
TraitObject(&'hir [PolyTraitRef<'hir>], &'hir Lifetime, TraitObjectSyntax),
|
||||||
/// Unused for now.
|
/// Unused for now.
|
||||||
Typeof(AnonConst),
|
Typeof(AnonConst),
|
||||||
/// `TyKind::Infer` means the type should be inferred instead of it having been
|
/// `TyKind::Infer` means the type should be inferred instead of it having been
|
||||||
|
@ -3474,7 +3474,7 @@ mod size_asserts {
|
||||||
static_assert_size!(ForeignItem<'_>, 72);
|
static_assert_size!(ForeignItem<'_>, 72);
|
||||||
static_assert_size!(ForeignItemKind<'_>, 40);
|
static_assert_size!(ForeignItemKind<'_>, 40);
|
||||||
#[cfg(not(bootstrap))]
|
#[cfg(not(bootstrap))]
|
||||||
static_assert_size!(GenericArg<'_>, 32);
|
static_assert_size!(GenericArg<'_>, 24);
|
||||||
static_assert_size!(GenericBound<'_>, 48);
|
static_assert_size!(GenericBound<'_>, 48);
|
||||||
static_assert_size!(Generics<'_>, 56);
|
static_assert_size!(Generics<'_>, 56);
|
||||||
static_assert_size!(Impl<'_>, 80);
|
static_assert_size!(Impl<'_>, 80);
|
||||||
|
@ -3494,9 +3494,9 @@ mod size_asserts {
|
||||||
static_assert_size!(Stmt<'_>, 32);
|
static_assert_size!(Stmt<'_>, 32);
|
||||||
static_assert_size!(StmtKind<'_>, 16);
|
static_assert_size!(StmtKind<'_>, 16);
|
||||||
#[cfg(not(bootstrap))]
|
#[cfg(not(bootstrap))]
|
||||||
static_assert_size!(TraitItem<'static>, 88);
|
static_assert_size!(TraitItem<'_>, 88);
|
||||||
#[cfg(not(bootstrap))]
|
#[cfg(not(bootstrap))]
|
||||||
static_assert_size!(TraitItemKind<'_>, 48);
|
static_assert_size!(TraitItemKind<'_>, 48);
|
||||||
static_assert_size!(Ty<'_>, 72);
|
static_assert_size!(Ty<'_>, 48);
|
||||||
static_assert_size!(TyKind<'_>, 56);
|
static_assert_size!(TyKind<'_>, 32);
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,6 +437,11 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
|
||||||
|
self.record("Lifetime", Id::Node(lifetime.hir_id), lifetime);
|
||||||
|
hir_visit::walk_lifetime(self, lifetime)
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) {
|
fn visit_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) {
|
||||||
self.record("Path", Id::None, path);
|
self.record("Path", Id::None, path);
|
||||||
hir_visit::walk_path(self, path)
|
hir_visit::walk_path(self, path)
|
||||||
|
|
|
@ -190,7 +190,7 @@ fn clean_poly_trait_ref_with_bindings<'tcx>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_lifetime<'tcx>(lifetime: hir::Lifetime, cx: &mut DocContext<'tcx>) -> Lifetime {
|
fn clean_lifetime<'tcx>(lifetime: &hir::Lifetime, cx: &mut DocContext<'tcx>) -> Lifetime {
|
||||||
let def = cx.tcx.named_region(lifetime.hir_id);
|
let def = cx.tcx.named_region(lifetime.hir_id);
|
||||||
if let Some(
|
if let Some(
|
||||||
rl::Region::EarlyBound(node_id)
|
rl::Region::EarlyBound(node_id)
|
||||||
|
@ -495,7 +495,7 @@ fn clean_generic_param<'tcx>(
|
||||||
.filter(|bp| !bp.in_where_clause)
|
.filter(|bp| !bp.in_where_clause)
|
||||||
.flat_map(|bp| bp.bounds)
|
.flat_map(|bp| bp.bounds)
|
||||||
.map(|bound| match bound {
|
.map(|bound| match bound {
|
||||||
hir::GenericBound::Outlives(lt) => clean_lifetime(*lt, cx),
|
hir::GenericBound::Outlives(lt) => clean_lifetime(lt, cx),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -1392,7 +1392,7 @@ fn maybe_expand_private_type_alias<'tcx>(
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
if let Some(lt) = lifetime.cloned() {
|
if let Some(lt) = lifetime {
|
||||||
let lt_def_id = cx.tcx.hir().local_def_id(param.hir_id);
|
let lt_def_id = cx.tcx.hir().local_def_id(param.hir_id);
|
||||||
let cleaned =
|
let cleaned =
|
||||||
if !lt.is_elided() { clean_lifetime(lt, cx) } else { Lifetime::elided() };
|
if !lt.is_elided() { clean_lifetime(lt, cx) } else { Lifetime::elided() };
|
||||||
|
|
|
@ -119,59 +119,60 @@ hir-stats HIR STATS
|
||||||
hir-stats Name Accumulated Size Count Item Size
|
hir-stats Name Accumulated Size Count Item Size
|
||||||
hir-stats ----------------------------------------------------------------
|
hir-stats ----------------------------------------------------------------
|
||||||
hir-stats ForeignItemRef 24 ( 0.2%) 1 24
|
hir-stats ForeignItemRef 24 ( 0.2%) 1 24
|
||||||
|
hir-stats Lifetime 32 ( 0.3%) 1 32
|
||||||
hir-stats Mod 32 ( 0.3%) 1 32
|
hir-stats Mod 32 ( 0.3%) 1 32
|
||||||
hir-stats ExprField 40 ( 0.4%) 1 40
|
hir-stats ExprField 40 ( 0.4%) 1 40
|
||||||
hir-stats TraitItemRef 56 ( 0.6%) 2 28
|
hir-stats TraitItemRef 56 ( 0.6%) 2 28
|
||||||
hir-stats Param 64 ( 0.6%) 2 32
|
hir-stats Local 64 ( 0.7%) 1 64
|
||||||
hir-stats Local 64 ( 0.6%) 1 64
|
hir-stats Param 64 ( 0.7%) 2 32
|
||||||
hir-stats InlineAsm 72 ( 0.7%) 1 72
|
hir-stats InlineAsm 72 ( 0.7%) 1 72
|
||||||
hir-stats ImplItemRef 72 ( 0.7%) 2 36
|
hir-stats ImplItemRef 72 ( 0.7%) 2 36
|
||||||
hir-stats FieldDef 96 ( 0.9%) 2 48
|
hir-stats Body 96 ( 1.0%) 3 32
|
||||||
hir-stats Arm 96 ( 0.9%) 2 48
|
hir-stats GenericArg 96 ( 1.0%) 4 24
|
||||||
hir-stats Body 96 ( 0.9%) 3 32
|
hir-stats - Type 24 ( 0.2%) 1
|
||||||
hir-stats Stmt 96 ( 0.9%) 3 32
|
hir-stats - Lifetime 72 ( 0.7%) 3
|
||||||
|
hir-stats FieldDef 96 ( 1.0%) 2 48
|
||||||
|
hir-stats Arm 96 ( 1.0%) 2 48
|
||||||
|
hir-stats Stmt 96 ( 1.0%) 3 32
|
||||||
hir-stats - Local 32 ( 0.3%) 1
|
hir-stats - Local 32 ( 0.3%) 1
|
||||||
hir-stats - Semi 32 ( 0.3%) 1
|
hir-stats - Semi 32 ( 0.3%) 1
|
||||||
hir-stats - Expr 32 ( 0.3%) 1
|
hir-stats - Expr 32 ( 0.3%) 1
|
||||||
hir-stats FnDecl 120 ( 1.2%) 3 40
|
hir-stats FnDecl 120 ( 1.2%) 3 40
|
||||||
hir-stats Attribute 128 ( 1.3%) 4 32
|
hir-stats Attribute 128 ( 1.3%) 4 32
|
||||||
hir-stats GenericArg 128 ( 1.3%) 4 32
|
hir-stats GenericArgs 144 ( 1.5%) 3 48
|
||||||
hir-stats - Type 32 ( 0.3%) 1
|
|
||||||
hir-stats - Lifetime 96 ( 0.9%) 3
|
|
||||||
hir-stats GenericArgs 144 ( 1.4%) 3 48
|
|
||||||
hir-stats Variant 160 ( 1.6%) 2 80
|
hir-stats Variant 160 ( 1.6%) 2 80
|
||||||
hir-stats GenericBound 192 ( 1.9%) 4 48
|
hir-stats WherePredicate 168 ( 1.7%) 3 56
|
||||||
hir-stats - Trait 192 ( 1.9%) 4
|
hir-stats - BoundPredicate 168 ( 1.7%) 3
|
||||||
hir-stats WherePredicate 216 ( 2.1%) 3 72
|
hir-stats GenericBound 192 ( 2.0%) 4 48
|
||||||
hir-stats - BoundPredicate 216 ( 2.1%) 3
|
hir-stats - Trait 192 ( 2.0%) 4
|
||||||
hir-stats Block 288 ( 2.8%) 6 48
|
hir-stats Block 288 ( 3.0%) 6 48
|
||||||
hir-stats GenericParam 400 ( 3.9%) 5 80
|
hir-stats GenericParam 400 ( 4.1%) 5 80
|
||||||
hir-stats Pat 440 ( 4.3%) 5 88
|
hir-stats Pat 440 ( 4.5%) 5 88
|
||||||
hir-stats - Wild 88 ( 0.9%) 1
|
hir-stats - Wild 88 ( 0.9%) 1
|
||||||
hir-stats - Struct 88 ( 0.9%) 1
|
hir-stats - Struct 88 ( 0.9%) 1
|
||||||
hir-stats - Binding 264 ( 2.6%) 3
|
hir-stats - Binding 264 ( 2.7%) 3
|
||||||
hir-stats Generics 560 ( 5.5%) 10 56
|
hir-stats Generics 560 ( 5.7%) 10 56
|
||||||
hir-stats Expr 768 ( 7.6%) 12 64
|
hir-stats Ty 720 ( 7.4%) 15 48
|
||||||
hir-stats - Path 64 ( 0.6%) 1
|
hir-stats - Ptr 48 ( 0.5%) 1
|
||||||
hir-stats - Struct 64 ( 0.6%) 1
|
hir-stats - Rptr 48 ( 0.5%) 1
|
||||||
hir-stats - Match 64 ( 0.6%) 1
|
hir-stats - Path 624 ( 6.4%) 13
|
||||||
hir-stats - InlineAsm 64 ( 0.6%) 1
|
hir-stats Expr 768 ( 7.9%) 12 64
|
||||||
|
hir-stats - Path 64 ( 0.7%) 1
|
||||||
|
hir-stats - Struct 64 ( 0.7%) 1
|
||||||
|
hir-stats - Match 64 ( 0.7%) 1
|
||||||
|
hir-stats - InlineAsm 64 ( 0.7%) 1
|
||||||
hir-stats - Lit 128 ( 1.3%) 2
|
hir-stats - Lit 128 ( 1.3%) 2
|
||||||
hir-stats - Block 384 ( 3.8%) 6
|
hir-stats - Block 384 ( 3.9%) 6
|
||||||
hir-stats Item 960 ( 9.4%) 12 80
|
hir-stats Item 960 ( 9.8%) 12 80
|
||||||
hir-stats - Trait 80 ( 0.8%) 1
|
hir-stats - Trait 80 ( 0.8%) 1
|
||||||
hir-stats - Enum 80 ( 0.8%) 1
|
hir-stats - Enum 80 ( 0.8%) 1
|
||||||
hir-stats - ExternCrate 80 ( 0.8%) 1
|
hir-stats - ExternCrate 80 ( 0.8%) 1
|
||||||
hir-stats - ForeignMod 80 ( 0.8%) 1
|
hir-stats - ForeignMod 80 ( 0.8%) 1
|
||||||
hir-stats - Impl 80 ( 0.8%) 1
|
hir-stats - Impl 80 ( 0.8%) 1
|
||||||
hir-stats - Fn 160 ( 1.6%) 2
|
hir-stats - Fn 160 ( 1.6%) 2
|
||||||
hir-stats - Use 400 ( 3.9%) 5
|
hir-stats - Use 400 ( 4.1%) 5
|
||||||
hir-stats Ty 1_080 (10.6%) 15 72
|
hir-stats Path 1_536 (15.7%) 32 48
|
||||||
hir-stats - Ptr 72 ( 0.7%) 1
|
hir-stats PathSegment 2_240 (23.0%) 40 56
|
||||||
hir-stats - Rptr 72 ( 0.7%) 1
|
|
||||||
hir-stats - Path 936 ( 9.2%) 13
|
|
||||||
hir-stats Path 1_536 (15.1%) 32 48
|
|
||||||
hir-stats PathSegment 2_240 (22.0%) 40 56
|
|
||||||
hir-stats ----------------------------------------------------------------
|
hir-stats ----------------------------------------------------------------
|
||||||
hir-stats Total 10_168
|
hir-stats Total 9_760
|
||||||
hir-stats
|
hir-stats
|
||||||
|
|
|
@ -929,7 +929,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_lifetime(&mut self, lifetime: Lifetime) {
|
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
|
||||||
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
|
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
|
||||||
if let LifetimeName::Param(param_id, ref name) = lifetime.name {
|
if let LifetimeName::Param(param_id, ref name) = lifetime.name {
|
||||||
std::mem::discriminant(name).hash(&mut self.s);
|
std::mem::discriminant(name).hash(&mut self.s);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue