// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
fn hot_perf_trace_noop(_ : String) -> Unit {
()
}
///|
pub struct HotPerfCounters {
node_allocs : Int32
child_span_allocs : Int32
side_table_allocs : Int32
region_splices : Int32
cfg_builds : Int32
dataflow_builds : Int32
traversal_visits : Int32
} derive(Eq, Debug)
///|
pub fn HotPerfCounters::new(
node_allocs~ : Int32 = 0,
child_span_allocs~ : Int32 = 0,
side_table_allocs~ : Int32 = 0,
region_splices~ : Int32 = 0,
cfg_builds~ : Int32 = 0,
dataflow_builds~ : Int32 = 0,
traversal_visits~ : Int32 = 0,
) -> HotPerfCounters {
{
node_allocs,
child_span_allocs,
side_table_allocs,
region_splices,
cfg_builds,
dataflow_builds,
traversal_visits,
}
}
///|
pub struct HotPerfDumpOptions {
hot_func : Bool
cfg : Bool
} derive(Eq, Debug)
///|
pub fn HotPerfDumpOptions::new(
hot_func~ : Bool = false,
cfg~ : Bool = false,
) -> HotPerfDumpOptions {
{ hot_func, cfg, }
}
///|
pub struct HotPerfHotSnapshot {
node_count : Int32
child_span_count : Int32
side_table_count : Int32
} derive(Eq, Debug)
///|
pub fn HotPerfHotSnapshot::empty() -> HotPerfHotSnapshot {
{ node_count: 0, child_span_count: 0, side_table_count: 0, }
}
///|
pub struct HotPerfSession {
enabled : Bool
trace : (String) -> Unit
trace_timings : Bool
trace_checkpoints : Bool
trace_counters : Bool
dump_options : HotPerfDumpOptions
mut counters : HotPerfCounters
timer_totals : Array[(String, UInt64)]
timer_indices : @hashmap.HashMap[String, Int32]
}
///|
pub fn HotPerfSession::new(
enabled~ : Bool = true,
trace~ : (String) -> Unit = hot_perf_trace_noop,
trace_timings~ : Bool = true,
trace_checkpoints~ : Bool = true,
trace_counters~ : Bool = true,
dump_options~ : HotPerfDumpOptions = HotPerfDumpOptions::new(),
) -> HotPerfSession {
{
enabled,
trace,
trace_timings,
trace_checkpoints,
trace_counters,
dump_options,
counters: HotPerfCounters::new(),
timer_totals: [],
timer_indices: @hashmap.new(),
}
}
///|
fn perf_positive_delta(after : Int32, before : Int32) -> Int32 {
if after > before {
after - before
} else {
0
}
}
///|
fn perf_side_table_count(func : @ir.HotFunc) -> Int32 {
func.consts.length() +
func.memargs.length() +
func.branch_tables.length() +
func.catches.length() +
func.call_sigs.length() +
func.instr_payloads.length()
}
///|
fn perf_timer_total_commit(
session : HotPerfSession,
name : String,
elapsed_us : UInt64,
) -> UInt64 {
match session.timer_indices.get(name) {
Some(i) => {
let total = session.timer_totals[i].1 + elapsed_us
session.timer_totals[i] = (name, total)
return total
}
None => ()
}
session.timer_indices.set(name, session.timer_totals.length())
session.timer_totals.push((name, elapsed_us))
elapsed_us
}
///|
test "perf timer index preserves first-seen order and accumulated totals" {
let session = HotPerfSession::new()
assert_eq(perf_timer_total_commit(session, "raw:func:1", 2UL), 2UL)
assert_eq(perf_timer_total_commit(session, "raw:func:2", 3UL), 3UL)
assert_eq(perf_timer_total_commit(session, "raw:func:1", 5UL), 7UL)
assert_eq(session.timer_totals.length(), 2)
assert_eq(session.timer_totals[0], ("raw:func:1", 7UL))
assert_eq(session.timer_totals[1], ("raw:func:2", 3UL))
}
///|
fn perf_cfg_option_block(block_id : Int32?) -> String {
match block_id {
Some(value) => value.to_string()
None => "-"
}
}
///|
pub fn perf_snapshot_hot_func(func : @ir.HotFunc) -> HotPerfHotSnapshot {
{
node_count: @ir.hot_node_count(func),
child_span_count: @ir.hot_child_storage_count(func),
side_table_count: perf_side_table_count(func),
}
}
///|
pub fn perf_counters(session : HotPerfSession) -> HotPerfCounters {
session.counters
}
///|
pub fn perf_start_timer(session : HotPerfSession, name : String) -> UInt64 {
let _ = name
if !session.enabled || !session.trace_timings {
return 0UL
}
@lib.trace_now_us()
}
///|
fn perf_elapsed_since_start(
session : HotPerfSession,
start_us : UInt64,
) -> UInt64 {
if !session.enabled || !session.trace_timings {
return 0UL
}
@lib.trace_elapsed_us_since(start_us)
}
///|
fn perf_record_timer_elapsed(
session : HotPerfSession,
name : String,
elapsed_us : UInt64,
) -> Unit {
if !session.enabled || !session.trace_timings {
return
}
let total_us = perf_timer_total_commit(session, name, elapsed_us)
(session.trace)(
"perf:timer name=\{name} elapsed_us=\{elapsed_us} total_us=\{total_us}",
)
}
///|
pub fn perf_stop_timer(
session : HotPerfSession,
name : String,
start_us : UInt64,
) -> Unit {
if !session.enabled || !session.trace_timings {
return
}
perf_record_timer_elapsed(
session,
name,
perf_elapsed_since_start(session, start_us),
)
}
///|
pub fn perf_count_node_alloc(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
node_allocs: session.counters.node_allocs + count,
}
}
///|
pub fn perf_count_child_span_alloc(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
child_span_allocs: session.counters.child_span_allocs + count,
}
}
///|
pub fn perf_count_side_table_alloc(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
side_table_allocs: session.counters.side_table_allocs + count,
}
}
///|
pub fn perf_count_region_splice(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
region_splices: session.counters.region_splices + count,
}
}
///|
pub fn perf_count_cfg_build(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
cfg_builds: session.counters.cfg_builds + count,
}
}
///|
pub fn perf_count_dataflow_build(
session : HotPerfSession,
name : String,
count~ : Int32 = 1,
) -> Unit {
let _ = name
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
dataflow_builds: session.counters.dataflow_builds + count,
}
}
///|
pub fn perf_count_traversal_visit(
session : HotPerfSession,
count~ : Int32 = 1,
) -> Unit {
if !session.enabled || count <= 0 {
return
}
session.counters = {
..session.counters,
traversal_visits: session.counters.traversal_visits + count,
}
}
///|
pub fn perf_count_hot_alloc_delta(
session : HotPerfSession,
before : HotPerfHotSnapshot,
after : HotPerfHotSnapshot,
) -> Unit {
perf_count_node_alloc(
session,
count=perf_positive_delta(after.node_count, before.node_count),
)
perf_count_child_span_alloc(
session,
count=perf_positive_delta(after.child_span_count, before.child_span_count),
)
perf_count_side_table_alloc(
session,
count=perf_positive_delta(after.side_table_count, before.side_table_count),
)
}
///|
pub fn perf_dump_hot_func(
session : HotPerfSession,
func : @ir.HotFunc,
label : String,
) -> Unit {
if !session.enabled || !session.dump_options.hot_func {
return
}
(session.trace)(
"perf:dump hot-func label=\{label} \{@ir.hot_debug_dump_core(func)}",
)
}
///|
pub fn perf_dump_cfg(
session : HotPerfSession,
cfg : @ir.HotCfg,
label : String,
) -> Unit {
if !session.enabled || !session.dump_options.cfg {
return
}
(session.trace)(
"perf:dump cfg label=\{label} entry=\{@ir.cfg_entry_block(cfg)} exit=\{@ir.cfg_exit_block(cfg)} exceptional=\{perf_cfg_option_block(@ir.cfg_exceptional_exit_block(cfg))} blocks=\{@ir.cfg_block_count(cfg)}",
)
}
///|
pub fn perf_validation_checkpoint(
session : HotPerfSession,
name : String,
) -> Unit {
if !session.enabled || !session.trace_checkpoints {
return
}
(session.trace)("perf:checkpoint name=\{name}")
}
///|
pub fn perf_emit_counters(session : HotPerfSession, label : String) -> Unit {
if !session.enabled || !session.trace_counters {
return
}
let counters = session.counters
(session.trace)(
"perf:counters label=\{label} node_allocs=\{counters.node_allocs} child_span_allocs=\{counters.child_span_allocs} side_table_allocs=\{counters.side_table_allocs} region_splices=\{counters.region_splices} cfg_builds=\{counters.cfg_builds} dataflow_builds=\{counters.dataflow_builds} traversal_visits=\{counters.traversal_visits}",
)
}