Skip to content

Commit eacf966

Browse files
committed
Support emission of loops in Adaptive_RIFLA
This change adds logic to support emission of loops into QIR when use the `Adaptive_RIFLA` profile. The strategy employed uses changes to both RCA and Partial Evaluation to accomplish this. In RCA, we add a new check that identifies loops whose condition is marked as static and speculatively marks them as dynamic to compute what capabilities they would require at runtime. If the resulting capabilities are compatible with the current target, the loop remains dynamic, otherwise the RCA state is reset and the loop is marked as the originally analyzed static. In Partial Evaluation, new support for dynamic loops is added, where a loop marked as dynamic is emitted as a sequence of blocks with appropriate backward branches to capture the while-loop structure. This change includes new unit tests to verify analysis and emission of loop structures into RIR and new integration tests to verify the whole program stucture and execution of QIR v2 with loops via qir-runner.
1 parent d3520dc commit eacf966

27 files changed

Lines changed: 1394 additions & 885 deletions

File tree

source/compiler/qsc/benches/rca.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl CompilationContext {
9292
}
9393

9494
fn analyze_all(&mut self) {
95-
let analyzer = Analyzer::init(&self.fir_store);
95+
let analyzer = Analyzer::init(&self.fir_store, TargetCapabilityFlags::all());
9696
let compute_properties = analyzer.analyze_all();
9797
self.compute_properties = Some(compute_properties);
9898
}
@@ -108,8 +108,11 @@ impl CompilationContext {
108108
package_compute_properties.clear();
109109

110110
// Analyze the open package without re-analyzing the other packages.
111-
let analyzer =
112-
Analyzer::init_with_compute_properties(&self.fir_store, compute_properties.clone());
111+
let analyzer = Analyzer::init_with_compute_properties(
112+
&self.fir_store,
113+
TargetCapabilityFlags::all(),
114+
compute_properties.clone(),
115+
);
113116
self.compute_properties = Some(analyzer.analyze_package(open_package_id));
114117
}
115118

source/compiler/qsc_codegen/src/qir.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub fn fir_to_qir_from_callable(
6565
args: Value,
6666
) -> Result<String, qsc_partial_eval::Error> {
6767
let compute_properties = compute_properties.unwrap_or_else(|| {
68-
let analyzer = qsc_rca::Analyzer::init(fir_store);
68+
let analyzer = qsc_rca::Analyzer::init(fir_store, capabilities);
6969
analyzer.analyze_all()
7070
});
7171

@@ -97,7 +97,7 @@ pub fn fir_to_rir_from_callable(
9797
partial_eval_config: PartialEvalConfig,
9898
) -> Result<(Program, Program), qsc_partial_eval::Error> {
9999
let compute_properties = compute_properties.unwrap_or_else(|| {
100-
let analyzer = qsc_rca::Analyzer::init(fir_store);
100+
let analyzer = qsc_rca::Analyzer::init(fir_store, capabilities);
101101
analyzer.analyze_all()
102102
});
103103

@@ -122,7 +122,7 @@ fn get_rir_from_compilation(
122122
partial_eval_config: PartialEvalConfig,
123123
) -> Result<rir::Program, qsc_partial_eval::Error> {
124124
let compute_properties = compute_properties.unwrap_or_else(|| {
125-
let analyzer = qsc_rca::Analyzer::init(fir_store);
125+
let analyzer = qsc_rca::Analyzer::init(fir_store, capabilities);
126126
analyzer.analyze_all()
127127
});
128128

source/compiler/qsc_partial_eval/src/lib.rs

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,11 +2315,18 @@ impl<'a> PartialEvaluator<'a> {
23152315
.get_current_scope()
23162316
.get_hybrid_local_value(*local_var_id);
23172317

2318-
// Check whether the bound value is a mutable variable, and if so, return its value directly rather than
2319-
// the variable if it is static at this moment.
2318+
// Check whether the bound value is a mutable variable and we are not currently evaluating a branch.
2319+
// If so, return its value directly rather than the variable if it is static at this moment.
23202320
if let Value::Var(var) = bound_value {
23212321
let current_scope = self.eval_context.get_current_scope();
2322-
if let Some(literal) = current_scope.get_static_value(var.id.into()) {
2322+
if let Some(literal) = current_scope.get_static_value(var.id.into())
2323+
&& (!current_scope.is_currently_evaluating_branch()
2324+
|| !self
2325+
.program
2326+
.config
2327+
.capabilities
2328+
.contains(TargetCapabilityFlags::BackwardsBranching))
2329+
{
23232330
map_rir_literal_to_eval_value(*literal)
23242331
} else {
23252332
bound_value.clone()
@@ -2337,6 +2344,18 @@ impl<'a> PartialEvaluator<'a> {
23372344
condition_expr_id: ExprId,
23382345
body_block_id: BlockId,
23392346
) -> Result<EvalControlFlow, Error> {
2347+
if self
2348+
.program
2349+
.config
2350+
.capabilities
2351+
.contains(TargetCapabilityFlags::BackwardsBranching)
2352+
&& !self.is_static_expr(condition_expr_id)
2353+
{
2354+
// If backwards branching is supported and the loop condition is not static,
2355+
// we can generate a while loop structure in RIR without unrolling the loop.
2356+
return self.eval_expr_emit_while(loop_expr_id, condition_expr_id, body_block_id);
2357+
}
2358+
23402359
// Verify assumptions: the condition expression must either static (such that it can be fully evaluated) or
23412360
// dynamic but constant at runtime (such that it can be partially evaluated to a known value).
23422361
assert!(
@@ -2393,6 +2412,89 @@ impl<'a> PartialEvaluator<'a> {
23932412
Ok(EvalControlFlow::Continue(Value::unit()))
23942413
}
23952414

2415+
fn eval_expr_emit_while(
2416+
&mut self,
2417+
loop_expr_id: ExprId,
2418+
condition_expr_id: ExprId,
2419+
body_block_id: BlockId,
2420+
) -> Result<EvalControlFlow, Error> {
2421+
// Pop the current block node and create the necessary block nodes for the loop structure.
2422+
let current_block_node = self.eval_context.pop_block_node();
2423+
let conditional_block_node_id = self.create_program_block();
2424+
let conditional_block_node = BlockNode {
2425+
id: conditional_block_node_id,
2426+
successor: current_block_node.successor,
2427+
};
2428+
let continuation_block_node_id = self.create_program_block();
2429+
let continuation_block_node = BlockNode {
2430+
id: continuation_block_node_id,
2431+
successor: current_block_node.successor,
2432+
};
2433+
self.eval_context.push_block_node(continuation_block_node);
2434+
2435+
// Insert the jump instruction to the conditional block from the current block.
2436+
let jump_to_condition_ins = Instruction::Jump(conditional_block_node_id);
2437+
self.get_program_block_mut(current_block_node.id)
2438+
.0
2439+
.push(jump_to_condition_ins);
2440+
2441+
// In the conditional block, evaluate the condition expression and generate the branch instruction.
2442+
self.eval_context.push_block_node(conditional_block_node);
2443+
let condition_control_flow = self.try_eval_expr(condition_expr_id)?;
2444+
if condition_control_flow.is_return() {
2445+
return Err(Error::Unexpected(
2446+
"embedded return in loop condition".to_string(),
2447+
self.get_expr_package_span(condition_expr_id),
2448+
));
2449+
}
2450+
let condition_value = condition_control_flow.into_value();
2451+
2452+
if let Value::Bool(false) = condition_value {
2453+
// If the condition is statically false, jump directly to the continuation block.
2454+
let jump_to_continuation_ins = Instruction::Jump(continuation_block_node_id);
2455+
self.get_current_rir_block_mut()
2456+
.0
2457+
.push(jump_to_continuation_ins);
2458+
let _ = self.eval_context.pop_block_node();
2459+
return Ok(EvalControlFlow::Continue(Value::unit()));
2460+
}
2461+
2462+
// Otherwise, branch to either the body block or the continuation block.
2463+
let body_block_node_id = self.create_program_block();
2464+
let body_block_node = BlockNode {
2465+
id: body_block_node_id,
2466+
successor: Some(conditional_block_node_id),
2467+
};
2468+
let condition_value_var = condition_value.unwrap_var();
2469+
let condition_rir_var = map_eval_var_to_rir_var(condition_value_var);
2470+
let metadata = self.metadata_from_expr(loop_expr_id);
2471+
let branch_ins = Instruction::Branch(
2472+
condition_rir_var,
2473+
body_block_node_id,
2474+
continuation_block_node_id,
2475+
metadata,
2476+
);
2477+
self.get_current_rir_block_mut().0.push(branch_ins);
2478+
let _ = self.eval_context.pop_block_node();
2479+
2480+
// In the body block, evaluate the loop body and jump back to the conditional block.
2481+
self.eval_context.push_block_node(body_block_node);
2482+
let body_control_flow = self.try_eval_block(body_block_id)?;
2483+
if body_control_flow.is_return() {
2484+
return Err(Error::Unexpected(
2485+
"embedded return in loop body".to_string(),
2486+
self.get_expr_package_span(condition_expr_id),
2487+
));
2488+
}
2489+
let jump_to_condition_ins = Instruction::Jump(conditional_block_node_id);
2490+
self.get_current_rir_block_mut()
2491+
.0
2492+
.push(jump_to_condition_ins);
2493+
let _ = self.eval_context.pop_block_node();
2494+
2495+
Ok(EvalControlFlow::Continue(Value::unit()))
2496+
}
2497+
23962498
fn eval_result_as_bool_operand(&mut self, result: val::Result) -> Operand {
23972499
match result {
23982500
val::Result::Id(id) => {

source/compiler/qsc_partial_eval/src/tests.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,7 @@ pub fn assert_error(error: &Error, expected_error: &Expect) {
6767

6868
#[must_use]
6969
pub fn get_partial_evaluation_error(source: &str) -> Error {
70-
let maybe_program = compile_and_partially_evaluate(
71-
source,
72-
TargetCapabilityFlags::all(),
73-
PartialEvalConfig {
74-
generate_debug_metadata: false,
75-
},
76-
);
77-
match maybe_program {
78-
Ok(_) => panic!("partial evaluation succeeded"),
79-
Err(error) => error,
80-
}
70+
get_partial_evaluation_error_with_capabilities(source, Profile::AdaptiveRIF.into())
8171
}
8272

8373
#[must_use]
@@ -100,21 +90,7 @@ pub fn get_partial_evaluation_error_with_capabilities(
10090

10191
#[must_use]
10292
pub fn get_rir_program(source: &str) -> Program {
103-
let maybe_program = compile_and_partially_evaluate(
104-
source,
105-
Profile::AdaptiveRIF.into(),
106-
PartialEvalConfig {
107-
generate_debug_metadata: false,
108-
},
109-
);
110-
match maybe_program {
111-
Ok(program) => {
112-
// Verify the program can go through transformations.
113-
check_and_transform(&mut program.clone());
114-
program
115-
}
116-
Err(error) => panic!("partial evaluation failed: {error:?}"),
117-
}
93+
get_rir_program_with_capabilities(source, Profile::AdaptiveRIF.into())
11894
}
11995

12096
#[must_use]
@@ -242,7 +218,7 @@ impl CompilationContext {
242218
.expect("should be able to create a new compiler");
243219
let package_id = map_hir_package_to_fir(compiler.source_package_id());
244220
let fir_store = lower_hir_package_store(compiler.package_store());
245-
let analyzer = Analyzer::init(&fir_store);
221+
let analyzer = Analyzer::init(&fir_store, capabilities);
246222
let compute_properties = analyzer.analyze_all();
247223
let package = fir_store.get(package_id);
248224
let entry = ProgramEntry {

0 commit comments

Comments
 (0)