1use crate::extension::ExtensionRegistry;
3use crate::hugr::internal::HugrInternals;
4use crate::{
5 extension::{ExtensionId, OpDef, SignatureFunc},
6 hugr::IdentList,
7 ops::{
8 constant::CustomSerialized, DataflowBlock, DataflowOpTrait, OpName, OpTrait, OpType, Value,
9 },
10 std_extensions::{
11 arithmetic::{float_types::ConstF64, int_types::ConstInt},
12 collections::array::ArrayValue,
13 },
14 types::{
15 type_param::{TypeArgVariable, TypeParam},
16 type_row::TypeRowBase,
17 CustomType, EdgeKind, FuncTypeBase, MaybeRV, PolyFuncTypeBase, RowVariable, SumType,
18 TypeArg, TypeBase, TypeBound, TypeEnum, TypeRow,
19 },
20 Direction, Hugr, HugrView, IncomingPort, Node, NodeIndex as _, Port,
21};
22
23use fxhash::{FxBuildHasher, FxHashMap};
24use hugr_model::v0::{
25 self as model,
26 bumpalo::{collections::String as BumpString, collections::Vec as BumpVec, Bump},
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 (links, ports) = self.links.exit();
149 self.symbols.exit();
150
151 self.module.regions[self.module.root.index()] = table::Region {
152 kind: model::RegionKind::Module,
153 sources: &[],
154 targets: &[],
155 children: all_children.into_bump_slice(),
156 meta: &[], signature: None,
158 scope: Some(table::RegionScope { links, ports }),
159 };
160 }
161
162 pub fn make_ports(
163 &mut self,
164 node: Node,
165 direction: Direction,
166 num_ports: usize,
167 ) -> &'a [table::LinkIndex] {
168 let ports = self.hugr.node_ports(node, direction);
169 let mut links = BumpVec::with_capacity_in(ports.size_hint().0, self.bump);
170
171 for port in ports.take(num_ports) {
172 links.push(self.links.use_link(node, port));
173 }
174
175 links.into_bump_slice()
176 }
177
178 pub fn make_term(&mut self, term: table::Term<'a>) -> table::TermId {
179 if term == table::Term::Wildcard {
181 return self.module.insert_term(term);
182 }
183
184 *self
185 .term_map
186 .entry(term.clone())
187 .or_insert_with(|| self.module.insert_term(term))
188 }
189
190 pub fn make_qualified_name(
191 &mut self,
192 extension: &ExtensionId,
193 name: impl AsRef<str>,
194 ) -> &'a str {
195 let capacity = extension.len() + name.as_ref().len() + 1;
196 let mut output = BumpString::with_capacity_in(capacity, self.bump);
197 let _ = write!(&mut output, "{}.{}", extension, name.as_ref());
198 output.into_bump_str()
199 }
200
201 pub fn make_named_global_ref(
202 &mut self,
203 extension: &IdentList,
204 name: impl AsRef<str>,
205 ) -> table::NodeId {
206 let symbol = self.make_qualified_name(extension, name);
207 self.resolve_symbol(symbol)
208 }
209
210 fn connected_function(&self, node: Node) -> Option<Node> {
213 let func_node = self.hugr.static_source(node)?;
214
215 match self.hugr.get_optype(func_node) {
216 OpType::FuncDecl(_) => Some(func_node),
217 OpType::FuncDefn(_) => Some(func_node),
218 _ => None,
219 }
220 }
221
222 fn get_func_name(&self, func_node: Node) -> Option<&'a str> {
225 match self.hugr.get_optype(func_node) {
226 OpType::FuncDecl(func_decl) => Some(&func_decl.name),
227 OpType::FuncDefn(func_defn) => Some(&func_defn.name),
228 _ => None,
229 }
230 }
231
232 fn with_local_scope<T>(&mut self, node: table::NodeId, f: impl FnOnce(&mut Self) -> T) -> T {
233 let prev_local_scope = self.local_scope.replace(node);
234 let prev_local_constraints = std::mem::take(&mut self.local_constraints);
235 let result = f(self);
236 self.local_scope = prev_local_scope;
237 self.local_constraints = prev_local_constraints;
238 result
239 }
240
241 fn export_node_shallow(&mut self, node: Node) -> Option<table::NodeId> {
242 let optype = self.hugr.get_optype(node);
243
244 if let OpType::Const(_)
246 | OpType::Input(_)
247 | OpType::Output(_)
248 | OpType::ExitBlock(_)
249 | OpType::Case(_) = optype
250 {
251 return None;
252 }
253
254 let node_id = self.module.insert_node(table::Node::default());
255 self.node_to_id.insert(node, node_id);
256 self.id_to_node.insert(node_id, node);
257
258 let symbol = match optype {
260 OpType::FuncDefn(func_defn) => Some(func_defn.name.as_str()),
261 OpType::FuncDecl(func_decl) => Some(func_decl.name.as_str()),
262 OpType::AliasDecl(alias_decl) => Some(alias_decl.name.as_str()),
263 OpType::AliasDefn(alias_defn) => Some(alias_defn.name.as_str()),
264 _ => None,
265 };
266
267 if let Some(symbol) = symbol {
268 self.symbols
269 .insert(symbol, node_id)
270 .expect("duplicate symbol");
271 }
272
273 Some(node_id)
274 }
275
276 fn export_node_deep(&mut self, node_id: table::NodeId) {
277 let mut regions: &[_] = &[];
281
282 let node = self.id_to_node[&node_id];
283 let optype = self.hugr.get_optype(node);
284
285 let operation = match optype {
286 OpType::Module(_) => todo!("this should be an error"),
287
288 OpType::Input(_) => {
289 panic!("input nodes should have been handled by the region export")
290 }
291
292 OpType::Output(_) => {
293 panic!("output nodes should have been handled by the region export")
294 }
295
296 OpType::DFG(_) => {
297 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
298 node,
299 model::ScopeClosure::Open,
300 false,
301 )]);
302 table::Operation::Dfg
303 }
304
305 OpType::CFG(_) => {
306 regions = self
307 .bump
308 .alloc_slice_copy(&[self.export_cfg(node, model::ScopeClosure::Open)]);
309 table::Operation::Cfg
310 }
311
312 OpType::ExitBlock(_) => {
313 panic!("exit blocks should have been handled by the region export")
314 }
315
316 OpType::Case(_) => {
317 todo!("case nodes should have been handled by the region export")
318 }
319
320 OpType::DataflowBlock(_) => {
321 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
322 node,
323 model::ScopeClosure::Open,
324 false,
325 )]);
326 table::Operation::Block
327 }
328
329 OpType::FuncDefn(func) => self.with_local_scope(node_id, |this| {
330 let name = this.get_func_name(node).unwrap();
331 let symbol = this.export_poly_func_type(name, &func.signature);
332 regions = this.bump.alloc_slice_copy(&[this.export_dfg(
333 node,
334 model::ScopeClosure::Closed,
335 false,
336 )]);
337 table::Operation::DefineFunc(symbol)
338 }),
339
340 OpType::FuncDecl(func) => self.with_local_scope(node_id, |this| {
341 let name = this.get_func_name(node).unwrap();
342 let symbol = this.export_poly_func_type(name, &func.signature);
343 table::Operation::DeclareFunc(symbol)
344 }),
345
346 OpType::AliasDecl(alias) => self.with_local_scope(node_id, |this| {
347 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
349 let symbol = this.bump.alloc(table::Symbol {
350 name: &alias.name,
351 params: &[],
352 constraints: &[],
353 signature,
354 });
355 table::Operation::DeclareAlias(symbol)
356 }),
357
358 OpType::AliasDefn(alias) => self.with_local_scope(node_id, |this| {
359 let value = this.export_type(&alias.definition);
360 let signature = this.make_term_apply(model::CORE_TYPE, &[]);
362 let symbol = this.bump.alloc(table::Symbol {
363 name: &alias.name,
364 params: &[],
365 constraints: &[],
366 signature,
367 });
368 table::Operation::DefineAlias(symbol, value)
369 }),
370
371 OpType::Call(call) => {
372 let node = self.connected_function(node).unwrap();
374 let symbol = self.node_to_id[&node];
375 let mut args = BumpVec::new_in(self.bump);
376 args.extend(call.type_args.iter().map(|arg| self.export_type_arg(arg)));
377 let args = args.into_bump_slice();
378 let func = self.make_term(table::Term::Apply(symbol, args));
379
380 let signature = call.signature();
382 let inputs = self.export_type_row(&signature.input);
383 let outputs = self.export_type_row(&signature.output);
384 let operation = self.make_term_apply(model::CORE_CALL, &[inputs, outputs, func]);
385 table::Operation::Custom(operation)
386 }
387
388 OpType::LoadFunction(load) => {
389 let node = self.connected_function(node).unwrap();
390 let symbol = self.node_to_id[&node];
391 let mut args = BumpVec::new_in(self.bump);
392 args.extend(load.type_args.iter().map(|arg| self.export_type_arg(arg)));
393 let args = args.into_bump_slice();
394 let func = self.make_term(table::Term::Apply(symbol, args));
395 let runtime_type = self.make_term(table::Term::Wildcard);
396 let operation = self.make_term_apply(model::CORE_LOAD_CONST, &[runtime_type, func]);
397 table::Operation::Custom(operation)
398 }
399
400 OpType::Const(_) => {
401 unreachable!("const nodes are filtered out by `export_node_shallow`")
402 }
403
404 OpType::LoadConstant(_) => {
405 let const_node = self.hugr.static_source(node).unwrap();
407 let const_node_op = self.hugr.get_optype(const_node);
408
409 let OpType::Const(const_node_data) = const_node_op else {
410 panic!("expected `LoadConstant` node to be connected to a `Const` node");
411 };
412
413 let runtime_type = self.make_term(table::Term::Wildcard);
416 let value = self.export_value(&const_node_data.value);
417 let operation =
418 self.make_term_apply(model::CORE_LOAD_CONST, &[runtime_type, value]);
419 table::Operation::Custom(operation)
420 }
421
422 OpType::CallIndirect(call) => {
423 let inputs = self.export_type_row(&call.signature.input);
424 let outputs = self.export_type_row(&call.signature.output);
425 let operation = self.make_term_apply(model::CORE_CALL_INDIRECT, &[inputs, outputs]);
426 table::Operation::Custom(operation)
427 }
428
429 OpType::Tag(tag) => {
430 let variants = self.make_term(table::Term::Wildcard);
431 let types = self.make_term(table::Term::Wildcard);
432 let tag = self.make_term(model::Literal::Nat(tag.tag as u64).into());
433 let operation = self.make_term_apply(model::CORE_MAKE_ADT, &[variants, types, tag]);
434 table::Operation::Custom(operation)
435 }
436
437 OpType::TailLoop(_) => {
438 regions = self.bump.alloc_slice_copy(&[self.export_dfg(
439 node,
440 model::ScopeClosure::Open,
441 false,
442 )]);
443 table::Operation::TailLoop
444 }
445
446 OpType::Conditional(_) => {
447 regions = self.export_conditional_regions(node);
448 table::Operation::Conditional
449 }
450
451 OpType::ExtensionOp(op) => {
452 let node = self.export_opdef(op.def());
453 let params = self
454 .bump
455 .alloc_slice_fill_iter(op.args().iter().map(|arg| self.export_type_arg(arg)));
456 let operation = self.make_term(table::Term::Apply(node, params));
457 table::Operation::Custom(operation)
458 }
459
460 OpType::OpaqueOp(op) => {
461 let node = self.make_named_global_ref(op.extension(), op.unqualified_id());
462 let params = self
463 .bump
464 .alloc_slice_fill_iter(op.args().iter().map(|arg| self.export_type_arg(arg)));
465 let operation = self.make_term(table::Term::Apply(node, params));
466 table::Operation::Custom(operation)
467 }
468 };
469
470 let (signature, num_inputs, num_outputs) = match optype {
471 OpType::DataflowBlock(block) => {
472 let signature = self.export_block_signature(block);
473 (Some(signature), 1, block.sum_rows.len())
474 }
475
476 _ => match &optype.dataflow_signature() {
480 Some(signature) => {
481 let num_inputs = signature.input_types().len();
482 let num_outputs = signature.output_types().len();
483 let signature = self.export_func_type(signature);
484 (Some(signature), num_inputs, num_outputs)
485 }
486 None => (None, 0, 0),
487 },
488 };
489
490 let inputs = self.make_ports(node, Direction::Incoming, num_inputs);
491 let outputs = self.make_ports(node, Direction::Outgoing, num_outputs);
492
493 let meta = {
494 let mut meta = Vec::new();
495 self.export_node_json_metadata(node, &mut meta);
496 self.export_node_order_metadata(node, &mut meta);
497 self.bump.alloc_slice_copy(&meta)
498 };
499
500 self.module.nodes[node_id.index()] = table::Node {
501 operation,
502 inputs,
503 outputs,
504 regions,
505 meta,
506 signature,
507 };
508 }
509
510 pub fn export_opdef(&mut self, opdef: &OpDef) -> table::NodeId {
518 use std::collections::hash_map::Entry;
519
520 let poly_func_type = match opdef.signature_func() {
521 SignatureFunc::PolyFuncType(poly_func_type) => poly_func_type,
522 _ => return self.make_named_global_ref(opdef.extension_id(), opdef.name()),
523 };
524
525 let key = (opdef.extension_id().clone(), opdef.name().clone());
526 let entry = self.decl_operations.entry(key);
527
528 let node = match entry {
529 Entry::Occupied(occupied_entry) => return *occupied_entry.get(),
530 Entry::Vacant(vacant_entry) => {
531 *vacant_entry.insert(self.module.insert_node(table::Node::default()))
532 }
533 };
534
535 let symbol = self.with_local_scope(node, |this| {
536 let name = this.make_qualified_name(opdef.extension_id(), opdef.name());
537 this.export_poly_func_type(name, poly_func_type)
538 });
539
540 let meta = {
541 let description = Some(opdef.description()).filter(|d| !d.is_empty());
542 let meta_len = opdef.iter_misc().len() + description.is_some() as usize;
543 let mut meta = BumpVec::with_capacity_in(meta_len, self.bump);
544
545 if let Some(description) = description {
546 let value = self.make_term(model::Literal::Str(description.into()).into());
547 meta.push(self.make_term_apply(model::CORE_META_DESCRIPTION, &[value]));
548 }
549
550 for (name, value) in opdef.iter_misc() {
551 meta.push(self.make_json_meta(name, value));
552 }
553
554 self.bump.alloc_slice_copy(&meta)
555 };
556
557 let node_data = self.module.get_node_mut(node).unwrap();
558 node_data.operation = table::Operation::DeclareOperation(symbol);
559 node_data.meta = meta;
560
561 node
562 }
563
564 pub fn export_block_signature(&mut self, block: &DataflowBlock) -> table::TermId {
567 let inputs = {
568 let inputs = self.export_type_row(&block.inputs);
569 let inputs = self.make_term_apply(model::CORE_CTRL, &[inputs]);
570 self.make_term(table::Term::List(
571 self.bump.alloc_slice_copy(&[table::SeqPart::Item(inputs)]),
572 ))
573 };
574
575 let tail = self.export_type_row(&block.other_outputs);
576
577 let outputs = {
578 let mut outputs = BumpVec::with_capacity_in(block.sum_rows.len(), self.bump);
579 for sum_row in block.sum_rows.iter() {
580 let variant = self.export_type_row_with_tail(sum_row, Some(tail));
581 let control = self.make_term_apply(model::CORE_CTRL, &[variant]);
582 outputs.push(table::SeqPart::Item(control));
583 }
584 self.make_term(table::Term::List(outputs.into_bump_slice()))
585 };
586
587 self.make_term_apply(model::CORE_FN, &[inputs, outputs])
588 }
589
590 pub fn export_dfg(
594 &mut self,
595 node: Node,
596 closure: model::ScopeClosure,
597 export_json_meta: bool,
598 ) -> table::RegionId {
599 let region = self.module.insert_region(table::Region::default());
600
601 self.symbols.enter(region);
602 if closure == model::ScopeClosure::Closed {
603 self.links.enter(region);
604 }
605
606 let mut sources: &[_] = &[];
607 let mut targets: &[_] = &[];
608 let mut input_types = None;
609 let mut output_types = None;
610
611 let mut meta = Vec::new();
612
613 if export_json_meta {
614 self.export_node_json_metadata(node, &mut meta);
615 }
616 self.export_node_entrypoint_metadata(node, &mut meta);
617
618 let children = self.hugr.children(node);
619 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 2, self.bump);
620
621 let mut output_node = None;
622
623 for child in children {
624 match self.hugr.get_optype(child) {
625 OpType::Input(input) => {
626 sources = self.make_ports(child, Direction::Outgoing, input.types.len());
627 input_types = Some(&input.types);
628 }
629 OpType::Output(output) => {
630 targets = self.make_ports(child, Direction::Incoming, output.types.len());
631 output_types = Some(&output.types);
632 output_node = Some(child);
633 }
634 child_optype => {
635 if let Some(child_id) = self.export_node_shallow(child) {
636 region_children.push(child_id);
637
638 let successors = child_optype
640 .other_output_port()
641 .into_iter()
642 .flat_map(|port| self.hugr.linked_inputs(child, port))
643 .map(|(successor, _)| successor)
644 .filter(|successor| Some(*successor) != output_node);
645
646 for successor in successors {
647 let a =
648 self.make_term(model::Literal::Nat(child.index() as u64).into());
649 let b = self
650 .make_term(model::Literal::Nat(successor.index() as u64).into());
651 meta.push(self.make_term_apply(model::ORDER_HINT_ORDER, &[a, b]));
652 }
653 }
654 }
655 }
656 }
657
658 for child_id in ®ion_children {
659 self.export_node_deep(*child_id);
660 }
661
662 let signature = {
663 let inputs = self.export_type_row(input_types.unwrap());
664 let outputs = self.export_type_row(output_types.unwrap());
665 Some(self.make_term_apply(model::CORE_FN, &[inputs, outputs]))
666 };
667
668 let scope = match closure {
669 model::ScopeClosure::Closed => {
670 let (links, ports) = self.links.exit();
671 Some(table::RegionScope { links, ports })
672 }
673 model::ScopeClosure::Open => None,
674 };
675 self.symbols.exit();
676
677 self.module.regions[region.index()] = table::Region {
678 kind: model::RegionKind::DataFlow,
679 sources,
680 targets,
681 children: region_children.into_bump_slice(),
682 meta: self.bump.alloc_slice_copy(&meta),
683 signature,
684 scope,
685 };
686
687 region
688 }
689
690 pub fn export_cfg(&mut self, node: Node, closure: model::ScopeClosure) -> table::RegionId {
692 let region = self.module.insert_region(table::Region::default());
693 self.symbols.enter(region);
694
695 if closure == model::ScopeClosure::Closed {
696 self.links.enter(region);
697 }
698
699 let mut source = None;
700 let mut targets: &[_] = &[];
701
702 let mut meta = Vec::new();
703 self.export_node_json_metadata(node, &mut meta);
704 self.export_node_entrypoint_metadata(node, &mut meta);
705
706 let children = self.hugr.children(node);
707 let mut region_children = BumpVec::with_capacity_in(children.size_hint().0 - 1, self.bump);
708
709 for child in children {
710 match self.hugr.get_optype(child) {
711 OpType::ExitBlock(_) => {
712 targets = self.make_ports(child, Direction::Incoming, 1);
713 }
714 _ => {
715 if let Some(child_id) = self.export_node_shallow(child) {
716 region_children.push(child_id);
717 }
718
719 if source.is_none() {
720 source = Some(self.links.use_link(child, IncomingPort::from(0)));
721 }
722 }
723 }
724 }
725
726 for child_id in ®ion_children {
727 self.export_node_deep(*child_id);
728 }
729
730 let signature = {
732 let node_signature = self.hugr.signature(node).unwrap();
733
734 let mut wrap_ctrl = |types: &TypeRow| {
735 let types = self.export_type_row(types);
736 let types_ctrl = self.make_term_apply(model::CORE_CTRL, &[types]);
737 self.make_term(table::Term::List(
738 self.bump
739 .alloc_slice_copy(&[table::SeqPart::Item(types_ctrl)]),
740 ))
741 };
742
743 let inputs = wrap_ctrl(node_signature.input());
744 let outputs = wrap_ctrl(node_signature.output());
745 Some(self.make_term_apply(model::CORE_FN, &[inputs, outputs]))
746 };
747
748 let scope = match closure {
749 model::ScopeClosure::Closed => {
750 let (links, ports) = self.links.exit();
751 Some(table::RegionScope { links, ports })
752 }
753 model::ScopeClosure::Open => None,
754 };
755 self.symbols.exit();
756
757 self.module.regions[region.index()] = table::Region {
758 kind: model::RegionKind::ControlFlow,
759 sources: self.bump.alloc_slice_copy(&[source.unwrap()]),
760 targets,
761 children: region_children.into_bump_slice(),
762 meta: self.bump.alloc_slice_copy(&meta),
763 signature,
764 scope,
765 };
766
767 region
768 }
769
770 pub fn export_conditional_regions(&mut self, node: Node) -> &'a [table::RegionId] {
772 let children = self.hugr.children(node);
773 let mut regions = BumpVec::with_capacity_in(children.size_hint().0, self.bump);
774
775 for child in children {
776 let OpType::Case(_) = self.hugr.get_optype(child) else {
777 panic!("expected a `Case` node as a child of a `Conditional` node");
778 };
779
780 regions.push(self.export_dfg(child, model::ScopeClosure::Open, true));
781 }
782
783 regions.into_bump_slice()
784 }
785
786 pub fn export_poly_func_type<RV: MaybeRV>(
788 &mut self,
789 name: &'a str,
790 t: &PolyFuncTypeBase<RV>,
791 ) -> &'a table::Symbol<'a> {
792 let mut params = BumpVec::with_capacity_in(t.params().len(), self.bump);
793 let scope = self
794 .local_scope
795 .expect("exporting poly func type outside of local scope");
796
797 for (i, param) in t.params().iter().enumerate() {
798 let name = self.bump.alloc_str(&i.to_string());
799 let r#type = self.export_type_param(param, Some((scope, i as _)));
800 let param = table::Param { name, r#type };
801 params.push(param)
802 }
803
804 let constraints = self.bump.alloc_slice_copy(&self.local_constraints);
805 let body = self.export_func_type(t.body());
806
807 self.bump.alloc(table::Symbol {
808 name,
809 params: params.into_bump_slice(),
810 constraints,
811 signature: body,
812 })
813 }
814
815 pub fn export_type<RV: MaybeRV>(&mut self, t: &TypeBase<RV>) -> table::TermId {
816 self.export_type_enum(t.as_type_enum())
817 }
818
819 pub fn export_type_enum<RV: MaybeRV>(&mut self, t: &TypeEnum<RV>) -> table::TermId {
820 match t {
821 TypeEnum::Extension(ext) => self.export_custom_type(ext),
822 TypeEnum::Alias(alias) => {
823 let symbol = self.resolve_symbol(self.bump.alloc_str(alias.name()));
824 self.make_term(table::Term::Apply(symbol, &[]))
825 }
826 TypeEnum::Function(func) => self.export_func_type(func),
827 TypeEnum::Variable(index, _) => {
828 let node = self.local_scope.expect("local variable out of scope");
829 self.make_term(table::Term::Var(table::VarId(node, *index as _)))
830 }
831 TypeEnum::RowVar(rv) => self.export_row_var(rv.as_rv()),
832 TypeEnum::Sum(sum) => self.export_sum_type(sum),
833 }
834 }
835
836 pub fn export_func_type<RV: MaybeRV>(&mut self, t: &FuncTypeBase<RV>) -> table::TermId {
837 let inputs = self.export_type_row(t.input());
838 let outputs = self.export_type_row(t.output());
839 self.make_term_apply(model::CORE_FN, &[inputs, outputs])
840 }
841
842 pub fn export_custom_type(&mut self, t: &CustomType) -> table::TermId {
843 let symbol = self.make_named_global_ref(t.extension(), t.name());
844
845 let args = self
846 .bump
847 .alloc_slice_fill_iter(t.args().iter().map(|p| self.export_type_arg(p)));
848 let term = table::Term::Apply(symbol, args);
849 self.make_term(term)
850 }
851
852 pub fn export_type_arg(&mut self, t: &TypeArg) -> table::TermId {
853 match t {
854 TypeArg::Type { ty } => self.export_type(ty),
855 TypeArg::BoundedNat { n } => self.make_term(model::Literal::Nat(*n).into()),
856 TypeArg::String { arg } => self.make_term(model::Literal::Str(arg.into()).into()),
857 TypeArg::Sequence { elems } => {
858 let parts = self.bump.alloc_slice_fill_iter(
860 elems
861 .iter()
862 .map(|elem| table::SeqPart::Item(self.export_type_arg(elem))),
863 );
864 self.make_term(table::Term::List(parts))
865 }
866 TypeArg::Variable { v } => self.export_type_arg_var(v),
867 }
868 }
869
870 pub fn export_type_arg_var(&mut self, var: &TypeArgVariable) -> 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 = BumpVec::with_capacity_in(row.len() + tail.is_some() as usize, self.bump);
915
916 for t in row.iter() {
917 match t.as_type_enum() {
918 TypeEnum::RowVar(var) => {
919 parts.push(table::SeqPart::Splice(self.export_row_var(var.as_rv())));
920 }
921 _ => {
922 parts.push(table::SeqPart::Item(self.export_type(t)));
923 }
924 }
925 }
926
927 if let Some(tail) = tail {
928 parts.push(table::SeqPart::Splice(tail));
929 }
930
931 let parts = parts.into_bump_slice();
932 self.make_term(table::Term::List(parts))
933 }
934
935 pub fn export_type_param(
942 &mut self,
943 t: &TypeParam,
944 var: Option<(table::NodeId, table::VarIndex)>,
945 ) -> table::TermId {
946 match t {
947 TypeParam::Type { b } => {
948 if let (Some((node, index)), TypeBound::Copyable) = (var, b) {
949 let term = self.make_term(table::Term::Var(table::VarId(node, index)));
950 let non_linear = self.make_term_apply(model::CORE_NON_LINEAR, &[term]);
951 self.local_constraints.push(non_linear);
952 }
953
954 self.make_term_apply(model::CORE_TYPE, &[])
955 }
956 TypeParam::BoundedNat { .. } => self.make_term_apply(model::CORE_NAT_TYPE, &[]),
958 TypeParam::String => self.make_term_apply(model::CORE_STR_TYPE, &[]),
959 TypeParam::List { param } => {
960 let item_type = self.export_type_param(param, None);
961 self.make_term_apply(model::CORE_LIST_TYPE, &[item_type])
962 }
963 TypeParam::Tuple { params } => {
964 let parts = self.bump.alloc_slice_fill_iter(
965 params
966 .iter()
967 .map(|param| table::SeqPart::Item(self.export_type_param(param, None))),
968 );
969 let types = self.make_term(table::Term::List(parts));
970 self.make_term_apply(model::CORE_TUPLE_TYPE, &[types])
971 }
972 }
973 }
974
975 fn export_value(&mut self, value: &'a Value) -> table::TermId {
976 match value {
977 Value::Extension { e } => {
978 if let Some(array) = e.value().downcast_ref::<ArrayValue>() {
982 let len = self
983 .make_term(model::Literal::Nat(array.get_contents().len() as u64).into());
984 let element_type = self.export_type(array.get_element_type());
985 let mut contents =
986 BumpVec::with_capacity_in(array.get_contents().len(), self.bump);
987
988 for element in array.get_contents() {
989 contents.push(table::SeqPart::Item(self.export_value(element)));
990 }
991
992 let contents = self.make_term(table::Term::List(contents.into_bump_slice()));
993
994 let symbol = self.resolve_symbol(ArrayValue::CTR_NAME);
995 let args = self.bump.alloc_slice_copy(&[len, element_type, contents]);
996 return self.make_term(table::Term::Apply(symbol, args));
997 }
998
999 if let Some(v) = e.value().downcast_ref::<ConstInt>() {
1000 let bitwidth = self.make_term(model::Literal::Nat(v.log_width() as u64).into());
1001 let literal = self.make_term(model::Literal::Nat(v.value_u()).into());
1002
1003 let symbol = self.resolve_symbol(ConstInt::CTR_NAME);
1004 let args = self.bump.alloc_slice_copy(&[bitwidth, literal]);
1005 return self.make_term(table::Term::Apply(symbol, args));
1006 }
1007
1008 if let Some(v) = e.value().downcast_ref::<ConstF64>() {
1009 let literal = self.make_term(model::Literal::Float(v.value().into()).into());
1010 let symbol = self.resolve_symbol(ConstF64::CTR_NAME);
1011 let args = self.bump.alloc_slice_copy(&[literal]);
1012 return self.make_term(table::Term::Apply(symbol, args));
1013 }
1014
1015 let json = match e.value().downcast_ref::<CustomSerialized>() {
1016 Some(custom) => serde_json::to_string(custom.value()).unwrap(),
1017 None => serde_json::to_string(e.value())
1018 .expect("custom extension values should be serializable"),
1019 };
1020
1021 let json = self.make_term(model::Literal::Str(json.into()).into());
1022 let runtime_type = self.export_type(&e.get_type());
1023 let args = self.bump.alloc_slice_copy(&[runtime_type, json]);
1024 let symbol = self.resolve_symbol(model::COMPAT_CONST_JSON);
1025 self.make_term(table::Term::Apply(symbol, args))
1026 }
1027
1028 Value::Function { hugr } => {
1029 let outer_hugr = std::mem::replace(&mut self.hugr, hugr);
1030 let outer_node_to_id = std::mem::take(&mut self.node_to_id);
1031
1032 let region = match hugr.entrypoint_optype() {
1033 OpType::DFG(_) => {
1034 self.export_dfg(hugr.entrypoint(), model::ScopeClosure::Closed, true)
1035 }
1036 _ => panic!("Value::Function root must be a DFG"),
1037 };
1038
1039 self.node_to_id = outer_node_to_id;
1040 self.hugr = outer_hugr;
1041
1042 self.make_term(table::Term::Func(region))
1043 }
1044
1045 Value::Sum(sum) => {
1046 let variants = self.export_sum_variants(&sum.sum_type);
1047 let types = self.make_term(table::Term::Wildcard);
1048 let tag = self.make_term(model::Literal::Nat(sum.tag as u64).into());
1049
1050 let values = {
1051 let mut values = BumpVec::with_capacity_in(sum.values.len(), self.bump);
1052
1053 for value in &sum.values {
1054 values.push(table::SeqPart::Item(self.export_value(value)));
1055 }
1056
1057 self.make_term(table::Term::Tuple(values.into_bump_slice()))
1058 };
1059
1060 self.make_term_apply(model::CORE_CONST_ADT, &[variants, types, tag, values])
1061 }
1062 }
1063 }
1064
1065 fn export_node_json_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1066 let metadata_map = self.hugr.node_metadata_map(node);
1067 meta.reserve(metadata_map.len());
1068
1069 for (name, value) in metadata_map {
1070 meta.push(self.make_json_meta(name, value));
1071 }
1072 }
1073
1074 fn export_node_order_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1075 fn is_relevant_node(hugr: &Hugr, node: Node) -> bool {
1076 let optype = hugr.get_optype(node);
1077 !optype.is_input() && !optype.is_output()
1078 }
1079
1080 let optype = self.hugr.get_optype(node);
1081
1082 let has_order_edges = Direction::BOTH
1083 .iter()
1084 .filter(|dir| optype.other_port_kind(**dir) == Some(EdgeKind::StateOrder))
1085 .filter_map(|dir| optype.other_port(*dir))
1086 .flat_map(|port| self.hugr.linked_ports(node, port))
1087 .any(|(other, _)| is_relevant_node(self.hugr, other));
1088
1089 if has_order_edges {
1090 let key = self.make_term(model::Literal::Nat(node.index() as u64).into());
1091 meta.push(self.make_term_apply(model::ORDER_HINT_KEY, &[key]));
1092 }
1093 }
1094
1095 fn export_node_entrypoint_metadata(&mut self, node: Node, meta: &mut Vec<table::TermId>) {
1096 if self.hugr.entrypoint() == node {
1097 meta.push(self.make_term_apply(model::CORE_ENTRYPOINT, &[]));
1098 }
1099 }
1100
1101 pub fn make_json_meta(&mut self, name: &str, value: &serde_json::Value) -> table::TermId {
1102 let value = serde_json::to_string(value).expect("json values are always serializable");
1103 let value = self.make_term(model::Literal::Str(value.into()).into());
1104 let name = self.make_term(model::Literal::Str(name.into()).into());
1105 self.make_term_apply(model::COMPAT_META_JSON, &[name, value])
1106 }
1107
1108 fn resolve_symbol(&mut self, name: &'a str) -> table::NodeId {
1109 let result = self.symbols.resolve(name);
1110
1111 match result {
1112 Ok(node) => node,
1113 Err(_) => *self.implicit_imports.entry(name).or_insert_with(|| {
1114 self.module.insert_node(table::Node {
1115 operation: table::Operation::Import { name },
1116 ..table::Node::default()
1117 })
1118 }),
1119 }
1120 }
1121
1122 fn make_term_apply(&mut self, name: &'a str, args: &[table::TermId]) -> table::TermId {
1123 let symbol = self.resolve_symbol(name);
1124 let args = self.bump.alloc_slice_copy(args);
1125 self.make_term(table::Term::Apply(symbol, args))
1126 }
1127}
1128
1129type FxIndexSet<T> = indexmap::IndexSet<T, FxBuildHasher>;
1130
1131struct Links {
1134 scope: model::scope::LinkTable<u32>,
1137
1138 groups: FxHashMap<(Node, Port), u32>,
1140}
1141
1142impl Links {
1143 pub fn new(hugr: &Hugr) -> Self {
1146 let scope = model::scope::LinkTable::new();
1147
1148 let node_ports: FxIndexSet<(Node, Port)> = hugr
1151 .nodes()
1152 .flat_map(|node| hugr.all_node_ports(node).map(move |port| (node, port)))
1153 .collect();
1154
1155 let mut uf = UnionFind::<u32>::new(node_ports.len());
1157
1158 for (i, (node, port)) in node_ports.iter().enumerate() {
1159 if let Ok(port) = port.as_incoming() {
1160 for (other_node, other_port) in hugr.linked_outputs(*node, port) {
1161 let other_port = Port::from(other_port);
1162 let j = node_ports.get_index_of(&(other_node, other_port)).unwrap();
1163 uf.union(i as u32, j as u32);
1164 }
1165 }
1166 }
1167
1168 let groups = node_ports
1170 .into_iter()
1171 .enumerate()
1172 .map(|(i, node_port)| (node_port, uf.find(i as u32)))
1173 .collect();
1174
1175 Self { scope, groups }
1176 }
1177
1178 pub fn enter(&mut self, region: table::RegionId) {
1180 self.scope.enter(region);
1181 }
1182
1183 pub fn exit(&mut self) -> (u32, u32) {
1189 self.scope.exit()
1190 }
1191
1192 pub fn use_link(&mut self, node: Node, port: impl Into<Port>) -> table::LinkIndex {
1198 let port = port.into();
1199 let group = self.groups[&(node, port)];
1200 self.scope.use_link(group)
1201 }
1202}
1203
1204#[cfg(test)]
1205mod test {
1206 use rstest::{fixture, rstest};
1207
1208 use crate::{
1209 builder::{Dataflow, DataflowSubContainer},
1210 extension::prelude::qb_t,
1211 types::Signature,
1212 utils::test_quantum_extension::{cx_gate, h_gate},
1213 Hugr,
1214 };
1215
1216 #[fixture]
1217 fn test_simple_circuit() -> Hugr {
1218 crate::builder::test::build_main(
1219 Signature::new_endo(vec![qb_t(), qb_t()]).into(),
1220 |mut f_build| {
1221 let wires: Vec<_> = f_build.input_wires().collect();
1222 let mut linear = f_build.as_circuit(wires);
1223
1224 assert_eq!(linear.n_wires(), 2);
1225
1226 linear
1227 .append(h_gate(), [0])?
1228 .append(cx_gate(), [0, 1])?
1229 .append(cx_gate(), [1, 0])?;
1230
1231 let outs = linear.finish();
1232 f_build.finish_with_outputs(outs)
1233 },
1234 )
1235 .unwrap()
1236 }
1237
1238 #[rstest]
1239 #[case(test_simple_circuit())]
1240 fn test_export(#[case] hugr: Hugr) {
1241 use hugr_model::v0::bumpalo::Bump;
1242 let bump = Bump::new();
1243 let _model = super::export_hugr(&hugr, &bump);
1244 }
1245}