1use crate::extension::ExtensionRegistry;
3use crate::hugr::internal::HugrInternals;
4use crate::{
5 Direction, Hugr, HugrView, IncomingPort, Node, NodeIndex as _, Port,
6 extension::{ExtensionId, OpDef, SignatureFunc},
7 hugr::IdentList,
8 ops::{
9 DataflowBlock, DataflowOpTrait, OpName, OpTrait, OpType, Value, constant::CustomSerialized,
10 },
11 std_extensions::{
12 arithmetic::{float_types::ConstF64, int_types::ConstInt},
13 collections::array::ArrayValue,
14 },
15 types::{
16 CustomType, EdgeKind, FuncTypeBase, MaybeRV, PolyFuncTypeBase, RowVariable, SumType,
17 TypeArg, TypeBase, TypeBound, TypeEnum, TypeRow,
18 type_param::{TypeArgVariable, TypeParam},
19 type_row::TypeRowBase,
20 },
21};
22
23use fxhash::{FxBuildHasher, FxHashMap};
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
98impl<'a> Context<'a> {
99 pub fn new(hugr: &'a Hugr, bump: &'a Bump) -> Self {
100 let mut module = table::Module::default();
101 module.nodes.reserve(hugr.num_nodes());
102 let links = Links::new(hugr);
103
104 Self {
105 hugr,
106 module,
107 bump,
108 links,
109 term_map: FxHashMap::default(),
110 local_scope: None,
111 decl_operations: FxHashMap::default(),
112 local_constraints: Vec::new(),
113 symbols: model::scope::SymbolTable::default(),
114 implicit_imports: FxHashMap::default(),
115 node_to_id: FxHashMap::default(),
116 id_to_node: FxHashMap::default(),
117 }
118 }
119
120 pub fn export_root(&mut self) {
122 self.module.root = self.module.insert_region(table::Region::default());
123 self.symbols.enter(self.module.root);
124 self.links.enter(self.module.root);
125
126 let hugr_children = self.hugr.children(self.hugr.module_root());
127 let mut children = Vec::with_capacity(hugr_children.size_hint().0);
128
129 for child in hugr_children.clone() {
130 if let Some(child_id) = self.export_node_shallow(child) {
131 children.push(child_id);
132 }
133 }
134
135 for child in &children {
136 self.export_node_deep(*child);
137 }
138
139 let mut all_children = BumpVec::with_capacity_in(
140 children.len() + self.decl_operations.len() + self.implicit_imports.len(),
141 self.bump,
142 );
143
144 all_children.extend(self.implicit_imports.drain().map(|(_, id)| id));
145 all_children.extend(self.decl_operations.values().copied());
146 all_children.extend(children);
147
148 let mut meta = Vec::new();
149 self.export_node_json_metadata(self.hugr.module_root(), &mut meta);
150
151 let (links, ports) = self.links.exit();
152 self.symbols.exit();
153
154 self.module.regions[self.module.root.index()] = table::Region {
155 kind: model::RegionKind::Module,
156 sources: &[],
157 targets: &[],
158 children: all_children.into_bump_slice(),
159 meta: self.bump.alloc_slice_copy(&meta),
160 signature: None,
161 scope: Some(table::RegionScope { links, ports }),
162 };
163 }
164
165 pub fn make_ports(
166 &mut self,
167 node: Node,
168 direction: Direction,
169 num_ports: usize,
170 ) -> &'a [table::LinkIndex] {
171 let ports = self.hugr.node_ports(node, direction);
172 let mut links = BumpVec::with_capacity_in(ports.size_hint().0, self.bump);
173
174 for port in ports.take(num_ports) {
175 links.push(self.links.use_link(node, port));
176 }
177
178 links.into_bump_slice()
179 }
180
181 pub fn make_term(&mut self, term: table::Term<'a>) -> table::TermId {
182 if term == table::Term::Wildcard {
184 return table::TermId::default();
185 }
186
187 let term = match term {
189 table::Term::Apply(symbol, args) => {
190 let prefix = args.iter().take_while(|arg| !arg.is_valid()).count();
191 table::Term::Apply(symbol, &args[prefix..])
192 }
193 term => term,
194 };
195
196 *self
197 .term_map
198 .entry(term.clone())
199 .or_insert_with(|| self.module.insert_term(term))
200 }
201
202 pub fn make_qualified_name(
203 &mut self,
204 extension: &ExtensionId,
205 name: impl AsRef<str>,
206 ) -> &'a str {
207 let capacity = extension.len() + name.as_ref().len() + 1;
208 let mut output = BumpString::with_capacity_in(capacity, self.bump);
209 let _ = write!(&mut output, "{}.{}", extension, name.as_ref());
210 output.into_bump_str()
211 }
212
213 pub fn make_named_global_ref(
214 &mut self,
215 extension: &IdentList,
216 name: impl AsRef<str>,
217 ) -> table::NodeId {
218 let symbol = self.make_qualified_name(extension, name);
219 self.resolve_symbol(symbol)
220 }
221
222 fn connected_function(&self, node: Node) -> Option<Node> {
225 let func_node = self.hugr.static_source(node)?;
226
227 match self.hugr.get_optype(func_node) {
228 OpType::FuncDecl(_) => Some(func_node),
229 OpType::FuncDefn(_) => Some(func_node),
230 _ => None,
231 }
232 }
233
234 fn get_func_name(&self, func_node: Node) -> Option<&'a str> {
237 match self.hugr.get_optype(func_node) {
238 OpType::FuncDecl(func_decl) => Some(func_decl.func_name()),
239 OpType::FuncDefn(func_defn) => Some(func_defn.func_name()),
240 _ => None,
241 }
242 }
243
244 fn with_local_scope<T>(&mut self, node: table::NodeId, f: impl FnOnce(&mut Self) -> T) -> T {
245 let prev_local_scope = self.local_scope.replace(node);
246 let prev_local_constraints = std::mem::take(&mut self.local_constraints);
247 let result = f(self);
248 self.local_scope = prev_local_scope;
249 self.local_constraints = prev_local_constraints;
250 result
251 }
252
253 fn export_node_shallow(&mut self, node: Node) -> Option<table::NodeId> {
254 let optype = self.hugr.get_optype(node);
255
256 if let OpType::Const(_)
258 | OpType::Input(_)
259 | OpType::Output(_)
260 | OpType::ExitBlock(_)
261 | OpType::Case(_) = optype
262 {
263 return None;
264 }
265
266 let node_id = self.module.insert_node(table::Node::default());
267 self.node_to_id.insert(node, node_id);
268 self.id_to_node.insert(node_id, node);
269
270 let symbol = match optype {
272 OpType::FuncDefn(func_defn) => Some(func_defn.func_name().as_str()),
273 OpType::FuncDecl(func_decl) => Some(func_decl.func_name().as_str()),
274 OpType::AliasDecl(alias_decl) => Some(alias_decl.name.as_str()),
275 OpType::AliasDefn(alias_defn) => Some(alias_defn.name.as_str()),
276 _ => None,
277 };
278
279 if let Some(symbol) = symbol {
280 self.symbols
281 .insert(symbol, node_id)
282 .expect("duplicate symbol");
283 }
284
285 Some(node_id)
286 }
287
288 fn export_node_deep(&mut self, node_id: table::NodeId) {
289 let mut regions: &[_] = &[];
293
294 let node = self.id_to_node[&node_id];
295 let optype = self.hugr.get_optype(node);
296
297 let operation = match optype {
298 OpType::Module(_) => todo!("this should be an error"),
299
300 OpType::Input(_) => {
301 panic!("input nodes should have been handled by the region export")
302 }
303
304 OpType::Output(_) => {
305 panic!("output nodes should have been handled by the region export")
306 }
307
308 OpType::DFG(_) => {
309 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
310 node,
311 model::ScopeClosure::Open,
312 false,
313 )]);
314 table::Operation::Dfg
315 }
316
317 OpType::CFG(_) => {
318 regions = self
319 .bump
320 .alloc_slice_copy(&[self.export_cfg(node, model::ScopeClosure::Open)]);
321 table::Operation::Cfg
322 }
323
324 OpType::ExitBlock(_) => {
325 panic!("exit blocks should have been handled by the region export")
326 }
327
328 OpType::Case(_) => {
329 todo!("case nodes should have been handled by the region export")
330 }
331
332 OpType::DataflowBlock(_) => {
333 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
334 node,
335 model::ScopeClosure::Open,
336 false,
337 )]);
338 table::Operation::Block
339 }
340
341 OpType::FuncDefn(func) => self.with_local_scope(node_id, |this| {
342 let name = this.get_func_name(node).unwrap();
343 let symbol = this.export_poly_func_type(name, func.signature());
344 regions = this.bump.alloc_slice_copy(&[this.export_dfg(
345 node,
346 model::ScopeClosure::Closed,
347 false,
348 )]);
349 table::Operation::DefineFunc(symbol)
350 }),
351
352 OpType::FuncDecl(func) => self.with_local_scope(node_id, |this| {
353 let name = this.get_func_name(node).unwrap();
354 let symbol = this.export_poly_func_type(name, func.signature());
355 table::Operation::DeclareFunc(symbol)
356 }),
357
358 OpType::AliasDecl(alias) => self.with_local_scope(node_id, |this| {
359 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
361 let symbol = this.bump.alloc(table::Symbol {
362 name: &alias.name,
363 params: &[],
364 constraints: &[],
365 signature,
366 });
367 table::Operation::DeclareAlias(symbol)
368 }),
369
370 OpType::AliasDefn(alias) => self.with_local_scope(node_id, |this| {
371 let value = this.export_type(&alias.definition);
372 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
374 let symbol = this.bump.alloc(table::Symbol {
375 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_type_arg(arg)));
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_type_arg(arg)));
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_type_arg(arg)));
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_type_arg(arg)));
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, 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 let inputs = self.make_term_apply(model::CORE_CTRL, &[inputs]);
582 self.make_term(table::Term::List(
583 self.bump.alloc_slice_copy(&[table::SeqPart::Item(inputs)]),
584 ))
585 };
586
587 let tail = self.export_type_row(&block.other_outputs);
588
589 let outputs = {
590 let mut outputs = BumpVec::with_capacity_in(block.sum_rows.len(), self.bump);
591 for sum_row in &block.sum_rows {
592 let variant = self.export_type_row_with_tail(sum_row, Some(tail));
593 let control = self.make_term_apply(model::CORE_CTRL, &[variant]);
594 outputs.push(table::SeqPart::Item(control));
595 }
596 self.make_term(table::Term::List(outputs.into_bump_slice()))
597 };
598
599 self.make_term_apply(model::CORE_FN, &[inputs, outputs])
600 }
601
602 pub fn export_dfg(
606 &mut self,
607 node: Node,
608 closure: model::ScopeClosure,
609 export_json_meta: bool,
610 ) -> table::RegionId {
611 let region = self.module.insert_region(table::Region::default());
612
613 self.symbols.enter(region);
614 if closure == model::ScopeClosure::Closed {
615 self.links.enter(region);
616 }
617
618 let mut sources: &[_] = &[];
619 let mut targets: &[_] = &[];
620 let mut input_types = None;
621 let mut output_types = None;
622
623 let mut meta = Vec::new();
624
625 if export_json_meta {
626 self.export_node_json_metadata(node, &mut meta);
627 }
628 self.export_node_entrypoint_metadata(node, &mut meta);
629
630 let children = self.hugr.children(node);
631 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 2, self.bump);
632
633 let mut output_node = None;
634
635 for child in children {
636 match self.hugr.get_optype(child) {
637 OpType::Input(input) => {
638 sources = self.make_ports(child, Direction::Outgoing, input.types.len());
639 input_types = Some(&input.types);
640 }
641 OpType::Output(output) => {
642 targets = self.make_ports(child, Direction::Incoming, output.types.len());
643 output_types = Some(&output.types);
644 output_node = Some(child);
645 }
646 child_optype => {
647 if let Some(child_id) = self.export_node_shallow(child) {
648 region_children.push(child_id);
649
650 let successors = child_optype
652 .other_output_port()
653 .into_iter()
654 .flat_map(|port| self.hugr.linked_inputs(child, port))
655 .map(|(successor, _)| successor)
656 .filter(|successor| Some(*successor) != output_node);
657
658 for successor in successors {
659 let a =
660 self.make_term(model::Literal::Nat(child.index() as u64).into());
661 let b = self
662 .make_term(model::Literal::Nat(successor.index() as u64).into());
663 meta.push(self.make_term_apply(model::ORDER_HINT_ORDER, &[a, b]));
664 }
665 }
666 }
667 }
668 }
669
670 for child_id in ®ion_children {
671 self.export_node_deep(*child_id);
672 }
673
674 let signature = {
675 let inputs = self.export_type_row(input_types.unwrap());
676 let outputs = self.export_type_row(output_types.unwrap());
677 Some(self.make_term_apply(model::CORE_FN, &[inputs, outputs]))
678 };
679
680 let scope = match closure {
681 model::ScopeClosure::Closed => {
682 let (links, ports) = self.links.exit();
683 Some(table::RegionScope { links, ports })
684 }
685 model::ScopeClosure::Open => None,
686 };
687 self.symbols.exit();
688
689 self.module.regions[region.index()] = table::Region {
690 kind: model::RegionKind::DataFlow,
691 sources,
692 targets,
693 children: region_children.into_bump_slice(),
694 meta: self.bump.alloc_slice_copy(&meta),
695 signature,
696 scope,
697 };
698
699 region
700 }
701
702 pub fn export_cfg(&mut self, node: Node, closure: model::ScopeClosure) -> table::RegionId {
704 let region = self.module.insert_region(table::Region::default());
705 self.symbols.enter(region);
706
707 if closure == model::ScopeClosure::Closed {
708 self.links.enter(region);
709 }
710
711 let mut source = None;
712 let mut targets: &[_] = &[];
713
714 let mut meta = Vec::new();
715 self.export_node_json_metadata(node, &mut meta);
716 self.export_node_entrypoint_metadata(node, &mut meta);
717
718 let children = self.hugr.children(node);
719 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 1, self.bump);
720
721 for child in children {
722 if let OpType::ExitBlock(_) = self.hugr.get_optype(child) {
723 targets = self.make_ports(child, Direction::Incoming, 1);
724 } else {
725 if let Some(child_id) = self.export_node_shallow(child) {
726 region_children.push(child_id);
727 }
728
729 if source.is_none() {
730 source = Some(self.links.use_link(child, IncomingPort::from(0)));
731 }
732 }
733 }
734
735 for child_id in ®ion_children {
736 self.export_node_deep(*child_id);
737 }
738
739 let signature = {
741 let node_signature = self.hugr.signature(node).unwrap();
742
743 let mut wrap_ctrl = |types: &TypeRow| {
744 let types = self.export_type_row(types);
745 let types_ctrl = self.make_term_apply(model::CORE_CTRL, &[types]);
746 self.make_term(table::Term::List(
747 self.bump
748 .alloc_slice_copy(&[table::SeqPart::Item(types_ctrl)]),
749 ))
750 };
751
752 let inputs = wrap_ctrl(node_signature.input());
753 let outputs = wrap_ctrl(node_signature.output());
754 Some(self.make_term_apply(model::CORE_FN, &[inputs, outputs]))
755 };
756
757 let scope = match closure {
758 model::ScopeClosure::Closed => {
759 let (links, ports) = self.links.exit();
760 Some(table::RegionScope { links, ports })
761 }
762 model::ScopeClosure::Open => None,
763 };
764 self.symbols.exit();
765
766 self.module.regions[region.index()] = table::Region {
767 kind: model::RegionKind::ControlFlow,
768 sources: self.bump.alloc_slice_copy(&[source.unwrap()]),
769 targets,
770 children: region_children.into_bump_slice(),
771 meta: self.bump.alloc_slice_copy(&meta),
772 signature,
773 scope,
774 };
775
776 region
777 }
778
779 pub fn export_conditional_regions(&mut self, node: Node) -> &'a [table::RegionId] {
781 let children = self.hugr.children(node);
782 let mut regions = BumpVec::with_capacity_in(children.size_hint().0, self.bump);
783
784 for child in children {
785 let OpType::Case(_) = self.hugr.get_optype(child) else {
786 panic!("expected a `Case` node as a child of a `Conditional` node");
787 };
788
789 regions.push(self.export_dfg(child, model::ScopeClosure::Open, true));
790 }
791
792 regions.into_bump_slice()
793 }
794
795 pub fn export_poly_func_type<RV: MaybeRV>(
797 &mut self,
798 name: &'a str,
799 t: &PolyFuncTypeBase<RV>,
800 ) -> &'a table::Symbol<'a> {
801 let mut params = BumpVec::with_capacity_in(t.params().len(), self.bump);
802 let scope = self
803 .local_scope
804 .expect("exporting poly func type outside of local scope");
805
806 for (i, param) in t.params().iter().enumerate() {
807 let name = self.bump.alloc_str(&i.to_string());
808 let r#type = self.export_type_param(param, Some((scope, i as _)));
809 let param = table::Param { name, r#type };
810 params.push(param);
811 }
812
813 let constraints = self.bump.alloc_slice_copy(&self.local_constraints);
814 let body = self.export_func_type(t.body());
815
816 self.bump.alloc(table::Symbol {
817 name,
818 params: params.into_bump_slice(),
819 constraints,
820 signature: body,
821 })
822 }
823
824 pub fn export_type<RV: MaybeRV>(&mut self, t: &TypeBase<RV>) -> table::TermId {
825 self.export_type_enum(t.as_type_enum())
826 }
827
828 pub fn export_type_enum<RV: MaybeRV>(&mut self, t: &TypeEnum<RV>) -> table::TermId {
829 match t {
830 TypeEnum::Extension(ext) => self.export_custom_type(ext),
831 TypeEnum::Alias(alias) => {
832 let symbol = self.resolve_symbol(self.bump.alloc_str(alias.name()));
833 self.make_term(table::Term::Apply(symbol, &[]))
834 }
835 TypeEnum::Function(func) => self.export_func_type(func),
836 TypeEnum::Variable(index, _) => {
837 let node = self.local_scope.expect("local variable out of scope");
838 self.make_term(table::Term::Var(table::VarId(node, *index as _)))
839 }
840 TypeEnum::RowVar(rv) => self.export_row_var(rv.as_rv()),
841 TypeEnum::Sum(sum) => self.export_sum_type(sum),
842 }
843 }
844
845 pub fn export_func_type<RV: MaybeRV>(&mut self, t: &FuncTypeBase<RV>) -> table::TermId {
846 let inputs = self.export_type_row(t.input());
847 let outputs = self.export_type_row(t.output());
848 self.make_term_apply(model::CORE_FN, &[inputs, outputs])
849 }
850
851 pub fn export_custom_type(&mut self, t: &CustomType) -> table::TermId {
852 let symbol = self.make_named_global_ref(t.extension(), t.name());
853
854 let args = self
855 .bump
856 .alloc_slice_fill_iter(t.args().iter().map(|p| self.export_type_arg(p)));
857 let term = table::Term::Apply(symbol, args);
858 self.make_term(term)
859 }
860
861 pub fn export_type_arg(&mut self, t: &TypeArg) -> table::TermId {
862 match t {
863 TypeArg::Type { ty } => self.export_type(ty),
864 TypeArg::BoundedNat { n } => self.make_term(model::Literal::Nat(*n).into()),
865 TypeArg::String { arg } => self.make_term(model::Literal::Str(arg.into()).into()),
866 TypeArg::Sequence { elems } => {
867 let parts = self.bump.alloc_slice_fill_iter(
869 elems
870 .iter()
871 .map(|elem| table::SeqPart::Item(self.export_type_arg(elem))),
872 );
873 self.make_term(table::Term::List(parts))
874 }
875 TypeArg::Variable { v } => self.export_type_arg_var(v),
876 }
877 }
878
879 pub fn export_type_arg_var(&mut self, var: &TypeArgVariable) -> table::TermId {
880 let node = self.local_scope.expect("local variable out of scope");
881 self.make_term(table::Term::Var(table::VarId(node, var.index() as _)))
882 }
883
884 pub fn export_row_var(&mut self, t: &RowVariable) -> table::TermId {
885 let node = self.local_scope.expect("local variable out of scope");
886 self.make_term(table::Term::Var(table::VarId(node, t.0 as _)))
887 }
888
889 pub fn export_sum_variants(&mut self, t: &SumType) -> table::TermId {
890 match t {
891 SumType::Unit { size } => {
892 let parts = self.bump.alloc_slice_fill_iter(
893 (0..*size)
894 .map(|_| table::SeqPart::Item(self.make_term(table::Term::List(&[])))),
895 );
896 self.make_term(table::Term::List(parts))
897 }
898 SumType::General { rows } => {
899 let parts = self.bump.alloc_slice_fill_iter(
900 rows.iter()
901 .map(|row| table::SeqPart::Item(self.export_type_row(row))),
902 );
903 self.make_term(table::Term::List(parts))
904 }
905 }
906 }
907
908 pub fn export_sum_type(&mut self, t: &SumType) -> table::TermId {
909 let variants = self.export_sum_variants(t);
910 self.make_term_apply(model::CORE_ADT, &[variants])
911 }
912
913 #[inline]
914 pub fn export_type_row<RV: MaybeRV>(&mut self, row: &TypeRowBase<RV>) -> table::TermId {
915 self.export_type_row_with_tail(row, None)
916 }
917
918 pub fn export_type_row_with_tail<RV: MaybeRV>(
919 &mut self,
920 row: &TypeRowBase<RV>,
921 tail: Option<table::TermId>,
922 ) -> table::TermId {
923 let mut parts =
924 BumpVec::with_capacity_in(row.len() + usize::from(tail.is_some()), self.bump);
925
926 for t in row.iter() {
927 match t.as_type_enum() {
928 TypeEnum::RowVar(var) => {
929 parts.push(table::SeqPart::Splice(self.export_row_var(var.as_rv())));
930 }
931 _ => {
932 parts.push(table::SeqPart::Item(self.export_type(t)));
933 }
934 }
935 }
936
937 if let Some(tail) = tail {
938 parts.push(table::SeqPart::Splice(tail));
939 }
940
941 let parts = parts.into_bump_slice();
942 self.make_term(table::Term::List(parts))
943 }
944
945 pub fn export_type_param(
952 &mut self,
953 t: &TypeParam,
954 var: Option<(table::NodeId, table::VarIndex)>,
955 ) -> table::TermId {
956 match t {
957 TypeParam::Type { b } => {
958 if let (Some((node, index)), TypeBound::Copyable) = (var, b) {
959 let term = self.make_term(table::Term::Var(table::VarId(node, index)));
960 let non_linear = self.make_term_apply(model::CORE_NON_LINEAR, &[term]);
961 self.local_constraints.push(non_linear);
962 }
963
964 self.make_term_apply(model::CORE_TYPE, &[])
965 }
966 TypeParam::BoundedNat { .. } => self.make_term_apply(model::CORE_NAT_TYPE, &[]),
968 TypeParam::String => self.make_term_apply(model::CORE_STR_TYPE, &[]),
969 TypeParam::List { param } => {
970 let item_type = self.export_type_param(param, None);
971 self.make_term_apply(model::CORE_LIST_TYPE, &[item_type])
972 }
973 TypeParam::Tuple { params } => {
974 let parts = self.bump.alloc_slice_fill_iter(
975 params
976 .iter()
977 .map(|param| table::SeqPart::Item(self.export_type_param(param, None))),
978 );
979 let types = self.make_term(table::Term::List(parts));
980 self.make_term_apply(model::CORE_TUPLE_TYPE, &[types])
981 }
982 }
983 }
984
985 fn export_value(&mut self, value: &'a Value) -> table::TermId {
986 match value {
987 Value::Extension { e } => {
988 if let Some(array) = e.value().downcast_ref::<ArrayValue>() {
992 let len = self
993 .make_term(model::Literal::Nat(array.get_contents().len() as u64).into());
994 let element_type = self.export_type(array.get_element_type());
995 let mut contents =
996 BumpVec::with_capacity_in(array.get_contents().len(), self.bump);
997
998 for element in array.get_contents() {
999 contents.push(table::SeqPart::Item(self.export_value(element)));
1000 }
1001
1002 let contents = self.make_term(table::Term::List(contents.into_bump_slice()));
1003
1004 let symbol = self.resolve_symbol(ArrayValue::CTR_NAME);
1005 let args = self.bump.alloc_slice_copy(&[len, element_type, contents]);
1006 return self.make_term(table::Term::Apply(symbol, args));
1007 }
1008
1009 if let Some(v) = e.value().downcast_ref::<ConstInt>() {
1010 let bitwidth =
1011 self.make_term(model::Literal::Nat(u64::from(v.log_width())).into());
1012 let literal = self.make_term(model::Literal::Nat(v.value_u()).into());
1013
1014 let symbol = self.resolve_symbol(ConstInt::CTR_NAME);
1015 let args = self.bump.alloc_slice_copy(&[bitwidth, literal]);
1016 return self.make_term(table::Term::Apply(symbol, args));
1017 }
1018
1019 if let Some(v) = e.value().downcast_ref::<ConstF64>() {
1020 let literal = self.make_term(model::Literal::Float(v.value().into()).into());
1021 let symbol = self.resolve_symbol(ConstF64::CTR_NAME);
1022 let args = self.bump.alloc_slice_copy(&[literal]);
1023 return self.make_term(table::Term::Apply(symbol, args));
1024 }
1025
1026 let json = match e.value().downcast_ref::<CustomSerialized>() {
1027 Some(custom) => serde_json::to_string(custom.value()).unwrap(),
1028 None => serde_json::to_string(e.value())
1029 .expect("custom extension values should be serializable"),
1030 };
1031
1032 let json = self.make_term(model::Literal::Str(json.into()).into());
1033 let runtime_type = self.export_type(&e.get_type());
1034 let args = self.bump.alloc_slice_copy(&[runtime_type, json]);
1035 let symbol = self.resolve_symbol(model::COMPAT_CONST_JSON);
1036 self.make_term(table::Term::Apply(symbol, args))
1037 }
1038
1039 Value::Function { hugr } => {
1040 let outer_hugr = std::mem::replace(&mut self.hugr, hugr);
1041 let outer_node_to_id = std::mem::take(&mut self.node_to_id);
1042
1043 let region = match hugr.entrypoint_optype() {
1044 OpType::DFG(_) => {
1045 self.export_dfg(hugr.entrypoint(), model::ScopeClosure::Closed, true)
1046 }
1047 _ => panic!("Value::Function root must be a DFG"),
1048 };
1049
1050 self.node_to_id = outer_node_to_id;
1051 self.hugr = outer_hugr;
1052
1053 self.make_term(table::Term::Func(region))
1054 }
1055
1056 Value::Sum(sum) => {
1057 let variants = self.export_sum_variants(&sum.sum_type);
1058 let types = self.make_term(table::Term::Wildcard);
1059 let tag = self.make_term(model::Literal::Nat(sum.tag as u64).into());
1060
1061 let values = {
1062 let mut values = BumpVec::with_capacity_in(sum.values.len(), self.bump);
1063
1064 for value in &sum.values {
1065 values.push(table::SeqPart::Item(self.export_value(value)));
1066 }
1067
1068 self.make_term(table::Term::Tuple(values.into_bump_slice()))
1069 };
1070
1071 self.make_term_apply(model::CORE_CONST_ADT, &[variants, types, tag, values])
1072 }
1073 }
1074 }
1075
1076 fn export_node_json_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1077 let metadata_map = self.hugr.node_metadata_map(node);
1078 meta.reserve(metadata_map.len());
1079
1080 for (name, value) in metadata_map {
1081 meta.push(self.make_json_meta(name, value));
1082 }
1083 }
1084
1085 fn export_node_order_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1086 fn is_relevant_node(hugr: &Hugr, node: Node) -> bool {
1087 let optype = hugr.get_optype(node);
1088 !optype.is_input() && !optype.is_output()
1089 }
1090
1091 let optype = self.hugr.get_optype(node);
1092
1093 let has_order_edges = Direction::BOTH
1094 .iter()
1095 .filter(|dir| optype.other_port_kind(**dir) == Some(EdgeKind::StateOrder))
1096 .filter_map(|dir| optype.other_port(*dir))
1097 .flat_map(|port| self.hugr.linked_ports(node, port))
1098 .any(|(other, _)| is_relevant_node(self.hugr, other));
1099
1100 if has_order_edges {
1101 let key = self.make_term(model::Literal::Nat(node.index() as u64).into());
1102 meta.push(self.make_term_apply(model::ORDER_HINT_KEY, &[key]));
1103 }
1104 }
1105
1106 fn export_node_entrypoint_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1107 if self.hugr.entrypoint() == node {
1108 meta.push(self.make_term_apply(model::CORE_ENTRYPOINT, &[]));
1109 }
1110 }
1111
1112 pub fn make_json_meta(&mut self, name: &str, value: &serde_json::Value) -> table::TermId {
1113 let value = serde_json::to_string(value).expect("json values are always serializable");
1114 let value = self.make_term(model::Literal::Str(value.into()).into());
1115 let name = self.make_term(model::Literal::Str(name.into()).into());
1116 self.make_term_apply(model::COMPAT_META_JSON, &[name, value])
1117 }
1118
1119 fn resolve_symbol(&mut self, name: &'a str) -> table::NodeId {
1120 let result = self.symbols.resolve(name);
1121
1122 match result {
1123 Ok(node) => node,
1124 Err(_) => *self.implicit_imports.entry(name).or_insert_with(|| {
1125 self.module.insert_node(table::Node {
1126 operation: table::Operation::Import { name },
1127 ..table::Node::default()
1128 })
1129 }),
1130 }
1131 }
1132
1133 fn make_term_apply(&mut self, name: &'a str, args: &[table::TermId]) -> table::TermId {
1134 let symbol = self.resolve_symbol(name);
1135 let args = self.bump.alloc_slice_copy(args);
1136 self.make_term(table::Term::Apply(symbol, args))
1137 }
1138}
1139
1140type FxIndexSet<T> = indexmap::IndexSet<T, FxBuildHasher>;
1141
1142struct Links {
1145 scope: model::scope::LinkTable<u32>,
1148
1149 groups: FxHashMap<(Node, Port), u32>,
1151}
1152
1153impl Links {
1154 pub fn new(hugr: &Hugr) -> Self {
1157 let scope = model::scope::LinkTable::new();
1158
1159 let node_ports: FxIndexSet<(Node, Port)> = hugr
1162 .nodes()
1163 .flat_map(|node| hugr.all_node_ports(node).map(move |port| (node, port)))
1164 .collect();
1165
1166 let mut uf = UnionFind::<u32>::new(node_ports.len());
1168
1169 for (i, (node, port)) in node_ports.iter().enumerate() {
1170 if let Ok(port) = port.as_incoming() {
1171 for (other_node, other_port) in hugr.linked_outputs(*node, port) {
1172 let other_port = Port::from(other_port);
1173 let j = node_ports.get_index_of(&(other_node, other_port)).unwrap();
1174 uf.union(i as u32, j as u32);
1175 }
1176 }
1177 }
1178
1179 let groups = node_ports
1181 .into_iter()
1182 .enumerate()
1183 .map(|(i, node_port)| (node_port, uf.find(i as u32)))
1184 .collect();
1185
1186 Self { scope, groups }
1187 }
1188
1189 pub fn enter(&mut self, region: table::RegionId) {
1191 self.scope.enter(region);
1192 }
1193
1194 pub fn exit(&mut self) -> (u32, u32) {
1200 self.scope.exit()
1201 }
1202
1203 pub fn use_link(&mut self, node: Node, port: impl Into<Port>) -> table::LinkIndex {
1209 let port = port.into();
1210 let group = self.groups[&(node, port)];
1211 self.scope.use_link(group)
1212 }
1213}
1214
1215#[cfg(test)]
1216mod test {
1217 use rstest::{fixture, rstest};
1218
1219 use crate::{
1220 Hugr,
1221 builder::{Dataflow, DataflowSubContainer},
1222 extension::prelude::qb_t,
1223 types::Signature,
1224 utils::test_quantum_extension::{cx_gate, h_gate},
1225 };
1226
1227 #[fixture]
1228 fn test_simple_circuit() -> Hugr {
1229 crate::builder::test::build_main(
1230 Signature::new_endo(vec![qb_t(), qb_t()]).into(),
1231 |mut f_build| {
1232 let wires: Vec<_> = f_build.input_wires().collect();
1233 let mut linear = f_build.as_circuit(wires);
1234
1235 assert_eq!(linear.n_wires(), 2);
1236
1237 linear
1238 .append(h_gate(), [0])?
1239 .append(cx_gate(), [0, 1])?
1240 .append(cx_gate(), [1, 0])?;
1241
1242 let outs = linear.finish();
1243 f_build.finish_with_outputs(outs)
1244 },
1245 )
1246 .unwrap()
1247 }
1248
1249 #[rstest]
1250 #[case(test_simple_circuit())]
1251 fn test_export(#[case] hugr: Hugr) {
1252 use hugr_model::v0::bumpalo::Bump;
1253 let bump = Bump::new();
1254 let _model = super::export_hugr(&hugr, &bump);
1255 }
1256}