1use crate::extension::ExtensionRegistry;
3use crate::hugr::internal::HugrInternals;
4use crate::types::type_param::Term;
5use crate::{
6 Direction, Hugr, HugrView, IncomingPort, Node, NodeIndex as _, Port,
7 extension::{ExtensionId, OpDef, SignatureFunc},
8 hugr::IdentList,
9 ops::{
10 DataflowBlock, DataflowOpTrait, OpName, OpTrait, OpType, Value, constant::CustomSerialized,
11 },
12 std_extensions::{
13 arithmetic::{float_types::ConstF64, int_types::ConstInt},
14 collections::array::ArrayValue,
15 },
16 types::{
17 CustomType, EdgeKind, FuncTypeBase, MaybeRV, PolyFuncTypeBase, RowVariable, SumType,
18 TypeBase, TypeBound, TypeEnum, type_param::TermVar, type_row::TypeRowBase,
19 },
20};
21
22use fxhash::{FxBuildHasher, FxHashMap};
23use hugr_model::v0::Visibility;
24use hugr_model::v0::{
25 self as model,
26 bumpalo::{Bump, collections::String as BumpString, collections::Vec as BumpVec},
27 table,
28};
29use petgraph::unionfind::UnionFind;
30use std::fmt::Write;
31
32pub fn export_package<'a, 'h: 'a>(
34 hugrs: impl IntoIterator<Item = &'h Hugr>,
35 _extensions: &ExtensionRegistry,
36 bump: &'a Bump,
37) -> table::Package<'a> {
38 let modules = hugrs
39 .into_iter()
40 .map(|module| export_hugr(module, bump))
41 .collect();
42 table::Package { modules }
43}
44
45pub fn export_hugr<'a>(hugr: &'a Hugr, bump: &'a Bump) -> table::Module<'a> {
47 let mut ctx = Context::new(hugr, bump);
48 ctx.export_root();
49 ctx.module
50}
51
52struct Context<'a> {
54 hugr: &'a Hugr,
56 module: table::Module<'a>,
58 bump: &'a Bump,
60 term_map: FxHashMap<table::Term<'a>, table::TermId>,
62
63 local_scope: Option<table::NodeId>,
68
69 local_constraints: Vec<table::TermId>,
76
77 decl_operations: FxHashMap<(ExtensionId, OpName), table::NodeId>,
79
80 links: Links,
82
83 symbols: model::scope::SymbolTable<'a>,
85
86 implicit_imports: FxHashMap<&'a str, table::NodeId>,
88
89 node_to_id: FxHashMap<Node, table::NodeId>,
91
92 id_to_node: FxHashMap<table::NodeId, Node>,
94 }
97
98const NO_VIS: Option<Visibility> = None;
99
100impl<'a> Context<'a> {
101 pub fn new(hugr: &'a Hugr, bump: &'a Bump) -> Self {
102 let mut module = table::Module::default();
103 module.nodes.reserve(hugr.num_nodes());
104 let links = Links::new(hugr);
105
106 Self {
107 hugr,
108 module,
109 bump,
110 links,
111 term_map: FxHashMap::default(),
112 local_scope: None,
113 decl_operations: FxHashMap::default(),
114 local_constraints: Vec::new(),
115 symbols: model::scope::SymbolTable::default(),
116 implicit_imports: FxHashMap::default(),
117 node_to_id: FxHashMap::default(),
118 id_to_node: FxHashMap::default(),
119 }
120 }
121
122 pub fn export_root(&mut self) {
124 self.module.root = self.module.insert_region(table::Region::default());
125 self.symbols.enter(self.module.root);
126 self.links.enter(self.module.root);
127
128 let hugr_children = self.hugr.children(self.hugr.module_root());
129 let mut children = Vec::with_capacity(hugr_children.size_hint().0);
130
131 for child in hugr_children.clone() {
132 if let Some(child_id) = self.export_node_shallow(child) {
133 children.push(child_id);
134 }
135 }
136
137 for child in &children {
138 self.export_node_deep(*child);
139 }
140
141 let mut all_children = BumpVec::with_capacity_in(
142 children.len() + self.decl_operations.len() + self.implicit_imports.len(),
143 self.bump,
144 );
145
146 all_children.extend(self.implicit_imports.drain().map(|(_, id)| id));
147 all_children.extend(self.decl_operations.values().copied());
148 all_children.extend(children);
149
150 let mut meta = Vec::new();
151 self.export_node_json_metadata(self.hugr.module_root(), &mut meta);
152
153 let (links, ports) = self.links.exit();
154 self.symbols.exit();
155
156 self.module.regions[self.module.root.index()] = table::Region {
157 kind: model::RegionKind::Module,
158 sources: &[],
159 targets: &[],
160 children: all_children.into_bump_slice(),
161 meta: self.bump.alloc_slice_copy(&meta),
162 signature: None,
163 scope: Some(table::RegionScope { links, ports }),
164 };
165 }
166
167 pub fn make_ports(
168 &mut self,
169 node: Node,
170 direction: Direction,
171 num_ports: usize,
172 ) -> &'a [table::LinkIndex] {
173 let ports = self.hugr.node_ports(node, direction);
174 let mut links = BumpVec::with_capacity_in(ports.size_hint().0, self.bump);
175
176 for port in ports.take(num_ports) {
177 links.push(self.links.use_link(node, port));
178 }
179
180 links.into_bump_slice()
181 }
182
183 pub fn make_term(&mut self, term: table::Term<'a>) -> table::TermId {
184 if term == table::Term::Wildcard {
186 return table::TermId::default();
187 }
188
189 let term = match term {
191 table::Term::Apply(symbol, args) => {
192 let prefix = args.iter().take_while(|arg| !arg.is_valid()).count();
193 table::Term::Apply(symbol, &args[prefix..])
194 }
195 term => term,
196 };
197
198 *self
199 .term_map
200 .entry(term.clone())
201 .or_insert_with(|| self.module.insert_term(term))
202 }
203
204 pub fn make_qualified_name(
205 &mut self,
206 extension: &ExtensionId,
207 name: impl AsRef<str>,
208 ) -> &'a str {
209 let capacity = extension.len() + name.as_ref().len() + 1;
210 let mut output = BumpString::with_capacity_in(capacity, self.bump);
211 let _ = write!(&mut output, "{}.{}", extension, name.as_ref());
212 output.into_bump_str()
213 }
214
215 pub fn make_named_global_ref(
216 &mut self,
217 extension: &IdentList,
218 name: impl AsRef<str>,
219 ) -> table::NodeId {
220 let symbol = self.make_qualified_name(extension, name);
221 self.resolve_symbol(symbol)
222 }
223
224 fn connected_function(&self, node: Node) -> Option<Node> {
227 let func_node = self.hugr.static_source(node)?;
228
229 match self.hugr.get_optype(func_node) {
230 OpType::FuncDecl(_) => Some(func_node),
231 OpType::FuncDefn(_) => Some(func_node),
232 _ => None,
233 }
234 }
235
236 fn with_local_scope<T>(&mut self, node: table::NodeId, f: impl FnOnce(&mut Self) -> T) -> T {
237 let prev_local_scope = self.local_scope.replace(node);
238 let prev_local_constraints = std::mem::take(&mut self.local_constraints);
239 let result = f(self);
240 self.local_scope = prev_local_scope;
241 self.local_constraints = prev_local_constraints;
242 result
243 }
244
245 fn export_node_shallow(&mut self, node: Node) -> Option<table::NodeId> {
246 let optype = self.hugr.get_optype(node);
247
248 if let OpType::Const(_)
250 | OpType::Input(_)
251 | OpType::Output(_)
252 | OpType::ExitBlock(_)
253 | OpType::Case(_) = optype
254 {
255 return None;
256 }
257
258 let node_id = self.module.insert_node(table::Node::default());
259 self.node_to_id.insert(node, node_id);
260 self.id_to_node.insert(node_id, node);
261
262 let symbol = match optype {
264 OpType::FuncDefn(func_defn) => Some(func_defn.func_name().as_str()),
265 OpType::FuncDecl(func_decl) => Some(func_decl.func_name().as_str()),
266 OpType::AliasDecl(alias_decl) => Some(alias_decl.name.as_str()),
267 OpType::AliasDefn(alias_defn) => Some(alias_defn.name.as_str()),
268 _ => None,
269 };
270
271 if let Some(symbol) = symbol {
272 self.symbols
273 .insert(symbol, node_id)
274 .expect("duplicate symbol");
275 }
276
277 Some(node_id)
278 }
279
280 fn export_node_deep(&mut self, node_id: table::NodeId) {
281 let mut regions: &[_] = &[];
285
286 let node = self.id_to_node[&node_id];
287 let optype = self.hugr.get_optype(node);
288
289 let operation = match optype {
290 OpType::Module(_) => todo!("this should be an error"),
291
292 OpType::Input(_) => {
293 panic!("input nodes should have been handled by the region export")
294 }
295
296 OpType::Output(_) => {
297 panic!("output nodes should have been handled by the region export")
298 }
299
300 OpType::DFG(_) => {
301 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
302 node,
303 model::ScopeClosure::Open,
304 false,
305 )]);
306 table::Operation::Dfg
307 }
308
309 OpType::CFG(_) => {
310 regions = self
311 .bump
312 .alloc_slice_copy(&[self.export_cfg(node, model::ScopeClosure::Open)]);
313 table::Operation::Cfg
314 }
315
316 OpType::ExitBlock(_) => {
317 panic!("exit blocks should have been handled by the region export")
318 }
319
320 OpType::Case(_) => {
321 todo!("case nodes should have been handled by the region export")
322 }
323
324 OpType::DataflowBlock(_) => {
325 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
326 node,
327 model::ScopeClosure::Open,
328 false,
329 )]);
330 table::Operation::Block
331 }
332
333 OpType::FuncDefn(func) => self.with_local_scope(node_id, |this| {
334 let symbol = this.export_poly_func_type(
335 func.func_name(),
336 Some(func.visibility().clone().into()),
337 func.signature(),
338 );
339 regions = this.bump.alloc_slice_copy(&[this.export_dfg(
340 node,
341 model::ScopeClosure::Closed,
342 false,
343 )]);
344 table::Operation::DefineFunc(symbol)
345 }),
346
347 OpType::FuncDecl(func) => self.with_local_scope(node_id, |this| {
348 let symbol = this.export_poly_func_type(
349 func.func_name(),
350 Some(func.visibility().clone().into()),
351 func.signature(),
352 );
353 table::Operation::DeclareFunc(symbol)
354 }),
355
356 OpType::AliasDecl(alias) => self.with_local_scope(node_id, |this| {
357 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
359 let symbol = this.bump.alloc(table::Symbol {
360 visibility: &NO_VIS, name: &alias.name,
362 params: &[],
363 constraints: &[],
364 signature,
365 });
366 table::Operation::DeclareAlias(symbol)
367 }),
368
369 OpType::AliasDefn(alias) => self.with_local_scope(node_id, |this| {
370 let value = this.export_type(&alias.definition);
371 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
373 let symbol = this.bump.alloc(table::Symbol {
374 visibility: &NO_VIS, name: &alias.name,
376 params: &[],
377 constraints: &[],
378 signature,
379 });
380 table::Operation::DefineAlias(symbol, value)
381 }),
382
383 OpType::Call(call) => {
384 let node = self.connected_function(node).unwrap();
386 let symbol = self.node_to_id[&node];
387 let mut args = BumpVec::new_in(self.bump);
388 args.extend(call.type_args.iter().map(|arg| self.export_term(arg, None)));
389 let args = args.into_bump_slice();
390 let func = self.make_term(table::Term::Apply(symbol, args));
391
392 let signature = call.signature();
394 let inputs = self.export_type_row(&signature.input);
395 let outputs = self.export_type_row(&signature.output);
396 let operation = self.make_term_apply(model::CORE_CALL, &[inputs, outputs, func]);
397 table::Operation::Custom(operation)
398 }
399
400 OpType::LoadFunction(load) => {
401 let node = self.connected_function(node).unwrap();
402 let symbol = self.node_to_id[&node];
403 let mut args = BumpVec::new_in(self.bump);
404 args.extend(load.type_args.iter().map(|arg| self.export_term(arg, None)));
405 let args = args.into_bump_slice();
406 let func = self.make_term(table::Term::Apply(symbol, args));
407 let runtime_type = self.make_term(table::Term::Wildcard);
408 let operation = self.make_term_apply(model::CORE_LOAD_CONST, &[runtime_type, func]);
409 table::Operation::Custom(operation)
410 }
411
412 OpType::Const(_) => {
413 unreachable!("const nodes are filtered out by `export_node_shallow`")
414 }
415
416 OpType::LoadConstant(_) => {
417 let const_node = self.hugr.static_source(node).unwrap();
419 let const_node_op = self.hugr.get_optype(const_node);
420
421 let OpType::Const(const_node_data) = const_node_op else {
422 panic!("expected `LoadConstant` node to be connected to a `Const` node");
423 };
424
425 let runtime_type = self.make_term(table::Term::Wildcard);
428 let value = self.export_value(&const_node_data.value);
429 let operation =
430 self.make_term_apply(model::CORE_LOAD_CONST, &[runtime_type, value]);
431 table::Operation::Custom(operation)
432 }
433
434 OpType::CallIndirect(call) => {
435 let inputs = self.export_type_row(&call.signature.input);
436 let outputs = self.export_type_row(&call.signature.output);
437 let operation = self.make_term_apply(model::CORE_CALL_INDIRECT, &[inputs, outputs]);
438 table::Operation::Custom(operation)
439 }
440
441 OpType::Tag(tag) => {
442 let variants = self.make_term(table::Term::Wildcard);
443 let types = self.make_term(table::Term::Wildcard);
444 let tag = self.make_term(model::Literal::Nat(tag.tag as u64).into());
445 let operation = self.make_term_apply(model::CORE_MAKE_ADT, &[variants, types, tag]);
446 table::Operation::Custom(operation)
447 }
448
449 OpType::TailLoop(_) => {
450 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
451 node,
452 model::ScopeClosure::Open,
453 false,
454 )]);
455 table::Operation::TailLoop
456 }
457
458 OpType::Conditional(_) => {
459 regions = self.export_conditional_regions(node);
460 table::Operation::Conditional
461 }
462
463 OpType::ExtensionOp(op) => {
464 let node = self.export_opdef(op.def());
465 let params = self
466 .bump
467 .alloc_slice_fill_iter(op.args().iter().map(|arg| self.export_term(arg, None)));
468 let operation = self.make_term(table::Term::Apply(node, params));
469 table::Operation::Custom(operation)
470 }
471
472 OpType::OpaqueOp(op) => {
473 let node = self.make_named_global_ref(op.extension(), op.unqualified_id());
474 let params = self
475 .bump
476 .alloc_slice_fill_iter(op.args().iter().map(|arg| self.export_term(arg, None)));
477 let operation = self.make_term(table::Term::Apply(node, params));
478 table::Operation::Custom(operation)
479 }
480 };
481
482 let (signature, num_inputs, num_outputs) = match optype {
483 OpType::DataflowBlock(block) => {
484 let signature = self.export_block_signature(block);
485 (Some(signature), 1, block.sum_rows.len())
486 }
487
488 _ => match &optype.dataflow_signature() {
492 Some(signature) => {
493 let num_inputs = signature.input_types().len();
494 let num_outputs = signature.output_types().len();
495 let signature = self.export_func_type(signature);
496 (Some(signature), num_inputs, num_outputs)
497 }
498 None => (None, 0, 0),
499 },
500 };
501
502 let inputs = self.make_ports(node, Direction::Incoming, num_inputs);
503 let outputs = self.make_ports(node, Direction::Outgoing, num_outputs);
504
505 let meta = {
506 let mut meta = Vec::new();
507 self.export_node_json_metadata(node, &mut meta);
508 self.export_node_order_metadata(node, &mut meta);
509 self.bump.alloc_slice_copy(&meta)
510 };
511
512 self.module.nodes[node_id.index()] = table::Node {
513 operation,
514 inputs,
515 outputs,
516 regions,
517 meta,
518 signature,
519 };
520 }
521
522 pub fn export_opdef(&mut self, opdef: &OpDef) -> table::NodeId {
530 use std::collections::hash_map::Entry;
531
532 let poly_func_type = match opdef.signature_func() {
533 SignatureFunc::PolyFuncType(poly_func_type) => poly_func_type,
534 _ => return self.make_named_global_ref(opdef.extension_id(), opdef.name()),
535 };
536
537 let key = (opdef.extension_id().clone(), opdef.name().clone());
538 let entry = self.decl_operations.entry(key);
539
540 let node = match entry {
541 Entry::Occupied(occupied_entry) => return *occupied_entry.get(),
542 Entry::Vacant(vacant_entry) => {
543 *vacant_entry.insert(self.module.insert_node(table::Node::default()))
544 }
545 };
546
547 let symbol = self.with_local_scope(node, |this| {
548 let name = this.make_qualified_name(opdef.extension_id(), opdef.name());
549 this.export_poly_func_type(name, None, poly_func_type)
550 });
551
552 let meta = {
553 let description = Some(opdef.description()).filter(|d| !d.is_empty());
554 let meta_len = opdef.iter_misc().len() + usize::from(description.is_some());
555 let mut meta = BumpVec::with_capacity_in(meta_len, self.bump);
556
557 if let Some(description) = description {
558 let value = self.make_term(model::Literal::Str(description.into()).into());
559 meta.push(self.make_term_apply(model::CORE_META_DESCRIPTION, &[value]));
560 }
561
562 for (name, value) in opdef.iter_misc() {
563 meta.push(self.make_json_meta(name, value));
564 }
565
566 self.bump.alloc_slice_copy(&meta)
567 };
568
569 let node_data = self.module.get_node_mut(node).unwrap();
570 node_data.operation = table::Operation::DeclareOperation(symbol);
571 node_data.meta = meta;
572
573 node
574 }
575
576 pub fn export_block_signature(&mut self, block: &DataflowBlock) -> table::TermId {
579 let inputs = {
580 let inputs = self.export_type_row(&block.inputs);
581 self.make_term(table::Term::List(
582 self.bump.alloc_slice_copy(&[table::SeqPart::Item(inputs)]),
583 ))
584 };
585
586 let tail = self.export_type_row(&block.other_outputs);
587
588 let outputs = {
589 let mut outputs = BumpVec::with_capacity_in(block.sum_rows.len(), self.bump);
590 for sum_row in &block.sum_rows {
591 let variant = self.export_type_row_with_tail(sum_row, Some(tail));
592 outputs.push(table::SeqPart::Item(variant));
593 }
594 self.make_term(table::Term::List(outputs.into_bump_slice()))
595 };
596
597 self.make_term_apply(model::CORE_CTRL, &[inputs, outputs])
598 }
599
600 pub fn export_dfg(
604 &mut self,
605 node: Node,
606 closure: model::ScopeClosure,
607 export_json_meta: bool,
608 ) -> table::RegionId {
609 let region = self.module.insert_region(table::Region::default());
610
611 self.symbols.enter(region);
612 if closure == model::ScopeClosure::Closed {
613 self.links.enter(region);
614 }
615
616 let mut sources: &[_] = &[];
617 let mut targets: &[_] = &[];
618 let mut input_types = None;
619 let mut output_types = None;
620
621 let mut meta = Vec::new();
622
623 if export_json_meta {
624 self.export_node_json_metadata(node, &mut meta);
625 }
626 self.export_node_entrypoint_metadata(node, &mut meta);
627
628 let children = self.hugr.children(node);
629 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 2, self.bump);
630
631 for child in children {
632 match self.hugr.get_optype(child) {
633 OpType::Input(input) => {
634 sources = self.make_ports(child, Direction::Outgoing, input.types.len());
635 input_types = Some(&input.types);
636
637 if has_order_edges(self.hugr, child) {
638 let key = self.make_term(model::Literal::Nat(child.index() as u64).into());
639 meta.push(self.make_term_apply(model::ORDER_HINT_INPUT_KEY, &[key]));
640 }
641 }
642 OpType::Output(output) => {
643 targets = self.make_ports(child, Direction::Incoming, output.types.len());
644 output_types = Some(&output.types);
645
646 if has_order_edges(self.hugr, child) {
647 let key = self.make_term(model::Literal::Nat(child.index() as u64).into());
648 meta.push(self.make_term_apply(model::ORDER_HINT_OUTPUT_KEY, &[key]));
649 }
650 }
651 _ => {
652 if let Some(child_id) = self.export_node_shallow(child) {
653 region_children.push(child_id);
654 }
655 }
656 }
657
658 let successors = self
660 .hugr
661 .get_optype(child)
662 .other_output_port()
663 .into_iter()
664 .flat_map(|port| self.hugr.linked_inputs(child, port))
665 .map(|(successor, _)| successor);
666
667 for successor in successors {
668 let a = self.make_term(model::Literal::Nat(child.index() as u64).into());
669 let b = self.make_term(model::Literal::Nat(successor.index() as u64).into());
670 meta.push(self.make_term_apply(model::ORDER_HINT_ORDER, &[a, b]));
671 }
672 }
673
674 for child_id in ®ion_children {
675 self.export_node_deep(*child_id);
676 }
677
678 let signature = {
679 let inputs = self.export_type_row(input_types.unwrap());
680 let outputs = self.export_type_row(output_types.unwrap());
681 Some(self.make_term_apply(model::CORE_FN, &[inputs, outputs]))
682 };
683
684 let scope = match closure {
685 model::ScopeClosure::Closed => {
686 let (links, ports) = self.links.exit();
687 Some(table::RegionScope { links, ports })
688 }
689 model::ScopeClosure::Open => None,
690 };
691 self.symbols.exit();
692
693 self.module.regions[region.index()] = table::Region {
694 kind: model::RegionKind::DataFlow,
695 sources,
696 targets,
697 children: region_children.into_bump_slice(),
698 meta: self.bump.alloc_slice_copy(&meta),
699 signature,
700 scope,
701 };
702
703 region
704 }
705
706 pub fn export_cfg(&mut self, node: Node, closure: model::ScopeClosure) -> table::RegionId {
708 let region = self.module.insert_region(table::Region::default());
709 self.symbols.enter(region);
710
711 if closure == model::ScopeClosure::Closed {
712 self.links.enter(region);
713 }
714
715 let mut source = None;
716 let mut targets: &[_] = &[];
717
718 let mut meta = Vec::new();
719 self.export_node_json_metadata(node, &mut meta);
720 self.export_node_entrypoint_metadata(node, &mut meta);
721
722 let children = self.hugr.children(node);
723 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 1, self.bump);
724
725 for child in children {
726 if let OpType::ExitBlock(_) = self.hugr.get_optype(child) {
727 targets = self.make_ports(child, Direction::Incoming, 1);
728 } else {
729 if let Some(child_id) = self.export_node_shallow(child) {
730 region_children.push(child_id);
731 }
732
733 if source.is_none() {
734 source = Some(self.links.use_link(child, IncomingPort::from(0)));
735 }
736 }
737 }
738
739 for child_id in ®ion_children {
740 self.export_node_deep(*child_id);
741 }
742
743 let signature = {
745 let node_signature = self.hugr.signature(node).unwrap();
746
747 let inputs = {
748 let types = self.export_type_row(node_signature.input());
749 self.make_term(table::Term::List(
750 self.bump.alloc_slice_copy(&[table::SeqPart::Item(types)]),
751 ))
752 };
753
754 let outputs = {
755 let types = self.export_type_row(node_signature.output());
756 self.make_term(table::Term::List(
757 self.bump.alloc_slice_copy(&[table::SeqPart::Item(types)]),
758 ))
759 };
760
761 Some(self.make_term_apply(model::CORE_CTRL, &[inputs, outputs]))
762 };
763
764 let scope = match closure {
765 model::ScopeClosure::Closed => {
766 let (links, ports) = self.links.exit();
767 Some(table::RegionScope { links, ports })
768 }
769 model::ScopeClosure::Open => None,
770 };
771 self.symbols.exit();
772
773 self.module.regions[region.index()] = table::Region {
774 kind: model::RegionKind::ControlFlow,
775 sources: self.bump.alloc_slice_copy(&[source.unwrap()]),
776 targets,
777 children: region_children.into_bump_slice(),
778 meta: self.bump.alloc_slice_copy(&meta),
779 signature,
780 scope,
781 };
782
783 region
784 }
785
786 pub fn export_conditional_regions(&mut self, node: Node) -> &'a [table::RegionId] {
788 let children = self.hugr.children(node);
789 let mut regions = BumpVec::with_capacity_in(children.size_hint().0, self.bump);
790
791 for child in children {
792 let OpType::Case(_) = self.hugr.get_optype(child) else {
793 panic!("expected a `Case` node as a child of a `Conditional` node");
794 };
795
796 regions.push(self.export_dfg(child, model::ScopeClosure::Open, true));
797 }
798
799 regions.into_bump_slice()
800 }
801
802 pub fn export_poly_func_type<RV: MaybeRV>(
804 &mut self,
805 name: &'a str,
806 visibility: Option<Visibility>,
807 t: &PolyFuncTypeBase<RV>,
808 ) -> &'a table::Symbol<'a> {
809 let mut params = BumpVec::with_capacity_in(t.params().len(), self.bump);
810 let scope = self
811 .local_scope
812 .expect("exporting poly func type outside of local scope");
813 let visibility = self.bump.alloc(visibility);
814 for (i, param) in t.params().iter().enumerate() {
815 let name = self.bump.alloc_str(&i.to_string());
816 let r#type = self.export_term(param, Some((scope, i as _)));
817 let param = table::Param { name, r#type };
818 params.push(param);
819 }
820
821 let constraints = self.bump.alloc_slice_copy(&self.local_constraints);
822 let body = self.export_func_type(t.body());
823
824 self.bump.alloc(table::Symbol {
825 visibility,
826 name,
827 params: params.into_bump_slice(),
828 constraints,
829 signature: body,
830 })
831 }
832
833 pub fn export_type<RV: MaybeRV>(&mut self, t: &TypeBase<RV>) -> table::TermId {
834 self.export_type_enum(t.as_type_enum())
835 }
836
837 pub fn export_type_enum<RV: MaybeRV>(&mut self, t: &TypeEnum<RV>) -> table::TermId {
838 match t {
839 TypeEnum::Extension(ext) => self.export_custom_type(ext),
840 TypeEnum::Alias(alias) => {
841 let symbol = self.resolve_symbol(self.bump.alloc_str(alias.name()));
842 self.make_term(table::Term::Apply(symbol, &[]))
843 }
844 TypeEnum::Function(func) => self.export_func_type(func),
845 TypeEnum::Variable(index, _) => {
846 let node = self.local_scope.expect("local variable out of scope");
847 self.make_term(table::Term::Var(table::VarId(node, *index as _)))
848 }
849 TypeEnum::RowVar(rv) => self.export_row_var(rv.as_rv()),
850 TypeEnum::Sum(sum) => self.export_sum_type(sum),
851 }
852 }
853
854 pub fn export_func_type<RV: MaybeRV>(&mut self, t: &FuncTypeBase<RV>) -> table::TermId {
855 let inputs = self.export_type_row(t.input());
856 let outputs = self.export_type_row(t.output());
857 self.make_term_apply(model::CORE_FN, &[inputs, outputs])
858 }
859
860 pub fn export_custom_type(&mut self, t: &CustomType) -> table::TermId {
861 let symbol = self.make_named_global_ref(t.extension(), t.name());
862
863 let args = self
864 .bump
865 .alloc_slice_fill_iter(t.args().iter().map(|p| self.export_term(p, None)));
866 let term = table::Term::Apply(symbol, args);
867 self.make_term(term)
868 }
869
870 pub fn export_type_arg_var(&mut self, var: &TermVar) -> table::TermId {
871 let node = self.local_scope.expect("local variable out of scope");
872 self.make_term(table::Term::Var(table::VarId(node, var.index() as _)))
873 }
874
875 pub fn export_row_var(&mut self, t: &RowVariable) -> table::TermId {
876 let node = self.local_scope.expect("local variable out of scope");
877 self.make_term(table::Term::Var(table::VarId(node, t.0 as _)))
878 }
879
880 pub fn export_sum_variants(&mut self, t: &SumType) -> table::TermId {
881 match t {
882 SumType::Unit { size } => {
883 let parts = self.bump.alloc_slice_fill_iter(
884 (0..*size)
885 .map(|_| table::SeqPart::Item(self.make_term(table::Term::List(&[])))),
886 );
887 self.make_term(table::Term::List(parts))
888 }
889 SumType::General { rows } => {
890 let parts = self.bump.alloc_slice_fill_iter(
891 rows.iter()
892 .map(|row| table::SeqPart::Item(self.export_type_row(row))),
893 );
894 self.make_term(table::Term::List(parts))
895 }
896 }
897 }
898
899 pub fn export_sum_type(&mut self, t: &SumType) -> table::TermId {
900 let variants = self.export_sum_variants(t);
901 self.make_term_apply(model::CORE_ADT, &[variants])
902 }
903
904 #[inline]
905 pub fn export_type_row<RV: MaybeRV>(&mut self, row: &TypeRowBase<RV>) -> table::TermId {
906 self.export_type_row_with_tail(row, None)
907 }
908
909 pub fn export_type_row_with_tail<RV: MaybeRV>(
910 &mut self,
911 row: &TypeRowBase<RV>,
912 tail: Option<table::TermId>,
913 ) -> table::TermId {
914 let mut parts =
915 BumpVec::with_capacity_in(row.len() + usize::from(tail.is_some()), self.bump);
916
917 for t in row.iter() {
918 match t.as_type_enum() {
919 TypeEnum::RowVar(var) => {
920 parts.push(table::SeqPart::Splice(self.export_row_var(var.as_rv())));
921 }
922 _ => {
923 parts.push(table::SeqPart::Item(self.export_type(t)));
924 }
925 }
926 }
927
928 if let Some(tail) = tail {
929 parts.push(table::SeqPart::Splice(tail));
930 }
931
932 let parts = parts.into_bump_slice();
933 self.make_term(table::Term::List(parts))
934 }
935
936 pub fn export_term(
943 &mut self,
944 t: &Term,
945 var: Option<(table::NodeId, table::VarIndex)>,
946 ) -> table::TermId {
947 match t {
948 Term::RuntimeType(b) => {
949 if let (Some((node, index)), TypeBound::Copyable) = (var, b) {
950 let term = self.make_term(table::Term::Var(table::VarId(node, index)));
951 let non_linear = self.make_term_apply(model::CORE_NON_LINEAR, &[term]);
952 self.local_constraints.push(non_linear);
953 }
954
955 self.make_term_apply(model::CORE_TYPE, &[])
956 }
957 Term::BoundedNatType(_) => self.make_term_apply(model::CORE_NAT_TYPE, &[]),
958 Term::StringType => self.make_term_apply(model::CORE_STR_TYPE, &[]),
959 Term::BytesType => self.make_term_apply(model::CORE_BYTES_TYPE, &[]),
960 Term::FloatType => self.make_term_apply(model::CORE_FLOAT_TYPE, &[]),
961 Term::ListType(item_type) => {
962 let item_type = self.export_term(item_type, None);
963 self.make_term_apply(model::CORE_LIST_TYPE, &[item_type])
964 }
965 Term::TupleType(item_types) => {
966 let item_types = self.export_term(item_types, None);
967 self.make_term_apply(model::CORE_TUPLE_TYPE, &[item_types])
968 }
969 Term::Runtime(ty) => self.export_type(ty),
970 Term::BoundedNat(value) => self.make_term(model::Literal::Nat(*value).into()),
971 Term::String(value) => self.make_term(model::Literal::Str(value.into()).into()),
972 Term::Float(value) => self.make_term(model::Literal::Float(*value).into()),
973 Term::Bytes(value) => self.make_term(model::Literal::Bytes(value.clone()).into()),
974 Term::List(elems) => {
975 let parts = self.bump.alloc_slice_fill_iter(
976 elems
977 .iter()
978 .map(|elem| table::SeqPart::Item(self.export_term(elem, None))),
979 );
980 self.make_term(table::Term::List(parts))
981 }
982 Term::ListConcat(lists) => {
983 let parts = self.bump.alloc_slice_fill_iter(
984 lists
985 .iter()
986 .map(|elem| table::SeqPart::Splice(self.export_term(elem, None))),
987 );
988 self.make_term(table::Term::List(parts))
989 }
990 Term::Tuple(elems) => {
991 let parts = self.bump.alloc_slice_fill_iter(
992 elems
993 .iter()
994 .map(|elem| table::SeqPart::Item(self.export_term(elem, None))),
995 );
996 self.make_term(table::Term::Tuple(parts))
997 }
998 Term::TupleConcat(tuples) => {
999 let parts = self.bump.alloc_slice_fill_iter(
1000 tuples
1001 .iter()
1002 .map(|elem| table::SeqPart::Splice(self.export_term(elem, None))),
1003 );
1004 self.make_term(table::Term::Tuple(parts))
1005 }
1006 Term::Variable(v) => self.export_type_arg_var(v),
1007 Term::StaticType => self.make_term_apply(model::CORE_STATIC, &[]),
1008 }
1009 }
1010
1011 fn export_value(&mut self, value: &'a Value) -> table::TermId {
1012 match value {
1013 Value::Extension { e } => {
1014 if let Some(array) = e.value().downcast_ref::<ArrayValue>() {
1018 let len = self
1019 .make_term(model::Literal::Nat(array.get_contents().len() as u64).into());
1020 let element_type = self.export_type(array.get_element_type());
1021 let mut contents =
1022 BumpVec::with_capacity_in(array.get_contents().len(), self.bump);
1023
1024 for element in array.get_contents() {
1025 contents.push(table::SeqPart::Item(self.export_value(element)));
1026 }
1027
1028 let contents = self.make_term(table::Term::List(contents.into_bump_slice()));
1029
1030 let symbol = self.resolve_symbol(ArrayValue::CTR_NAME);
1031 let args = self.bump.alloc_slice_copy(&[len, element_type, contents]);
1032 return self.make_term(table::Term::Apply(symbol, args));
1033 }
1034
1035 if let Some(v) = e.value().downcast_ref::<ConstInt>() {
1036 let bitwidth =
1037 self.make_term(model::Literal::Nat(u64::from(v.log_width())).into());
1038 let literal = self.make_term(model::Literal::Nat(v.value_u()).into());
1039
1040 let symbol = self.resolve_symbol(ConstInt::CTR_NAME);
1041 let args = self.bump.alloc_slice_copy(&[bitwidth, literal]);
1042 return self.make_term(table::Term::Apply(symbol, args));
1043 }
1044
1045 if let Some(v) = e.value().downcast_ref::<ConstF64>() {
1046 let literal = self.make_term(model::Literal::Float(v.value().into()).into());
1047 let symbol = self.resolve_symbol(ConstF64::CTR_NAME);
1048 let args = self.bump.alloc_slice_copy(&[literal]);
1049 return self.make_term(table::Term::Apply(symbol, args));
1050 }
1051
1052 let json = match e.value().downcast_ref::<CustomSerialized>() {
1053 Some(custom) => serde_json::to_string(custom.value()).unwrap(),
1054 None => serde_json::to_string(e.value())
1055 .expect("custom extension values should be serializable"),
1056 };
1057
1058 let json = self.make_term(model::Literal::Str(json.into()).into());
1059 let runtime_type = self.export_type(&e.get_type());
1060 let args = self.bump.alloc_slice_copy(&[runtime_type, json]);
1061 let symbol = self.resolve_symbol(model::COMPAT_CONST_JSON);
1062 self.make_term(table::Term::Apply(symbol, args))
1063 }
1064
1065 Value::Function { hugr } => {
1066 let outer_hugr = std::mem::replace(&mut self.hugr, hugr);
1067 let outer_node_to_id = std::mem::take(&mut self.node_to_id);
1068
1069 let region = match hugr.entrypoint_optype() {
1070 OpType::DFG(_) => {
1071 self.export_dfg(hugr.entrypoint(), model::ScopeClosure::Closed, true)
1072 }
1073 _ => panic!("Value::Function root must be a DFG"),
1074 };
1075
1076 self.node_to_id = outer_node_to_id;
1077 self.hugr = outer_hugr;
1078
1079 self.make_term(table::Term::Func(region))
1080 }
1081
1082 Value::Sum(sum) => {
1083 let variants = self.export_sum_variants(&sum.sum_type);
1084 let types = self.make_term(table::Term::Wildcard);
1085 let tag = self.make_term(model::Literal::Nat(sum.tag as u64).into());
1086
1087 let values = {
1088 let mut values = BumpVec::with_capacity_in(sum.values.len(), self.bump);
1089
1090 for value in &sum.values {
1091 values.push(table::SeqPart::Item(self.export_value(value)));
1092 }
1093
1094 self.make_term(table::Term::Tuple(values.into_bump_slice()))
1095 };
1096
1097 self.make_term_apply(model::CORE_CONST_ADT, &[variants, types, tag, values])
1098 }
1099 }
1100 }
1101
1102 fn export_node_json_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1103 let metadata_map = self.hugr.node_metadata_map(node);
1104 meta.reserve(metadata_map.len());
1105
1106 for (name, value) in metadata_map {
1107 meta.push(self.make_json_meta(name, value));
1108 }
1109 }
1110
1111 fn export_node_order_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1112 if has_order_edges(self.hugr, node) {
1113 let key = self.make_term(model::Literal::Nat(node.index() as u64).into());
1114 meta.push(self.make_term_apply(model::ORDER_HINT_KEY, &[key]));
1115 }
1116 }
1117
1118 fn export_node_entrypoint_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1119 if self.hugr.entrypoint() == node {
1120 meta.push(self.make_term_apply(model::CORE_ENTRYPOINT, &[]));
1121 }
1122 }
1123
1124 pub fn make_json_meta(&mut self, name: &str, value: &serde_json::Value) -> table::TermId {
1125 let value = serde_json::to_string(value).expect("json values are always serializable");
1126 let value = self.make_term(model::Literal::Str(value.into()).into());
1127 let name = self.make_term(model::Literal::Str(name.into()).into());
1128 self.make_term_apply(model::COMPAT_META_JSON, &[name, value])
1129 }
1130
1131 fn resolve_symbol(&mut self, name: &'a str) -> table::NodeId {
1132 let result = self.symbols.resolve(name);
1133
1134 match result {
1135 Ok(node) => node,
1136 Err(_) => *self.implicit_imports.entry(name).or_insert_with(|| {
1137 self.module.insert_node(table::Node {
1138 operation: table::Operation::Import { name },
1139 ..table::Node::default()
1140 })
1141 }),
1142 }
1143 }
1144
1145 fn make_term_apply(&mut self, name: &'a str, args: &[table::TermId]) -> table::TermId {
1146 let symbol = self.resolve_symbol(name);
1147 let args = self.bump.alloc_slice_copy(args);
1148 self.make_term(table::Term::Apply(symbol, args))
1149 }
1150}
1151
1152type FxIndexSet<T> = indexmap::IndexSet<T, FxBuildHasher>;
1153
1154struct Links {
1157 scope: model::scope::LinkTable<u32>,
1160
1161 groups: FxHashMap<(Node, Port), u32>,
1163}
1164
1165impl Links {
1166 pub fn new(hugr: &Hugr) -> Self {
1169 let scope = model::scope::LinkTable::new();
1170
1171 let node_ports: FxIndexSet<(Node, Port)> = hugr
1174 .nodes()
1175 .flat_map(|node| hugr.all_node_ports(node).map(move |port| (node, port)))
1176 .collect();
1177
1178 let mut uf = UnionFind::<u32>::new(node_ports.len());
1180
1181 for (i, (node, port)) in node_ports.iter().enumerate() {
1182 if let Ok(port) = port.as_incoming() {
1183 for (other_node, other_port) in hugr.linked_outputs(*node, port) {
1184 let other_port = Port::from(other_port);
1185 let j = node_ports.get_index_of(&(other_node, other_port)).unwrap();
1186 uf.union(i as u32, j as u32);
1187 }
1188 }
1189 }
1190
1191 let groups = node_ports
1193 .into_iter()
1194 .enumerate()
1195 .map(|(i, node_port)| (node_port, uf.find(i as u32)))
1196 .collect();
1197
1198 Self { scope, groups }
1199 }
1200
1201 pub fn enter(&mut self, region: table::RegionId) {
1203 self.scope.enter(region);
1204 }
1205
1206 pub fn exit(&mut self) -> (u32, u32) {
1212 self.scope.exit()
1213 }
1214
1215 pub fn use_link(&mut self, node: Node, port: impl Into<Port>) -> table::LinkIndex {
1221 let port = port.into();
1222 let group = self.groups[&(node, port)];
1223 self.scope.use_link(group)
1224 }
1225}
1226
1227fn has_order_edges(hugr: &Hugr, node: Node) -> bool {
1229 let optype = hugr.get_optype(node);
1230 Direction::BOTH
1231 .iter()
1232 .filter(|dir| optype.other_port_kind(**dir) == Some(EdgeKind::StateOrder))
1233 .filter_map(|dir| optype.other_port(*dir))
1234 .flat_map(|port| hugr.linked_ports(node, port))
1235 .next()
1236 .is_some()
1237}
1238
1239#[cfg(test)]
1240mod test {
1241 use rstest::{fixture, rstest};
1242
1243 use crate::{
1244 Hugr,
1245 builder::{Dataflow, DataflowSubContainer},
1246 extension::prelude::qb_t,
1247 types::Signature,
1248 utils::test_quantum_extension::{cx_gate, h_gate},
1249 };
1250
1251 #[fixture]
1252 fn test_simple_circuit() -> Hugr {
1253 crate::builder::test::build_main(
1254 Signature::new_endo(vec![qb_t(), qb_t()]).into(),
1255 |mut f_build| {
1256 let wires: Vec<_> = f_build.input_wires().collect();
1257 let mut linear = f_build.as_circuit(wires);
1258
1259 assert_eq!(linear.n_wires(), 2);
1260
1261 linear
1262 .append(h_gate(), [0])?
1263 .append(cx_gate(), [0, 1])?
1264 .append(cx_gate(), [1, 0])?;
1265
1266 let outs = linear.finish();
1267 f_build.finish_with_outputs(outs)
1268 },
1269 )
1270 .unwrap()
1271 }
1272
1273 #[rstest]
1274 #[case(test_simple_circuit())]
1275 fn test_export(#[case] hugr: Hugr) {
1276 use hugr_model::v0::bumpalo::Bump;
1277 let bump = Bump::new();
1278 let _model = super::export_hugr(&hugr, &bump);
1279 }
1280}