diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 01853eab530da..284974c561045 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -345,7 +345,8 @@ impl<'tcx> Queries<'tcx> { pub fn linker(&'tcx self) -> Result { let dep_graph = self.dep_graph()?; let prepare_outputs = self.prepare_outputs()?; - let crate_hash = self.global_ctxt()?.peek_mut().enter(|tcx| tcx.crate_hash(LOCAL_CRATE)); + let crate_hash = + self.global_ctxt()?.peek_mut().enter(|tcx| tcx.crate_hash(LOCAL_CRATE).svh); let ongoing_codegen = self.ongoing_codegen()?; let sess = self.session().clone(); diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 3d0a9d553b028..b0d50e9cefed3 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1606,84 +1606,92 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { /// Proc macro crates don't currently export spans, so this function does not have /// to work for them. fn imported_source_files(&self, sess: &Session) -> &'a [ImportedSourceFile] { - // Translate the virtual `/rustc/$hash` prefix back to a real directory - // that should hold actual sources, where possible. - // - // NOTE: if you update this, you might need to also update bootstrap's code for generating - // the `rust-src` component in `Src::run` in `src/bootstrap/dist.rs`. - let virtual_rust_source_base_dir = option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR") - .map(Path::new) - .filter(|_| { - // Only spend time on further checks if we have what to translate *to*. - sess.real_rust_source_base_dir.is_some() - }) - .filter(|virtual_dir| { - // Don't translate away `/rustc/$hash` if we're still remapping to it, - // since that means we're still building `std`/`rustc` that need it, - // and we don't want the real path to leak into codegen/debuginfo. - !sess.opts.remap_path_prefix.iter().any(|(_from, to)| to == virtual_dir) - }); - let try_to_translate_virtual_to_real = |name: &mut rustc_span::FileName| { - debug!( - "try_to_translate_virtual_to_real(name={:?}): \ - virtual_rust_source_base_dir={:?}, real_rust_source_base_dir={:?}", - name, virtual_rust_source_base_dir, sess.real_rust_source_base_dir, - ); + self.cdata.source_map_import_info.get_or_init(|| { + // Translate the virtual `/rustc/$hash` prefix back to a real directory + // that should hold actual sources, where possible. + // + // NOTE: if you update this, you might need to also update bootstrap's code for generating + // the `rust-src` component in `Src::run` in `src/bootstrap/dist.rs`. + let virtual_rust_source_base_dir = sess + .opts + .debugging_opts + .force_virtual_source_base + .as_deref() + .or(option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR")) + .map(Path::new) + .filter(|_| { + // Only spend time on further checks if we have what to translate *to*. + sess.real_rust_source_base_dir.is_some() + }) + .filter(|virtual_dir| { + // Don't translate away `/rustc/$hash` if we're still remapping to it, + // since that means we're still building `std`/`rustc` that need it, + // and we don't want the real path to leak into codegen/debuginfo. + !sess.opts.remap_path_prefix.iter().any(|(_from, to)| to == virtual_dir) + }); + let try_to_translate_virtual_to_real = + |name: &mut rustc_span::FileName, name_hash: &mut u128| { + debug!( + "try_to_translate_virtual_to_real(name={:?}): \ + virtual_rust_source_base_dir={:?}, real_rust_source_base_dir={:?}", + name, virtual_rust_source_base_dir, sess.real_rust_source_base_dir, + ); - if let Some(virtual_dir) = virtual_rust_source_base_dir { - if let Some(real_dir) = &sess.real_rust_source_base_dir { - if let rustc_span::FileName::Real(old_name) = name { - if let rustc_span::RealFileName::Named(one_path) = old_name { - if let Ok(rest) = one_path.strip_prefix(virtual_dir) { - let virtual_name = one_path.clone(); - - // The std library crates are in - // `$sysroot/lib/rustlib/src/rust/library`, whereas other crates - // may be in `$sysroot/lib/rustlib/src/rust/` directly. So we - // detect crates from the std libs and handle them specially. - const STD_LIBS: &[&str] = &[ - "core", - "alloc", - "std", - "test", - "term", - "unwind", - "proc_macro", - "panic_abort", - "panic_unwind", - "profiler_builtins", - "rtstartup", - "rustc-std-workspace-core", - "rustc-std-workspace-alloc", - "rustc-std-workspace-std", - "backtrace", - ]; - let is_std_lib = STD_LIBS.iter().any(|l| rest.starts_with(l)); - - let new_path = if is_std_lib { - real_dir.join("library").join(rest) - } else { - real_dir.join(rest) - }; - - debug!( - "try_to_translate_virtual_to_real: `{}` -> `{}`", - virtual_name.display(), - new_path.display(), - ); - let new_name = rustc_span::RealFileName::Devirtualized { - local_path: new_path, - virtual_name, - }; - *old_name = new_name; + if let Some(virtual_dir) = virtual_rust_source_base_dir { + if let Some(real_dir) = &sess.real_rust_source_base_dir { + if let rustc_span::FileName::Real(old_name) = name { + if let rustc_span::RealFileName::Named(one_path) = old_name { + if let Ok(rest) = one_path.strip_prefix(virtual_dir) { + let virtual_name = one_path.clone(); + + // The std library crates are in + // `$sysroot/lib/rustlib/src/rust/library`, whereas other crates + // may be in `$sysroot/lib/rustlib/src/rust/` directly. So we + // detect crates from the std libs and handle them specially. + const STD_LIBS: &[&str] = &[ + "core", + "alloc", + "std", + "test", + "term", + "unwind", + "proc_macro", + "panic_abort", + "panic_unwind", + "profiler_builtins", + "rtstartup", + "rustc-std-workspace-core", + "rustc-std-workspace-alloc", + "rustc-std-workspace-std", + "backtrace", + ]; + let is_std_lib = + STD_LIBS.iter().any(|l| rest.starts_with(l)); + + let new_path = if is_std_lib { + real_dir.join("library").join(rest) + } else { + real_dir.join(rest) + }; + + debug!( + "try_to_translate_virtual_to_real: `{}` -> `{}`", + virtual_name.display(), + new_path.display(), + ); + let new_name = rustc_span::RealFileName::Devirtualized { + local_path: new_path, + virtual_name, + }; + *old_name = new_name; + *name_hash = name.name_hash(); + } + } } } } - } - } - }; + }; - self.cdata.source_map_import_info.get_or_init(|| { let external_source_map = self.root.source_map.decode(self); external_source_map @@ -1700,7 +1708,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { mut multibyte_chars, mut non_narrow_chars, mut normalized_pos, - name_hash, + mut name_hash, .. } = source_file_to_import; @@ -1709,7 +1717,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { // on `try_to_translate_virtual_to_real`). // FIXME(eddyb) we could check `name_was_remapped` here, // but in practice it seems to be always `false`. - try_to_translate_virtual_to_real(&mut name); + try_to_translate_virtual_to_real(&mut name, &mut name_hash); let source_length = (end_pos - start_pos).to_usize(); diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index bebee9dac3b73..5c27bac20aad7 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -12,7 +12,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::definitions::{DefKey, DefPath, DefPathHash}; use rustc_middle::hir::exports::Export; -use rustc_middle::middle::cstore::ForeignModule; +use rustc_middle::middle::cstore::{CrateHashAndState, ForeignModule, OptCrateHashAndState}; use rustc_middle::middle::cstore::{CrateSource, CrateStore, EncodedMetadata}; use rustc_middle::middle::exported_symbols::ExportedSymbol; use rustc_middle::middle::stability::DeprecationEntry; @@ -199,8 +199,8 @@ provide! { <'tcx> tcx, def_id, other, cdata, }) } crate_disambiguator => { cdata.root.disambiguator } - crate_hash => { cdata.root.hash } - crate_host_hash => { cdata.host_hash } + crate_hash => { CrateHashAndState::new(tcx, cdata.root.hash) } + crate_host_hash => { OptCrateHashAndState::new(tcx, cdata.host_hash) } original_crate_name => { cdata.root.name } extra_filename => { cdata.root.extra_filename.clone() } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index a5157854e15c0..b72464439c1e4 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -645,7 +645,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { name: tcx.crate_name(LOCAL_CRATE), extra_filename: tcx.sess.opts.cg.extra_filename.clone(), triple: tcx.sess.opts.target_triple.clone(), - hash: tcx.crate_hash(LOCAL_CRATE), + hash: tcx.crate_hash(LOCAL_CRATE).svh, disambiguator: tcx.sess.local_crate_disambiguator(), stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(), panic_strategy: tcx.sess.panic_strategy(), @@ -1646,8 +1646,8 @@ impl EncodeContext<'a, 'tcx> { .map(|&cnum| { let dep = CrateDep { name: self.tcx.original_crate_name(cnum), - hash: self.tcx.crate_hash(cnum), - host_hash: self.tcx.crate_host_hash(cnum), + hash: self.tcx.crate_hash(cnum).svh, + host_hash: self.tcx.crate_host_hash(cnum).svh, kind: self.tcx.dep_kind(cnum), extra_filename: self.tcx.extra_filename(cnum), }; diff --git a/compiler/rustc_middle/src/hir/map/collector.rs b/compiler/rustc_middle/src/hir/map/collector.rs index 501e7d624d2e4..6aa0308fee352 100644 --- a/compiler/rustc_middle/src/hir/map/collector.rs +++ b/compiler/rustc_middle/src/hir/map/collector.rs @@ -15,7 +15,6 @@ use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::*; use rustc_index::vec::{Idx, IndexVec}; use rustc_session::{CrateDisambiguator, Session}; -use rustc_span::source_map::SourceMap; use rustc_span::{Span, Symbol, DUMMY_SP}; use std::iter::repeat; @@ -27,8 +26,7 @@ pub(super) struct NodeCollector<'a, 'hir> { /// The crate krate: &'hir Crate<'hir>, - /// Source map - source_map: &'a SourceMap, + sess: &'a Session, map: IndexVec>, @@ -126,7 +124,7 @@ impl<'a, 'hir> NodeCollector<'a, 'hir> { let mut collector = NodeCollector { arena, krate, - source_map: sess.source_map(), + sess, parent_node: hir::CRATE_HIR_ID, current_dep_node_owner: LocalDefId { local_def_index: CRATE_DEF_INDEX }, definitions, @@ -174,7 +172,8 @@ impl<'a, 'hir> NodeCollector<'a, 'hir> { // reproducible builds by compiling from the same directory. So we just // hash the result of the mapping instead of the mapping itself. let mut source_file_names: Vec<_> = self - .source_map + .sess + .source_map() .files() .iter() .filter(|source_file| source_file.cnum == LOCAL_CRATE) @@ -186,6 +185,7 @@ impl<'a, 'hir> NodeCollector<'a, 'hir> { let crate_hash_input = ( ((node_hashes, upstream_crates), source_file_names), (commandline_args_hash, crate_disambiguator.to_fingerprint()), + &self.sess.real_rust_source_base_dir, ); let mut stable_hasher = StableHasher::new(); @@ -262,7 +262,7 @@ impl<'a, 'hir> NodeCollector<'a, 'hir> { span, "inconsistent DepNode at `{:?}` for `{}`: \ current_dep_node_owner={} ({:?}), hir_id.owner={} ({:?})", - self.source_map.span_to_string(span), + self.sess.source_map().span_to_string(span), node_str, self.definitions .def_path(self.current_dep_node_owner) diff --git a/compiler/rustc_middle/src/middle/cstore.rs b/compiler/rustc_middle/src/middle/cstore.rs index 4f1ca968c3018..6f2ffde9649d4 100644 --- a/compiler/rustc_middle/src/middle/cstore.rs +++ b/compiler/rustc_middle/src/middle/cstore.rs @@ -6,6 +6,8 @@ use crate::ty::TyCtxt; use rustc_ast as ast; use rustc_ast::expand::allocator::AllocatorKind; +use rustc_data_structures::fingerprint::Fingerprint; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::{self, MetadataRef}; use rustc_hir::def::DefKind; @@ -255,3 +257,35 @@ pub fn used_crates(tcx: TyCtxt<'_>, prefer: LinkagePreference) -> Vec<(CrateNum, libs.sort_by_cached_key(|&(a, _)| ordering.iter().position(|x| *x == a)); libs } + +/// Holds a crate hash, together with additional global state. +/// This is used as the return value of the `crate_hash` and `crate_host_hash` +/// queries. When the global state changes between compilation sessions, +/// the hash will change, causing the query system to re-run any queries +/// which depend on the crate hash. +#[derive(Clone, Debug, HashStable, Encodable, Decodable)] +pub struct HashAndState { + pub svh: T, + /// The value of `tcx.sess.real_rust_source_base_dir` in the *current* session. + /// This global state is special - it influences how we decode foreign `SourceFile`s + /// (and therefore `Span`s). As a result, a foreign crate's hash can be unchanged across + /// two compilation sessions, but queries run on that crate can nevertheless return + /// different results across those compilation sessions. + /// This field is private, and never needs to be accessed by callers of `crate_hash` + /// or `host_crate_hash`. Its sole purpose is to be hashed as part of the + /// `HashStable` impl, so that its value influences the hash used by incremental compilation. + real_rust_source_base_dir: Fingerprint, +} + +impl HashAndState { + pub fn new(tcx: TyCtxt<'tcx>, svh: T) -> HashAndState { + let mut hasher = StableHasher::new(); + let mut hcx = tcx.create_stable_hashing_context(); + tcx.sess.real_rust_source_base_dir.hash_stable(&mut hcx, &mut hasher); + let real_rust_source_base_dir = hasher.finish(); + HashAndState { svh, real_rust_source_base_dir } + } +} + +pub type CrateHashAndState = HashAndState; +pub type OptCrateHashAndState = HashAndState>; diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index bac69e282a521..092bf0faad630 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1207,11 +1207,11 @@ rustc_queries! { } // The macro which defines `rustc_metadata::provide_extern` depends on this query's name. // Changing the name should cause a compiler error, but in case that changes, be aware. - query crate_hash(_: CrateNum) -> Svh { + query crate_hash(_: CrateNum) -> rustc_middle::middle::cstore::CrateHashAndState { eval_always desc { "looking up the hash a crate" } } - query crate_host_hash(_: CrateNum) -> Option { + query crate_host_hash(_: CrateNum) -> rustc_middle::middle::cstore::OptCrateHashAndState { eval_always desc { "looking up the hash of a host version of a crate" } } diff --git a/compiler/rustc_middle/src/ty/query/mod.rs b/compiler/rustc_middle/src/ty/query/mod.rs index c170858ba85a1..132a53ed9e4d1 100644 --- a/compiler/rustc_middle/src/ty/query/mod.rs +++ b/compiler/rustc_middle/src/ty/query/mod.rs @@ -34,7 +34,6 @@ use crate::ty::{self, AdtSizedConstraint, CrateInherentImpls, ParamEnvAnd, Ty, T use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_data_structures::stable_hasher::StableVec; use rustc_data_structures::steal::Steal; -use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::Lrc; use rustc_errors::{ErrorReported, Handler}; use rustc_hir as hir; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index a184608ed29bf..2e3f9692f4a4d 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -970,6 +970,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, (default: no)"), force_overflow_checks: Option = (None, parse_opt_bool, [TRACKED], "force overflow checks on or off"), + force_virtual_source_base: Option = (None, parse_opt_string, [UNTRACKED], + "forces using the specified directory as the virtual prefix to remap in filenames"), force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], "force all crates to be `rustc_private` unstable (default: no)"), fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index cc2583be94474..750aa2b362a51 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1394,6 +1394,9 @@ pub fn build_session( // Only use this directory if it has a file we can expect to always find. if candidate.join("library/std/src/lib.rs").is_file() { Some(candidate) } else { None } }; + if real_rust_source_base_dir.is_some() { + tracing::info!("Got base source dir: {:?}", real_rust_source_base_dir); + } let asm_arch = if target_cfg.allow_asm { InlineAsmArch::from_str(&target_cfg.arch).ok() } else { None }; diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 6f6ff37c525a2..e026725350921 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -188,6 +188,14 @@ pub enum FileName { InlineAsm(u64), } +impl FileName { + pub fn name_hash(&self) -> u128 { + let mut hasher: StableHasher = StableHasher::new(); + self.hash(&mut hasher); + hasher.finish::() + } +} + impl std::fmt::Display for FileName { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use FileName::*; @@ -1318,11 +1326,7 @@ impl SourceFile { let src_hash = SourceFileHash::new(hash_kind, &src); let normalized_pos = normalize_src(&mut src, start_pos); - let name_hash = { - let mut hasher: StableHasher = StableHasher::new(); - name.hash(&mut hasher); - hasher.finish::() - }; + let name_hash = name.name_hash(); let end_pos = start_pos.to_usize() + src.len(); assert!(end_pos <= u32::MAX as usize); diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index 29f1761b84d2b..68aeabefbd422 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -1,8 +1,8 @@ use rustc_data_structures::fx::FxIndexSet; -use rustc_data_structures::svh::Svh; use rustc_hir as hir; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; use rustc_middle::hir::map as hir_map; +use rustc_middle::middle::cstore::CrateHashAndState; use rustc_middle::ty::subst::Subst; use rustc_middle::ty::{ self, Binder, Predicate, PredicateKind, ToPredicate, Ty, TyCtxt, WithConstness, @@ -400,8 +400,8 @@ fn original_crate_name(tcx: TyCtxt<'_>, crate_num: CrateNum) -> Symbol { tcx.crate_name } -fn crate_hash(tcx: TyCtxt<'_>, crate_num: CrateNum) -> Svh { - tcx.index_hir(crate_num).crate_hash +fn crate_hash(tcx: TyCtxt<'_>, crate_num: CrateNum) -> CrateHashAndState { + CrateHashAndState::new(tcx, tcx.index_hir(crate_num).crate_hash) } fn instance_def_size_estimate<'tcx>( diff --git a/src/test/run-make-fulldeps/incr-add-rust-src-component/Makefile b/src/test/run-make-fulldeps/incr-add-rust-src-component/Makefile index 50ff3dd56ce92..440eed96c5061 100644 --- a/src/test/run-make-fulldeps/incr-add-rust-src-component/Makefile +++ b/src/test/run-make-fulldeps/incr-add-rust-src-component/Makefile @@ -25,6 +25,13 @@ INCR=$(TMPDIR)/incr # prefix, then loop on the next prefix. This way, we basically create a copy of # the context around root/lib/rustlib/src, and can freely add/remove the src # component itself. +# +# Normally, path remapping only occured when the compiler was build with +# `remap-debuginfo=true` in the `config.toml`. +# We don't do this on CI, and it would be very wasteful to rebuild the entire +# compiler for a single test. Instead, we use the `-Z force-virtual-source-base` option, +# which makes the compiler act as though it was built with debuginfo remapped to the +# specified directory. all: mkdir $(FAKEROOT) ln -s $(SYSROOT)/* $(FAKEROOT) @@ -38,7 +45,11 @@ all: mkdir $(FAKEROOT)/lib/rustlib/src ln -s $(SYSROOT)/lib/rustlib/src/* $(FAKEROOT)/lib/rustlib/src rm -f $(FAKEROOT)/lib/rustlib/src/rust - $(RUSTC) --sysroot $(FAKEROOT) -C incremental=$(INCR) main.rs - mkdir -p $(FAKEROOT)/lib/rustlib/src/rust/src/libstd - touch $(FAKEROOT)/lib/rustlib/src/rust/src/libstd/lib.rs - $(RUSTC) --sysroot $(FAKEROOT) -C incremental=$(INCR) main.rs + $(RUSTC) --sysroot $(FAKEROOT) -C incremental=$(INCR) -Z force-virtual-source-base=$(S)/library main.rs + # Run the compiled binary, and check for a non-remapped path in the panic message + $(call RUN,main) 2>&1 | grep $(S)/library/core/src/sync/atomic.rs + mkdir -p $(FAKEROOT)/lib/rustlib/src/rust/library/std/src/ + touch $(FAKEROOT)/lib/rustlib/src/rust/library/std/src/lib.rs + $(RUSTC) --sysroot $(FAKEROOT) -C incremental=$(INCR) -Z force-virtual-source-base=$(S)/library main.rs + # Run the newly compiled binary, and check that the path in the panic message is now remapped + $(call RUN,main) 2>&1 | grep $(FAKEROOT)/lib/rustlib/src/rust/library/core/src/sync/atomic.rs diff --git a/src/test/run-make-fulldeps/incr-add-rust-src-component/main.rs b/src/test/run-make-fulldeps/incr-add-rust-src-component/main.rs index f6320bcb04aa8..c3c6d93a8e602 100644 --- a/src/test/run-make-fulldeps/incr-add-rust-src-component/main.rs +++ b/src/test/run-make-fulldeps/incr-add-rust-src-component/main.rs @@ -1,3 +1,10 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + fn main() { println!("Hello World"); + // This generates a panic pointing into libstd, + // so we don't have `#[track_caller]` on `AtomicUsize::load`. + // If `#[track_caller]` ever gets added to this method, the test + // will start failing, and we'll need to find a new method to call + AtomicUsize::new(0).load(Ordering::Release); }