Skip to content

Commit

Permalink
[Profiling] Multi-component programs (#2269)
Browse files Browse the repository at this point in the history
This PR deals with profiling multi-component programs. It contains:

- Modifications to TDCC to produce a single json after _processing the
whole program_ (rather than outputting JSON after each component)
- A `component-cells` tool that produces information about the
user-defined component cells (so, no primitives and no constant cells)
in each component.
- Modifications to existing profiling scripts to take multi-component
programs into account. I also cleaned the scripts up a bit and left more
documentation in the comments.

The component-cells backend is necessary because the instantiation of a
component is used in the VCD, but we need to know the name of the
original component to fetch the groups that could execute in that cell.
i.e., in
[`multi-component.futil`](https://github.com/calyxir/calyx/blob/main/examples/futil/multi-component.futil),
we have `id`, which is an instantiation of the `identity()` component.
The `go` signal for the `save` group would subsequently be
`TOP.TOP.main.id.save_go...`. So, we need some way of relating that
there is a `id` cell that instantiates `identity` within the `main`
component.

Any and all feedback would be appreciated :) Additionally, if anyone has
better names for `component-cells` (or the labels in the JSON, in the
Usage section below), it would be very helpful! (Tagging @ekiwi here
since he wasn't in the list of suggested reviewers)

We are now in a nice place where we can start profiling (non-optimized)
Calyx programs! My next step is to try the profiler on some "real-life"
programs (maybe the benchmarks on the performance dashboard).

## Usage for `component-cells` tool

The tool lives in `tools/component_cells/`, so to run:
ex)
```
cargo run --manifest-path tools/component_cells/Cargo.toml examples/futil/multi-component.futil
```
will produce
```
[
  {
    "component": "main",
    "is_main_component": true,
    "cell_info": [
      {
        "cell_name": "id",
        "component_name": "identity"
      }
    ]
  },
  {
    "component": "identity",
    "is_main_component": false,
    "cell_info": []
  }
]
```
indicating that (1) `main` is the main/entry point component, (2) `main`
contains a cell named `id` that instantiates the `identity` component,
(3) the `identity` component doesn't have any (non-primitive,
non-constant) cells.
  • Loading branch information
ayakayorihiro authored Aug 27, 2024
1 parent 176da7a commit 0ed827f
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 83 deletions.
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ members = [
"fud2/fud-core",
"tools/data-conversion",
"tools/btor2/btor2i",
"tools/cider-data-converter",
"tools/calyx-pass-explorer",
"tools/cider-data-converter",
"tools/component_cells",
"tools/component_groups",
"tools/yxi",
"tools/calyx-writer",
]
Expand Down
36 changes: 19 additions & 17 deletions calyx-opt/src/passes/top_down_compile_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,23 +1215,17 @@ impl Named for TopDownCompileControl {
}

/// Helper function to emit profiling information when the control consists of a single group.
fn emit_single_enable(
fn extract_single_enable(
con: &mut ir::Control,
component: Id,
json_out_file: &OutputFile,
) {
) -> Option<SingleEnableInfo> {
if let ir::Control::Enable(enable) = con {
let mut profiling_info_set: HashSet<ProfilingInfo> = HashSet::new();
profiling_info_set.insert(ProfilingInfo::SingleEnable(
SingleEnableInfo {
component,
group: enable.group.borrow().name(),
},
));
let _ = serde_json::to_writer_pretty(
json_out_file.get_write(),
&profiling_info_set,
);
return Some(SingleEnableInfo {
component,
group: enable.group.borrow().name(),
});
} else {
None
}
}

Expand All @@ -1244,8 +1238,11 @@ impl Visitor for TopDownCompileControl {
) -> VisResult {
let mut con = comp.control.borrow_mut();
if matches!(*con, ir::Control::Empty(..) | ir::Control::Enable(..)) {
if let Some(json_out_file) = &self.dump_fsm_json {
emit_single_enable(&mut con, comp.name, json_out_file);
if let Some(enable_info) =
extract_single_enable(&mut con, comp.name)
{
self.fsm_groups
.insert(ProfilingInfo::SingleEnable(enable_info));
}
return Ok(Action::Stop);
}
Expand Down Expand Up @@ -1459,12 +1456,17 @@ impl Visitor for TopDownCompileControl {
let comp_group =
sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups, fsm_impl);

Ok(Action::change(ir::Control::enable(comp_group)))
}

/// If requested, emit FSM json after all components are processed
fn finish_context(&mut self, _ctx: &mut calyx_ir::Context) -> VisResult {
if let Some(json_out_file) = &self.dump_fsm_json {
let _ = serde_json::to_writer_pretty(
json_out_file.get_write(),
&self.fsm_groups,
);
}
Ok(Action::change(ir::Control::enable(comp_group)))
Ok(Action::Continue)
}
}
28 changes: 28 additions & 0 deletions tools/component_cells/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "component_cells"
authors.workspace = true
license-file.workspace = true
keywords.workspace = true
repository.workspace = true
readme.workspace = true
description.workspace = true
categories.workspace = true
homepage.workspace = true
edition.workspace = true
version.workspace = true
rust-version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde.workspace = true
argh.workspace = true
serde_json = "1.0.79"

calyx-utils = { path = "../../calyx-utils" }
calyx-frontend = { path = "../../calyx-frontend" }
calyx-opt = { path = "../../calyx-opt" }

[dependencies.calyx-ir]
path = "../../calyx-ir"
features = ["serialize"]
111 changes: 111 additions & 0 deletions tools/component_cells/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use argh::FromArgs;
use calyx_frontend as frontend;
use calyx_ir::{self as ir, Id};
use calyx_utils::{CalyxResult, OutputFile};
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::{collections::HashSet, io};

#[derive(FromArgs)]
/// Path for library and path for file to read from
struct Args {
/// file path to read data from
#[argh(positional, from_str_fn(read_path))]
file_path: Option<PathBuf>,

/// library path
#[argh(option, short = 'l', default = "Path::new(\".\").into()")]
pub lib_path: PathBuf,

/// output file
#[argh(option, short = 'o', default = "OutputFile::Stdout")]
pub output: OutputFile,
}

fn read_path(path: &str) -> Result<PathBuf, String> {
Ok(Path::new(path).into())
}

#[derive(Default)]
pub struct ComponentCellsBackend;

fn main() -> CalyxResult<()> {
let p: Args = argh::from_env();

let ws = frontend::Workspace::construct(&p.file_path, &p.lib_path)?;

let ctx: ir::Context = ir::from_ast::ast_to_ir(ws)?;

let main_comp = ctx.entrypoint();

let mut component_info: HashSet<ComponentInfo> = HashSet::new();
gen_component_info(&ctx, main_comp, true, &mut component_info);
write_json(component_info.clone(), p.output)?;
Ok(())
}

fn id_serialize_passthrough<S>(id: &Id, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
id.to_string().serialize(ser)
}

#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
struct ComponentInfo {
#[serde(serialize_with = "id_serialize_passthrough")]
pub component: Id,
pub is_main_component: bool,
pub cell_info: Vec<ComponentCellInfo>,
}

#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
struct ComponentCellInfo {
#[serde(serialize_with = "id_serialize_passthrough")]
pub cell_name: Id,
#[serde(serialize_with = "id_serialize_passthrough")]
pub component_name: Id,
}

/// Accumulates a set of components to the cells that they contain
/// in the program with entrypoint `main_comp`. The contained cells
/// are denoted with the name of the cell and the name of the component
/// the cell is associated with.
fn gen_component_info(
ctx: &ir::Context,
comp: &ir::Component,
is_main_comp: bool,
component_info: &mut HashSet<ComponentInfo>,
) {
let mut curr_comp_info = ComponentInfo {
component: comp.name,
is_main_component: is_main_comp,
cell_info: Vec::new(),
};
for cell in comp.cells.iter() {
let cell_ref = cell.borrow();
if let ir::CellType::Component { name } = cell_ref.prototype {
curr_comp_info.cell_info.push(ComponentCellInfo {
cell_name: cell_ref.name(),
component_name: name,
});
let component = ctx
.components
.iter()
.find(|comp| comp.name == name)
.unwrap();
gen_component_info(ctx, component, false, component_info);
}
}
component_info.insert(curr_comp_info);
}

/// Write the collected set of component information to a JSON file.
fn write_json(
component_info: HashSet<ComponentInfo>,
file: OutputFile,
) -> Result<(), io::Error> {
let created_vec: Vec<ComponentInfo> = component_info.into_iter().collect();
serde_json::to_writer_pretty(file.get_write(), &created_vec)?;
Ok(())
}
28 changes: 28 additions & 0 deletions tools/component_groups/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "component_groups"
authors.workspace = true
license-file.workspace = true
keywords.workspace = true
repository.workspace = true
readme.workspace = true
description.workspace = true
categories.workspace = true
homepage.workspace = true
edition.workspace = true
version.workspace = true
rust-version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde.workspace = true
argh.workspace = true
serde_json = "1.0.79"

calyx-utils = { path = "../../calyx-utils" }
calyx-frontend = { path = "../../calyx-frontend" }
calyx-opt = { path = "../../calyx-opt" }

[dependencies.calyx-ir]
path = "../../calyx-ir"
features = ["serialize"]
100 changes: 100 additions & 0 deletions tools/component_groups/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use argh::FromArgs;
use calyx_frontend as frontend;
use calyx_ir::{self as ir, Id};
use calyx_utils::{CalyxResult, OutputFile};
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::{collections::HashSet, io};

#[derive(FromArgs)]
/// Path for library and path for file to read from
struct Args {
/// file path to read data from
#[argh(positional, from_str_fn(read_path))]
file_path: Option<PathBuf>,

/// library path
#[argh(option, short = 'l', default = "Path::new(\".\").into()")]
pub lib_path: PathBuf,

/// output file
#[argh(option, short = 'o', default = "OutputFile::Stdout")]
pub output: OutputFile,
}

fn read_path(path: &str) -> Result<PathBuf, String> {
Ok(Path::new(path).into())
}

#[derive(Default)]
pub struct ComponentCellsBackend;

fn main() -> CalyxResult<()> {
let p: Args = argh::from_env();

let ws = frontend::Workspace::construct(&p.file_path, &p.lib_path)?;

let ctx: ir::Context = ir::from_ast::ast_to_ir(ws)?;

let main_comp = ctx.entrypoint();

let mut component_info: HashSet<ComponentGroupInfo> = HashSet::new();
gen_component_info(&ctx, main_comp, &mut component_info);
write_json(component_info.clone(), p.output)?;
Ok(())
}

fn id_serialize_passthrough<S>(id: &Id, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
id.to_string().serialize(ser)
}

#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
struct ComponentGroupInfo {
#[serde(serialize_with = "id_serialize_passthrough")]
pub component: Id,
pub groups: Vec<Id>,
}

/// Accumulates a set of components to the cells that they contain
/// in the program with entrypoint `main_comp`. The contained cells
/// are denoted with the name of the cell and the name of the component
/// the cell is associated with.
fn gen_component_info(
ctx: &ir::Context,
comp: &ir::Component,
component_info: &mut HashSet<ComponentGroupInfo>,
) {
let mut curr_comp_info = ComponentGroupInfo {
component: comp.name,
groups: Vec::new(),
};
for group_wrapped in comp.get_groups() {
curr_comp_info.groups.push(group_wrapped.borrow().name());
}
for cell in comp.cells.iter() {
let cell_ref = cell.borrow();
if let ir::CellType::Component { name } = cell_ref.prototype {
let component = ctx
.components
.iter()
.find(|comp| comp.name == name)
.unwrap();
gen_component_info(ctx, component, component_info);
}
}
component_info.insert(curr_comp_info);
}

/// Write the collected set of component information to a JSON file.
fn write_json(
component_info: HashSet<ComponentGroupInfo>,
file: OutputFile,
) -> Result<(), io::Error> {
let created_vec: Vec<ComponentGroupInfo> =
component_info.into_iter().collect();
serde_json::to_writer_pretty(file.get_write(), &created_vec)?;
Ok(())
}
Loading

0 comments on commit 0ed827f

Please sign in to comment.