Skip to content

Commit

Permalink
WIP; supporting multicomponent programS
Browse files Browse the repository at this point in the history
  • Loading branch information
ayakayorihiro committed Nov 15, 2024
1 parent e098091 commit 5dd7804
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 55 deletions.
54 changes: 50 additions & 4 deletions calyx-opt/src/passes/profiler_instrumentation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor};
use calyx_ir::{self as ir, BoolAttr, Guard, Nothing};
use calyx_ir::{self as ir, BoolAttr, Guard, Id, Nothing};
use calyx_utils::CalyxResult;

/// Adds probe wires to each group to detect when a group is active.
Expand Down Expand Up @@ -43,15 +43,17 @@ impl Visitor for ProfilerInstrumentation {
let mut acc = 0;
let comp_name = comp.name;
let mut structural_enable_map: HashMap<
ir::Id,
Vec<(ir::Id, ir::Guard<Nothing>)>,
Id,
Vec<(Id, ir::Guard<Nothing>)>,
> = HashMap::new();
// groups to cells that they invoked?
let mut cell_invoke_map: HashMap<Id, Vec<Id>> = HashMap::new();
let group_names = comp
.groups
.iter()
.map(|group| group.borrow().name())
.collect::<Vec<_>>();
// iterate and check for structural enables
// iterate and check for structural enables and for cell invokes
for group_ref in comp.groups.iter() {
let group = &group_ref.borrow();
for assigment_ref in group.assignments.iter() {
Expand Down Expand Up @@ -79,6 +81,20 @@ impl Visitor for ProfilerInstrumentation {
acc += 1; // really sad hack
}
}
if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
if dst_borrow.name == "go" {
let cell_name = cell_ref.upgrade().borrow().name();
match cell_invoke_map.get_mut(&group.name()) {
Some(vec_ref) => {
vec_ref.push(cell_name);
}
None => {
cell_invoke_map
.insert(group.name(), vec![cell_name]);
}
}
}
}
}
}
// build probe and assignments for every group + all structural invokes
Expand Down Expand Up @@ -138,6 +154,36 @@ impl Visitor for ProfilerInstrumentation {
));
}
}
// probe cell and assignments for structural cell invocations (the group is structurally invoking a cell.)
for (invoker_group, invoked_cells) in cell_invoke_map.iter() {
for invoked_cell in invoked_cells {
let probe_cell_name = format!(
"{}__{}__{}_cell_probe",
invoked_cell, invoker_group, comp_name
);
let probe_cell = builder.add_primitive(
probe_cell_name,
"std_wire",
&[1],
);
probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
probe_cell
.borrow_mut()
.add_attribute(BoolAttr::Protected, 1);
// FIXME: for correctness we'd probably want to get the go guard of the cell, but it doesn't *really* matter for the first pass
let probe_asgn: ir::Assignment<Nothing> = builder
.build_assignment(
probe_cell.borrow().get("in"),
one.borrow().get("out"),
Guard::True,
);
group_name_assign_and_cell.push((
invoker_group.clone(),

Check failure on line 181 in calyx-opt/src/passes/profiler_instrumentation.rs

View workflow job for this annotation

GitHub Actions / Check Formatting

using `clone` on type `Id` which implements the `Copy` trait
probe_asgn,
probe_cell,
));
}
}
}
// ugh so ugly
for group in comp.groups.iter() {
Expand Down
103 changes: 56 additions & 47 deletions tools/profiler/new-parse-vcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, name, data):
class ProfilingInfo:
def __init__(self, name, callsite=None, component=None, is_cell=False):
self.name = name
self.callsite = callsite
self.callsite = callsite # "official" call site. Should only be used for probes?
self.component = component
self.shortname = self.name.split(".")[-1]
self.closed_segments = [] # Segments will be (start_time, end_time)
Expand Down Expand Up @@ -59,7 +59,7 @@ def nice_repr (self):

def start_new_segment(self, curr_clock_cycle):
if self.current_segment is None:
self.current_segment = {"start": curr_clock_cycle, "end": -1}
self.current_segment = {"start": curr_clock_cycle, "end": -1, "callsite": self.callsite} # NOTE: see if this backfires
else:
print(f"Error! The group {self.name} is starting a new segment while the current segment is not closed.")
print(f"Current segment: {self.current_segment}")
Expand All @@ -72,11 +72,14 @@ def end_current_segment(self, curr_clock_cycle):
self.total_cycles += curr_clock_cycle - self.current_segment["start"]
self.current_segment = None # Reset current segment

def is_active_at_cycle(self, i): # is the probe active at cycle i?
def get_segment_active_at_cycle(self, i): # get the segment that contains cycle i if one exists. Otherwise return None
for segment in self.closed_segments:
if segment["start"] <= i and i < segment["end"]:
return True
return False
return segment
return None

def is_active_at_cycle(self, i): # is the probe active at cycle i?
return self.get_segment_active_at_cycle(i) != None

def id(self):
return f"{self.name}_{self.component}"
Expand All @@ -91,7 +94,8 @@ def __init__(self, main_component, cells_to_components):
self.timestamps_to_events = {}
self.cells = cells_to_components
self.active_elements_info = {} # for every group/cell, maps to a corresponding ProfilingInfo object signalling when group/cell was active
self.call_stack_probe_info = {} # group --> {parent group --> ProfilingInfo object}
self.call_stack_probe_info = {} # group --> {parent group --> ProfilingInfo object}. This is for tracking structural enables within the same component.
self.cell_invoke_probe_info = {} # {cell}_{component the cell was called from} --> {parent group --> ProfilingInfo Object} (FIXME: maybe we want this the other way around?)
for cell in self.cells:
self.active_elements_info[cell] = ProfilingInfo(cell, is_cell=True)

Expand Down Expand Up @@ -142,6 +146,20 @@ def enddefinitions(self, vcd, signals, cur_sig_vals):
self.call_stack_probe_info[group_id][group_parent] = ProfilingInfo(group_name, callsite=group_parent, component=group_component)
signal_id_dict[sid].append(name)

elif "cell_probe_out" in name:
encoded_info = name.split("_cell_probe_out")[0]
probe_info_split = encoded_info.split("__")
cell_name = probe_info_split[0]
invoker_group = probe_info_split[1]
component = probe_info_split[2]
probe_info_obj = ProfilingInfo(cell_name, callsite=invoker_group, component=component, is_cell=True)
cell_id = cell_name + "_" + component
if cell_name not in self.cell_invoke_probe_info:
self.cell_invoke_probe_info[cell_id] = {invoker_group: probe_info_obj}
else:
self.call_stack_probe_info[cell_id][invoker_group] = probe_info_obj
signal_id_dict[sid].append(name)

# don't need to check for signal ids that don't pertain to signals we're interested in
self.signal_id_to_names = {k:v for k,v in signal_id_dict.items() if len(v) > 0}
print(self.signal_id_to_names)
Expand Down Expand Up @@ -171,7 +189,8 @@ def postprocess(self):
clock_cycles = -1
started = False
currently_active = set()
se_currently_active = set()
se_currently_active = set() # structural group enables
ci_currently_active = set() # cell invokes
for ts in self.timestamps_to_events:
events = self.timestamps_to_events[ts]
started = started or [x for x in events if x["signal"] == f"{self.main_component}.go" and x["value"] == 1]
Expand Down Expand Up @@ -215,6 +234,20 @@ def postprocess(self):
group_id = child_group_name + "_" + child_group_component
self.call_stack_probe_info[group_id][parent].end_current_segment(clock_cycles)
se_currently_active.remove(group_id)
elif "cell_probe_out" in signal_name and value == 1:
encoded_info_split = signal_name.split("_cell_probe_out")[0].split("__")
cell_name = encoded_info_split[0]
parent = encoded_info_split[1]
parent_component = encoded_info_split[2]
cell_id = cell_name + "_" + parent_component
self.cell_invoke_probe_info[cell_id][parent].start_new_segment(clock_cycles)
elif "cell_probe_out" in signal_name and value == 0:
encoded_info_split = signal_name.split("_cell_probe_out")[0].split("__")
cell_name = encoded_info_split[0]
parent = encoded_info_split[1]
parent_component = encoded_info_split[2]
cell_id = cell_name + "_" + parent_component
self.cell_invoke_probe_info[cell_id][parent].end_current_segment(clock_cycles)
for active in currently_active: # end any group/cell activitations that are still around...
self.active_elements_info[active].end_current_segment(clock_cycles)
for active in se_currently_active: # end any structural enables that are still around...
Expand Down Expand Up @@ -271,13 +304,12 @@ def create_traces(active_element_probes_info, call_stack_probes_info, total_cycl
new_timeline_map = {i : [] for i in range(total_cycles)}
# now, we need to figure out the sets of traces
for i in timeline_map:
print(f"cycle {i}")
parents = set()
i_mapping = {} # each unique group inv mapping to its stack. the "group" should be the last item on each stack
# FIXME: probably want to wrap this around a while loop or sth?
curr_component = main_component
main_component_info = list(filter(lambda x : x.name == main_component, timeline_map[i]))[0]
i_mapping[main_component] = [main_component]
i_mapping[main_component] = ["main"] # [main_component]

# find all enables from control. these are all units that either (1) don't have any maps in call_stack_probes_info, or (2) have no active parent calls in call_stack_probes_info
for active_unit in timeline_map[i]:
Expand Down Expand Up @@ -309,45 +341,16 @@ def create_traces(active_element_probes_info, call_stack_probes_info, total_cycl
for elem in i_mapping:
if elem not in parents:
new_timeline_map[i].append(i_mapping[elem])


print(new_timeline_map[i])
exit(1)


# # find all of the invocations from control
# for elem in filter((lambda x: x.callsite is not None and "instrumentation_wrapper" in x.callsite), timeline_map[i]):
# i_mapping[elem] = i_mapping[main_component_info] + [elem]
# parents.add(main_component_info)
# # now, walk through everything else before saturation
# new_groups = set()
# started = False
# while not started or len(new_groups) == 1:
# started = True
# new_groups = set()
# for elem in timeline_map[i]:
# if elem in i_mapping:
# continue
# parent_find_attempt = list(filter(lambda x : x.shortname == elem.callsite, i_mapping))
# if len(parent_find_attempt) == 1: # found a match!
# parent_info = parent_find_attempt[0]
# i_mapping[elem] = i_mapping[parent_info] + [elem]
# parents.add(parent_info)
# new_groups.add(elem)

# for elem in i_mapping:
# if elem not in parents:
# new_timeline_map[i].append(i_mapping[elem])

# for i in new_timeline_map:
# print(i)
# for stack in new_timeline_map[i]:
# print(f"\t{stack}")
for i in new_timeline_map:
print(i)
for stack in new_timeline_map[i]:
print(f"\t{stack}")

return new_timeline_map

def create_output(timeline_map, out_dir):
shutil.rmtree(out_dir)
# shutil.rmtree(out_dir)
os.mkdir(out_dir)
for i in timeline_map:
fpath = os.path.join(out_dir, f"cycle{i}.dot")
Expand All @@ -357,16 +360,20 @@ def create_output(timeline_map, out_dir):
for stack in timeline_map[i]:
acc = "\t"
for stack_elem in stack[0:-1]:
acc += stack_elem.shortname + " -> "
acc += stack[-1].shortname + ";\n"
acc += stack_elem + " -> "
acc += stack[-1] + ";\n"
# for stack_elem in stack[0:-1]:
# acc += stack_elem.shortname + " -> "
# acc += stack[-1].shortname + ";\n"
f.write(acc)
f.write("}")

# make flame graph folded file
stacks = {} # stack to number of cycles
for i in timeline_map:
for stack_list in timeline_map[i]:
stack_str = ";".join(map(lambda x : x.flame_repr(), stack_list))
# stack_str = ";".join(map(lambda x : x.flame_repr(), stack_list))
stack_str = ";".join(stack_list)
if stack_str not in stacks:
stacks[stack_str] = 1
else:
Expand All @@ -387,10 +394,12 @@ def main(vcd_filename, cells_json_file, out_dir):
print()
print("Call stack info: " + str(converter.call_stack_probe_info))
print()
print("Cell stack info: " + str(converter.cell_invoke_probe_info))
print()

# NOTE: for a more robust implementation, we can even skip the part where we store active
# cycles per group.
new_timeline_map = create_traces(converter.active_elements_info, converter.call_stack_probe_info, converter.clock_cycles, main_component)
# new_timeline_map = create_traces(converter.active_elements_info, converter.call_stack_probe_info, converter.clock_cycles, main_component)

# create_output(new_timeline_map, out_dir)

Expand Down
5 changes: 1 addition & 4 deletions tools/profiler/new-profiler-approach.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ if [ ! -f ${VCD_FILE} ]; then
exit 1
fi

echo exiting early
exit 1

# Run script to get cycle level counts
echo "[${SCRIPT_NAME}] Using FSM info and VCD file to obtain cycle level counts"
(
Expand All @@ -86,7 +83,7 @@ echo "[${SCRIPT_NAME}] Using FSM info and VCD file to obtain cycle level counts"
set +o xtrace
) &> ${LOGS_DIR}/gol-process

# # Convert all dot files to pdf
# Convert all dot files to pdf
# TREES_PDF_DIR=${OUT_DIR}-pdf
# mkdir -p ${TREES_PDF_DIR}
# for f in $( ls ${OUT_DIR} | grep dot$ ); do
Expand Down

0 comments on commit 5dd7804

Please sign in to comment.