microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
44658e234ef2cfa1fd31c0231ec658df5d48b48f

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc/src/codegen.rs

1524lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7pub mod qsharp {
8 pub use qsc_codegen::qsharp::write_package_string;
9 pub use qsc_codegen::qsharp::write_stmt_string;
10}
11
12pub mod qir {
13 use qsc_codegen::qir::{fir_to_qir, fir_to_rir};
14 use qsc_eval::val::Value;
15 use qsc_fir::fir::Package;
16
17 use qsc_data_structures::{
18 error::WithSource, functors::FunctorApp, language_features::LanguageFeatures,
19 source::SourceMap, target::TargetCapabilityFlags,
20 };
21 use qsc_frontend::compile::{Dependencies, PackageStore};
22 use qsc_partial_eval::{PartialEvalConfig, ProgramEntry};
23 use qsc_passes::{PackageType, PassContext, run_fir_passes_for_callable};
24 use rustc_hash::FxHashSet;
25
26 use crate::interpret::Error;
27
28 /// Flat Intermediate Representation (FIR) ready for QIR/RIR code generation.
29 ///
30 /// Contains:
31 /// - `fir_store`: Complete lowered FIR package store after all compiler passes
32 /// - `fir_package_id`: Main package ID within the store
33 /// - `compute_properties`: Resource analysis (qubit/instruction counts, etc.)
34 ///
35 /// Invariants (when created with full pipeline):
36 /// - No type parameters remain (monomorphization complete)
37 /// - No return statements (return unification complete)
38 /// - No arrow types or closures (defunctionalization complete)
39 /// - No UDT types (UDT erasure complete)
40 /// - Execution graphs fully populated
41 pub struct CodegenFir {
42 pub fir_store: qsc_fir::fir::PackageStore,
43 pub fir_package_id: qsc_fir::fir::PackageId,
44 pub compute_properties: qsc_rca::PackageStoreComputeProperties,
45 }
46
47 /// Extracts the entry point expression from codegen FIR.
48 ///
49 /// Forms a `ProgramEntry` suitable for downstream codegen (QIR, RIR generation)
50 /// by combining the entry expression and its associated execution graph.
51 pub(crate) fn entry_from_codegen_fir(prepared_fir: &CodegenFir) -> ProgramEntry {
52 let package = prepared_fir.fir_store.get(prepared_fir.fir_package_id);
53 ProgramEntry {
54 exec_graph: package.entry_exec_graph.clone(),
55 expr: (
56 prepared_fir.fir_package_id,
57 package
58 .entry
59 .expect("package must have an entry expression"),
60 )
61 .into(),
62 }
63 }
64
65 fn clone_fir_package(package: &Package) -> Package {
66 Package {
67 items: package.items.clone(),
68 entry: package.entry,
69 entry_exec_graph: package.entry_exec_graph.clone(),
70 blocks: package.blocks.clone(),
71 exprs: package.exprs.clone(),
72 pats: package.pats.clone(),
73 stmts: package.stmts.clone(),
74 }
75 }
76
77 fn clone_fir_store(fir_store: &qsc_fir::fir::PackageStore) -> qsc_fir::fir::PackageStore {
78 let mut cloned_store = qsc_fir::fir::PackageStore::new();
79 for (package_id, package) in fir_store {
80 cloned_store.insert(package_id, clone_fir_package(package));
81 }
82 cloned_store
83 }
84
85 fn lower_to_fir(
86 package_store: &PackageStore,
87 package_id: qsc_hir::hir::PackageId,
88 package_override: Option<&qsc_hir::hir::Package>,
89 ) -> (
90 qsc_fir::fir::PackageStore,
91 qsc_fir::fir::PackageId,
92 qsc_fir::assigner::Assigner,
93 ) {
94 if let Some(package_override) = package_override {
95 let mut fir_store = qsc_fir::fir::PackageStore::new();
96 let mut fir_assigner = qsc_fir::assigner::Assigner::new();
97
98 for (id, unit) in package_store {
99 let hir_package = if id == package_id {
100 package_override
101 } else {
102 &unit.package
103 };
104
105 let mut lowerer = qsc_lowerer::Lowerer::new();
106 let fir_package = if id == package_id {
107 let mut fir_package = Package {
108 items: Default::default(),
109 entry: None,
110 entry_exec_graph: Default::default(),
111 blocks: Default::default(),
112 exprs: Default::default(),
113 pats: Default::default(),
114 stmts: Default::default(),
115 };
116 lowerer.lower_and_update_package(&mut fir_package, hir_package);
117 fir_package.entry_exec_graph = lowerer.take_exec_graph();
118 fir_package
119 } else {
120 lowerer.lower_package(hir_package, &fir_store)
121 };
122 if id == package_id {
123 fir_assigner = lowerer.into_assigner();
124 }
125 fir_store.insert(qsc_lowerer::map_hir_package_to_fir(id), fir_package);
126 }
127
128 (
129 fir_store,
130 qsc_lowerer::map_hir_package_to_fir(package_id),
131 fir_assigner,
132 )
133 } else {
134 qsc_passes::lower_hir_to_fir(package_store, package_id)
135 }
136 }
137
138 /// Runs the full FIR transformation pipeline through all stages.
139 ///
140 /// Applies compiler passes (monomorphization, defunctionalization, UDT erasure, etc.)
141 /// to produce codegen-ready FIR satisfying full invariants.
142 pub fn run_codegen_pipeline(
143 package_store: &PackageStore,
144 package_id: qsc_hir::hir::PackageId,
145 fir_store: &mut qsc_fir::fir::PackageStore,
146 fir_package_id: qsc_fir::fir::PackageId,
147 ) -> Result<(), Vec<Error>> {
148 run_codegen_pipeline_to(
149 package_store,
150 package_id,
151 fir_store,
152 fir_package_id,
153 qsc_fir_transforms::PipelineStage::Full,
154 &[],
155 )
156 }
157
158 /// Runs the FIR pipeline up to a specified stage with optional item pinning.
159 ///
160 /// Allows fine-grained control over pipeline execution:
161 /// - `stage`: Which pipeline stage to stop at (e.g., `PipelineStage::Full` for all passes)
162 /// - `pinned_items`: Callables to preserve even if not reached from entry
163 /// (useful for callable arguments that might otherwise be eliminated by DCE)
164 ///
165 /// This is critical for higher-order function support: when a callable is passed
166 /// as an argument, it may not be directly reachable from entry and would normally be
167 /// removed during dead-code elimination. Pinning preserves these for specialization.
168 pub fn run_codegen_pipeline_to(
169 package_store: &PackageStore,
170 package_id: qsc_hir::hir::PackageId,
171 fir_store: &mut qsc_fir::fir::PackageStore,
172 fir_package_id: qsc_fir::fir::PackageId,
173 stage: qsc_fir_transforms::PipelineStage,
174 pinned_items: &[qsc_fir::fir::StoreItemId],
175 ) -> Result<(), Vec<Error>> {
176 // CONTRACT: On success, `run_pipeline_to` with `PipelineStage::Full` produces FIR
177 // satisfying `InvariantLevel::PostAll`:
178 // - No `Ty::Param` in reachable code (monomorphization completed).
179 // - No `ExprKind::Return` in reachable code (return unification completed).
180 // - No `Ty::Arrow` params / `ExprKind::Closure` (defunctionalization completed).
181 // - No `Ty::Udt` / `ExprKind::Struct` / `Field::Path` (UDT erasure completed).
182 // - All exec-graph ranges populated (exec-graph rebuild completed).
183 // Downstream codegen (QIR lowering, partial evaluation) assumes these invariants hold.
184 // See `qsc_fir_transforms::invariants::check` for the authoritative checker.
185 let pipeline_errors =
186 qsc_fir_transforms::run_pipeline_to(fir_store, fir_package_id, stage, pinned_items);
187 if !pipeline_errors.is_empty() {
188 let source_package = package_store
189 .get(package_id)
190 .expect("package should be in store");
191 return Err(pipeline_errors
192 .into_iter()
193 .map(|e| Error::FirTransform(WithSource::from_map(&source_package.sources, e)))
194 .collect());
195 }
196
197 Ok(())
198 }
199
200 fn map_pass_errors(
201 package_store: &PackageStore,
202 package_id: qsc_hir::hir::PackageId,
203 errors: Vec<qsc_passes::Error>,
204 ) -> Vec<Error> {
205 let source_package = package_store
206 .get(package_id)
207 .expect("package should be in store");
208
209 errors
210 .into_iter()
211 .map(|e| Error::Pass(WithSource::from_map(&source_package.sources, e)))
212 .collect()
213 }
214
215 fn validate_callable_capabilities(
216 package_store: &PackageStore,
217 fir_store: &qsc_fir::fir::PackageStore,
218 compute_properties: &qsc_rca::PackageStoreComputeProperties,
219 callable: qsc_fir::fir::StoreItemId,
220 capabilities: TargetCapabilityFlags,
221 ) -> Result<(), Vec<Error>> {
222 let errors =
223 run_fir_passes_for_callable(fir_store, compute_properties, callable, capabilities);
224 if errors.is_empty() {
225 Ok(())
226 } else {
227 Err(map_pass_errors(
228 package_store,
229 qsc_lowerer::map_fir_package_to_hir(callable.package),
230 errors,
231 ))
232 }
233 }
234
235 /// Returns true if a type is, or structurally contains, a callable arrow type.
236 ///
237 /// Arrays, tuples, and UDT pure types are traversed recursively so callers can
238 /// detect callable fields even before UDT erasure has normalized the type shape.
239 fn ty_contains_arrow(ty: &qsc_fir::ty::Ty, fir_store: &qsc_fir::fir::PackageStore) -> bool {
240 match ty {
241 qsc_fir::ty::Ty::Array(item) => ty_contains_arrow(item, fir_store),
242 qsc_fir::ty::Ty::Arrow(_) => true,
243 qsc_fir::ty::Ty::Tuple(items) => {
244 items.iter().any(|item| ty_contains_arrow(item, fir_store))
245 }
246 qsc_fir::ty::Ty::Udt(res) => {
247 let qsc_fir::fir::Res::Item(item_id) = res else {
248 return false;
249 };
250 let package = fir_store.get(item_id.package);
251 let item = package
252 .items
253 .get(item_id.item)
254 .expect("UDT item should exist");
255 let qsc_fir::fir::ItemKind::Ty(_, udt) = &item.kind else {
256 return false;
257 };
258 ty_contains_arrow(&udt.get_pure_ty(), fir_store)
259 }
260 qsc_fir::ty::Ty::Infer(_)
261 | qsc_fir::ty::Ty::Param(_)
262 | qsc_fir::ty::Ty::Prim(_)
263 | qsc_fir::ty::Ty::Err => false,
264 }
265 }
266
267 fn callable_has_arrow_input(
268 fir_store: &qsc_fir::fir::PackageStore,
269 callable: qsc_hir::hir::ItemId,
270 ) -> bool {
271 use qsc_fir::fir::{Global, PackageLookup};
272
273 let callable_store_id = qsc_fir::fir::StoreItemId {
274 package: qsc_lowerer::map_hir_package_to_fir(callable.package),
275 item: qsc_lowerer::map_hir_local_item_to_fir(callable.item),
276 };
277
278 let package = fir_store.get(callable_store_id.package);
279 let Some(Global::Callable(callable_decl)) = package.get_global(callable_store_id.item)
280 else {
281 // Item removed by DCE or not a callable — treat as not having arrow input.
282 return false;
283 };
284
285 ty_contains_arrow(&package.get_pat(callable_decl.input).ty, fir_store)
286 }
287
288 fn seed_entry_with_callable(
289 fir_store: &mut qsc_fir::fir::PackageStore,
290 fir_package_id: qsc_fir::fir::PackageId,
291 callable: qsc_hir::hir::ItemId,
292 assigner: &mut qsc_fir::assigner::Assigner,
293 ) {
294 let callable_store_id = qsc_fir::fir::StoreItemId {
295 package: qsc_lowerer::map_hir_package_to_fir(callable.package),
296 item: qsc_lowerer::map_hir_local_item_to_fir(callable.item),
297 };
298
299 let (span, ty) = {
300 use qsc_fir::fir::{Global, PackageLookup};
301
302 let package = fir_store.get(callable_store_id.package);
303 let Some(Global::Callable(callable_decl)) = package.get_global(callable_store_id.item)
304 else {
305 panic!("callable should exist in lowered package");
306 };
307
308 let input = package.get_pat(callable_decl.input).ty.clone();
309 let ty = qsc_fir::ty::Ty::Arrow(Box::new(qsc_fir::ty::Arrow {
310 kind: callable_decl.kind,
311 input: Box::new(input),
312 output: Box::new(callable_decl.output.clone()),
313 functors: qsc_fir::ty::FunctorSet::Value(callable_decl.functors),
314 }));
315
316 (callable_decl.span, ty)
317 };
318
319 let entry_expr_id = assigner.next_expr();
320 let package = fir_store.get_mut(fir_package_id);
321 package.exprs.insert(
322 entry_expr_id,
323 qsc_fir::fir::Expr {
324 id: entry_expr_id,
325 span,
326 ty,
327 kind: qsc_fir::fir::ExprKind::Var(
328 qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
329 package: callable_store_id.package,
330 item: callable_store_id.item,
331 }),
332 Vec::new(),
333 ),
334 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
335 ..qsc_fir::fir::ExecGraphIdx::ZERO,
336 },
337 );
338 package.entry = Some(entry_expr_id);
339 package.entry_exec_graph = Default::default();
340 }
341
342 fn callable_expr_span_and_ty(
343 fir_store: &qsc_fir::fir::PackageStore,
344 callable_store_id: qsc_fir::fir::StoreItemId,
345 ) -> (qsc_data_structures::span::Span, qsc_fir::ty::Ty) {
346 use qsc_fir::fir::{Global, PackageLookup};
347
348 let package = fir_store.get(callable_store_id.package);
349 let Some(Global::Callable(callable_decl)) = package.get_global(callable_store_id.item)
350 else {
351 panic!("callable should exist in lowered package");
352 };
353
354 let input = package.get_pat(callable_decl.input).ty.clone();
355 let ty = qsc_fir::ty::Ty::Arrow(Box::new(qsc_fir::ty::Arrow {
356 kind: callable_decl.kind,
357 input: Box::new(input),
358 output: Box::new(callable_decl.output.clone()),
359 functors: qsc_fir::ty::FunctorSet::Value(callable_decl.functors),
360 }));
361
362 (callable_decl.span, ty)
363 }
364
365 fn seed_entry_with_callables(
366 fir_store: &mut qsc_fir::fir::PackageStore,
367 fir_package_id: qsc_fir::fir::PackageId,
368 callables: &FxHashSet<qsc_fir::fir::StoreItemId>,
369 ) {
370 if callables.is_empty() {
371 return;
372 }
373
374 let mut assigner = qsc_fir::assigner::Assigner::from_package(fir_store.get(fir_package_id));
375
376 let mut entry_exprs = Vec::with_capacity(callables.len());
377 let mut entry_tys = Vec::with_capacity(callables.len());
378 let mut entry_span = None;
379
380 for callable in callables {
381 let (span, ty) = callable_expr_span_and_ty(fir_store, *callable);
382 let expr_id = assigner.next_expr();
383 let package = fir_store.get_mut(fir_package_id);
384 package.exprs.insert(
385 expr_id,
386 qsc_fir::fir::Expr {
387 id: expr_id,
388 span,
389 ty: ty.clone(),
390 kind: qsc_fir::fir::ExprKind::Var(
391 qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
392 package: callable.package,
393 item: callable.item,
394 }),
395 Vec::new(),
396 ),
397 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
398 ..qsc_fir::fir::ExecGraphIdx::ZERO,
399 },
400 );
401 entry_exprs.push(expr_id);
402 entry_tys.push(ty);
403 entry_span.get_or_insert(span);
404 }
405
406 let entry_expr_id = if entry_exprs.len() == 1 {
407 entry_exprs[0]
408 } else {
409 let entry_expr_id = assigner.next_expr();
410 let package = fir_store.get_mut(fir_package_id);
411 package.exprs.insert(
412 entry_expr_id,
413 qsc_fir::fir::Expr {
414 id: entry_expr_id,
415 span: entry_span.expect("tuple entry should have a span"),
416 ty: qsc_fir::ty::Ty::Tuple(entry_tys),
417 kind: qsc_fir::fir::ExprKind::Tuple(entry_exprs),
418 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
419 ..qsc_fir::fir::ExecGraphIdx::ZERO,
420 },
421 );
422 entry_expr_id
423 };
424
425 let package = fir_store.get_mut(fir_package_id);
426 package.entry = Some(entry_expr_id);
427 package.entry_exec_graph = Default::default();
428 }
429
430 /// Builds a pre-computed map of callable types for all Global/Closure values in `args`.
431 ///
432 /// This allows `lower_value_to_expr` to look up arrow types without holding an immutable
433 /// reference to the package store while also mutating a package.
434 fn build_callable_type_map(
435 fir_store: &qsc_fir::fir::PackageStore,
436 callables: &FxHashSet<qsc_fir::fir::StoreItemId>,
437 ) -> rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty> {
438 let mut map =
439 rustc_hash::FxHashMap::with_capacity_and_hasher(callables.len(), Default::default());
440 for id in callables {
441 let (_, ty) = callable_expr_span_and_ty(fir_store, *id);
442 map.insert(*id, ty);
443 }
444 map
445 }
446
447 /// Seeds the package entry with a synthetic `Call(target, args)` expression.
448 ///
449 /// Builds args matching the target callable's pure input type: callable-typed positions
450 /// are filled with Var references to the concrete callables from the `args` Value;
451 /// non-callable positions get typed placeholder literals (which are never evaluated —
452 /// they exist only to make the Call structurally valid for defunctionalization).
453 fn seed_entry_with_call_to_target(
454 fir_store: &mut qsc_fir::fir::PackageStore,
455 fir_package_id: qsc_fir::fir::PackageId,
456 target_callable: qsc_fir::fir::StoreItemId,
457 args: &Value,
458 callable_types: &rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty>,
459 ) {
460 use qsc_fir::fir::{Global, PackageLookup};
461
462 // Pre-compute target's arrow type and input pattern type (immutable borrow of store).
463 let package = fir_store.get(target_callable.package);
464 let Some(Global::Callable(callable_decl)) = package.get_global(target_callable.item) else {
465 panic!("target callable must exist in lowered package");
466 };
467 let span = callable_decl.span;
468 let input_pat = package.get_pat(callable_decl.input);
469 let input_ty = resolve_functor_params(&resolve_udt_ty(fir_store, &input_pat.ty));
470 let output_ty = resolve_functor_params(&resolve_udt_ty(fir_store, &callable_decl.output));
471 let arrow_ty = qsc_fir::ty::Ty::Arrow(Box::new(qsc_fir::ty::Arrow {
472 kind: callable_decl.kind,
473 input: Box::new(input_ty.clone()),
474 output: Box::new(output_ty.clone()),
475 functors: qsc_fir::ty::FunctorSet::Value(callable_decl.functors),
476 }));
477
478 // Build concrete generic args for the callee Var so monomorphization can
479 // resolve FunctorSet::Param in the specialized clone's body types.
480 let generic_args = build_concrete_generic_args(&callable_decl.generics);
481
482 // Build assigner from the package's current ID counters.
483 let mut assigner = qsc_fir::assigner::Assigner::from_package(fir_store.get(fir_package_id));
484
485 // Get the package mutably and build args expression matching the input type.
486 let package = fir_store.get_mut(fir_package_id);
487 let args_expr_id =
488 build_synthetic_args(package, &mut assigner, &input_ty, args, callable_types);
489
490 // Create callee Var expression referencing the target callable.
491 let callee_expr_id = assigner.next_expr();
492 package.exprs.insert(
493 callee_expr_id,
494 qsc_fir::fir::Expr {
495 id: callee_expr_id,
496 span,
497 ty: arrow_ty,
498 kind: qsc_fir::fir::ExprKind::Var(
499 qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
500 package: target_callable.package,
501 item: target_callable.item,
502 }),
503 generic_args,
504 ),
505 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
506 ..qsc_fir::fir::ExecGraphIdx::ZERO,
507 },
508 );
509
510 // Create Call expression: Call(callee, args) with output type.
511 let call_expr_id = assigner.next_expr();
512 package.exprs.insert(
513 call_expr_id,
514 qsc_fir::fir::Expr {
515 id: call_expr_id,
516 span,
517 ty: output_ty,
518 kind: qsc_fir::fir::ExprKind::Call(callee_expr_id, args_expr_id),
519 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
520 ..qsc_fir::fir::ExecGraphIdx::ZERO,
521 },
522 );
523
524 // Set entry to the synthetic Call.
525 package.entry = Some(call_expr_id);
526 package.entry_exec_graph = Default::default();
527 }
528
529 /// Builds an args expression matching the target's input type.
530 ///
531 /// For callable-typed positions, uses the corresponding callable from `args`.
532 /// For non-callable positions, uses `lower_value_to_expr` if the value is available
533 /// in `args`, otherwise creates a typed placeholder literal.
534 fn build_synthetic_args(
535 package: &mut qsc_fir::fir::Package,
536 assigner: &mut qsc_fir::assigner::Assigner,
537 input_ty: &qsc_fir::ty::Ty,
538 args: &Value,
539 callable_types: &rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty>,
540 ) -> qsc_fir::fir::ExprId {
541 match input_ty {
542 qsc_fir::ty::Ty::Tuple(elem_tys) if elem_tys.is_empty() => {
543 // Unit input — create empty tuple expression.
544 let expr_id = assigner.next_expr();
545 package.exprs.insert(
546 expr_id,
547 qsc_fir::fir::Expr {
548 id: expr_id,
549 span: qsc_data_structures::span::Span::default(),
550 ty: qsc_fir::ty::Ty::Tuple(Vec::new()),
551 kind: qsc_fir::fir::ExprKind::Tuple(Vec::new()),
552 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
553 ..qsc_fir::fir::ExecGraphIdx::ZERO,
554 },
555 );
556 expr_id
557 }
558 qsc_fir::ty::Ty::Tuple(elem_tys) => {
559 // Multi-param input — walk each position.
560 // If args is a Tuple of same length, pair element-wise.
561 // Otherwise, match the first callable-typed position to args.
562 let arg_elems: Vec<&Value> = match args {
563 Value::Tuple(vs, _) if vs.len() == elem_tys.len() => vs.iter().collect(),
564 _ => {
565 // Args doesn't match tuple structure — build with
566 // args placed at the first arrow-typed position.
567 let mut elem_ids = Vec::with_capacity(elem_tys.len());
568 let mut args_used = false;
569 for elem_ty in elem_tys {
570 if !args_used && ty_is_arrow_or_contains_arrow(elem_ty) {
571 elem_ids.push(lower_value_to_expr(
572 package,
573 assigner,
574 args,
575 callable_types,
576 ));
577 args_used = true;
578 } else {
579 elem_ids.push(make_placeholder_expr(package, assigner, elem_ty));
580 }
581 }
582 let expr_id = assigner.next_expr();
583 package.exprs.insert(
584 expr_id,
585 qsc_fir::fir::Expr {
586 id: expr_id,
587 span: qsc_data_structures::span::Span::default(),
588 ty: input_ty.clone(),
589 kind: qsc_fir::fir::ExprKind::Tuple(elem_ids),
590 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
591 ..qsc_fir::fir::ExecGraphIdx::ZERO,
592 },
593 );
594 return expr_id;
595 }
596 };
597
598 // Element-wise matching: lower each arg against its declared type.
599 let mut elem_ids = Vec::with_capacity(elem_tys.len());
600 for (elem_ty, arg_val) in elem_tys.iter().zip(arg_elems.iter()) {
601 elem_ids.push(build_synthetic_args(
602 package,
603 assigner,
604 elem_ty,
605 arg_val,
606 callable_types,
607 ));
608 }
609 let expr_id = assigner.next_expr();
610 package.exprs.insert(
611 expr_id,
612 qsc_fir::fir::Expr {
613 id: expr_id,
614 span: qsc_data_structures::span::Span::default(),
615 ty: input_ty.clone(),
616 kind: qsc_fir::fir::ExprKind::Tuple(elem_ids),
617 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
618 ..qsc_fir::fir::ExecGraphIdx::ZERO,
619 },
620 );
621 expr_id
622 }
623 qsc_fir::ty::Ty::Arrow(_) => {
624 // Arrow-typed position — the args must be a callable value.
625 lower_value_to_expr(package, assigner, args, callable_types)
626 }
627 _ => {
628 // Non-callable position — lower value if possible, otherwise placeholder.
629 match args {
630 Value::Qubit(_) | Value::Var(_) => {
631 make_placeholder_expr(package, assigner, input_ty)
632 }
633 _ => lower_value_to_expr(package, assigner, args, callable_types),
634 }
635 }
636 }
637 }
638
639 /// Replaces UDT types with their pure structural FIR type, recursively.
640 ///
641 /// Synthetic call construction operates on the post-erasure shape so callable
642 /// fields hidden inside UDTs can be discovered by defunctionalization.
643 fn resolve_udt_ty(
644 fir_store: &qsc_fir::fir::PackageStore,
645 ty: &qsc_fir::ty::Ty,
646 ) -> qsc_fir::ty::Ty {
647 match ty {
648 qsc_fir::ty::Ty::Udt(qsc_fir::fir::Res::Item(item_id)) => {
649 let package = fir_store.get(item_id.package);
650 let item = package
651 .items
652 .get(item_id.item)
653 .expect("UDT item should exist");
654 let qsc_fir::fir::ItemKind::Ty(_, udt) = &item.kind else {
655 return ty.clone();
656 };
657 resolve_udt_ty(fir_store, &udt.get_pure_ty())
658 }
659 qsc_fir::ty::Ty::Tuple(elems) => qsc_fir::ty::Ty::Tuple(
660 elems
661 .iter()
662 .map(|elem| resolve_udt_ty(fir_store, elem))
663 .collect(),
664 ),
665 qsc_fir::ty::Ty::Array(elem) => {
666 qsc_fir::ty::Ty::Array(Box::new(resolve_udt_ty(fir_store, elem)))
667 }
668 qsc_fir::ty::Ty::Arrow(arrow) => qsc_fir::ty::Ty::Arrow(Box::new(qsc_fir::ty::Arrow {
669 kind: arrow.kind,
670 input: Box::new(resolve_udt_ty(fir_store, &arrow.input)),
671 output: Box::new(resolve_udt_ty(fir_store, &arrow.output)),
672 functors: arrow.functors,
673 })),
674 _ => ty.clone(),
675 }
676 }
677
678 /// Returns true if the type is an Arrow or contains an Arrow in tuple structure.
679 fn ty_is_arrow_or_contains_arrow(ty: &qsc_fir::ty::Ty) -> bool {
680 match ty {
681 qsc_fir::ty::Ty::Arrow(_) => true,
682 qsc_fir::ty::Ty::Tuple(elems) => elems.iter().any(ty_is_arrow_or_contains_arrow),
683 _ => false,
684 }
685 }
686
687 /// Creates a typed placeholder expression for a non-callable input position.
688 ///
689 /// Uses `Lit(Int(0))` with the declared type. The placeholder is never evaluated —
690 /// it exists only to make the synthetic Call structurally valid for pipeline passes.
691 fn make_placeholder_expr(
692 package: &mut qsc_fir::fir::Package,
693 assigner: &mut qsc_fir::assigner::Assigner,
694 ty: &qsc_fir::ty::Ty,
695 ) -> qsc_fir::fir::ExprId {
696 let expr_id = assigner.next_expr();
697 package.exprs.insert(
698 expr_id,
699 qsc_fir::fir::Expr {
700 id: expr_id,
701 span: qsc_data_structures::span::Span::default(),
702 ty: ty.clone(),
703 kind: qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Int(0)),
704 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
705 ..qsc_fir::fir::ExecGraphIdx::ZERO,
706 },
707 );
708 expr_id
709 }
710
711 /// Resolves `FunctorSet::Param` to `FunctorSet::Value(Empty)` recursively in a type.
712 ///
713 /// The lowerer may produce parametric functor sets for arrow-typed inputs. The synthetic
714 /// Call uses concrete types to satisfy post-mono invariants without requiring actual
715 /// monomorphization specialization of the pinned target.
716 fn resolve_functor_params(ty: &qsc_fir::ty::Ty) -> qsc_fir::ty::Ty {
717 match ty {
718 qsc_fir::ty::Ty::Arrow(arrow) => {
719 let functors = match arrow.functors {
720 qsc_fir::ty::FunctorSet::Param(_) | qsc_fir::ty::FunctorSet::Infer(_) => {
721 qsc_fir::ty::FunctorSet::Value(qsc_fir::ty::FunctorSetValue::Empty)
722 }
723 other @ qsc_fir::ty::FunctorSet::Value(_) => other,
724 };
725 qsc_fir::ty::Ty::Arrow(Box::new(qsc_fir::ty::Arrow {
726 kind: arrow.kind,
727 input: Box::new(resolve_functor_params(&arrow.input)),
728 output: Box::new(resolve_functor_params(&arrow.output)),
729 functors,
730 }))
731 }
732 qsc_fir::ty::Ty::Tuple(elems) => {
733 qsc_fir::ty::Ty::Tuple(elems.iter().map(resolve_functor_params).collect())
734 }
735 qsc_fir::ty::Ty::Array(inner) => {
736 qsc_fir::ty::Ty::Array(Box::new(resolve_functor_params(inner)))
737 }
738 other => other.clone(),
739 }
740 }
741
742 /// Builds concrete generic args from a callable's generic parameter list.
743 ///
744 /// For each `TypeParameter::Functor`, produces `GenericArg::Functor(Value(Empty))`.
745 /// For each `TypeParameter::Ty`, produces `GenericArg::Ty(Tuple([]))` (unit).
746 /// These concrete args let monomorphization create a fully resolved specialization.
747 fn build_concrete_generic_args(
748 generics: &[qsc_fir::ty::TypeParameter],
749 ) -> Vec<qsc_fir::ty::GenericArg> {
750 generics
751 .iter()
752 .map(|param| match param {
753 qsc_fir::ty::TypeParameter::Functor(_) => qsc_fir::ty::GenericArg::Functor(
754 qsc_fir::ty::FunctorSet::Value(qsc_fir::ty::FunctorSetValue::Empty),
755 ),
756 qsc_fir::ty::TypeParameter::Ty { .. } => {
757 qsc_fir::ty::GenericArg::Ty(qsc_fir::ty::Ty::Tuple(Vec::new()))
758 }
759 })
760 .collect()
761 }
762
763 /// Extracts the specialized target callable from the entry Call expression after pipeline.
764 ///
765 /// After defunctionalization, the entry Call's callee Var references the specialized
766 /// (post-defunc) version of the target callable. This function extracts that ID.
767 #[allow(dead_code)]
768 fn extract_target_from_entry_call(
769 fir_store: &qsc_fir::fir::PackageStore,
770 fir_package_id: qsc_fir::fir::PackageId,
771 ) -> qsc_fir::fir::StoreItemId {
772 let package = fir_store.get(fir_package_id);
773 let entry_id = package
774 .entry
775 .expect("package must have entry after pipeline");
776 let entry_expr = package.exprs.get(entry_id).expect("entry expr must exist");
777
778 let qsc_fir::fir::ExprKind::Call(callee_id, _) = &entry_expr.kind else {
779 panic!(
780 "entry expression must be a Call after pipeline, found {:?}",
781 entry_expr.kind
782 );
783 };
784
785 let callee_expr = package
786 .exprs
787 .get(*callee_id)
788 .expect("callee expr must exist");
789 let qsc_fir::fir::ExprKind::Var(qsc_fir::fir::Res::Item(item_id), _) = &callee_expr.kind
790 else {
791 panic!(
792 "entry Call callee must be a Var(Res::Item(...)) after pipeline, found {:?}",
793 callee_expr.kind
794 );
795 };
796
797 qsc_fir::fir::StoreItemId {
798 package: item_id.package,
799 item: item_id.item,
800 }
801 }
802
803 /// Lowers an interpreter `Value` into a FIR expression for the synthetic entry.
804 ///
805 /// Scalar values become literals, aggregate values are lowered recursively, and
806 /// callable values are represented by global or closure variables with their
807 /// runtime functor application preserved.
808 #[allow(clippy::too_many_lines)]
809 fn lower_value_to_expr(
810 package: &mut qsc_fir::fir::Package,
811 assigner: &mut qsc_fir::assigner::Assigner,
812 value: &Value,
813 callable_types: &rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty>,
814 ) -> qsc_fir::fir::ExprId {
815 let (kind, ty) = match value {
816 Value::Int(n) => (
817 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Int(*n)),
818 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Int),
819 ),
820 Value::Double(d) => (
821 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Double(*d)),
822 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Double),
823 ),
824 Value::Bool(b) => (
825 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Bool(*b)),
826 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Bool),
827 ),
828 Value::BigInt(b) => (
829 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::BigInt(b.clone())),
830 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::BigInt),
831 ),
832 Value::Pauli(p) => (
833 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Pauli(*p)),
834 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Pauli),
835 ),
836 Value::Result(qsc_eval::val::Result::Val(b)) => (
837 qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Result(if *b {
838 qsc_fir::fir::Result::One
839 } else {
840 qsc_fir::fir::Result::Zero
841 })),
842 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Result),
843 ),
844 Value::String(s) => (
845 qsc_fir::fir::ExprKind::String(vec![qsc_fir::fir::StringComponent::Lit(s.clone())]),
846 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::String),
847 ),
848 Value::Tuple(vs, _) => {
849 let mut lowered_ids = Vec::with_capacity(vs.len());
850 let mut lowered_tys = Vec::with_capacity(vs.len());
851 for v in vs.iter() {
852 let id = lower_value_to_expr(package, assigner, v, callable_types);
853 lowered_tys.push(package.exprs.get(id).expect("just inserted").ty.clone());
854 lowered_ids.push(id);
855 }
856 (
857 qsc_fir::fir::ExprKind::Tuple(lowered_ids),
858 qsc_fir::ty::Ty::Tuple(lowered_tys),
859 )
860 }
861 Value::Array(vs) => {
862 let mut lowered_ids = Vec::with_capacity(vs.len());
863 for v in vs.iter() {
864 lowered_ids.push(lower_value_to_expr(package, assigner, v, callable_types));
865 }
866 let elem_ty = lowered_ids.first().map_or(qsc_fir::ty::Ty::Err, |id| {
867 package.exprs.get(*id).expect("just inserted").ty.clone()
868 });
869 (
870 qsc_fir::fir::ExprKind::Array(lowered_ids),
871 qsc_fir::ty::Ty::Array(Box::new(elem_ty)),
872 )
873 }
874 Value::Range(r) => {
875 let lower_opt = |opt: Option<i64>,
876 pkg: &mut qsc_fir::fir::Package,
877 a: &mut qsc_fir::assigner::Assigner|
878 -> Option<qsc_fir::fir::ExprId> {
879 opt.map(|n| {
880 let id = a.next_expr();
881 pkg.exprs.insert(
882 id,
883 qsc_fir::fir::Expr {
884 id,
885 span: qsc_data_structures::span::Span::default(),
886 ty: qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Int),
887 kind: qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Int(n)),
888 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
889 ..qsc_fir::fir::ExecGraphIdx::ZERO,
890 },
891 );
892 id
893 })
894 };
895 let start = lower_opt(r.start, package, assigner);
896 let step = lower_opt(Some(r.step), package, assigner);
897 let end = lower_opt(r.end, package, assigner);
898 (
899 qsc_fir::fir::ExprKind::Range(start, step, end),
900 qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Range),
901 )
902 }
903 Value::Global(id, functor) => {
904 return lower_global_to_expr(package, assigner, *id, *functor, callable_types);
905 }
906 Value::Closure(c) => {
907 return lower_closure_to_expr(package, assigner, c, callable_types);
908 }
909 _ => panic!("cannot lower {value:?} to FIR expression"),
910 };
911
912 let expr_id = assigner.next_expr();
913 package.exprs.insert(
914 expr_id,
915 qsc_fir::fir::Expr {
916 id: expr_id,
917 span: qsc_data_structures::span::Span::default(),
918 ty,
919 kind,
920 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
921 ..qsc_fir::fir::ExecGraphIdx::ZERO,
922 },
923 );
924 expr_id
925 }
926
927 /// Lowers a global callable value to a FIR variable expression.
928 ///
929 /// The callable's stored `FunctorApp` is applied as FIR functor wrappers so
930 /// adjoint and controlled runtime values survive the synthetic entry path.
931 fn lower_global_to_expr(
932 package: &mut qsc_fir::fir::Package,
933 assigner: &mut qsc_fir::assigner::Assigner,
934 id: qsc_fir::fir::StoreItemId,
935 functor: FunctorApp,
936 callable_types: &rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty>,
937 ) -> qsc_fir::fir::ExprId {
938 let ty = callable_types
939 .get(&id)
940 .expect("Global callable type must be pre-computed")
941 .clone();
942 let expr_id = assigner.next_expr();
943 package.exprs.insert(
944 expr_id,
945 qsc_fir::fir::Expr {
946 id: expr_id,
947 span: qsc_data_structures::span::Span::default(),
948 ty: ty.clone(),
949 kind: qsc_fir::fir::ExprKind::Var(
950 qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
951 package: id.package,
952 item: id.item,
953 }),
954 Vec::new(),
955 ),
956 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
957 ..qsc_fir::fir::ExecGraphIdx::ZERO,
958 },
959 );
960 wrap_expr_with_functor_app(package, assigner, expr_id, &ty, functor)
961 }
962
963 /// Wraps a callable expression with the FIR functor operations in `functor`.
964 ///
965 /// Adjoint is applied before each controlled application to match the runtime
966 /// `FunctorApp` representation used by interpreter values.
967 fn wrap_expr_with_functor_app(
968 package: &mut qsc_fir::fir::Package,
969 assigner: &mut qsc_fir::assigner::Assigner,
970 expr_id: qsc_fir::fir::ExprId,
971 ty: &qsc_fir::ty::Ty,
972 functor: FunctorApp,
973 ) -> qsc_fir::fir::ExprId {
974 let mut current_id = expr_id;
975 if functor.adjoint {
976 current_id = wrap_expr_with_functor(
977 package,
978 assigner,
979 current_id,
980 ty,
981 qsc_fir::fir::Functor::Adj,
982 );
983 }
984 for _ in 0..functor.controlled {
985 current_id = wrap_expr_with_functor(
986 package,
987 assigner,
988 current_id,
989 ty,
990 qsc_fir::fir::Functor::Ctl,
991 );
992 }
993 current_id
994 }
995
996 /// Creates a FIR unary functor expression around an existing callable expression.
997 fn wrap_expr_with_functor(
998 package: &mut qsc_fir::fir::Package,
999 assigner: &mut qsc_fir::assigner::Assigner,
1000 inner_id: qsc_fir::fir::ExprId,
1001 ty: &qsc_fir::ty::Ty,
1002 functor: qsc_fir::fir::Functor,
1003 ) -> qsc_fir::fir::ExprId {
1004 let expr_id = assigner.next_expr();
1005 package.exprs.insert(
1006 expr_id,
1007 qsc_fir::fir::Expr {
1008 id: expr_id,
1009 span: qsc_data_structures::span::Span::default(),
1010 ty: ty.clone(),
1011 kind: qsc_fir::fir::ExprKind::UnOp(qsc_fir::fir::UnOp::Functor(functor), inner_id),
1012 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
1013 ..qsc_fir::fir::ExecGraphIdx::ZERO,
1014 },
1015 );
1016 expr_id
1017 }
1018
1019 /// Lowers a captureless closure to its underlying callable variable expression.
1020 ///
1021 /// Capturing closures take the pinned fallback path before this is called, so
1022 /// this helper only has to preserve the closure target and runtime functor app.
1023 fn lower_closure_to_expr(
1024 package: &mut qsc_fir::fir::Package,
1025 assigner: &mut qsc_fir::assigner::Assigner,
1026 closure: &qsc_eval::val::Closure,
1027 callable_types: &rustc_hash::FxHashMap<qsc_fir::fir::StoreItemId, qsc_fir::ty::Ty>,
1028 ) -> qsc_fir::fir::ExprId {
1029 // For the synthetic entry, we emit a Var referencing the closure's underlying
1030 // callable. Captures are irrelevant for pipeline reachability — defunc handles
1031 // specialization. Both captureless and capturing closures use the same Var form.
1032 let ty = callable_types
1033 .get(&closure.id)
1034 .expect("Closure callable type must be pre-computed")
1035 .clone();
1036 let kind = qsc_fir::fir::ExprKind::Var(
1037 qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
1038 package: closure.id.package,
1039 item: closure.id.item,
1040 }),
1041 Vec::new(),
1042 );
1043
1044 let expr_id = assigner.next_expr();
1045 package.exprs.insert(
1046 expr_id,
1047 qsc_fir::fir::Expr {
1048 id: expr_id,
1049 span: qsc_data_structures::span::Span::default(),
1050 ty: ty.clone(),
1051 kind,
1052 exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
1053 ..qsc_fir::fir::ExecGraphIdx::ZERO,
1054 },
1055 );
1056 wrap_expr_with_functor_app(package, assigner, expr_id, &ty, closure.functor)
1057 }
1058
1059 fn collect_concrete_qsharp_callables(
1060 value: &Value,
1061 callables: &mut FxHashSet<qsc_fir::fir::StoreItemId>,
1062 ) {
1063 match value {
1064 Value::Array(values) => values
1065 .iter()
1066 .for_each(|value| collect_concrete_qsharp_callables(value, callables)),
1067 Value::Closure(closure) => {
1068 if !callables.contains(&closure.id) {
1069 callables.insert(closure.id);
1070 }
1071 closure
1072 .fixed_args
1073 .iter()
1074 .for_each(|value| collect_concrete_qsharp_callables(value, callables));
1075 }
1076 Value::Global(store_item_id, _) => {
1077 if !callables.contains(store_item_id) {
1078 callables.insert(*store_item_id);
1079 }
1080 }
1081 Value::Tuple(values, _) => values
1082 .iter()
1083 .for_each(|value| collect_concrete_qsharp_callables(value, callables)),
1084 Value::BigInt(_)
1085 | Value::Bool(_)
1086 | Value::Double(_)
1087 | Value::Int(_)
1088 | Value::Pauli(_)
1089 | Value::Qubit(_)
1090 | Value::Range(_)
1091 | Value::Result(_)
1092 | Value::String(_)
1093 | Value::Var(_) => {}
1094 }
1095 }
1096
1097 /// Prepares codegen FIR when a callable is invoked with concrete argument values.
1098 ///
1099 /// Uses a synthetic `Call(Var(target), args)` entry expression when callable args
1100 /// can be represented as FIR values, making the target and args entry-reachable for full
1101 /// pipeline participation. Falls back to a pin-based approach when:
1102 /// - Args contain closures with captures (partial applications require capture context
1103 /// that can't be represented in the synthetic Call)
1104 ///
1105 /// The original target is pinned for DCE survival so that `fir_to_qir_from_callable`
1106 /// can still use the original ID for partial evaluation.
1107 pub fn prepare_codegen_fir_from_callable_args(
1108 package_store: &PackageStore,
1109 callable: qsc_hir::hir::ItemId,
1110 args: &Value,
1111 capabilities: TargetCapabilityFlags,
1112 ) -> Result<CodegenFir, Vec<Error>> {
1113 let mut concrete_callables = FxHashSet::default();
1114 collect_concrete_qsharp_callables(args, &mut concrete_callables);
1115
1116 if concrete_callables.is_empty() {
1117 return prepare_codegen_fir_from_callable(package_store, callable, capabilities);
1118 }
1119
1120 // Closures with captures represent partial applications whose capture context
1121 // can't be lowered into a synthetic Call expression yet. They still use the
1122 // pin-based approach where partial eval handles specialization at QIR generation time.
1123 if has_closure_with_captures(args) {
1124 return prepare_codegen_fir_from_callable_args_pinned(
1125 package_store,
1126 callable,
1127 args,
1128 capabilities,
1129 concrete_callables,
1130 );
1131 }
1132
1133 let (mut fir_store, fir_package_id, _assigner) =
1134 lower_to_fir(package_store, callable.package, None);
1135
1136 let target_callable = qsc_fir::fir::StoreItemId {
1137 package: qsc_lowerer::map_hir_package_to_fir(callable.package),
1138 item: qsc_lowerer::map_hir_local_item_to_fir(callable.item),
1139 };
1140
1141 // Pre-compute callable type map (immutable store access) before mutating.
1142 let callable_types = build_callable_type_map(&fir_store, &concrete_callables);
1143
1144 // Build synthetic Call(Var(target), args) as the entry expression.
1145 // This makes the target and all callable args entry-reachable for pipeline transforms.
1146 seed_entry_with_call_to_target(
1147 &mut fir_store,
1148 fir_package_id,
1149 target_callable,
1150 args,
1151 &callable_types,
1152 );
1153
1154 // Pin the original target for DCE survival. After defunc rewrites the entry
1155 // Call callee to reference the specialized version, the original target becomes
1156 // unreachable. Pinning keeps it alive for `fir_to_qir_from_callable` which
1157 // uses the original ID with original-shaped args.
1158 run_codegen_pipeline_to(
1159 package_store,
1160 callable.package,
1161 &mut fir_store,
1162 fir_package_id,
1163 qsc_fir_transforms::PipelineStage::Full,
1164 &[target_callable],
1165 )?;
1166 let compute_properties = qsc_rca::Analyzer::init(&fir_store, capabilities).analyze_all();
1167 validate_callable_capabilities(
1168 package_store,
1169 &fir_store,
1170 &compute_properties,
1171 target_callable,
1172 capabilities,
1173 )?;
1174
1175 Ok(CodegenFir {
1176 fir_store,
1177 fir_package_id,
1178 compute_properties,
1179 })
1180 }
1181
1182 /// Pin-based fallback for callable args containing closures with captures.
1183 ///
1184 /// Seeds concrete (non-arrow-input) callables into the entry for reachability,
1185 /// pins arrow-input callables and the target for DCE survival, and lets
1186 /// `fir_to_qir_from_callable` handle specialization at QIR generation time.
1187 fn prepare_codegen_fir_from_callable_args_pinned(
1188 package_store: &PackageStore,
1189 callable: qsc_hir::hir::ItemId,
1190 _args: &Value,
1191 capabilities: TargetCapabilityFlags,
1192 mut concrete_callables: FxHashSet<qsc_fir::fir::StoreItemId>,
1193 ) -> Result<CodegenFir, Vec<Error>> {
1194 let (mut fir_store, fir_package_id, _assigner) =
1195 lower_to_fir(package_store, callable.package, None);
1196
1197 let mut pinned_callables: Vec<qsc_fir::fir::StoreItemId> = Vec::new();
1198 concrete_callables.retain(|store_item_id| {
1199 let hir_item_id = qsc_hir::hir::ItemId {
1200 package: qsc_lowerer::map_fir_package_to_hir(store_item_id.package),
1201 item: qsc_lowerer::map_fir_local_item_to_hir(store_item_id.item),
1202 };
1203 if callable_has_arrow_input(&fir_store, hir_item_id) {
1204 pinned_callables.push(*store_item_id);
1205 false
1206 } else {
1207 true
1208 }
1209 });
1210
1211 let target_callable = qsc_fir::fir::StoreItemId {
1212 package: qsc_lowerer::map_hir_package_to_fir(callable.package),
1213 item: qsc_lowerer::map_hir_local_item_to_fir(callable.item),
1214 };
1215
1216 seed_entry_with_callables(&mut fir_store, fir_package_id, &concrete_callables);
1217 pinned_callables.push(target_callable);
1218 run_codegen_pipeline_to(
1219 package_store,
1220 callable.package,
1221 &mut fir_store,
1222 fir_package_id,
1223 qsc_fir_transforms::PipelineStage::Full,
1224 &pinned_callables,
1225 )?;
1226 let compute_properties = qsc_rca::Analyzer::init(&fir_store, capabilities).analyze_all();
1227 validate_callable_capabilities(
1228 package_store,
1229 &fir_store,
1230 &compute_properties,
1231 target_callable,
1232 capabilities,
1233 )?;
1234
1235 Ok(CodegenFir {
1236 fir_store,
1237 fir_package_id,
1238 compute_properties,
1239 })
1240 }
1241
1242 /// Returns `true` if the value tree contains any closures with captures.
1243 fn has_closure_with_captures(value: &Value) -> bool {
1244 match value {
1245 Value::Closure(c) => !c.fixed_args.is_empty(),
1246 Value::Tuple(vs, _) => vs.iter().any(has_closure_with_captures),
1247 Value::Array(vs) => vs.iter().any(has_closure_with_captures),
1248 _ => false,
1249 }
1250 }
1251
1252 fn prepare_codegen_fir_inner(
1253 package_store: &PackageStore,
1254 package_id: qsc_hir::hir::PackageId,
1255 package_override: Option<&qsc_hir::hir::Package>,
1256 capabilities: TargetCapabilityFlags,
1257 ) -> Result<CodegenFir, Vec<Error>> {
1258 let (fir_store, fir_package_id, _) =
1259 lower_to_fir(package_store, package_id, package_override);
1260
1261 prepare_codegen_fir_from_lowered_store(
1262 package_store,
1263 package_id,
1264 fir_store,
1265 fir_package_id,
1266 capabilities,
1267 )
1268 }
1269
1270 fn prepare_codegen_fir_from_lowered_store(
1271 package_store: &PackageStore,
1272 package_id: qsc_hir::hir::PackageId,
1273 mut fir_store: qsc_fir::fir::PackageStore,
1274 fir_package_id: qsc_fir::fir::PackageId,
1275 capabilities: TargetCapabilityFlags,
1276 ) -> Result<CodegenFir, Vec<Error>> {
1277 run_codegen_pipeline(package_store, package_id, &mut fir_store, fir_package_id)?;
1278
1279 let compute_properties =
1280 PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities)
1281 .map_err(|errors| map_pass_errors(package_store, package_id, errors))?;
1282
1283 Ok(CodegenFir {
1284 fir_store,
1285 fir_package_id,
1286 compute_properties,
1287 })
1288 }
1289
1290 pub fn prepare_codegen_fir(
1291 package_store: &PackageStore,
1292 package_id: qsc_hir::hir::PackageId,
1293 capabilities: TargetCapabilityFlags,
1294 ) -> Result<CodegenFir, Vec<Error>> {
1295 prepare_codegen_fir_inner(package_store, package_id, None, capabilities)
1296 }
1297
1298 pub fn prepare_codegen_fir_from_fir_store(
1299 package_store: &PackageStore,
1300 package_id: qsc_hir::hir::PackageId,
1301 fir_store: &qsc_fir::fir::PackageStore,
1302 fir_package_id: qsc_fir::fir::PackageId,
1303 capabilities: TargetCapabilityFlags,
1304 ) -> Result<CodegenFir, Vec<Error>> {
1305 prepare_codegen_fir_from_lowered_store(
1306 package_store,
1307 package_id,
1308 clone_fir_store(fir_store),
1309 fir_package_id,
1310 capabilities,
1311 )
1312 }
1313
1314 /// Prepares codegen FIR for a single callable without inline arguments.
1315 ///
1316 /// Used when a callable is referenced but its concrete argument values are not yet known.
1317 /// For callables with arrow-typed inputs, skips the full pipeline to preserve abstract
1318 /// higher-order structure that will be specialized later via `prepare_codegen_fir_from_callable_args`.
1319 pub fn prepare_codegen_fir_from_callable(
1320 package_store: &PackageStore,
1321 callable: qsc_hir::hir::ItemId,
1322 capabilities: TargetCapabilityFlags,
1323 ) -> Result<CodegenFir, Vec<Error>> {
1324 let (mut fir_store, fir_package_id, mut assigner) =
1325 lower_to_fir(package_store, callable.package, None);
1326
1327 if callable_has_arrow_input(&fir_store, callable) {
1328 // Callable-based codegen receives the concrete callable arguments later through
1329 // partially_evaluate_call. Running the FIR transform pipeline from a bare callable
1330 // reference loses that higher-order call-site information and can leave functor-
1331 // parameterized arrow types unspecialized.
1332 return Ok(CodegenFir {
1333 compute_properties: qsc_rca::Analyzer::init(&fir_store, capabilities).analyze_all(),
1334 fir_store,
1335 fir_package_id,
1336 });
1337 }
1338
1339 seed_entry_with_callable(&mut fir_store, fir_package_id, callable, &mut assigner);
1340 run_codegen_pipeline(
1341 package_store,
1342 callable.package,
1343 &mut fir_store,
1344 fir_package_id,
1345 )?;
1346
1347 let compute_properties = qsc_rca::Analyzer::init(&fir_store, capabilities).analyze_all();
1348 validate_callable_capabilities(
1349 package_store,
1350 &fir_store,
1351 &compute_properties,
1352 qsc_fir::fir::StoreItemId {
1353 package: qsc_lowerer::map_hir_package_to_fir(callable.package),
1354 item: qsc_lowerer::map_hir_local_item_to_fir(callable.item),
1355 },
1356 capabilities,
1357 )?;
1358
1359 Ok(CodegenFir {
1360 fir_store,
1361 fir_package_id,
1362 compute_properties,
1363 })
1364 }
1365
1366 fn compile_to_codegen_fir(
1367 sources: SourceMap,
1368 language_features: LanguageFeatures,
1369 capabilities: TargetCapabilityFlags,
1370 package_store: &mut PackageStore,
1371 dependencies: &Dependencies,
1372 ) -> Result<(qsc_hir::hir::PackageId, CodegenFir), Vec<Error>> {
1373 if capabilities == TargetCapabilityFlags::all() {
1374 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1375 }
1376
1377 let (unit, errors) = crate::compile::compile(
1378 package_store,
1379 dependencies,
1380 sources,
1381 PackageType::Exe,
1382 capabilities,
1383 language_features,
1384 );
1385 if !errors.is_empty() {
1386 return Err(errors.iter().map(|e| Error::Compile(e.clone())).collect());
1387 }
1388
1389 let package_id = package_store.insert(unit);
1390 let prepared_fir = prepare_codegen_fir(package_store, package_id, capabilities)?;
1391 Ok((package_id, prepared_fir))
1392 }
1393
1394 pub fn get_qir_from_ast(
1395 store: &mut PackageStore,
1396 dependencies: &Dependencies,
1397 ast_package: qsc_ast::ast::Package,
1398 sources: SourceMap,
1399 capabilities: TargetCapabilityFlags,
1400 ) -> Result<String, Vec<Error>> {
1401 if capabilities == TargetCapabilityFlags::all() {
1402 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1403 }
1404
1405 let (unit, errors) = crate::compile::compile_ast(
1406 store,
1407 dependencies,
1408 ast_package,
1409 sources,
1410 PackageType::Exe,
1411 capabilities,
1412 );
1413
1414 // Ensure it compiles before trying to add it to the store.
1415 if !errors.is_empty() {
1416 return Err(errors.iter().map(|e| Error::Compile(e.clone())).collect());
1417 }
1418
1419 let package_id = store.insert(unit);
1420 let prepared_fir = prepare_codegen_fir(store, package_id, capabilities)?;
1421 let entry = entry_from_codegen_fir(&prepared_fir);
1422 let CodegenFir {
1423 fir_store,
1424 compute_properties,
1425 ..
1426 } = prepared_fir;
1427
1428 fir_to_qir(&fir_store, capabilities, &compute_properties, &entry).map_err(|e| {
1429 let source_package_id = match e.span() {
1430 Some(span) => span.package,
1431 None => package_id,
1432 };
1433 let source_package = store
1434 .get(source_package_id)
1435 .expect("package should be in store");
1436 vec![Error::PartialEvaluation(WithSource::from_map(
1437 &source_package.sources,
1438 e,
1439 ))]
1440 })
1441 }
1442
1443 pub fn get_rir(
1444 sources: SourceMap,
1445 language_features: LanguageFeatures,
1446 capabilities: TargetCapabilityFlags,
1447 mut package_store: PackageStore,
1448 dependencies: &Dependencies,
1449 ) -> Result<Vec<String>, Vec<Error>> {
1450 let (package_id, prepared_fir) = compile_to_codegen_fir(
1451 sources,
1452 language_features,
1453 capabilities,
1454 &mut package_store,
1455 dependencies,
1456 )?;
1457 let entry = entry_from_codegen_fir(&prepared_fir);
1458 let CodegenFir {
1459 fir_store,
1460 compute_properties,
1461 ..
1462 } = prepared_fir;
1463
1464 let (raw, ssa) = fir_to_rir(
1465 &fir_store,
1466 capabilities,
1467 &compute_properties,
1468 &entry,
1469 PartialEvalConfig {
1470 generate_debug_metadata: true,
1471 },
1472 )
1473 .map_err(|e| {
1474 let source_package_id = match e.span() {
1475 Some(span) => span.package,
1476 None => package_id,
1477 };
1478 let source_package = package_store
1479 .get(source_package_id)
1480 .expect("package should be in store");
1481 vec![Error::PartialEvaluation(WithSource::from_map(
1482 &source_package.sources,
1483 e,
1484 ))]
1485 })?;
1486 Ok(vec![raw.to_string(), ssa.to_string()])
1487 }
1488
1489 pub fn get_qir(
1490 sources: SourceMap,
1491 language_features: LanguageFeatures,
1492 capabilities: TargetCapabilityFlags,
1493 mut package_store: PackageStore,
1494 dependencies: &Dependencies,
1495 ) -> Result<String, Vec<Error>> {
1496 let (package_id, prepared_fir) = compile_to_codegen_fir(
1497 sources,
1498 language_features,
1499 capabilities,
1500 &mut package_store,
1501 dependencies,
1502 )?;
1503 let entry = entry_from_codegen_fir(&prepared_fir);
1504 let CodegenFir {
1505 fir_store,
1506 compute_properties,
1507 ..
1508 } = prepared_fir;
1509
1510 fir_to_qir(&fir_store, capabilities, &compute_properties, &entry).map_err(|e| {
1511 let source_package_id = match e.span() {
1512 Some(span) => span.package,
1513 None => package_id,
1514 };
1515 let source_package = package_store
1516 .get(source_package_id)
1517 .expect("package should be in store");
1518 vec![Error::PartialEvaluation(WithSource::from_map(
1519 &source_package.sources,
1520 e,
1521 ))]
1522 })
1523 }
1524}
1525