Skip to content

Commit 7eacda8

Browse files
committed
Add symbolic annotations to disassembly output
The --verify disassembly now annotates ops with their role in context: I2C parameters are labeled with bus and device names resolved from the archive. Idol Send calls show the task name and op_code. Loop patterns are identified (loop_start, counter, limit). Updated README and lib.rs docs with annotated examples.
1 parent f3af24d commit 7eacda8

7 files changed

Lines changed: 187 additions & 52 deletions

File tree

humility-hif-assembler/README.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,12 @@ Available: `push`, `push16`, `push32`, `push_none`, `drop`,
170170

171171
Shows program stats, expected resource usage, and a disassembly
172172
of the generated HIF ops. The disassembly uses the assembler's
173-
raw syntax (left column) with postcard byte encoding (right
174-
column), so it can be pasted into a `raw {}` block and re-assembled
175-
to produce the same bytecode. Function IDs are resolved back to
176-
names from the archive.
173+
raw syntax (left column) with postcard byte encoding and symbolic
174+
annotations (right column). The left column can be pasted into a
175+
`raw {}` block and re-assembled to produce the same bytecode.
176+
Function IDs are resolved back to names, push values are annotated
177+
with their role (controller, port, address, etc.), and bus/device
178+
names are shown where they can be inferred.
177179

178180
```
179181
OK
@@ -191,25 +193,39 @@ Ops (31 bytes, 18 ops):
191193
raw {
192194
push 0 # 00: 04 00
193195
push_none # 02: 07
194-
label 0 # 03: 00 00
196+
label 0 # 03: 00 00 loop_start
195197
drop # 05: 02
196-
push 1 # 06: 04 01
197-
push 1 # 08: 04 01
198-
push_none # 0a: 07
199-
push_none # 0b: 07
200-
push 0x48 # 0c: 04 48
201-
push 0 # 0e: 04 00
202-
push 2 # 10: 04 02
198+
push 1 # 06: 04 01 controller (northeast0)
199+
push 1 # 08: 04 01 port
200+
push_none # 0a: 07 mux
201+
push_none # 0b: 07 segment
202+
push 0x48 # 0c: 04 48 address (tmp117)
203+
push 0 # 0e: 04 00 register
204+
push 2 # 10: 04 02 nbytes
203205
call I2cRead # 12: 01 05
204206
drop_n 7 # 14: 03 07
205207
push 1 # 16: 04 01
206-
add # 18: 0a
207-
push 5 # 19: 04 05
208-
branch_gt 0 # 1b: 10 00
208+
add # 18: 0a counter += 1
209+
push 5 # 19: 04 05 limit
210+
branch_gt 0 # 1b: 10 00 loop
209211
done # 1d: 14
210212
}
211213
```
212214

215+
Idol calls are annotated with task and operation info:
216+
217+
```
218+
raw {
219+
push 7 # 00: 04 07 task (sprot)
220+
push 1 # 02: 04 01 op_code
221+
push 0 # 04: 04 00
222+
push 0x18 # 06: 04 18
223+
call Send # 08: 01 01
224+
drop_n 4 # 0a: 03 04
225+
done # 0c: 14
226+
}
227+
```
228+
213229
### Execution output (`--exec`, default)
214230

215231
```

humility-hif-assembler/src/archive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
1818
use std::path::Path;
1919

20-
use anyhow::{anyhow, Context, Result};
20+
use anyhow::{Context, Result, anyhow};
2121

2222
use humility::hubris::{
2323
HubrisArchive, HubrisArchiveDoneness, HubrisEncoding, HubrisGoff,

humility-hif-assembler/src/assembler.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
use std::collections::{BTreeSet, HashMap};
1212
use std::fmt;
1313

14-
use anyhow::{bail, Context, Result};
14+
use anyhow::{Context, Result, bail};
1515

16-
use crate::bundle::{BundleMetadata, HifBundle, BUNDLE_VERSION};
16+
use crate::bundle::{BUNDLE_VERSION, BundleMetadata, HifBundle};
1717
use crate::error::{HifError, HifErrorKind};
18-
use crate::lower::{normalize_function_name, RSTACK_BYTES_PER_RESULT};
18+
use crate::lower::{RSTACK_BYTES_PER_RESULT, normalize_function_name};
1919
use crate::parser::parse;
2020

2121
// Re-export types so `crate::assembler::Foo` still works for
@@ -479,11 +479,12 @@ mod tests {
479479
assert!(out.bundle.fits_in_target());
480480
assert!(!out.bundle.text.is_empty());
481481
assert_eq!(out.bundle.metadata.image_id, vec![0xDE, 0xAD]);
482-
assert!(out
483-
.bundle
484-
.metadata
485-
.functions_used
486-
.contains(&"i2c_read".to_string()));
482+
assert!(
483+
out.bundle
484+
.metadata
485+
.functions_used
486+
.contains(&"i2c_read".to_string())
487+
);
487488
}
488489

489490
#[test]
@@ -617,10 +618,12 @@ mod tests {
617618
let c = HifAssembler::new(config);
618619
let report = c.verify("i2c_read mid 0x48 reg=0x00 2");
619620
assert!(!report.ok);
620-
assert!(report
621-
.errors
622-
.iter()
623-
.any(|e| matches!(&e.kind, HifErrorKind::TextOverflow { .. })));
621+
assert!(
622+
report
623+
.errors
624+
.iter()
625+
.any(|e| matches!(&e.kind, HifErrorKind::TextOverflow { .. }))
626+
);
624627
}
625628

626629
#[test]
@@ -651,11 +654,9 @@ mod tests {
651654
let c = test_assembler();
652655
let out = c.assemble("idol Sensor.get id=3").unwrap();
653656
assert!(out.bundle.fits_in_target());
654-
assert!(out
655-
.bundle
656-
.metadata
657-
.functions_used
658-
.contains(&"Send".to_string()));
657+
assert!(
658+
out.bundle.metadata.functions_used.contains(&"Send".to_string())
659+
);
659660
}
660661

661662
#[test]

humility-hif-assembler/src/bundle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
//! line followed by a newline, then the raw binary text and data
1717
//! sections.
1818
19-
use anyhow::{bail, Context, Result};
19+
use anyhow::{Context, Result, bail};
2020
use hif::FunctionResult;
2121
use postcard::take_from_bytes;
2222
use serde::{Deserialize, Serialize};

humility-hif-assembler/src/lib.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,11 @@
175175
//! ## Disassembly
176176
//!
177177
//! [`HifAssembler::disassemble`] formats ops as raw assembler syntax
178-
//! with postcard byte encoding in trailing comments. The output is
179-
//! wrapped in `raw { }` and is valid assembler input — paste it into
180-
//! a `.hif` file and it will assemble to the same bytecode. Function
181-
//! IDs are resolved back to names from the `TargetConfig`.
178+
//! with postcard byte encoding and symbolic annotations in trailing
179+
//! comments. The left column is valid `raw { }` input. Function
180+
//! IDs are resolved back to names, push values are annotated with
181+
//! their role (controller, port, address, etc.), and bus/device
182+
//! names are shown where they can be inferred from the TargetConfig.
182183
//!
183184
//! ## Relationship to humility-hiffy
184185
//!
@@ -210,8 +211,8 @@ pub use bundle::{BundleMetadata, HifBundle, HifResult};
210211
pub use error::HifError;
211212
pub use parser::{ParsedProgram, Statement};
212213
pub use types::{
213-
BufferSizes, FunctionArg, FunctionError, FunctionInfo, I2cDeviceInfo,
214-
I2cMuxInfo, I2cMuxSegment, IdolArgInfo, IdolInterfaceInfo, IdolLeaseInfo,
215-
IdolOpInfo, ResolvedBus, SensorInfo, TargetConfig, I2C_WRITE_MAX_DATA,
216-
MAX_LABELS,
214+
BufferSizes, FunctionArg, FunctionError, FunctionInfo, I2C_WRITE_MAX_DATA,
215+
I2cDeviceInfo, I2cMuxInfo, I2cMuxSegment, IdolArgInfo, IdolInterfaceInfo,
216+
IdolLeaseInfo, IdolOpInfo, MAX_LABELS, ResolvedBus, SensorInfo,
217+
TargetConfig,
217218
};

humility-hif-assembler/src/listing.rs

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,19 @@ impl HifAssembler {
7070
/// Disassemble ops into raw syntax with hex and symbolic comments.
7171
///
7272
/// The output is wrapped in `raw { }` and is valid assembler input.
73+
/// Each line has the raw op, byte offset, hex encoding, and a
74+
/// symbolic annotation where the op's role can be inferred from
75+
/// context (e.g. "controller (mid)" for a push before I2cRead).
7376
pub fn disassemble(&self, ops: &[hif::Op]) -> String {
7477
use postcard::to_allocvec;
7578

79+
let annotations = annotate_ops(ops, self);
80+
7681
let mut out = String::new();
7782
out.push_str("raw {\n");
7883

7984
let mut offset = 0usize;
80-
for op in ops {
85+
for (i, op) in ops.iter().enumerate() {
8186
let raw = format_op(op, self);
8287
let hex = to_allocvec(op)
8388
.map(|bytes| {
@@ -89,7 +94,14 @@ impl HifAssembler {
8994
})
9095
.unwrap_or_else(|_| "??".into());
9196

92-
out.push_str(&format!(" {raw:<26} # {offset:02x}: {hex}\n",));
97+
let annotation = annotations
98+
.get(i)
99+
.map(|s| format!(" {s}"))
100+
.unwrap_or_default();
101+
102+
out.push_str(&format!(
103+
" {raw:<26} # {offset:02x}: {hex}{annotation}\n",
104+
));
93105
offset += to_allocvec(op).map(|b| b.len()).unwrap_or(0);
94106
}
95107

@@ -98,6 +110,113 @@ impl HifAssembler {
98110
}
99111
}
100112

113+
/// Produce symbolic annotations for each op by recognizing patterns.
114+
fn annotate_ops(ops: &[hif::Op], asm: &HifAssembler) -> Vec<String> {
115+
let mut notes: Vec<String> = vec![String::new(); ops.len()];
116+
117+
// Find I2cRead/Write call patterns: 7 pushes + Call + DropN(7)
118+
let i2c_read_id = asm
119+
.list_functions()
120+
.iter()
121+
.find(|f| f.name.eq_ignore_ascii_case("I2cRead"))
122+
.map(|f| f.id);
123+
let i2c_write_id = asm
124+
.list_functions()
125+
.iter()
126+
.find(|f| f.name.eq_ignore_ascii_case("I2cWrite"))
127+
.map(|f| f.id);
128+
129+
for (i, op) in ops.iter().enumerate() {
130+
if let hif::Op::Call(hif::TargetFunction(id)) = op {
131+
if Some(*id) == i2c_read_id || Some(*id) == i2c_write_id {
132+
// Annotate the 7 pushes before this call
133+
if i >= 7 {
134+
let labels = [
135+
"controller",
136+
"port",
137+
"mux",
138+
"segment",
139+
"address",
140+
"register",
141+
if Some(*id) == i2c_read_id {
142+
"nbytes"
143+
} else {
144+
"data/len"
145+
},
146+
];
147+
for (j, label) in labels.iter().enumerate() {
148+
let idx = i - 7 + j;
149+
// Enrich with bus/device names where possible
150+
let extra = match (j, &ops[idx]) {
151+
(0, hif::Op::Push(ctrl)) => {
152+
// Find bus name for this controller
153+
asm.target_config()
154+
.buses
155+
.iter()
156+
.find(|b| b.controller == *ctrl)
157+
.map(|b| format!(" ({})", b.name))
158+
.unwrap_or_default()
159+
}
160+
(4, hif::Op::Push(addr)) => {
161+
// Find device name for this address
162+
// on any bus (best effort)
163+
asm.target_config()
164+
.buses
165+
.iter()
166+
.flat_map(|b| b.devices.iter())
167+
.find(|d| d.address == *addr)
168+
.map(|d| format!(" ({})", d.device))
169+
.unwrap_or_default()
170+
}
171+
_ => String::new(),
172+
};
173+
notes[idx] = format!("{label}{extra}");
174+
}
175+
}
176+
}
177+
178+
// Annotate Send calls (Idol): task_id, op_code
179+
let send_id = asm
180+
.list_functions()
181+
.iter()
182+
.find(|f| f.name == "Send")
183+
.map(|f| f.id);
184+
if Some(*id) == send_id && i >= 4 {
185+
if let hif::Op::Push(task_id) = ops[i - 4] {
186+
let task_name = asm
187+
.target_config()
188+
.idol_interfaces
189+
.iter()
190+
.find(|iface| iface.task_id == task_id as u32)
191+
.map(|iface| iface.task.as_str())
192+
.unwrap_or("?");
193+
notes[i - 4] = format!("task ({task_name})");
194+
}
195+
notes[i - 3] = "op_code".into();
196+
}
197+
}
198+
199+
// Annotate loop patterns
200+
if let hif::Op::BranchGreaterThan(_)
201+
| hif::Op::BranchGreaterThanOrEqualTo(_) = op
202+
{
203+
if i >= 2 {
204+
if let hif::Op::Add = ops[i - 2] {
205+
notes[i - 2] = "counter += 1".into();
206+
notes[i - 1] = "limit".into();
207+
notes[i] = "loop".into();
208+
}
209+
}
210+
}
211+
212+
if let hif::Op::Label(_) = op {
213+
notes[i] = "loop_start".into();
214+
}
215+
}
216+
217+
notes
218+
}
219+
101220
/// Format a single op as raw assembler syntax.
102221
fn format_op(op: &hif::Op, asm: &HifAssembler) -> String {
103222
match op {

humility-hif-assembler/tests/equivalent_tests.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,9 @@ fn equivalent_i2c_scan() {
7272
let ops = assemble_file(load_gimlet(), "equivalent-i2c-scan.hif");
7373
// Should have: Label, BranchGreaterThanOrEqualTo (scan loop)
7474
assert!(ops.iter().any(|op| matches!(op, hif::Op::Label(_))));
75-
assert!(
76-
ops.iter()
77-
.any(|op| matches!(op, hif::Op::BranchGreaterThanOrEqualTo(_)))
78-
);
75+
assert!(ops
76+
.iter()
77+
.any(|op| matches!(op, hif::Op::BranchGreaterThanOrEqualTo(_))));
7978
// Should call I2cRead
8079
assert!(ops.iter().any(|op| matches!(op, hif::Op::Call(_))));
8180
// Should push 128 as the scan limit
@@ -98,10 +97,9 @@ fn equivalent_i2c_regscan() {
9897
let ops = assemble_file(load_gimlet(), "equivalent-i2c-regscan.hif");
9998
// Should have: Label, BranchGreaterThanOrEqualTo (regscan loop)
10099
assert!(ops.iter().any(|op| matches!(op, hif::Op::Label(_))));
101-
assert!(
102-
ops.iter()
103-
.any(|op| matches!(op, hif::Op::BranchGreaterThanOrEqualTo(_)))
104-
);
100+
assert!(ops
101+
.iter()
102+
.any(|op| matches!(op, hif::Op::BranchGreaterThanOrEqualTo(_))));
105103
// Should push 0xff as the scan limit
106104
assert!(ops.iter().any(|op| matches!(op, hif::Op::Push(0xff))));
107105
}

0 commit comments

Comments
 (0)