Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 53 additions & 13 deletions hugr-core/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Exporting HUGR graphs to their `hugr-model` representation.
use crate::Visibility;
use crate::extension::ExtensionRegistry;
use crate::hugr::internal::HugrInternals;
use crate::types::type_param::Term;
Expand All @@ -20,13 +21,14 @@ use crate::{
};

use fxhash::{FxBuildHasher, FxHashMap};
use hugr_model::v0::Visibility;
use hugr_model::v0::bumpalo;
use hugr_model::v0::{
self as model,
bumpalo::{Bump, collections::String as BumpString, collections::Vec as BumpVec},
table,
};
use petgraph::unionfind::UnionFind;
use smol_str::ToSmolStr;
use std::fmt::Write;

/// Exports a deconstructed `Package` to its representation in the model.
Expand Down Expand Up @@ -95,7 +97,7 @@ struct Context<'a> {
// that ensures that the `node_to_id` and `id_to_node` maps stay in sync.
}

const NO_VIS: Option<Visibility> = None;
const NO_VIS: Option<model::Visibility> = None;

impl<'a> Context<'a> {
pub fn new(hugr: &'a Hugr, bump: &'a Bump) -> Self {
Expand Down Expand Up @@ -261,8 +263,12 @@ impl<'a> Context<'a> {

// We record the name of the symbol defined by the node, if any.
let symbol = match optype {
OpType::FuncDefn(func_defn) => Some(func_defn.func_name().as_str()),
OpType::FuncDecl(func_decl) => Some(func_decl.func_name().as_str()),
OpType::FuncDefn(_) | OpType::FuncDecl(_) => {
// Functions aren't exported using their core name but with a mangled
// name derived from their id. The function's core name will be recorded
// using `core.title` metadata.
Some(self.mangled_name(node))
}
OpType::AliasDecl(alias_decl) => Some(alias_decl.name.as_str()),
OpType::AliasDefn(alias_defn) => Some(alias_defn.name.as_str()),
_ => None,
Expand All @@ -282,6 +288,7 @@ impl<'a> Context<'a> {
// the node id. This is necessary to establish the correct node id for the
// local scope introduced by some operations. We will overwrite this node later.
let mut regions: &[_] = &[];
let mut meta = Vec::new();

let node = self.id_to_node[&node_id];
let optype = self.hugr.get_optype(node);
Expand Down Expand Up @@ -331,8 +338,10 @@ impl<'a> Context<'a> {
}

OpType::FuncDefn(func) => self.with_local_scope(node_id, |this| {
let symbol_name = this.export_func_name(node, &mut meta);

let symbol = this.export_poly_func_type(
func.func_name(),
symbol_name,
Some(func.visibility().clone().into()),
func.signature(),
);
Expand All @@ -345,8 +354,10 @@ impl<'a> Context<'a> {
}),

OpType::FuncDecl(func) => self.with_local_scope(node_id, |this| {
let symbol_name = this.export_func_name(node, &mut meta);

let symbol = this.export_poly_func_type(
func.func_name(),
symbol_name,
Some(func.visibility().clone().into()),
func.signature(),
);
Expand Down Expand Up @@ -502,12 +513,9 @@ impl<'a> Context<'a> {
let inputs = self.make_ports(node, Direction::Incoming, num_inputs);
let outputs = self.make_ports(node, Direction::Outgoing, num_outputs);

let meta = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why you've moved the definition of meta higher up, it doesn't seem that you're using it. I personally prefer the old definition using a scoped block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using it: in the match on the operation, the function definition or declaration branches can push to the metadata.

let mut meta = Vec::new();
self.export_node_json_metadata(node, &mut meta);
self.export_node_order_metadata(node, &mut meta);
self.bump.alloc_slice_copy(&meta)
};
self.export_node_json_metadata(node, &mut meta);
self.export_node_order_metadata(node, &mut meta);
let meta = self.bump.alloc_slice_copy(&meta);

self.module.nodes[node_id.index()] = table::Node {
operation,
Expand Down Expand Up @@ -803,7 +811,7 @@ impl<'a> Context<'a> {
pub fn export_poly_func_type<RV: MaybeRV>(
&mut self,
name: &'a str,
visibility: Option<Visibility>,
visibility: Option<model::Visibility>,
t: &PolyFuncTypeBase<RV>,
) -> &'a table::Symbol<'a> {
let mut params = BumpVec::with_capacity_in(t.params().len(), self.bump);
Expand Down Expand Up @@ -1121,6 +1129,33 @@ impl<'a> Context<'a> {
}
}

/// Used when exporting function definitions or declarations. When the
/// function is public, its symbol name will be the core name. For private
/// functions, the symbol name is derived from the node id and the core name
/// is exported as `core.title` metadata.
///
/// This is a hack, necessary due to core names for functions being
/// non-functional. Once functions have a "link name", that should be used as the symbol name here.
fn export_func_name(&mut self, node: Node, meta: &mut Vec<table::TermId>) -> &'a str {
let (name, vis) = match self.hugr.get_optype(node) {
OpType::FuncDefn(func_defn) => (func_defn.func_name(), func_defn.visibility()),
OpType::FuncDecl(func_decl) => (func_decl.func_name(), func_decl.visibility()),
_ => panic!(
"`export_func_name` is only supposed to be used on function declarations and definitions"
),
};

match vis {
Visibility::Public => name,
Visibility::Private => {
let literal =
self.make_term(table::Term::Literal(model::Literal::Str(name.to_smolstr())));
meta.push(self.make_term_apply(model::CORE_TITLE, &[literal]));
self.mangled_name(node)
}
}
}

pub fn make_json_meta(&mut self, name: &str, value: &serde_json::Value) -> table::TermId {
let value = serde_json::to_string(value).expect("json values are always serializable");
let value = self.make_term(model::Literal::Str(value.into()).into());
Expand All @@ -1147,6 +1182,11 @@ impl<'a> Context<'a> {
let args = self.bump.alloc_slice_copy(args);
self.make_term(table::Term::Apply(symbol, args))
}

/// Creates a mangled name for a particular node.
fn mangled_name(&self, node: Node) -> &'a str {
bumpalo::format!(in &self.bump, "_{}", node.index()).into_bump_str()
}
}

type FxIndexSet<T> = indexmap::IndexSet<T, FxBuildHasher>;
Expand Down
32 changes: 30 additions & 2 deletions hugr-core/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,8 +970,10 @@ impl<'a> Context<'a> {
"No visibility for FuncDefn".to_string(),
))?;
self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
let func_name = ctx.import_title_metadata(node_id)?.unwrap_or(symbol.name);

let optype =
OpType::FuncDefn(FuncDefn::new_vis(symbol.name, signature, visibility.into()));
OpType::FuncDefn(FuncDefn::new_vis(func_name, signature, visibility.into()));

let node = ctx.make_node(node_id, optype, parent)?;

Expand Down Expand Up @@ -999,8 +1001,10 @@ impl<'a> Context<'a> {
"No visibility for FuncDecl".to_string(),
))?;
self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
let func_name = ctx.import_title_metadata(node_id)?.unwrap_or(symbol.name);

let optype =
OpType::FuncDecl(FuncDecl::new_vis(symbol.name, signature, visibility.into()));
OpType::FuncDecl(FuncDecl::new_vis(func_name, signature, visibility.into()));
let node = ctx.make_node(node_id, optype, parent)?;
Ok(node)
})
Expand Down Expand Up @@ -1817,6 +1821,30 @@ impl<'a> Context<'a> {
N
))
}

/// Searches for `core.title` metadata on the given node.
fn import_title_metadata(
&self,
node_id: table::NodeId,
) -> Result<Option<&'a str>, ImportError> {
let node_data = self.get_node(node_id)?;
for meta in node_data.meta {
let Some([name]) = self.match_symbol(*meta, model::CORE_TITLE)? else {
continue;
};

let table::Term::Literal(model::Literal::Str(name)) = self.get_term(name)? else {
return Err(error_invalid!(
"`{}` metadata expected a string literal as argument",
model::CORE_TITLE
));
};

return Ok(Some(name.as_str()));
}

Ok(None)
}
}

/// Information about a local variable.
Expand Down
4 changes: 2 additions & 2 deletions hugr-core/tests/snapshots/model__roundtrip_call.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ expression: ast
(meta (compat.meta_json "title" "\"Callee\"")))

(define-func
private
public
example.caller
(core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])
(meta
Expand All @@ -43,7 +43,7 @@ expression: ast
(core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])))))

(define-func
private
public
example.load
(core.fn [] [(core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])])
(dfg [] [%0]
Expand Down
2 changes: 1 addition & 1 deletion hugr-core/tests/snapshots/model__roundtrip_cfg.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ expression: ast

(import core.adt)

(define-func private example.cfg_loop (param ?0 core.type) (core.fn [?0] [?0])
(define-func public example.cfg_loop (param ?0 core.type) (core.fn [?0] [?0])
(dfg [%0] [%1]
(signature (core.fn [?0] [?0]))
(cfg [%0] [%1]
Expand Down
2 changes: 1 addition & 1 deletion hugr-core/tests/snapshots/model__roundtrip_cond.snap
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ expression: ast
"negation modulo 2^N (signed and unsigned versions are the same op)")))

(define-func
private
public
example.cond
(core.fn
[(core.adt [[] []]) (arithmetic.int.types.int 6)]
Expand Down
11 changes: 7 additions & 4 deletions hugr-core/tests/snapshots/model__roundtrip_constraints.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ expression: ast

(import core.nat)

(import core.type)

(import core.nonlinear)

(import core.type)

(import core.fn)

(import core.title)

(declare-func
private
array.replicate
_1
(param ?0 core.nat)
(param ?1 core.type)
(where (core.nonlinear ?1))
(core.fn [?1] [(collections.array.array ?0 ?1)]))
(core.fn [?1] [(collections.array.array ?0 ?1)])
(meta (core.title "array.replicate")))

(declare-func
public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ expression: ast

(import core.entrypoint)

(define-func private wrapper_dfg (core.fn [] [])
(define-func public wrapper_dfg (core.fn [] [])
(dfg (signature (core.fn [] [])) (meta core.entrypoint)))

(mod)
Expand Down
5 changes: 4 additions & 1 deletion hugr-core/tests/snapshots/model__roundtrip_loop.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ expression: ast

(import core.adt)

(define-func private example.loop (param ?0 core.type) (core.fn [?0] [?0])
(import core.title)

(define-func private _1 (param ?0 core.type) (core.fn [?0] [?0])
(meta (core.title "example.loop"))
(dfg [%0] [%1]
(signature (core.fn [?0] [?0]))
(tail-loop [%0] [%1]
Expand Down
13 changes: 8 additions & 5 deletions hugr-core/tests/snapshots/model__roundtrip_params.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ expression: ast

(mod)

(import core.bytes)

(import core.nat)

(import core.call)

(import core.type)

(import core.bytes)

(import core.nat)

(import core.fn)

(import core.str)

(import core.float)

(import core.title)

(define-func
public
example.swap
Expand All @@ -37,7 +39,8 @@ expression: ast
(param ?3 core.float)
(core.fn [] []))

(define-func private example.call_literals (core.fn [] [])
(define-func private _5 (core.fn [] [])
(meta (core.title "example.call_literals"))
(dfg
(signature (core.fn [] []))
((core.call
Expand Down
12 changes: 12 additions & 0 deletions hugr-model/src/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,18 @@ pub const ORDER_HINT_OUTPUT_KEY: &str = "core.order_hint.output_key";
/// - **Result:** `core.meta`
pub const ORDER_HINT_ORDER: &str = "core.order_hint.order";

/// Metadata constructor for symbol titles.
///
/// The names of functions in `hugr-core` are currently not used for symbol
/// resolution, but rather serve as a short description of the function.
/// As such, there is no requirement for uniqueness or formatting.
/// This metadata can be used to preserve that name when serializing through
/// `hugr-model`.
///
/// - **Parameter:** `?title: core.str`
/// - **Result:** `core.meta`
pub const CORE_TITLE: &str = "core.title";

pub mod ast;
pub mod binary;
pub mod scope;
Expand Down
4 changes: 2 additions & 2 deletions hugr-model/tests/fixtures/model-call.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
(meta (compat.meta_json "title" "\"Callee\""))
(meta (compat.meta_json "description" "\"This is a function declaration.\"")))

(define-func private example.caller
(define-func public example.caller
(core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])
(meta (compat.meta_json "title" "\"Caller\""))
(meta (compat.meta_json "description" "\"This defines a function that calls the function which we declared earlier.\""))
Expand All @@ -17,7 +17,7 @@
((core.call _ _ example.callee) [%3] [%4]
(signature (core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])))))

(define-func private
(define-func public
example.load
(core.fn [] [(core.fn [arithmetic.int.types.int] [arithmetic.int.types.int])])
(dfg
Expand Down
2 changes: 1 addition & 1 deletion hugr-model/tests/fixtures/model-cfg.edn
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

(mod)

(define-func private example.cfg_loop
(define-func public example.cfg_loop
(param ?a core.type)
(core.fn [?a] [?a])
(dfg [%0] [%1]
Expand Down
2 changes: 1 addition & 1 deletion hugr-model/tests/fixtures/model-cond.edn
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

(mod)

(define-func private
(define-func public
example.cond
(core.fn
[(core.adt [[] []]) (arithmetic.int.types.int 6)]
Expand Down
2 changes: 1 addition & 1 deletion hugr-model/tests/fixtures/model-entrypoint.edn
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

(mod)

(define-func private wrapper_dfg
(define-func public wrapper_dfg
(core.fn [] [])
(dfg [] []
(signature (core.fn [] []))
Expand Down
Loading
Loading