Skip to content

Commit b72e896

Browse files
committed
Auto merge of #109100 - Zoxc:merge-query-try, r=cjgillot
Refactor `try_execute_query` This merges `JobOwner::try_start` into `try_execute_query`, removing `TryGetJob` in the processes. 3 new functions are extracted from `try_execute_query`: `execute_job`, `cycle_error` and `wait_for_query`. This makes the control flow a bit clearer and improves performance. Based on #109046. <table><tr><td rowspan="2">Benchmark</td><td colspan="1"><b>Before</b></th><td colspan="2"><b>After</b></th></tr><tr><td align="right">Time</td><td align="right">Time</td><td align="right">%</th></tr><tr><td>🟣 <b>clap</b>:check</td><td align="right">1.7134s</td><td align="right">1.7061s</td><td align="right"> -0.43%</td></tr><tr><td>🟣 <b>hyper</b>:check</td><td align="right">0.2519s</td><td align="right">0.2510s</td><td align="right"> -0.35%</td></tr><tr><td>🟣 <b>regex</b>:check</td><td align="right">0.9517s</td><td align="right">0.9481s</td><td align="right"> -0.38%</td></tr><tr><td>🟣 <b>syn</b>:check</td><td align="right">1.5389s</td><td align="right">1.5338s</td><td align="right"> -0.33%</td></tr><tr><td>🟣 <b>syntex_syntax</b>:check</td><td align="right">5.9488s</td><td align="right">5.9258s</td><td align="right"> -0.39%</td></tr><tr><td>Total</td><td align="right">10.4048s</td><td align="right">10.3647s</td><td align="right"> -0.38%</td></tr><tr><td>Summary</td><td align="right">1.0000s</td><td align="right">0.9962s</td><td align="right"> -0.38%</td></tr></table> r? `@cjgillot`
2 parents e10eab5 + ced33ad commit b72e896

File tree

2 files changed

+134
-134
lines changed

2 files changed

+134
-134
lines changed

Diff for: compiler/rustc_query_system/src/query/job.rs

-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ impl<D: DepKind> QueryJob<D> {
124124
}
125125

126126
impl QueryJobId {
127-
#[cold]
128-
#[inline(never)]
129127
#[cfg(not(parallel_compiler))]
130128
pub(super) fn find_cycle_in_stack<D: DepKind>(
131129
&self,

Diff for: compiler/rustc_query_system/src/query/plumbing.rs

+134-132
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ use crate::dep_graph::{DepContext, DepKind, DepNode, DepNodeIndex, DepNodeParams
66
use crate::dep_graph::{DepGraphData, HasDepContext};
77
use crate::ich::StableHashingContext;
88
use crate::query::caches::QueryCache;
9+
#[cfg(parallel_compiler)]
10+
use crate::query::job::QueryLatch;
911
use crate::query::job::{report_cycle, QueryInfo, QueryJob, QueryJobId, QueryJobInfo};
1012
use crate::query::SerializedDepNodeIndex;
1113
use crate::query::{QueryContext, QueryMap, QuerySideEffects, QueryStackFrame};
1214
use crate::values::Value;
1315
use crate::HandleCycleError;
1416
use rustc_data_structures::fingerprint::Fingerprint;
1517
use rustc_data_structures::fx::FxHashMap;
16-
#[cfg(parallel_compiler)]
17-
use rustc_data_structures::profiling::TimingGuard;
18-
#[cfg(parallel_compiler)]
19-
use rustc_data_structures::sharded::Sharded;
2018
use rustc_data_structures::stack::ensure_sufficient_stack;
21-
use rustc_data_structures::sync::{Lock, LockGuard};
19+
use rustc_data_structures::sync::Lock;
20+
#[cfg(parallel_compiler)]
21+
use rustc_data_structures::{cold_path, sharded::Sharded};
2222
use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed, FatalError};
2323
use rustc_span::{Span, DUMMY_SP};
2424
use std::cell::Cell;
@@ -116,7 +116,6 @@ where
116116
{
117117
state: &'tcx QueryState<K, D>,
118118
key: K,
119-
id: QueryJobId,
120119
}
121120

122121
#[cold]
@@ -166,81 +165,6 @@ impl<'tcx, K, D: DepKind> JobOwner<'tcx, K, D>
166165
where
167166
K: Eq + Hash + Copy,
168167
{
169-
/// Either gets a `JobOwner` corresponding the query, allowing us to
170-
/// start executing the query, or returns with the result of the query.
171-
/// This function assumes that `try_get_cached` is already called and returned `lookup`.
172-
/// If the query is executing elsewhere, this will wait for it and return the result.
173-
/// If the query panicked, this will silently panic.
174-
///
175-
/// This function is inlined because that results in a noticeable speed-up
176-
/// for some compile-time benchmarks.
177-
#[inline(always)]
178-
fn try_start<'b, Qcx>(
179-
qcx: &'b Qcx,
180-
state: &'b QueryState<K, Qcx::DepKind>,
181-
mut state_lock: LockGuard<'b, FxHashMap<K, QueryResult<Qcx::DepKind>>>,
182-
span: Span,
183-
key: K,
184-
) -> TryGetJob<'b, K, D>
185-
where
186-
Qcx: QueryContext + HasDepContext<DepKind = D>,
187-
{
188-
let lock = &mut *state_lock;
189-
let current_job_id = qcx.current_query_job();
190-
191-
match lock.entry(key) {
192-
Entry::Vacant(entry) => {
193-
let id = qcx.next_job_id();
194-
let job = QueryJob::new(id, span, current_job_id);
195-
196-
let key = *entry.key();
197-
entry.insert(QueryResult::Started(job));
198-
199-
let owner = JobOwner { state, id, key };
200-
return TryGetJob::NotYetStarted(owner);
201-
}
202-
Entry::Occupied(mut entry) => {
203-
match entry.get_mut() {
204-
#[cfg(not(parallel_compiler))]
205-
QueryResult::Started(job) => {
206-
let id = job.id;
207-
drop(state_lock);
208-
209-
// If we are single-threaded we know that we have cycle error,
210-
// so we just return the error.
211-
return TryGetJob::Cycle(id.find_cycle_in_stack(
212-
qcx.try_collect_active_jobs().unwrap(),
213-
&current_job_id,
214-
span,
215-
));
216-
}
217-
#[cfg(parallel_compiler)]
218-
QueryResult::Started(job) => {
219-
// For parallel queries, we'll block and wait until the query running
220-
// in another thread has completed. Record how long we wait in the
221-
// self-profiler.
222-
let query_blocked_prof_timer = qcx.dep_context().profiler().query_blocked();
223-
224-
// Get the latch out
225-
let latch = job.latch();
226-
227-
drop(state_lock);
228-
229-
// With parallel queries we might just have to wait on some other
230-
// thread.
231-
let result = latch.wait_on(current_job_id, span);
232-
233-
match result {
234-
Ok(()) => TryGetJob::JobCompleted(query_blocked_prof_timer),
235-
Err(cycle) => TryGetJob::Cycle(cycle),
236-
}
237-
}
238-
QueryResult::Poisoned => FatalError.raise(),
239-
}
240-
}
241-
}
242-
}
243-
244168
/// Completes the query by updating the query cache with the `result`,
245169
/// signals the waiter and forgets the JobOwner, so it won't poison the query
246170
fn complete<C>(self, cache: &C, result: C::Value, dep_node_index: DepNodeIndex)
@@ -307,25 +231,6 @@ pub(crate) struct CycleError<D: DepKind> {
307231
pub cycle: Vec<QueryInfo<D>>,
308232
}
309233

310-
/// The result of `try_start`.
311-
enum TryGetJob<'tcx, K, D>
312-
where
313-
K: Eq + Hash + Copy,
314-
D: DepKind,
315-
{
316-
/// The query is not yet started. Contains a guard to the cache eventually used to start it.
317-
NotYetStarted(JobOwner<'tcx, K, D>),
318-
319-
/// The query was already completed.
320-
/// Returns the result of the query and its dep-node index
321-
/// if it succeeded or a cycle error if it failed.
322-
#[cfg(parallel_compiler)]
323-
JobCompleted(TimingGuard<'tcx>),
324-
325-
/// Trying to execute the query resulted in a cycle.
326-
Cycle(CycleError<D>),
327-
}
328-
329234
/// Checks if the query is already computed and in the cache.
330235
/// It returns the shard index and a lock guard to the shard,
331236
/// which will be used if the query is not in the cache and we need
@@ -346,6 +251,65 @@ where
346251
}
347252
}
348253

254+
#[cold]
255+
#[inline(never)]
256+
#[cfg(not(parallel_compiler))]
257+
fn cycle_error<Q, Qcx>(
258+
query: Q,
259+
qcx: Qcx,
260+
try_execute: QueryJobId,
261+
span: Span,
262+
) -> (Q::Value, Option<DepNodeIndex>)
263+
where
264+
Q: QueryConfig<Qcx>,
265+
Qcx: QueryContext,
266+
{
267+
let error = try_execute.find_cycle_in_stack(
268+
qcx.try_collect_active_jobs().unwrap(),
269+
&qcx.current_query_job(),
270+
span,
271+
);
272+
(mk_cycle(qcx, error, query.handle_cycle_error()), None)
273+
}
274+
275+
#[inline(always)]
276+
#[cfg(parallel_compiler)]
277+
fn wait_for_query<Q, Qcx>(
278+
query: Q,
279+
qcx: Qcx,
280+
span: Span,
281+
key: Q::Key,
282+
latch: QueryLatch<Qcx::DepKind>,
283+
current: Option<QueryJobId>,
284+
) -> (Q::Value, Option<DepNodeIndex>)
285+
where
286+
Q: QueryConfig<Qcx>,
287+
Qcx: QueryContext,
288+
{
289+
// For parallel queries, we'll block and wait until the query running
290+
// in another thread has completed. Record how long we wait in the
291+
// self-profiler.
292+
let query_blocked_prof_timer = qcx.dep_context().profiler().query_blocked();
293+
294+
// With parallel queries we might just have to wait on some other
295+
// thread.
296+
let result = latch.wait_on(current, span);
297+
298+
match result {
299+
Ok(()) => {
300+
let Some((v, index)) = query.query_cache(qcx).lookup(&key) else {
301+
cold_path(|| panic!("value must be in cache after waiting"))
302+
};
303+
304+
qcx.dep_context().profiler().query_cache_hit(index.into());
305+
query_blocked_prof_timer.finish_with_query_invocation_id(index.into());
306+
307+
(v, Some(index))
308+
}
309+
Err(cycle) => (mk_cycle(qcx, cycle, query.handle_cycle_error()), None),
310+
}
311+
}
312+
349313
#[inline(never)]
350314
fn try_execute_query<Q, Qcx>(
351315
query: Q,
@@ -360,9 +324,9 @@ where
360324
{
361325
let state = query.query_state(qcx);
362326
#[cfg(parallel_compiler)]
363-
let state_lock = state.active.get_shard_by_value(&key).lock();
327+
let mut state_lock = state.active.get_shard_by_value(&key).lock();
364328
#[cfg(not(parallel_compiler))]
365-
let state_lock = state.active.lock();
329+
let mut state_lock = state.active.lock();
366330

367331
// For the parallel compiler we need to check both the query cache and query state structures
368332
// while holding the state lock to ensure that 1) the query has not yet completed and 2) the
@@ -377,44 +341,82 @@ where
377341
}
378342
}
379343

380-
match JobOwner::<'_, Q::Key, Qcx::DepKind>::try_start(&qcx, state, state_lock, span, key) {
381-
TryGetJob::NotYetStarted(job) => {
382-
let (result, dep_node_index) = match qcx.dep_context().dep_graph().data() {
383-
None => execute_job_non_incr(query, qcx, key, job.id),
384-
Some(data) => execute_job_incr(query, qcx, data, key, dep_node, job.id),
385-
};
344+
let current_job_id = qcx.current_query_job();
345+
346+
match state_lock.entry(key) {
347+
Entry::Vacant(entry) => {
348+
// Nothing has computed or is computing the query, so we start a new job and insert it in the
349+
// state map.
350+
let id = qcx.next_job_id();
351+
let job = QueryJob::new(id, span, current_job_id);
352+
entry.insert(QueryResult::Started(job));
353+
354+
// Drop the lock before we start executing the query
355+
drop(state_lock);
356+
357+
execute_job(query, qcx, state, key, id, dep_node)
358+
}
359+
Entry::Occupied(mut entry) => {
360+
match entry.get_mut() {
361+
#[cfg(not(parallel_compiler))]
362+
QueryResult::Started(job) => {
363+
let id = job.id;
364+
drop(state_lock);
365+
366+
// If we are single-threaded we know that we have cycle error,
367+
// so we just return the error.
368+
cycle_error(query, qcx, id, span)
369+
}
370+
#[cfg(parallel_compiler)]
371+
QueryResult::Started(job) => {
372+
// Get the latch out
373+
let latch = job.latch();
374+
drop(state_lock);
386375

387-
let cache = query.query_cache(qcx);
388-
if query.feedable() {
389-
// We should not compute queries that also got a value via feeding.
390-
// This can't happen, as query feeding adds the very dependencies to the fed query
391-
// as its feeding query had. So if the fed query is red, so is its feeder, which will
392-
// get evaluated first, and re-feed the query.
393-
if let Some((cached_result, _)) = cache.lookup(&key) {
394-
panic!(
395-
"fed query later has its value computed. The already cached value: {cached_result:?}"
396-
);
376+
wait_for_query(query, qcx, span, key, latch, current_job_id)
397377
}
378+
QueryResult::Poisoned => FatalError.raise(),
398379
}
399-
job.complete(cache, result, dep_node_index);
400-
(result, Some(dep_node_index))
401380
}
402-
TryGetJob::Cycle(error) => {
403-
let result = mk_cycle(qcx, error, query.handle_cycle_error());
404-
(result, None)
405-
}
406-
#[cfg(parallel_compiler)]
407-
TryGetJob::JobCompleted(query_blocked_prof_timer) => {
408-
let Some((v, index)) = query.query_cache(qcx).lookup(&key) else {
409-
panic!("value must be in cache after waiting")
410-
};
381+
}
382+
}
411383

412-
qcx.dep_context().profiler().query_cache_hit(index.into());
413-
query_blocked_prof_timer.finish_with_query_invocation_id(index.into());
384+
#[inline(always)]
385+
fn execute_job<Q, Qcx>(
386+
query: Q,
387+
qcx: Qcx,
388+
state: &QueryState<Q::Key, Qcx::DepKind>,
389+
key: Q::Key,
390+
id: QueryJobId,
391+
dep_node: Option<DepNode<Qcx::DepKind>>,
392+
) -> (Q::Value, Option<DepNodeIndex>)
393+
where
394+
Q: QueryConfig<Qcx>,
395+
Qcx: QueryContext,
396+
{
397+
// Use `JobOwner` so the query will be poisoned if executing it panics.
398+
let job_owner = JobOwner { state, key };
414399

415-
(v, Some(index))
400+
let (result, dep_node_index) = match qcx.dep_context().dep_graph().data() {
401+
None => execute_job_non_incr(query, qcx, key, id),
402+
Some(data) => execute_job_incr(query, qcx, data, key, dep_node, id),
403+
};
404+
405+
let cache = query.query_cache(qcx);
406+
if query.feedable() {
407+
// We should not compute queries that also got a value via feeding.
408+
// This can't happen, as query feeding adds the very dependencies to the fed query
409+
// as its feeding query had. So if the fed query is red, so is its feeder, which will
410+
// get evaluated first, and re-feed the query.
411+
if let Some((cached_result, _)) = cache.lookup(&key) {
412+
panic!(
413+
"fed query later has its value computed. The already cached value: {cached_result:?}"
414+
);
416415
}
417416
}
417+
job_owner.complete(cache, result, dep_node_index);
418+
419+
(result, Some(dep_node_index))
418420
}
419421

420422
// Fast path for when incr. comp. is off.

0 commit comments

Comments
 (0)