Skip to content

Commit

Permalink
Profiling support (#6705)
Browse files Browse the repository at this point in the history
## Description

This pull request adds the profile feature in the Sway compiler.

It communicates with an external `forc-perf` executable in order to
signal the beginning and end of different compilation phases for
profiling purposes.

(re-opening of #6565)

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: Camden Smallwood <[email protected]>
Co-authored-by: GeorgiosDelkos <[email protected]>
Co-authored-by: IGI-111 <[email protected]>
  • Loading branch information
4 people authored Nov 15, 2024
1 parent 9951c1d commit 6e31144
Show file tree
Hide file tree
Showing 21 changed files with 173 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions forc-pkg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ regex = "^1.10.2"

[target.'cfg(not(target_os = "macos"))'.dependencies]
sysinfo = "0.29"

5 changes: 5 additions & 0 deletions forc-pkg/src/manifest/build_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct BuildProfile {
#[serde(default)]
pub time_phases: bool,
#[serde(default)]
pub profile: bool,
#[serde(default)]
pub metrics_outfile: Option<String>,
#[serde(default)]
pub include_tests: bool,
Expand Down Expand Up @@ -52,6 +54,7 @@ impl BuildProfile {
print_bytecode_spans: false,
terse: false,
time_phases: false,
profile: false,
metrics_outfile: None,
include_tests: false,
error_on_warnings: false,
Expand All @@ -72,6 +75,7 @@ impl BuildProfile {
print_bytecode_spans: false,
terse: false,
time_phases: false,
profile: false,
metrics_outfile: None,
include_tests: false,
error_on_warnings: false,
Expand Down Expand Up @@ -140,6 +144,7 @@ mod tests {
print_bytecode_spans: false,
terse: true,
time_phases: true,
profile: false,
metrics_outfile: Some("metrics_outfile".into()),
include_tests: true,
error_on_warnings: true,
Expand Down
95 changes: 93 additions & 2 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ pub struct BuildOpts {
pub release: bool,
/// Output the time elapsed over each part of the compilation process.
pub time_phases: bool,
/// Profile the build process.
pub profile: bool,
/// If set, outputs compilation metrics info in JSON format.
pub metrics_outfile: Option<String>,
/// Warnings must be treated as compiler errors.
Expand Down Expand Up @@ -1561,6 +1563,7 @@ pub fn sway_build_config(
.with_print_ir(build_profile.print_ir.clone())
.with_include_tests(build_profile.include_tests)
.with_time_phases(build_profile.time_phases)
.with_profile(build_profile.profile)
.with_metrics(build_profile.metrics_outfile.clone())
.with_optimization_level(build_profile.optimization_level);
Ok(build_config)
Expand Down Expand Up @@ -1780,6 +1783,7 @@ pub fn compile(

// First, compile to an AST. We'll update the namespace and check for JSON ABI output.
let ast_res = time_expr!(
pkg.name,
"compile to ast",
"compile_to_ast",
sway_core::compile_to_ast(
Expand Down Expand Up @@ -1819,6 +1823,7 @@ pub fn compile(
}

let asm_res = time_expr!(
pkg.name,
"compile ast to asm",
"compile_ast_to_asm",
sway_core::ast_to_asm(
Expand All @@ -1839,6 +1844,7 @@ pub fn compile(
let mut program_abi = match pkg.target {
BuildTarget::Fuel => {
let program_abi_res = time_expr!(
pkg.name,
"generate JSON ABI program",
"generate_json_abi",
fuel_abi::generate_program_abi(
Expand Down Expand Up @@ -1877,6 +1883,7 @@ pub fn compile(
};

let abi = time_expr!(
pkg.name,
"generate JSON ABI program",
"generate_json_abi",
evm_abi::generate_abi_program(typed_program, engines),
Expand All @@ -1899,15 +1906,22 @@ pub fn compile(
.map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines))
.collect::<anyhow::Result<_>>()?;

let asm = match asm_res {
let mut asm = match asm_res {
Err(_) => return fail(handler),
Ok(asm) => asm,
};

let bc_res = time_expr!(
pkg.name,
"compile asm to bytecode",
"compile_asm_to_bytecode",
sway_core::asm_to_bytecode(&handler, asm, source_map, engines.se(), &sway_build_config),
sway_core::asm_to_bytecode(
&handler,
&mut asm,
source_map,
engines.se(),
&sway_build_config
),
Some(sway_build_config.clone()),
metrics
);
Expand Down Expand Up @@ -1957,9 +1971,84 @@ pub fn compile(
warnings,
metrics,
};
if sway_build_config.profile {
report_assembly_information(&asm, &compiled_package);
}

Ok(compiled_package)
}

/// Reports assembly information for a compiled package to an external `dyno` process through `stdout`.
fn report_assembly_information(
compiled_asm: &sway_core::CompiledAsm,
compiled_package: &CompiledPackage,
) {
// Get the bytes of the compiled package.
let mut bytes = compiled_package.bytecode.bytes.clone();

// Attempt to get the data section offset out of the compiled package bytes.
let data_offset = u64::from_be_bytes(
bytes
.iter()
.skip(8)
.take(8)
.cloned()
.collect::<Vec<_>>()
.try_into()
.unwrap(),
);
let data_section_size = bytes.len() as u64 - data_offset;

// Remove the data section from the compiled package bytes.
bytes.truncate(data_offset as usize);

// Calculate the unpadded size of each data section section.
// Implementation based directly on `sway_core::asm_generation::Entry::to_bytes`, referenced here:
// https://github.com/FuelLabs/sway/blob/afd6a6709e7cb11c676059a5004012cc466e653b/sway-core/src/asm_generation/fuel/data_section.rs#L147
fn calculate_entry_size(entry: &sway_core::asm_generation::Entry) -> u64 {
match &entry.value {
sway_core::asm_generation::Datum::Byte(value) => std::mem::size_of_val(value) as u64,

sway_core::asm_generation::Datum::Word(value) => std::mem::size_of_val(value) as u64,

sway_core::asm_generation::Datum::ByteArray(bytes)
| sway_core::asm_generation::Datum::Slice(bytes) => {
if bytes.len() % 8 == 0 {
bytes.len() as u64
} else {
((bytes.len() + 7) & 0xfffffff8_usize) as u64
}
}

sway_core::asm_generation::Datum::Collection(items) => {
items.iter().map(calculate_entry_size).sum()
}
}
}

// Compute the assembly information to be reported.
let asm_information = sway_core::asm_generation::AsmInformation {
bytecode_size: bytes.len() as _,
data_section: sway_core::asm_generation::DataSectionInformation {
size: data_section_size,
used: compiled_asm
.0
.data_section
.value_pairs
.iter()
.map(calculate_entry_size)
.sum(),
value_pairs: compiled_asm.0.data_section.value_pairs.clone(),
},
};

// Report the assembly information to the `dyno` process through `stdout`.
println!(
"/dyno info {}",
serde_json::to_string(&asm_information).unwrap()
);
}

impl PkgEntry {
/// Returns whether this `PkgEntry` corresponds to a test.
pub fn is_test(&self) -> bool {
Expand Down Expand Up @@ -2062,6 +2151,7 @@ fn build_profile_from_opts(
pkg,
print,
time_phases,
profile: profile_opt,
build_profile,
release,
metrics_outfile,
Expand Down Expand Up @@ -2102,6 +2192,7 @@ fn build_profile_from_opts(
profile.print_bytecode_spans |= print.bytecode_spans;
profile.terse |= pkg.terse;
profile.time_phases |= time_phases;
profile.profile |= profile_opt;
if profile.metrics_outfile.is_none() {
profile.metrics_outfile.clone_from(metrics_outfile);
}
Expand Down
1 change: 1 addition & 0 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,7 @@ fn build_opts_from_cmd(cmd: &cmd::Deploy, member_filter: pkg::MemberFilter) -> p
reverse_order: cmd.print.reverse_order,
},
time_phases: cmd.print.time_phases,
profile: cmd.print.profile,
metrics_outfile: cmd.print.metrics_outfile.clone(),
minify: pkg::MinifyOpts {
json_abi: cmd.minify.json_abi,
Expand Down
1 change: 1 addition & 0 deletions forc-plugins/forc-client/src/op/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ fn build_opts_from_cmd(cmd: &cmd::Run) -> pkg::BuildOpts {
release: cmd.build_profile.release,
error_on_warnings: cmd.build_profile.error_on_warnings,
time_phases: cmd.print.time_phases,
profile: cmd.print.profile,
metrics_outfile: cmd.print.metrics_outfile.clone(),
binary_outfile: cmd.build_output.bin_file.clone(),
debug_outfile: cmd.build_output.debug_file.clone(),
Expand Down
4 changes: 4 additions & 0 deletions forc-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ pub struct TestOpts {
pub error_on_warnings: bool,
/// Output the time elapsed over each part of the compilation process.
pub time_phases: bool,
/// Profile the compilation process.
pub profile: bool,
/// Output compilation metrics into file.
pub metrics_outfile: Option<String>,
/// Set of enabled experimental flags
Expand Down Expand Up @@ -453,6 +455,7 @@ impl From<TestOpts> for pkg::BuildOpts {
release: val.release,
error_on_warnings: val.error_on_warnings,
time_phases: val.time_phases,
profile: val.profile,
metrics_outfile: val.metrics_outfile,
tests: true,
member_filter: Default::default(),
Expand All @@ -476,6 +479,7 @@ impl TestOpts {
release: self.release,
error_on_warnings: self.error_on_warnings,
time_phases: self.time_phases,
profile: self.profile,
metrics_outfile: self.metrics_outfile,
tests: true,
member_filter: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions forc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ whoami.workspace = true
default = []
test = []
util = []
profile = []

[dev-dependencies]
completest-pty = "0.5.0"
Expand Down
1 change: 1 addition & 0 deletions forc/src/cli/commands/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ fn opts_from_cmd(cmd: Command) -> forc_test::TestOpts {
reverse_order: cmd.build.print.reverse_order,
},
time_phases: cmd.build.print.time_phases,
profile: cmd.build.print.profile,
metrics_outfile: cmd.build.print.metrics_outfile,
minify: pkg::MinifyOpts {
json_abi: cmd.build.minify.json_abi,
Expand Down
3 changes: 3 additions & 0 deletions forc/src/cli/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ pub struct Print {
/// Output the time elapsed over each part of the compilation process.
#[clap(long)]
pub time_phases: bool,
/// Profile the compilation process.
#[clap(long)]
pub profile: bool,
/// Output build errors and warnings in reverse order.
#[clap(long)]
pub reverse_order: bool,
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fn opts_from_cmd(cmd: BuildCommand) -> pkg::BuildOpts {
reverse_order: cmd.build.print.reverse_order,
},
time_phases: cmd.build.print.time_phases,
profile: cmd.build.print.profile,
metrics_outfile: cmd.build.print.metrics_outfile,
minify: pkg::MinifyOpts {
json_abi: cmd.build.minify.json_abi,
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_contract_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ fn build_opts_from_cmd(cmd: &ContractIdCommand) -> pkg::BuildOpts {
reverse_order: cmd.print.reverse_order,
},
time_phases: cmd.print.time_phases,
profile: cmd.print.profile,
metrics_outfile: cmd.print.metrics_outfile.clone(),
minify: pkg::MinifyOpts {
json_abi: cmd.minify.json_abi,
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_predicate_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn build_opts_from_cmd(cmd: PredicateRootCommand) -> pkg::BuildOpts {
reverse_order: cmd.print.reverse_order,
},
time_phases: cmd.print.time_phases,
profile: cmd.print.profile,
metrics_outfile: cmd.print.metrics_outfile,
minify: pkg::MinifyOpts {
json_abi: cmd.minify.json_abi,
Expand Down
18 changes: 18 additions & 0 deletions sway-core/src/asm_generation/finalized_asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ use sway_types::SourceEngine;
use either::Either;
use std::{collections::BTreeMap, fmt};

/// Represents an ASM set which has had register allocation, jump elimination, and optimization
/// applied to it
#[derive(Clone, serde::Serialize)]
pub struct AsmInformation {
pub bytecode_size: u64,
pub data_section: DataSectionInformation,
}

#[derive(Default, Clone, Debug, serde::Serialize)]
pub struct DataSectionInformation {
/// The total size of the data section in bytes
pub size: u64,
/// The used size of the data section in bytes
pub used: u64,
/// The data to be put in the data section of the asm
pub value_pairs: Vec<Entry>,
}

/// Represents an ASM set which has had register allocation, jump elimination, and optimization
/// applied to it
#[derive(Clone)]
Expand Down
4 changes: 2 additions & 2 deletions sway-core/src/asm_generation/fuel/data_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{fmt, iter::repeat};

// An entry in the data section. It's important for the size to be correct, especially for unions
// where the size could be larger than the represented value.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct Entry {
pub value: Datum,
pub padding: Padding,
Expand All @@ -13,7 +13,7 @@ pub struct Entry {
pub name: Option<String>,
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
pub enum Datum {
Byte(u8),
Word(u64),
Expand Down
3 changes: 2 additions & 1 deletion sway-core/src/asm_generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ pub mod fuel;
pub mod instruction_set;

mod finalized_asm;
pub use finalized_asm::{CompiledBytecode, FinalizedAsm, FinalizedEntry};
pub use finalized_asm::*;
pub use fuel::data_section::{Datum, Entry};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProgramKind {
Expand Down
6 changes: 6 additions & 0 deletions sway-core/src/build_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub struct BuildConfig {
pub(crate) include_tests: bool,
pub(crate) optimization_level: OptLevel,
pub time_phases: bool,
pub profile: bool,
pub metrics_outfile: Option<String>,
pub lsp_mode: Option<LspConfig>,
}
Expand Down Expand Up @@ -234,6 +235,7 @@ impl BuildConfig {
print_ir: PrintIr::default(),
include_tests: false,
time_phases: false,
profile: false,
metrics_outfile: None,
optimization_level: OptLevel::Opt0,
lsp_mode: None,
Expand Down Expand Up @@ -280,6 +282,10 @@ impl BuildConfig {
}
}

pub fn with_profile(self, a: bool) -> Self {
Self { profile: a, ..self }
}

pub fn with_metrics(self, a: Option<String>) -> Self {
Self {
metrics_outfile: a,
Expand Down
Loading

0 comments on commit 6e31144

Please sign in to comment.