Skip to content

Commit 6fbf77a

Browse files
authored
feat: Add LSP command to profile opcodes in vscode (#3496)
1 parent fac19a3 commit 6fbf77a

File tree

16 files changed

+357
-40
lines changed

16 files changed

+357
-40
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/noirc_errors/src/debug_info.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct DebugInfo {
2222

2323
/// Holds OpCodes Counts for Acir and Brillig Opcodes
2424
/// To be printed with `nargo info --profile-info`
25+
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
2526
pub struct OpCodesCount {
2627
pub acir_size: usize,
2728
pub brillig_size: usize,
@@ -51,12 +52,12 @@ impl DebugInfo {
5152
self.locations.get(loc).cloned()
5253
}
5354

54-
pub fn count_span_opcodes(&self) -> HashMap<&Location, OpCodesCount> {
55-
let mut accumulator: HashMap<&Location, Vec<&OpcodeLocation>> = HashMap::new();
55+
pub fn count_span_opcodes(&self) -> HashMap<Location, OpCodesCount> {
56+
let mut accumulator: HashMap<Location, Vec<&OpcodeLocation>> = HashMap::new();
5657

5758
for (opcode_location, locations) in self.locations.iter() {
5859
for location in locations.iter() {
59-
let opcodes = accumulator.entry(location).or_insert(Vec::new());
60+
let opcodes = accumulator.entry(*location).or_insert(Vec::new());
6061
opcodes.push(opcode_location);
6162
}
6263
}

tooling/lsp/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ serde_json.workspace = true
2323
tower.workspace = true
2424
cfg-if.workspace = true
2525
async-lsp = { workspace = true, features = ["omni-trait"] }
26+
serde_with = "3.2.0"
27+
fm.workspace = true
2628

2729
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
2830
wasm-bindgen.workspace = true

tooling/lsp/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use notifications::{
2626
on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized,
2727
};
2828
use requests::{
29-
on_code_lens_request, on_initialize, on_shutdown, on_test_run_request, on_tests_request,
29+
on_code_lens_request, on_initialize, on_profile_run_request, on_shutdown, on_test_run_request,
30+
on_tests_request,
3031
};
3132
use serde_json::Value as JsonValue;
3233
use tower::Service;
@@ -66,6 +67,7 @@ impl NargoLspService {
6667
.request::<request::CodeLens, _>(on_code_lens_request)
6768
.request::<request::NargoTests, _>(on_tests_request)
6869
.request::<request::NargoTestRun, _>(on_test_run_request)
70+
.request::<request::NargoProfileRun, _>(on_profile_run_request)
6971
.notification::<notification::Initialized>(on_initialized)
7072
.notification::<notification::DidChangeConfiguration>(on_did_change_configuration)
7173
.notification::<notification::DidOpenTextDocument>(on_did_open_text_document)

tooling/lsp/src/requests/code_lens_request.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const INFO_CODELENS_TITLE: &str = "Info";
2323
const EXECUTE_COMMAND: &str = "nargo.execute";
2424
const EXECUTE_CODELENS_TITLE: &str = "Execute";
2525

26+
const PROFILE_COMMAND: &str = "nargo.profile";
27+
const PROFILE_CODELENS_TITLE: &str = "Profile";
28+
2629
fn with_arrow(title: &str) -> String {
2730
format!("{ARROW} {title}")
2831
}
@@ -163,6 +166,16 @@ fn on_code_lens_request_inner(
163166
let execute_lens = CodeLens { range, command: Some(execute_command), data: None };
164167

165168
lenses.push(execute_lens);
169+
170+
let profile_command = Command {
171+
title: PROFILE_CODELENS_TITLE.to_string(),
172+
command: PROFILE_COMMAND.into(),
173+
arguments: Some(package_selection_args(&workspace, package)),
174+
};
175+
176+
let profile_lens = CodeLens { range, command: Some(profile_command), data: None };
177+
178+
lenses.push(profile_lens);
166179
}
167180
}
168181

@@ -200,6 +213,16 @@ fn on_code_lens_request_inner(
200213
let info_lens = CodeLens { range, command: Some(info_command), data: None };
201214

202215
lenses.push(info_lens);
216+
217+
let profile_command = Command {
218+
title: PROFILE_CODELENS_TITLE.to_string(),
219+
command: PROFILE_COMMAND.into(),
220+
arguments: Some(package_selection_args(&workspace, package)),
221+
};
222+
223+
let profile_lens = CodeLens { range, command: Some(profile_command), data: None };
224+
225+
lenses.push(profile_lens);
203226
}
204227
}
205228
}

tooling/lsp/src/requests/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ use crate::{
1919
// and params passed in.
2020

2121
mod code_lens_request;
22+
mod profile_run;
2223
mod test_run;
2324
mod tests;
2425

2526
pub(crate) use {
26-
code_lens_request::on_code_lens_request, test_run::on_test_run_request, tests::on_tests_request,
27+
code_lens_request::on_code_lens_request, profile_run::on_profile_run_request,
28+
test_run::on_test_run_request, tests::on_tests_request,
2729
};
2830

2931
pub(crate) fn on_initialize(
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::{
2+
collections::{BTreeMap, HashMap},
3+
future::{self, Future},
4+
};
5+
6+
use acvm::{acir::circuit::Opcode, Language};
7+
use async_lsp::{ErrorCode, ResponseError};
8+
use nargo::artifacts::debug::DebugArtifact;
9+
use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection};
10+
use noirc_driver::{CompileOptions, DebugFile, NOIR_ARTIFACT_VERSION_STRING};
11+
use noirc_errors::{debug_info::OpCodesCount, Location};
12+
13+
use crate::{
14+
types::{NargoProfileRunParams, NargoProfileRunResult},
15+
LspState,
16+
};
17+
use fm::FileId;
18+
19+
pub(crate) fn on_profile_run_request(
20+
state: &mut LspState,
21+
params: NargoProfileRunParams,
22+
) -> impl Future<Output = Result<NargoProfileRunResult, ResponseError>> {
23+
future::ready(on_profile_run_request_inner(state, params))
24+
}
25+
26+
fn on_profile_run_request_inner(
27+
state: &LspState,
28+
params: NargoProfileRunParams,
29+
) -> Result<NargoProfileRunResult, ResponseError> {
30+
let root_path = state.root_path.as_deref().ok_or_else(|| {
31+
ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find project root")
32+
})?;
33+
34+
let toml_path = find_package_manifest(root_path, root_path).map_err(|err| {
35+
// If we cannot find a manifest, we can't run the test
36+
ResponseError::new(ErrorCode::REQUEST_FAILED, err)
37+
})?;
38+
39+
let crate_name = params.package;
40+
41+
let workspace = resolve_workspace_from_toml(
42+
&toml_path,
43+
PackageSelection::DefaultOrAll,
44+
Some(NOIR_ARTIFACT_VERSION_STRING.to_string()),
45+
)
46+
.map_err(|err| {
47+
// If we found a manifest, but the workspace is invalid, we raise an error about it
48+
ResponseError::new(ErrorCode::REQUEST_FAILED, err)
49+
})?;
50+
51+
// Since we filtered on crate name, this should be the only item in the iterator
52+
match workspace.into_iter().next() {
53+
Some(_package) => {
54+
let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace
55+
.into_iter()
56+
.filter(|package| !package.is_library())
57+
.cloned()
58+
.partition(|package| package.is_binary());
59+
60+
// # TODO(#3504): Consider how to incorporate Backend relevant information in wider context.
61+
let is_opcode_supported = |_opcode: &Opcode| true;
62+
let np_language = Language::PLONKCSat { width: 3 };
63+
64+
let (compiled_programs, compiled_contracts) = nargo::ops::compile_workspace(
65+
&workspace,
66+
&binary_packages,
67+
&contract_packages,
68+
np_language,
69+
is_opcode_supported,
70+
&CompileOptions::default(),
71+
)
72+
.map_err(|err| ResponseError::new(ErrorCode::REQUEST_FAILED, err))?;
73+
74+
let mut opcodes_counts: HashMap<Location, OpCodesCount> = HashMap::new();
75+
let mut file_map: BTreeMap<FileId, DebugFile> = BTreeMap::new();
76+
for compiled_program in &compiled_programs {
77+
let span_opcodes = compiled_program.debug.count_span_opcodes();
78+
let debug_artifact: DebugArtifact = compiled_program.clone().into();
79+
opcodes_counts.extend(span_opcodes);
80+
file_map.extend(debug_artifact.file_map);
81+
}
82+
83+
for compiled_contract in &compiled_contracts {
84+
let functions = &compiled_contract.functions;
85+
let debug_artifact: DebugArtifact = compiled_contract.clone().into();
86+
file_map.extend(debug_artifact.file_map);
87+
for contract_function in functions {
88+
let span_opcodes = contract_function.debug.count_span_opcodes();
89+
opcodes_counts.extend(span_opcodes);
90+
}
91+
}
92+
93+
let result = NargoProfileRunResult { file_map, opcodes_counts };
94+
95+
Ok(result)
96+
}
97+
None => Err(ResponseError::new(
98+
ErrorCode::REQUEST_FAILED,
99+
format!("Could not locate package named: {crate_name}"),
100+
)),
101+
}
102+
}

tooling/lsp/src/types.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
use fm::FileId;
2+
use noirc_driver::DebugFile;
3+
use noirc_errors::{debug_info::OpCodesCount, Location};
14
use noirc_frontend::graph::CrateName;
25
use serde::{Deserialize, Serialize};
6+
use serde_with::serde_as;
7+
use std::collections::{BTreeMap, HashMap};
38

49
// Re-providing lsp_types that we don't need to override
510
pub(crate) use lsp_types::{
@@ -14,8 +19,8 @@ pub(crate) mod request {
1419
use lsp_types::{request::Request, InitializeParams};
1520

1621
use super::{
17-
InitializeResult, NargoTestRunParams, NargoTestRunResult, NargoTestsParams,
18-
NargoTestsResult,
22+
InitializeResult, NargoProfileRunParams, NargoProfileRunResult, NargoTestRunParams,
23+
NargoTestRunResult, NargoTestsParams, NargoTestsResult,
1924
};
2025

2126
// Re-providing lsp_types that we don't need to override
@@ -44,6 +49,14 @@ pub(crate) mod request {
4449
type Result = NargoTestsResult;
4550
const METHOD: &'static str = "nargo/tests";
4651
}
52+
53+
#[derive(Debug)]
54+
pub(crate) struct NargoProfileRun;
55+
impl Request for NargoProfileRun {
56+
type Params = NargoProfileRunParams;
57+
type Result = NargoProfileRunResult;
58+
const METHOD: &'static str = "nargo/profile/run";
59+
}
4760
}
4861

4962
pub(crate) mod notification {
@@ -186,5 +199,16 @@ pub(crate) struct NargoTestRunResult {
186199
pub(crate) result: String,
187200
pub(crate) message: Option<String>,
188201
}
202+
#[derive(Debug, Serialize, Deserialize)]
203+
pub(crate) struct NargoProfileRunParams {
204+
pub(crate) package: CrateName,
205+
}
206+
#[serde_as]
207+
#[derive(Debug, Serialize, Deserialize)]
208+
pub(crate) struct NargoProfileRunResult {
209+
pub(crate) file_map: BTreeMap<FileId, DebugFile>,
210+
#[serde_as(as = "Vec<(_, _)>")]
211+
pub(crate) opcodes_counts: HashMap<Location, OpCodesCount>,
212+
}
189213

190214
pub(crate) type CodeLensResult = Option<Vec<CodeLens>>;

tooling/nargo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ iter-extended.workspace = true
2424
serde.workspace = true
2525
thiserror.workspace = true
2626
codespan-reporting.workspace = true
27+
rayon = "1.8.0"

tooling/nargo/src/errors.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,36 @@ use acvm::{
22
acir::circuit::OpcodeLocation,
33
pwg::{ErrorLocation, OpcodeResolutionError},
44
};
5-
use noirc_errors::{debug_info::DebugInfo, CustomDiagnostic, FileDiagnostic};
5+
use noirc_errors::{
6+
debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic,
7+
};
68

79
pub use noirc_errors::Location;
810

11+
use noirc_frontend::graph::CrateName;
912
use noirc_printable_type::ForeignCallError;
1013
use thiserror::Error;
1114

15+
/// Errors covering situations where a package cannot be compiled.
16+
#[derive(Debug, Error)]
17+
pub enum CompileError {
18+
#[error("Package `{0}` has type `lib` but only `bin` types can be compiled")]
19+
LibraryCrate(CrateName),
20+
21+
#[error("Package `{0}` is expected to have a `main` function but it does not")]
22+
MissingMainFunction(CrateName),
23+
24+
/// Errors encountered while compiling the Noir program.
25+
/// These errors are already written to stderr.
26+
#[error("Aborting due to {} previous error{}", .0.error_count, if .0.error_count == 1 { "" } else { "s" })]
27+
ReportedErrors(ReportedErrors),
28+
}
29+
impl From<ReportedErrors> for CompileError {
30+
fn from(errors: ReportedErrors) -> Self {
31+
Self::ReportedErrors(errors)
32+
}
33+
}
34+
1235
#[derive(Debug, Error)]
1336
pub enum NargoError {
1437
/// Error while compiling Noir into ACIR.

0 commit comments

Comments
 (0)