Skip to content

Commit 5572792

Browse files
committed
feat(tools): add cli to measure decorator size in masp package
1 parent 0ff3d97 commit 5572792

4 files changed

Lines changed: 292 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/objsize/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "objsize"
3+
description = "Inspect Miden package MAST forest sizes"
4+
version.workspace = true
5+
rust-version.workspace = true
6+
authors.workspace = true
7+
repository.workspace = true
8+
homepage.workspace = true
9+
documentation.workspace = true
10+
categories.workspace = true
11+
keywords.workspace = true
12+
license.workspace = true
13+
readme.workspace = true
14+
edition.workspace = true
15+
16+
[dependencies]
17+
anyhow.workspace = true
18+
clap.workspace = true
19+
miden-core.workspace = true
20+
miden-mast-package.workspace = true

tools/objsize/src/decorators.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
use std::{fs, path::PathBuf};
2+
3+
use anyhow::{Context, Result};
4+
use clap::Args;
5+
use miden_core::{
6+
mast::MastForest,
7+
utils::{Deserializable, Serializable},
8+
};
9+
use miden_mast_package::{MastArtifact, Package, PackageKind};
10+
11+
#[derive(Debug, Clone, Args)]
12+
pub struct DecoratorsCommand {
13+
/// Path to the input .masp file
14+
pub path: PathBuf,
15+
}
16+
17+
#[derive(Debug, Clone, Copy)]
18+
enum ArtifactKind {
19+
Program,
20+
Library,
21+
}
22+
23+
impl ArtifactKind {
24+
fn from_mast_artifact(mast: &MastArtifact) -> Self {
25+
match mast {
26+
MastArtifact::Executable(_) => ArtifactKind::Program,
27+
MastArtifact::Library(_) => ArtifactKind::Library,
28+
}
29+
}
30+
}
31+
32+
impl std::fmt::Display for ArtifactKind {
33+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34+
match self {
35+
Self::Program => write!(f, "program"),
36+
Self::Library => write!(f, "library"),
37+
}
38+
}
39+
}
40+
41+
pub fn run(command: DecoratorsCommand) -> Result<()> {
42+
let input_bytes = fs::read(&command.path)
43+
.with_context(|| format!("failed to read input file '{}'", command.path.display()))?;
44+
let masp_size = input_bytes.len();
45+
46+
let package = Package::read_from_bytes(&input_bytes)
47+
.with_context(|| format!("failed to decode package '{}'", command.path.display()))?;
48+
49+
let original_forest = package.mast.mast_forest().clone();
50+
let original_forest_size = forest_size(&original_forest);
51+
52+
let mut stripped_forest = original_forest.clone();
53+
stripped_forest.strip_decorators();
54+
55+
let mut compacted_forest = original_forest.clone();
56+
compacted_forest.strip_decorators();
57+
compacted_forest.compact();
58+
59+
let report = Report {
60+
input: command.path.display().to_string(),
61+
package_kind: package.kind,
62+
artifact_kind: ArtifactKind::from_mast_artifact(&package.mast),
63+
metric_points: vec![
64+
MetricPoint::reference("original masp", masp_size),
65+
MetricPoint::baseline("original forest", original_forest_size),
66+
MetricPoint::delta(
67+
"without decorators",
68+
forest_size(&stripped_forest),
69+
original_forest_size,
70+
),
71+
MetricPoint::delta(
72+
"compacted forest",
73+
forest_size(&compacted_forest),
74+
original_forest_size,
75+
),
76+
],
77+
};
78+
79+
println!("{report}");
80+
81+
Ok(())
82+
}
83+
84+
fn forest_size(forest: &MastForest) -> usize {
85+
forest.to_bytes().len()
86+
}
87+
88+
fn bytes_to_kb(bytes: usize) -> f64 {
89+
bytes as f64 / 1024.0
90+
}
91+
92+
#[derive(Debug, Clone)]
93+
struct Report {
94+
input: String,
95+
package_kind: PackageKind,
96+
artifact_kind: ArtifactKind,
97+
metric_points: Vec<MetricPoint>,
98+
}
99+
100+
#[derive(Debug, Clone)]
101+
struct MetricPoint {
102+
label: &'static str,
103+
bytes: usize,
104+
delta: Option<i64>,
105+
delta_percent: Option<f64>,
106+
}
107+
108+
impl MetricPoint {
109+
fn reference(label: &'static str, bytes: usize) -> Self {
110+
Self {
111+
label,
112+
bytes,
113+
delta: None,
114+
delta_percent: None,
115+
}
116+
}
117+
118+
fn baseline(label: &'static str, bytes: usize) -> Self {
119+
Self {
120+
label,
121+
bytes,
122+
delta: Some(0),
123+
delta_percent: Some(0.0),
124+
}
125+
}
126+
127+
fn delta(label: &'static str, bytes: usize, baseline: usize) -> Self {
128+
let delta = i64::try_from(bytes - baseline).unwrap();
129+
let delta_percent = if baseline == 0 {
130+
0.0
131+
} else {
132+
(delta as f64 / baseline as f64) * 100.0
133+
};
134+
135+
Self {
136+
label,
137+
bytes,
138+
delta: Some(delta),
139+
delta_percent: Some(delta_percent),
140+
}
141+
}
142+
}
143+
144+
fn format_delta(row: &MetricPoint) -> String {
145+
match row.delta {
146+
Some(delta) => {
147+
let delta_kb = delta as f64 / 1024.0;
148+
if delta_kb.abs() < 0.01 {
149+
"0.00".to_string()
150+
} else if delta_kb > 0.0 {
151+
format!("+{delta_kb:.2}")
152+
} else {
153+
format!("{delta_kb:.2}")
154+
}
155+
}
156+
None => "-".to_string(),
157+
}
158+
}
159+
160+
fn format_delta_percent(row: &MetricPoint) -> String {
161+
match row.delta_percent {
162+
Some(percent) => format!("{percent:+.2}%"),
163+
None => "-".to_string(),
164+
}
165+
}
166+
167+
impl std::fmt::Display for Report {
168+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169+
writeln!(f, "Input: {}", self.input)?;
170+
writeln!(f, "Package kind: {}", self.package_kind)?;
171+
writeln!(f, "Artifact: {}", self.artifact_kind)?;
172+
writeln!(f)?;
173+
174+
let kb_strings: Vec<String> = self
175+
.metric_points
176+
.iter()
177+
.map(|row| format!("{:.2}", bytes_to_kb(row.bytes)))
178+
.collect();
179+
let delta_strings: Vec<String> = self.metric_points.iter().map(format_delta).collect();
180+
let delta_percent_strings: Vec<String> =
181+
self.metric_points.iter().map(format_delta_percent).collect();
182+
183+
let metric_width = self
184+
.metric_points
185+
.iter()
186+
.map(|row| row.label.len())
187+
.chain(std::iter::once("Metric".len()))
188+
.max()
189+
.unwrap_or("Metric".len());
190+
let kb_width = kb_strings
191+
.iter()
192+
.map(String::len)
193+
.chain(std::iter::once("KB".len()))
194+
.max()
195+
.unwrap_or("KB".len());
196+
let delta_width = delta_strings
197+
.iter()
198+
.map(String::len)
199+
.chain(std::iter::once("Delta".len()))
200+
.max()
201+
.unwrap_or("Delta".len());
202+
let delta_percent_width = delta_percent_strings
203+
.iter()
204+
.map(String::len)
205+
.chain(std::iter::once("Delta %".len()))
206+
.max()
207+
.unwrap_or("Delta %".len());
208+
209+
writeln!(
210+
f,
211+
"{:<metric_width$} {:>kb_width$} {:>delta_width$} {:>delta_percent_width$}",
212+
"Metric", "KB", "Delta", "Delta %",
213+
)?;
214+
215+
for ((row, kb), (delta, delta_percent)) in self
216+
.metric_points
217+
.iter()
218+
.zip(kb_strings.iter())
219+
.zip(delta_strings.iter().zip(delta_percent_strings.iter()))
220+
{
221+
writeln!(
222+
f,
223+
"{:<metric_width$} {:>kb_width$} {:>delta_width$} {:>delta_percent_width$}",
224+
row.label, kb, delta, delta_percent,
225+
)?;
226+
}
227+
228+
Ok(())
229+
}
230+
}

tools/objsize/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
mod decorators;
2+
3+
use anyhow::Result;
4+
use clap::{Parser, Subcommand};
5+
6+
#[derive(Debug, Parser)]
7+
#[command(
8+
name = "objsize",
9+
bin_name = "objsize",
10+
version,
11+
about = "Inspect Miden compilation artifact sizes",
12+
long_about = None,
13+
arg_required_else_help = true,
14+
)]
15+
struct Cli {
16+
#[command(subcommand)]
17+
command: Commands,
18+
}
19+
20+
#[derive(Debug, Subcommand)]
21+
enum Commands {
22+
/// Compare serialized MAST forest sizes after stripping decorators.
23+
Decorators(decorators::DecoratorsCommand),
24+
}
25+
26+
fn main() -> Result<()> {
27+
let cli = Cli::parse();
28+
29+
match cli.command {
30+
Commands::Decorators(command) => decorators::run(command),
31+
}
32+
}

0 commit comments

Comments
 (0)