Query compile evidence¶
loom-compile-report show, diff, and suggest provide bounded views before a
reader needs the complete report schema. Raw JSON remains valuable when one of
those views identifies a specific scheduling, allocation, wait, memory, target,
or legalization question. This page maps those questions to query shapes for
the current report version.
Compile reports are version-zero same-checkout diagnostics. Regenerate a report with the compiler used to inspect it; schema mismatch is evidence that the report and tool do not share a contract.
Understand the report shape¶
A single-entry report exposes its selected function, lowered symbol, emitted
artifact summary, and compiler analysis at the top level. A multi-entry
artifact additionally uses .entries.rows[] as the per-entry summary index.
Those rows intentionally use compact flat names such as
allocation_spill_count, while top-level sections retain richer nested
objects.
Missing fields mean the compiler or target did not establish that evidence.
They are not zeros. // null is useful when comparing optional evidence
without manufacturing a number.
Detail mode populates focused collections rather than one undifferentiated trace:
| Collection | Question |
|---|---|
pressure_rows.rows[] and pressure_origin_rows.rows[] |
Which values and source families established a pressure peak? |
allocation_high_water_rows.rows[] and spill_rows.rows[] |
Which placements and spill actions followed from that pressure? |
source_low.rows[], source_low.memory_rows[], and source_low.selection_summaries.rows[] |
Which source operations selected each Low representation and memory route? |
schedule_band_summary_rows.rows[] |
Which semantic instruction families occupy each schedule band? |
wait_reason_summary_rows.rows[] and wait_action_rows.rows[] |
Why was each wait family required, and where was it placed? |
math_legalization.rows[] and target_legalization.rows[] |
Which source operations required representation or target repair? |
target_capability_rows.rows[] |
Which selected target facts constrained lowering? |
Read one single-entry summary and its multi-entry index:
jq '{function,
lowered,
target_export,
code_bytes: .emission.code_byte_count,
valu: .static_instruction_mix.vector_alu_count,
matrix: .static_instruction_mix.matrix_count,
wmma: .static_instruction_mix.wmma_count,
mfma: .static_instruction_mix.mfma_count,
planned_spills: .allocation.spill_count,
planned_spill_slots: .allocation.spill_plan_count,
materialized_spill_storage:
.allocation.materialized_spill_storage_count,
materialized_spill_bytes:
.allocation.materialized_spill_storage_bytes,
materialized_spill_stores:
.allocation.materialized_spill_store_count,
materialized_reloads: .allocation.materialized_reload_count,
materialized_reload_bytes: .allocation.materialized_reload_bytes,
source_packets:
(.economics.memory.source_low.packet_count // null),
source_unknown_dynamic_packets:
(.economics.memory.source_low.unknown_dynamic_packet_count // null),
low_dynamic_issued_bytes:
(.economics.memory.dispatch_issued.total_bytes // null),
low_global_loads:
(.economics.memory.dispatch_issued.global_load_count // null),
low_global_stores:
(.economics.memory.dispatch_issued.global_store_count // null),
final_vgprs: .target_resources.vector.final.register_count,
scheduled_vgpr_pressure:
.target_resources.vector.scheduled_pressure.peak_live_units,
private_bytes: .memory.private_bytes},
(.entries.rows[]? |
{function,
source_function,
target_export_symbol,
code_byte_count,
low_dynamic_issued_bytes:
(.economics.memory.dispatch_issued.total_bytes // null),
valu: .static_instruction_mix.vector_alu_count,
matrix: .static_instruction_mix.matrix_count,
wmma: .static_instruction_mix.wmma_count,
mfma: .static_instruction_mix.mfma_count,
allocation_spill_count,
allocation_spill_plan_count,
final_vgprs: .target_resources.vector.final.register_count,
scheduled_vgpr_pressure:
.target_resources.vector.scheduled_pressure.peak_live_units,
allocation_materialized_spill_storage_count,
allocation_materialized_spill_store_count,
allocation_materialized_reload_count,
private_memory_bytes})' report.json
Select every object associated with one source or lowered function when the relevant evidence may live in several detail arrays:
jq --arg f branchy '
.. | objects |
select(.function? == $f or
.source_function? == $f or
.function_name? == $f)' report.json
Explain a register or spill cliff¶
Summary mode identifies the existence of scheduled pressure, final register usage, planned spills, materialized spill traffic, and private memory. Detail mode adds the provenance needed to explain the cliff:
target_resources.{scalar,vector}.scheduled_pressure.peak_live_units is
scheduled virtual pressure before final allocation metadata.
target_resources.{scalar,vector}.final.register_count is the emitted physical
register count used for occupancy. Their difference is evidence about the
allocation boundary rather than a contradiction.
loom-compile kernel.loom \
--format=amdgpu-hsaco \
--target=amdgpu:gfx1151 \
--output=kernel.hsaco \
--compile-report=details \
--compile-report-output=report.details.json
Find the values live at each pressure peak:
jq '.pressure_origin_rows.rows[]? |
{function,
register_class,
peak_block,
peak_operation,
origin,
origin_operation,
semantic_tag,
sample_value,
live_units,
live_values} |
with_entries(select(.value != null))' report.details.json
Inspect the allocations establishing a high-water mark and the storage that blocked a lower placement:
jq '.allocation_high_water_rows.rows[]? |
{function,
value,
register_class,
origin,
semantic_tag,
required_unit_count,
location_base,
location_count,
high_water_units,
lower_largest_free_run_unit_count,
lower_pressure_releasable_largest_free_run_unit_count,
active_assignment_blocker_units,
active_storage_lease_blocker_units} |
with_entries(select(.value != null))' report.details.json
Read individual spill actions or group them by the source live-range family that produced them:
jq '.spill_rows.rows[]? |
{function,
kind,
value,
register_class,
origin,
semantic_tag,
byte_size,
store_count,
store_bytes,
reload_count,
reload_bytes} |
with_entries(select(.value != null))' report.details.json
jq '[.spill_rows.rows[]?] |
group_by(.origin + ":" + (.semantic_tag // ""))[] |
{count: length,
function: .[0].function,
origin: .[0].origin,
semantic_tag: .[0].semantic_tag,
bytes: (map(.byte_size) | add),
stores: (map(.store_count) | add),
reloads: (map(.reload_count) | add)} |
with_entries(select(.value != null))' report.details.json
Repeated rows with one origin and semantic tag usually describe one live-range family, not independent defects.
Rank source-to-Low selections¶
source_low.selection_summaries.rows[] aggregates target selection under the
authored source operation. It remains available in summary reports so large
generated packet families can be ranked before requesting detail rows.
jq '.source_low.selection_summaries.rows[]? |
{function,
source_op,
selection,
plan_key,
descriptor_key,
descriptor_semantic_tag,
selected_op_count,
emitted_low_op_count} |
with_entries(select(.value != null))' report.json
jq '[.source_low.selection_summaries.rows[]? |
{function,
source_op,
selection,
plan_key,
descriptor_key,
descriptor_semantic_tag,
selected_op_count,
emitted_low_op_count} |
with_entries(select(.value != null))] |
sort_by(.emitted_low_op_count // 0) |
reverse |
.[:20][]' report.json
Limit the view to matrix-family choices:
jq '.source_low.selection_summaries.rows[]? |
select((.descriptor_semantic_tag // "") |
startswith("matrix.")) |
{function,
source_op,
plan_key,
descriptor_key,
descriptor_semantic_tag,
selected_op_count,
emitted_low_op_count} |
with_entries(select(.value != null))' report.json
Inspect schedule bands¶
Schedule-band summaries retain semantic tags, band width, node counts,
instruction-family counts, and result pressure. When exact fixed-trip
multiplicity is proven, dynamic_instruction_mix also reports repeated packet
counts. Its absence means dynamic multiplicity was not proven.
Read matrix-family bands:
jq '.schedule_band_summary_rows.rows[]? |
select((.semantic_tag // "") | startswith("matrix.")) |
{function,
block,
semantic_tag,
band_count,
node_count,
matrix: .static_instruction_mix.matrix_count,
wmma: .static_instruction_mix.wmma_count,
mfma: .static_instruction_mix.mfma_count,
swmmac: .static_instruction_mix.swmmac_count,
smfmac: .static_instruction_mix.smfmac_count,
dynamic_matrix: (.dynamic_instruction_mix.matrix_count // null),
dynamic_wmma: (.dynamic_instruction_mix.wmma_count // null),
dynamic_mfma: (.dynamic_instruction_mix.mfma_count // null),
dynamic_swmmac: (.dynamic_instruction_mix.swmmac_count // null),
dynamic_smfmac: (.dynamic_instruction_mix.smfmac_count // null),
result_unit_count} |
with_entries(select(.value != null))' report.json
Read global and buffer memory bands:
jq '.schedule_band_summary_rows.rows[]? |
select(((.semantic_tag // "") | startswith("memory.global.")) or
((.semantic_tag // "") | startswith("memory.buffer."))) |
{function,
block,
semantic_tag,
band_count,
node_count,
max_band_node_count,
global_loads: .static_instruction_mix.global_load_count,
global_stores: .static_instruction_mix.global_store_count,
buffer_loads: .static_instruction_mix.buffer_load_count,
buffer_stores: .static_instruction_mix.buffer_store_count,
dynamic_global_loads:
(.dynamic_instruction_mix.global_load_count // null),
dynamic_global_stores:
(.dynamic_instruction_mix.global_store_count // null),
dynamic_buffer_loads:
(.dynamic_instruction_mix.buffer_load_count // null),
dynamic_buffer_stores:
(.dynamic_instruction_mix.buffer_store_count // null)} |
with_entries(select(.value != null))' report.json
max_band_node_count is the widest consecutive band represented by the row;
it is structural schedule evidence rather than a hardware-cycle estimate.
Attribute waits¶
Compact reports group waits by counter and reason. This is the first view for ranking full drains and partial waits:
jq '.wait_reason_summary_rows.rows[]? |
{function,
counter,
reason,
action_count: .summary.action_count,
full_drains: .summary.full_drain_count,
partial_waits: .summary.partial_wait_count,
max_outstanding_before: .summary.max_outstanding_before,
max_drained_count: .summary.max_drained_count} |
with_entries(select(.value != null))' report.json
jq '[.wait_reason_summary_rows.rows[]?] |
sort_by(-.summary.full_drain_count)[] |
{function,
counter,
reason,
full_drains: .summary.full_drain_count,
partial_waits: .summary.partial_wait_count,
max_outstanding_before: .summary.max_outstanding_before,
max_drained_count: .summary.max_drained_count} |
with_entries(select(.value != null))' report.json
Detail mode connects each action to scheduled producer and consumer semantics:
jq '.wait_action_rows.rows[]? |
{function,
counter,
action,
reason,
scheduled_ordinal,
producer: .producer_semantic_tag,
consumer: .consumer_semantic_tag,
target_count,
outstanding_before,
outstanding_after,
drained_count} |
with_entries(select(.value != null))' report.details.json
Account for memory traffic¶
Top-level economics separate authored logical traffic from target-Low issued traffic. Dynamic counts and interval envelopes appear only when the compiler proves their multiplicity and address coverage.
economics.memory.per_workitem_issued and
economics.memory.dispatch_issued estimate target-Low dynamic issued traffic.
They count modeled operation effects and descriptor widths rather than cache
transactions or executed branch paths.
jq '{source_issued_bytes:
(.economics.memory.source_low.dispatch_issued.total_bytes // null),
source_logical_bytes:
(.economics.memory.source_low.dispatch_source.total_bytes // null),
source_dynamic_bytes:
(.economics.memory.source_low.dynamic_source_byte_count // null),
source_read_dynamic_bytes:
(.economics.memory.source_low.dynamic_read_byte_count // null),
source_write_dynamic_bytes:
(.economics.memory.source_low.dynamic_write_byte_count // null),
source_unique_bytes:
(.economics.memory.source_low.interval_envelope.unique_byte_count //
null),
source_read_unique_bytes:
(.economics.memory.source_low.read_interval_envelope.unique_byte_count //
null),
source_write_unique_bytes:
(.economics.memory.source_low.write_interval_envelope.unique_byte_count //
null),
low_dynamic_issued_bytes:
(.economics.memory.dispatch_issued.total_bytes // null),
low_global_loads:
(.economics.memory.dispatch_issued.global_load_count // null),
low_global_stores:
(.economics.memory.dispatch_issued.global_store_count // null)}' \
report.json
Strategy rows explain which target packet and fallback route served a memory operation:
jq '.source_low.memory.strategies[]? |
{function,
memory_space,
operation,
packet,
strategy,
fallback_reason,
storage,
packet_count,
dispatch_source,
dispatch_issued,
scalar_packet_count,
vector_packet_count} |
with_entries(select(.value != null))' report.json
Per-argument rows group traffic by source binding:
jq '[.source_low.memory.arguments[]? |
{function,
source_root,
source_root_argument_index,
memory_space,
packet_count,
load_packet_count,
store_packet_count,
dispatch_source,
dispatch_issued,
dynamic_source_byte_count,
dynamic_read_byte_count,
dynamic_write_byte_count,
dynamic_issued_read_byte_count,
dynamic_issued_write_byte_count,
scalar_packet_count,
vector_packet_count,
read_unique_bytes: .read_interval_envelope.unique_byte_count,
write_unique_bytes: .write_interval_envelope.unique_byte_count} |
with_entries(select(.value != null))] |
sort_by(-(.dispatch_issued.total_bytes // 0))[]' report.json
Source-root and target-strategy rollups appear under
.source_low.memory.roots[] and .source_low.memory.strategies[] when that
evidence is available.
argument_packets is the cross product of binding, source root, operation,
packet, selected strategy, fallback reason, and storage contract:
jq '[.source_low.memory.argument_packets[]? |
{function,
source_root,
source_root_argument_index,
memory_space,
operation,
packet,
strategy,
fallback_reason,
storage,
packet_count,
load_packet_count,
store_packet_count,
dispatch_source,
dispatch_issued,
dynamic_source_byte_count,
dynamic_read_byte_count,
dynamic_write_byte_count,
dynamic_issued_read_byte_count,
dynamic_issued_write_byte_count,
scalar_packet_count,
vector_packet_count,
read_unique_bytes: .read_interval_envelope.unique_byte_count,
write_unique_bytes: .write_interval_envelope.unique_byte_count} |
with_entries(select(.value != null))] |
sort_by(-(.dispatch_issued.total_bytes // 0))[]' report.json
The physical storage schema supplies semantic facts that scalar element names
such as f8E4M3, f8E5M2, or BF8 cannot. For data known to exclude non-finite
payloads, rounding=finite_only records that contract. Storage that also
flushes subnormals uses rounding=finite_flush_subnormal. Strategy and fallback
rows then distinguish a proven packed decode from a repair path; raw scalar
spelling alone cannot establish that choice. For example,
fp8_packed_bf16_decode_repair_zero_subnormal and
fp8_packed_bf16_decode_repair_zero name different selected routes, while a
fallback such as missing_finite_not_subnormal names the storage fact that was
not proven.
Read target capabilities¶
Target-capability rows identify the selected processor, subgroup, index width, matrix profiles, and other provider facts used by lowering:
jq '.target_capability_rows.rows[]? |
select(.namespace == "amdgpu" or .namespace == "target") |
{function,
namespace,
key,
value_kind,
value_u64,
value_bool,
value_string} |
with_entries(select(.value != null))' report.json
AMDGPU narrow matrix rows describe whether a selected type has no native packet, an unscaled packet, a scaled packet, or both:
jq '.target_capability_rows.rows[]? |
select(.namespace == "amdgpu" and
(.key | startswith("matrix_") and
endswith("_native_kind"))) |
{function, key, native_kind: .value_string} |
with_entries(select(.value != null))' report.json
The keys currently include matrix_fp8_native_kind,
matrix_bf8_native_kind, matrix_fp6_native_kind,
matrix_bf6_native_kind, and matrix_fp4_native_kind. Values distinguish
none, unscaled, scaled, and unscaled_scaled without requiring
disassembly.
Follow an IR trace¶
IR traces answer a different question from compile reports: they preserve the
module at selected pass boundaries rather than aggregating compiler evidence.
loom-compile and loom-opt share the --dump-ir-* tracing flags. Each JSONL
trace row contains a whole-module snapshot in .ir, even when the recorded pass
was anchored on one function.
loom-compile kernel.loom \
--format=amdgpu-hsaco \
--target=amdgpu:gfx11-generic \
--output=kernel.hsaco \
--dump-ir-after-all \
--dump-ir-format=jsonl \
--dump-ir-output=trace.jsonl
jq 'select(.stage == "prepared-low") | .pass' trace.jsonl
jq -r 'select(.stage == "prepared-low") | .ir' trace.jsonl
When the exact compiler boundary is known, a focused capture such as
--dump-ir-after=source-to-low avoids retaining every intermediate module.
A prepared-Low snapshot can be assembled with --pipeline=none only when it
is an ABI-complete emitter input. That includes target live-ins such as the
AMDGPU kernarg segment pointer when the selected ABI uses kernargs. The
supported reconstruction and acceptance boundary is described in
Raise a native schedule into Loom.
Query reports retained by a benchmark¶
Debug and full benchmark bundles write per-candidate report sidecars. Their paths are the cleanest route into the recipes above:
jq 'select(.row == "compile" and .compile_report_path) |
{candidate_id, path: .compile_report_path}' results.jsonl
When a report is embedded directly, retain the candidate identity around the query result:
jq 'select(.row == "compile" and .compile_report) as $event |
$event.compile_report.pressure_origin_rows.rows[]? |
{candidate_id: $event.candidate_id,
function,
register_class,
origin,
semantic_tag,
live_units}' results.jsonl
Some consumers retain the same report below a completed benchmark result. The adapter changes, but the report query does not:
jq 'select(.row == "benchmark" and
.benchmark_result.compile_report) as $event |
$event.benchmark_result.compile_report |
{candidate_id: $event.candidate_id,
sample_id: $event.sample_id,
code_bytes: .emission.code_byte_count,
final_vgprs: .target_resources.vector.final.register_count,
scheduled_vgpr_pressure:
.target_resources.vector.scheduled_pressure.peak_live_units,
materialized_spills:
.allocation.materialized_spill_storage_count}' results.jsonl
Timing establishes whether performance changed. Report evidence explains which emitted or modeled properties changed. A controlled conclusion needs both.