Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
47b8f66
Dump debug info in hugr-cli
cgh-qtnm Mar 10, 2026
82fc4b2
Make it compile and pass existing tests
cgh-qtnm Mar 25, 2026
435a456
Work on 2qb
cgh-qtnm Apr 7, 2026
5e9e3c7
Working 2qb LLVM debug info
cgh-qtnm Apr 8, 2026
e2f837f
Working O0 debug info in binary
cgh-qtnm Apr 8, 2026
5f3419f
Add support for opaque pointers
cgh-qtnm Apr 10, 2026
1220995
Generate pretty function names
cgh-qtnm Apr 13, 2026
26db7ea
Implement random debug info testing
cgh-qtnm Apr 14, 2026
cf9e51d
Add random debug info to snapshots
cgh-qtnm Apr 15, 2026
f848fdc
Refactor test code
cgh-qtnm Apr 15, 2026
1485b15
Correctly handle compiler-generated wrapper
cgh-qtnm Apr 15, 2026
54b5a97
Make add_random_debug_info public
cgh-qtnm Apr 15, 2026
27f4fd1
Get pointer size from caller
cgh-qtnm Apr 16, 2026
84226c6
hugr-llvm: add unit tests for debug info generation
cgh-qtnm Apr 16, 2026
a4bdfc8
hugr-llvm: add error path tests for debug info generation
cgh-qtnm Apr 16, 2026
3376256
hugr-llvm: simplify debug info tests
cgh-qtnm Apr 16, 2026
8c3c2cf
Fixes from copilot
cgh-qtnm Apr 17, 2026
2feac5a
Documentation
cgh-qtnm Apr 17, 2026
18f1fb4
hugr-llvm: test failure on invalid debug info JSON schema
cgh-qtnm Apr 17, 2026
6a0e206
Fixes from copilot and self
cgh-qtnm Apr 17, 2026
dae040b
fix(hugr-persistent): handle NodeLabel::MetadataKey in mermaid_string…
cgh-qtnm Apr 17, 2026
4ebb9b8
hugr-llvm: extract unmangle_hugr_func_name and add unit tests
cgh-qtnm Apr 20, 2026
3bd16c6
Add 'notail' to all HUGR function calls
cgh-qtnm Apr 21, 2026
2d59311
Revert "Add 'notail' to all HUGR function calls"
cgh-qtnm Apr 22, 2026
08bddaf
Document and restructure hugr-core::metadata::debug_info
cgh-qtnm Apr 22, 2026
6aa4bf1
Minor fixes from Augustin:
cgh-qtnm Apr 22, 2026
5413ea7
Conform to updates in guppylang metadata gen
cgh-qtnm Apr 22, 2026
e0b44be
Enhance metadata node labels to print a list of keys
cgh-qtnm Apr 22, 2026
60f1585
Move try_get_metadata into HugrView implementation
cgh-qtnm Apr 29, 2026
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
42 changes: 41 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions hugr-cli/src/mermaid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use anyhow::Result;
use clap::Parser;
use clio::Output;
use hugr::HugrView;
use hugr::hugr::views::render::NodeLabel;
use hugr::metadata::debug_info::DEBUGINFO_META_KEY;
use hugr::package::PackageValidationError;

/// Dump the standard extensions.
Expand All @@ -26,6 +28,16 @@ pub struct MermaidArgs {
help = "Validate before rendering, includes extension inference."
)]
pub validate: bool,

/// Print debug metadata.
#[arg(
short = 'D',
long,
help = "Print debug info attached to nodes if it exists. For rendering purposes, \
we replace double quotes with single quotes and newlines with spaces."
)]
pub debug_info: bool,

/// Output file '-' for stdout
#[clap(long, short, value_parser, default_value = "-")]
output: Output,
Expand Down Expand Up @@ -64,10 +76,17 @@ impl MermaidArgs {
}

for hugr in package.modules {
let mmd_fmt = if self.debug_info {
hugr.mermaid_format()
.with_node_labels(NodeLabel::MetadataKey(DEBUGINFO_META_KEY.to_string()))
} else {
hugr.mermaid_format()
};

if let Some(ref mut writer) = output_override {
writeln!(writer, "{}", hugr.mermaid_string())?;
writeln!(writer, "{}", mmd_fmt.finish())?;
} else {
writeln!(self.output, "{}", hugr.mermaid_string())?;
writeln!(self.output, "{}", mmd_fmt.finish())?;
}
}
Ok(())
Expand Down
4 changes: 4 additions & 0 deletions hugr-core/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ impl Node {
pub(crate) fn into_portgraph(self) -> portgraph::NodeIndex {
self.index
}

pub(crate) fn from_portgraph(index: portgraph::NodeIndex) -> Self {
Self { index }
}
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated
}

impl Port {
Expand Down
47 changes: 36 additions & 11 deletions hugr-core/src/hugr/views/render.rs
Comment thread
cgh-qtnm marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub enum NodeLabel<N: HugrNode = Node> {
/// Display the node index as a number.
#[default]
Numeric,
/// Display the node index and JSON metadata for a given key.
MetadataKey(String),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Document what is the string doing here.

  • If we're printing metadata, why not print more than one entry? The python renders metadata entries one-per line. Could we have something similar? Perhaps we want a "print all entries" option and a "print these white-listed ones".

Image
  • It'd be easier to read if this used struct variants;
MetadataKey {
  metadata_entries: Vec<String>,
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll extend the functionality as you described.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, looks like adding the ability to enumerate the metadata map to print all entries would perturb a lot of interfaces so I won't do that. But taking a list of keys is easy.

/// Display the labels corresponding to the node indices.
Custom(HashMap<N, String>),
}
Expand All @@ -196,6 +198,14 @@ pub(in crate::hugr) fn node_style<'a>(
}
}

fn numeric_label(h: &Hugr, n: NodeIndex, is_entry: bool) -> String {
if is_entry {
format!("({}) [**{}**]", n.index(), node_name(h, n))
} else {
format!("({}) {}", n.index(), node_name(h, n))
}
}

let mut entrypoint_style = PresentationStyle::default();
entrypoint_style.stroke = Some("#832561".to_string());
entrypoint_style.stroke_width = Some("3px".to_string());
Expand All @@ -204,18 +214,9 @@ pub(in crate::hugr) fn node_style<'a>(
match formatter.node_labels {
NodeLabel::Numeric => Box::new(move |n| {
if Some(n) == entrypoint {
NodeStyle::boxed(format!(
"({ni}) [**{name}**]",
ni = n.index(),
name = node_name(h, n)
))
.with_attrs(entrypoint_style.clone())
NodeStyle::boxed(numeric_label(h, n, true)).with_attrs(entrypoint_style.clone())
} else {
NodeStyle::boxed(format!(
"({ni}) {name}",
ni = n.index(),
name = node_name(h, n)
))
NodeStyle::boxed(numeric_label(h, n, false))
}
}),
NodeLabel::None => Box::new(move |n| {
Expand All @@ -226,6 +227,30 @@ pub(in crate::hugr) fn node_style<'a>(
NodeStyle::boxed(node_name(h, n))
}
}),
NodeLabel::MetadataKey(label) => Box::new(move |n| {
let metadata = serde_json::to_string(
h.get_metadata_any(Node::from_portgraph(n), label.clone())
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated
.unwrap_or(&serde_json::Value::Null),
)
.expect("Could not render JSON metadata");
// mermaid renderer in portgraph does not like double quotes or newlines
let metadata_clean = metadata.replace('\n', " ").replace('"', "\'");

if Some(n) == entrypoint {
NodeStyle::boxed(format!(
"{} <{}>",
numeric_label(h, n, true),
metadata_clean
))
.with_attrs(entrypoint_style.clone())
} else {
NodeStyle::boxed(format!(
"{} <{}>",
numeric_label(h, n, false),
metadata_clean
))
}
}),
NodeLabel::Custom(labels) => Box::new(move |n| {
if Some(n) == entrypoint {
NodeStyle::boxed(format!(
Expand Down
1 change: 1 addition & 0 deletions hugr-core/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//! let payload = hugr.get_metadata::<SomeMetadata>(hugr.module_root());
//! assert_eq!(payload, Some("payload"));
//! ```
pub mod debug_info;
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated
//
// When adding new metadata keys, they should be re-exported by the python bindings.
// See hugr-py/rust/metadata.rs
Comment thread
cgh-qtnm marked this conversation as resolved.
Expand Down
112 changes: 112 additions & 0 deletions hugr-core/src/metadata/debug_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#![allow(missing_docs)]
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated

use std::any::type_name;
use std::fmt;

use crate::metadata::Metadata;
use crate::{HugrView, Node};
use serde::{
Deserialize, Serialize,
de::{DeserializeOwned, Deserializer, Error as DeError, Visitor},
};
use serde_json::{Error as JsonError, Value as JsonValue};
use thiserror::Error;

pub const DEBUGINFO_META_KEY: &str = "core.debug_info";

/// Visitor and wrapper function passed as "deserialize_with" attribute
/// in order to deserialize a usize from a string using serde_json
struct JsonStrToIntVisitor;
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated

impl<'de> Visitor<'de> for JsonStrToIntVisitor {
type Value = usize;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a usize or a string convertible with str::parse<usize>()")
}

fn visit_str<E: DeError>(self, s: &str) -> Result<Self::Value, E> {
s.parse::<usize>().map_err(E::custom)
}

fn visit_u64<E: DeError>(self, x: u64) -> Result<Self::Value, E> {
x.try_into().map_err(E::custom)
}
}

fn deserialize_usize_str<'de, D: Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
deserializer.deserialize_any(JsonStrToIntVisitor)
}

#[derive(Serialize, Deserialize)]
pub struct CompileUnitRecord {
pub directory: String,
#[serde(deserialize_with = "deserialize_usize_str")]
pub filename: usize,
pub file_table: Vec<String>,
}

impl Metadata for CompileUnitRecord {
type Type<'hugr> = CompileUnitRecord;
const KEY: &'static str = DEBUGINFO_META_KEY;
}

#[derive(Debug, Error)]
pub enum DebugInfoError {
/// There is a specific required mapping between HUGR nodes and debug record types,
/// if present
#[error("Debug metadata does not deserialize to {0}: {1}\n{2}")]
DRTypeMismatchError(&'static str, JsonError, JsonValue),
}

#[derive(Serialize, Deserialize)]
pub struct SubprogramRecord {
#[serde(deserialize_with = "deserialize_usize_str")]
pub file: usize,
#[serde(deserialize_with = "deserialize_usize_str")]
pub line_no: usize,
// TODO
//scope: Option<ScopeRecord>,
#[serde(deserialize_with = "deserialize_usize_str")]
pub scope_line: usize,
}

impl Metadata for SubprogramRecord {
type Type<'hugr> = SubprogramRecord;
const KEY: &'static str = DEBUGINFO_META_KEY;
}

#[derive(Serialize, Deserialize)]
pub struct LocationRecord {
#[serde(deserialize_with = "deserialize_usize_str")]
pub column: usize,
#[serde(deserialize_with = "deserialize_usize_str")]
pub line_no: usize,
}

impl Metadata for LocationRecord {
type Type<'hugr> = LocationRecord;
const KEY: &'static str = DEBUGINFO_META_KEY;
}

/// Inspect the debug metadata attached to the HUGR node.
///
/// If there is no debug metadata, return Ok(None). If it is present but does not
/// deserialize into `T`, return DRTypeMismatchError. Otherwise, return the deserialized
/// Some(`T`).
pub fn try_get_debug_meta<
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why can't we use get_metadata directly?

Copy link
Copy Markdown
Contributor Author

@cgh-qtnm cgh-qtnm Apr 22, 2026

Choose a reason for hiding this comment

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

I need to distinguish between the "key not present" case and the "value does not deserialize into T" case, I believe get_metadata just returns None in both cases

Copy link
Copy Markdown
Contributor Author

@cgh-qtnm cgh-qtnm Apr 22, 2026

Choose a reason for hiding this comment

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

I can move this into the core HugrView implementation if you'd like, as get_metadata_checked or similar.

Copy link
Copy Markdown
Collaborator

@aborgna-q aborgna-q Apr 28, 2026

Choose a reason for hiding this comment

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

Yeah, let's add a

fn try_get_metadata<M: Metadata>(&self, node: Self::Node) -> Result<Option<...>, serde_json::Error>

'h,
H: HugrView<Node = Node>,
T: Metadata<Type<'h> = T> + DeserializeOwned,
>(
hugr: &'h H,
node: Node,
) -> Result<Option<T>, DebugInfoError> {
if let Some(json) = hugr.get_metadata_any(node, DEBUGINFO_META_KEY) {
serde_json::from_value::<T>(json.clone())
.map_err(|e| DebugInfoError::DRTypeMismatchError(type_name::<T>(), e, json.clone()))
.map(|debug_record| Some(debug_record))
} else {
Ok(None)
}
}
2 changes: 2 additions & 0 deletions hugr-llvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ insta = { workspace = true, optional = true }
rstest = { workspace = true, optional = true }
portgraph = { workspace = true, optional = true }
derive_more = { workspace = true, features = ["debug"] }
rand = "0.10.1"
Comment thread
cgh-qtnm marked this conversation as resolved.
Outdated

[dev-dependencies]
hugr-llvm = { "path" = ".", features = ["test-utils"] }
serde_json.workspace = true

[build-dependencies]
cc = "1.2.57"
Loading
Loading