Skip to content

GCI: Don't evaluate the initializer of free const items that have trivially unsatisfied predicates #142293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions compiler/rustc_hir_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,18 @@ pub fn check_crate(tcx: TyCtxt<'_>) {
check::maybe_check_static_with_link_section(tcx, item_def_id);
}
DefKind::Const if !tcx.generics_of(item_def_id).own_requires_monomorphization() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect !own_requires_monomorphization means there are no generics to begin with? Or what does that function mean? It is sadly undocumented...

It does not make sense to me that we'd ever evaluate a generic free const item. How would we even pick the instance for the generic parameters...?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, there was #136429 which I already forgot about. IMO the obvious fix is to just revert that PR...

... but I guess that wouldn't even be enough since even non-generic consts can have where clauses. Right. Uh, we may have to just skip all of those?

Copy link
Member Author

@fmease fmease Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

own_requires_monomorphization is synonymous with "has non-region generic params" meaning we do eval lifetime-generic const exprs (more precisely early-bound-lifetime-generic).

Copy link
Member Author

@fmease fmease Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we even pick the instance for the generic parameters...?

That's obviously fine since we're only dealing with lifetime params since trait resolution etc. happens modulo non-late-bound regions / most if not all regions get fully erased. No problem there.

// FIXME(generic_const_items): Passing empty instead of identity args is fishy but
// seems to be fine for now. Revisit this!
let instance = ty::Instance::new_raw(item_def_id.into(), ty::GenericArgs::empty());
let cid = GlobalId { instance, promoted: None };
let typing_env = ty::TypingEnv::fully_monomorphized();
tcx.ensure_ok().eval_to_const_value_raw(typing_env.as_query_input(cid));
let predicates = tcx.predicates_of(item_def_id);
let predicates = predicates.instantiate_identity(tcx).predicates;

if !traits::impossible_predicates(tcx, predicates) {
Copy link
Member

@RalfJung RalfJung Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it guaranteed that !impossible means "possible"? Or at least, does the contract for impossible_predicates imply that a predicate considered "possible" will not ICE later when one does normal type system things and makes basic assumptions about primitive MIR ops, sizedness, and things like that?

(I feel I asked @BoxyUwU that same question already earlier this year in a different context and the answer was "yes it's fine", but I'm not sure...)

Copy link
Member Author

@fmease fmease Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I can't answer that with 100% confidence since I'm not super familiar with the innards and all the little details of const eval.

I know that layout computation has been made more robust against that somewhat recently but I'm not sure about the rest.

I know that the MIR pass ImpossiblePredicates replaces MIR bodies with Unreachable1 if the body has unsatisfiable ("trivially unsatisfied") global2 predicates. However, I don't actually know how early that pass gets run (and it doesn't affect e.g., layout computation unless I'm totally off base).

It's certainly a valid question and I need to have researched that before opening the RFC. Tho if my tired brain doesn't trick me right now, my change should in theory eliminate potential ICEs rather than introducing new ones because we eval less.

Footnotes

  1. These actually trigger during const eval on master.

  2. Meaning it disagrees with the 3 checks I've added in this PR when there are predicates containing ReEarlyBound (erasing wouldn't help because ReErased also turns a predicate non-global). My new checks should be stricter than the one used during said MIR pass, so that shouldn't be problematic (or at least not more problematic compared to master).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my change should in theory eliminate potential ICEs rather than introducing new ones because we eval less.
Footnotes

Yeah, definitely. I was just wondering if it evals less enough. ;)

// FIXME(generic_const_items): Passing empty instead of identity args is fishy but
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be fixed yeah 🤔 I don't think ctfe should have to handle there being instances with wrong args

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we should. If you're okay with it, I'd prefer to address this in a separate PR because this is super perf sensitive.

This used to be a call to fn const_eval_poly for the longest time which used to construct identity args etc. After a reshuffle (#121087), it was changed to the current no-args fully-mono version (#121387) to fix perf. Maybe the identity vs. no args isn't mega perf sensitive and it's paramenv norm that's the culprit but in #136429 I also did some tests, so it's unclear.

E.g., ea1cdda (perf results: #136429 (comment)) and 6f11489 (perf results: #136429 (comment)). Ofc, they also include unrelated changes to WF-check, so the results are skewed.

// seems to be fine for now. Revisit this!
let instance =
ty::Instance::new_raw(item_def_id.into(), ty::GenericArgs::empty());
let cid = GlobalId { instance, promoted: None };
let typing_env = ty::TypingEnv::fully_monomorphized();
tcx.ensure_ok().eval_to_const_value_raw(typing_env.as_query_input(cid));
}
}
_ => (),
}
Expand Down
16 changes: 10 additions & 6 deletions compiler/rustc_middle/src/mir/interpret/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ use crate::ty::{self, ConstToValTreeResult, GenericArgs, TyCtxt, TypeVisitableEx
use crate::{error, mir};

impl<'tcx> TyCtxt<'tcx> {
/// Evaluates a constant without providing any generic parameters. This is useful to evaluate consts
/// that can't take any generic arguments like const items or enum discriminants. If a
/// generic parameter is used within the constant `ErrorHandled::TooGeneric` will be returned.
/// Evaluates a constant without providing any generic parameters.
///
/// This is useful to evaluate consts that can't take any generic arguments like enum
/// discriminants. If a non-region generic parameter is used within the constant
Copy link
Member Author

@fmease fmease Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(apart splitting of the synopsis / introductory paragraph, here & below I changed generic parameter to non-region generic parameter and dropped the mention of const items since they can be generic, GCI duh)

/// `ErrorHandled::TooGeneric` will be returned.
#[instrument(skip(self), level = "debug")]
pub fn const_eval_poly(self, def_id: DefId) -> EvalToConstValueResult<'tcx> {
// In some situations def_id will have generic parameters within scope, but they aren't allowed
Expand All @@ -28,9 +30,11 @@ impl<'tcx> TyCtxt<'tcx> {
self.const_eval_global_id(typing_env, cid, DUMMY_SP)
}

/// Evaluates a constant without providing any generic parameters. This is useful to evaluate consts
/// that can't take any generic arguments like const items or enum discriminants. If a
/// generic parameter is used within the constant `ErrorHandled::TooGeneric` will be returned.
/// Evaluates a constant without providing any generic parameters.
///
/// This is useful to evaluate consts that can't take any generic arguments like enum
/// discriminants. If a non-region generic parameter is used within the constant
/// `ErrorHandled::TooGeneric` will be returned.
#[instrument(skip(self), level = "debug")]
pub fn const_eval_poly_to_alloc(self, def_id: DefId) -> EvalToAllocationRawResult<'tcx> {
// In some situations def_id will have generic parameters within scope, but they aren't allowed
Expand Down
122 changes: 53 additions & 69 deletions compiler/rustc_monomorphize/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1424,74 +1424,54 @@ struct RootCollector<'a, 'tcx> {
entry_fn: Option<(DefId, EntryFnType)>,
}

impl<'v> RootCollector<'_, 'v> {
impl<'tcx> RootCollector<'_, 'tcx> {
fn process_item(&mut self, id: hir::ItemId) {
match self.tcx.def_kind(id.owner_id) {
let tcx = self.tcx;
let def_id = id.owner_id.to_def_id();

match tcx.def_kind(def_id) {
DefKind::Enum | DefKind::Struct | DefKind::Union => {
if self.strategy == MonoItemCollectionStrategy::Eager
&& !self.tcx.generics_of(id.owner_id).requires_monomorphization(self.tcx)
&& !tcx.generics_of(def_id).own_requires_monomorphization()
&& let args = ty::GenericArgs::for_item(tcx, def_id, |param, _| {
expect_and_erase_regions(tcx, param)
})
&& !tcx.instantiate_and_check_impossible_predicates((def_id, args))
{
debug!("RootCollector: ADT drop-glue for `{id:?}`",);
let id_args =
ty::GenericArgs::for_item(self.tcx, id.owner_id.to_def_id(), |param, _| {
match param.kind {
GenericParamDefKind::Lifetime => {
self.tcx.lifetimes.re_erased.into()
}
GenericParamDefKind::Type { .. }
| GenericParamDefKind::Const { .. } => {
unreachable!(
"`own_requires_monomorphization` check means that \
we should have no type/const params"
)
}
}
});

// This type is impossible to instantiate, so we should not try to
// generate a `drop_in_place` instance for it.
if self.tcx.instantiate_and_check_impossible_predicates((
id.owner_id.to_def_id(),
id_args,
)) {
return;
}
let ty = tcx.type_of(def_id).instantiate(tcx, args);
debug_assert!(!ty.has_non_region_param());

let ty =
self.tcx.type_of(id.owner_id.to_def_id()).instantiate(self.tcx, id_args);
assert!(!ty.has_non_region_param());
visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.output);
debug!("RootCollector: ADT drop-glue for `{id:?}`");
visit_drop_use(tcx, ty, true, DUMMY_SP, self.output);
}
}
DefKind::GlobalAsm => {
debug!(
"RootCollector: ItemKind::GlobalAsm({})",
self.tcx.def_path_str(id.owner_id)
);
debug!("RootCollector: ItemKind::GlobalAsm({})", tcx.def_path_str(def_id));
self.output.push(dummy_spanned(MonoItem::GlobalAsm(id)));
}
DefKind::Static { .. } => {
let def_id = id.owner_id.to_def_id();
debug!("RootCollector: ItemKind::Static({})", self.tcx.def_path_str(def_id));
debug!("RootCollector: ItemKind::Static({})", tcx.def_path_str(def_id));
self.output.push(dummy_spanned(MonoItem::Static(def_id)));
}
DefKind::Const => {
// Const items only generate mono items if they are actually used somewhere.
// Just declaring them is insufficient.

// If we're collecting items eagerly, then recurse into all constants.
// Otherwise the value is only collected when explicitly mentioned in other items.
if self.strategy == MonoItemCollectionStrategy::Eager {
if !self.tcx.generics_of(id.owner_id).own_requires_monomorphization()
&& let Ok(val) = self.tcx.const_eval_poly(id.owner_id.to_def_id())
{
collect_const_value(self.tcx, val, self.output);
}
if self.strategy == MonoItemCollectionStrategy::Eager
&& !tcx.generics_of(def_id).own_requires_monomorphization()
&& let args = ty::GenericArgs::for_item(tcx, def_id, |param, _| {
expect_and_erase_regions(tcx, param)
})
&& !tcx.instantiate_and_check_impossible_predicates((def_id, args))
&& let Ok(val) = tcx.const_eval_poly(def_id)
{
collect_const_value(tcx, val, self.output);
}
}
DefKind::Impl { .. } => {
if self.strategy == MonoItemCollectionStrategy::Eager {
create_mono_items_for_default_impls(self.tcx, id, self.output);
create_mono_items_for_default_impls(tcx, id, self.output);
}
}
DefKind::Fn => {
Expand Down Expand Up @@ -1614,35 +1594,23 @@ fn create_mono_items_for_default_impls<'tcx>(
item: hir::ItemId,
output: &mut MonoItems<'tcx>,
) {
let Some(impl_) = tcx.impl_trait_header(item.owner_id) else {
return;
};

let impl_def_id = item.owner_id.to_def_id();
let Some(impl_) = tcx.impl_trait_header(impl_def_id) else { return };
if matches!(impl_.polarity, ty::ImplPolarity::Negative) {
return;
}

if tcx.generics_of(item.owner_id).own_requires_monomorphization() {
return;
}

// Lifetimes never affect trait selection, so we are allowed to eagerly
// instantiate an instance of an impl method if the impl (and method,
// which we check below) is only parameterized over lifetime. In that case,
// we use the ReErased, which has no lifetime information associated with
// it, to validate whether or not the impl is legal to instantiate at all.
let only_region_params = |param: &ty::GenericParamDef, _: &_| match param.kind {
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
unreachable!(
"`own_requires_monomorphization` check means that \
we should have no type/const params"
)
}
};
let impl_args = GenericArgs::for_item(tcx, item.owner_id.to_def_id(), only_region_params);
let trait_ref = impl_.trait_ref.instantiate(tcx, impl_args);

if tcx.generics_of(impl_def_id).own_requires_monomorphization() {
return;
}
let impl_args = ty::GenericArgs::for_item(tcx, impl_def_id, |param, _| {
expect_and_erase_regions(tcx, param)
});
// Unlike 'lazy' monomorphization that begins by collecting items transitively
// called by `main` or other global items, when eagerly monomorphizing impl
// items, we never actually check that the predicates of this impl are satisfied
Expand All @@ -1652,13 +1620,15 @@ fn create_mono_items_for_default_impls<'tcx>(
// consider higher-ranked predicates such as `for<'a> &'a mut [u8]: Copy` to
// be trivially false. We must now check that the impl has no impossible-to-satisfy
// predicates.
if tcx.instantiate_and_check_impossible_predicates((item.owner_id.to_def_id(), impl_args)) {
if tcx.instantiate_and_check_impossible_predicates((impl_def_id, impl_args)) {
return;
}

let trait_ref = impl_.trait_ref.instantiate(tcx, impl_args);

let typing_env = ty::TypingEnv::fully_monomorphized();
let trait_ref = tcx.normalize_erasing_regions(typing_env, trait_ref);
let overridden_methods = tcx.impl_item_implementor_ids(item.owner_id);
let overridden_methods = tcx.impl_item_implementor_ids(impl_def_id);
for method in tcx.provided_trait_methods(trait_ref.def_id) {
if overridden_methods.contains_key(&method.def_id) {
continue;
Expand All @@ -1671,7 +1641,9 @@ fn create_mono_items_for_default_impls<'tcx>(
// As mentioned above, the method is legal to eagerly instantiate if it
// only has lifetime generic parameters. This is validated by calling
// `own_requires_monomorphization` on both the impl and method.
let args = trait_ref.args.extend_to(tcx, method.def_id, only_region_params);
let args = trait_ref
.args
.extend_to(tcx, method.def_id, |param, _| expect_and_erase_regions(tcx, param));
let instance = ty::Instance::expect_resolve(tcx, typing_env, method.def_id, args, DUMMY_SP);

let mono_item = create_fn_mono_item(tcx, instance, DUMMY_SP);
Expand All @@ -1681,6 +1653,18 @@ fn create_mono_items_for_default_impls<'tcx>(
}
}

fn expect_and_erase_regions<'tcx>(
tcx: TyCtxt<'tcx>,
param: &ty::GenericParamDef,
) -> ty::GenericArg<'tcx> {
match param.kind {
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
bug!("unexpected non-region param")
}
}
}

//=-----------------------------------------------------------------------------
// Top-level entry point, tying it all together
//=-----------------------------------------------------------------------------
Expand Down
32 changes: 24 additions & 8 deletions compiler/rustc_passes/src/reachable.rs
Copy link
Member Author

@fmease fmease Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider moving all of these call-site checks into ctfe itself, e.g. eval_to_const_value_raw_provider. This wont affect const patterns which go through _for_typeck versions of the CTFE entry points.

As discussed, long term, that would be the way to go. However, it makes me uncomfortable that eval_to_const_value_raw has so many transitive callers which I wasn't yet able to audit entirely. I know that you told me that this function should be safe to modify from a soundness perspective and you're probably right, I just haven't finished tracing the entire caller graph. WIP:

Caller graph (X called by Y)
+ eval_to_const_value_raw  [eval_to_const_value_raw_provider]  (???)
+-- check_crate/par_hir_body_owners/DefKind::Const  (OK)
+-- const_eval_global_id  (???)
    +-- eval_intrinsic  (OK)
    +-- const_eval_poly  (OK)
    |   +-- codegen_global_asm  (OK)
    |   +-- check_type_defn/variants/fields [default_field_values]  (OK)
    |   +-- check_type_defn/ty::VariantDiscr::Explicit  (OK)
    |   +-- eval_explicit_discr  (OK)
    |   +-- monomorphize/RootCollector/process_item/DefKind::Const  (OK)
    +-- const_eval_resolve  (???)
    |   +-- Const::eval  (???)
    |       +-- eval_mir_constant  (???)
    |       |   +... [~20 call sites]  (TODO)
    |       +-- inline_to_global_operand/InlineAsmOperand::Const  (OK)
    |       +-- push_stack_frame_raw/body.required_consts()  (???)
    |       +-- try_eval_scalar...  (TODO)
    |       +-- MIR transform/simplify_repeated_aggregate  (???)
    |       +-- eval_constant  (???)
    |           +-- eval_operand...  (TODO)
    |           +-- MIR transform/ConstPropagator/visit_const_operand  (???)
    |           +-- collect_items_of_instance/body.requires_consts()  (???)
    +-- const_eval_instance  (???)
        +-- codegen_regular_intrinsic_call  (OK)
        +-- codegen_intrinsic_call  (OK)
        +-- eval_instance  (???)
            +-- try_const_eval [SMIR]  (???)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(As an aside, even if we enriched eval_to_const_value_raw_provider with a call to impossible_predicates, here in mod reachable it would be of no use to us as it uses const_eval_poly_to_alloc which doesn't depend on that function)

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use rustc_middle::query::Providers;
use rustc_middle::ty::{self, ExistentialTraitRef, TyCtxt};
use rustc_privacy::DefIdVisitor;
use rustc_session::config::CrateType;
use rustc_trait_selection::traits;
use tracing::debug;

/// Determines whether this item is recursive for reachability. See `is_recursively_reachable_local`
Expand Down Expand Up @@ -205,19 +206,34 @@ impl<'tcx> ReachableContext<'tcx> {
}

hir::ItemKind::Const(_, _, _, init) => {
// Only things actually ending up in the final constant value are reachable
// for codegen. Everything else is only needed during const-eval, so even if
// const-eval happens in a downstream crate, all they need is
// `mir_for_ctfe`.
if self.tcx.generics_of(item.owner_id).own_requires_monomorphization() {
// In this case, we don't want to evaluate the const initializer.
// In lieu of that, we have to consider everything mentioned in it
// as reachable, since it *may* end up in the final value.
self.visit_nested_body(init);
return;
}

let predicates = self.tcx.predicates_of(item.owner_id);
let predicates = predicates.instantiate_identity(self.tcx).predicates;
if traits::impossible_predicates(self.tcx, predicates) {
// The constant is impossible to reference.
Copy link
Member Author

@fmease fmease Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend comment: If the usage site also assumes these impossible bounds (so of its ParamEnv also contains these bounds), it's still fine because the surrounding body of the usage site gets replaced with Unreachable by MIR pass ImpossiblePredicates.

We might want to add a test for all of this.

Note to self: However, there are cases where the MIR pass and reachability analysis disagrees about what constitutes an impossible bound (namely if the predicates contains early-bound regions). Check if this is problematic and can be exploited.

// Therefore nothing can be reachable via it.
return;
}

match self.tcx.const_eval_poly_to_alloc(item.owner_id.def_id.into()) {
Ok(alloc) => {
// Only things actually ending up in the final constant value are
// reachable for codegen. Everything else is only needed during
// const-eval, so even if const-eval happens in a downstream crate,
// all they need is `mir_for_ctfe`.
let alloc = self.tcx.global_alloc(alloc.alloc_id).unwrap_memory();
self.propagate_from_alloc(alloc);
}
// We can't figure out which value the constant will evaluate to. In
// lieu of that, we have to consider everything mentioned in the const
// initializer reachable, since it *may* end up in the final value.
Err(ErrorHandled::TooGeneric(_)) => self.visit_nested_body(init),
// We've checked at the start that there aren't any non-lifetime params
// in scope. The const initializer can't possibly be too generic.
Err(ErrorHandled::TooGeneric(_)) => bug!(),
// If there was an error evaluating the const, nothing can be reachable
// via it, and anyway compilation will fail.
Err(ErrorHandled::Reported(..)) => {}
Expand Down
22 changes: 12 additions & 10 deletions compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,8 +721,13 @@ fn replace_param_and_infer_args_with_placeholder<'tcx>(
/// returns true, then either normalize encountered an error or one of the predicates did not
/// hold. Used when creating vtables to check for unsatisfiable methods. This should not be
/// used during analysis.
#[instrument(level = "debug", skip(tcx))]
pub fn impossible_predicates<'tcx>(tcx: TyCtxt<'tcx>, predicates: Vec<ty::Clause<'tcx>>) -> bool {
debug!("impossible_predicates(predicates={:?})", predicates);
debug_assert!(!predicates.has_non_region_param());
if predicates.is_empty() {
return false;
}

let (infcx, param_env) = tcx
.infer_ctxt()
.with_next_trait_solver(true)
Expand All @@ -749,26 +754,23 @@ pub fn impossible_predicates<'tcx>(tcx: TyCtxt<'tcx>, predicates: Vec<ty::Clause
false
}

#[instrument(level = "debug", skip(tcx), ret)]
fn instantiate_and_check_impossible_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
key: (DefId, GenericArgsRef<'tcx>),
(def_id, args): (DefId, GenericArgsRef<'tcx>),
) -> bool {
debug!("instantiate_and_check_impossible_predicates(key={:?})", key);

let mut predicates = tcx.predicates_of(key.0).instantiate(tcx, key.1).predicates;
let mut predicates = tcx.predicates_of(def_id).instantiate(tcx, args).predicates;

// Specifically check trait fulfillment to avoid an error when trying to resolve
// associated items.
if let Some(trait_def_id) = tcx.trait_of_item(key.0) {
let trait_ref = ty::TraitRef::from_method(tcx, trait_def_id, key.1);
if let Some(trait_def_id) = tcx.trait_of_item(def_id) {
let trait_ref = ty::TraitRef::from_method(tcx, trait_def_id, args);
predicates.push(trait_ref.upcast(tcx));
}

predicates.retain(|predicate| !predicate.has_param());
let result = impossible_predicates(tcx, predicates);

debug!("instantiate_and_check_impossible_predicates(key={:?}) = {:?}", key, result);
result
impossible_predicates(tcx, predicates)
}

/// Checks whether a trait's associated item is impossible to reference on a given impl.
Expand Down

This file was deleted.

Loading
Loading