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