Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ serde_json.workspace = true
thiserror.workspace = true
toml = "0.9.8"
tracing-subscriber = { version = "0.3.17", default-features = false, features = ["registry", "fmt", "tracing-log",] }
tracing-chrome = "0.7.2"
tracing.workspace = true
triomphe.workspace = true

Expand Down
247 changes: 191 additions & 56 deletions crates/base-db/src/source_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,20 @@ impl SourceFileKind {
}

fn parse_src(db: &dyn SourceDb, file_id: FileId) -> SyntaxTree {
let _span = tracing::info_span!("slang.parse_src", ?file_id).entered();
let text = db.file_text(file_id);

match db.file_kind(file_id) {
SourceFileKind::SystemVerilog | SourceFileKind::IncludeHeader => {
// HIR source maps are local to the queried file; project-aware include
// expansion belongs to parse_src_for_compilation.
let _span = tracing::info_span!(
"slang.syntax_tree.from_text",
?file_id,
bytes = text.len(),
include_buffer_count = 0usize
)
.entered();
SyntaxTree::from_text_with_options(
&text,
"",
Expand Down Expand Up @@ -180,6 +188,7 @@ fn syntax_tree_options_for_file(
db: &dyn SourceRootDb,
file_id: FileId,
) -> syntax::SyntaxTreeOptions {
let _span = tracing::info_span!("slang.syntax_tree_options.file", ?file_id).entered();
let project_config = db.project_config();
let profile_id = db.file_compilation_profile(file_id);
let include_buffers = db.include_buffers_for_profile(profile_id).as_ref().clone();
Expand All @@ -202,12 +211,25 @@ fn syntax_tree_options_for_profile(
}

fn parse_src_for_compilation(db: &dyn SourceRootDb, file_id: FileId) -> SyntaxTree {
let text = db.file_text(file_id);
let _span = tracing::info_span!("slang.parse_for_compilation", ?file_id).entered();
let text = {
let _span =
tracing::info_span!("slang.parse_for_compilation.file_text", ?file_id).entered();
db.file_text(file_id)
};
let identity = source_file_identity(db, file_id);

match db.file_kind(file_id) {
SourceFileKind::SystemVerilog | SourceFileKind::IncludeHeader => {
let options = syntax_tree_options_for_file(db, file_id);
let include_buffer_count = options.include_buffers.len();
let _span = tracing::info_span!(
"slang.parse_for_compilation.from_text",
?file_id,
bytes = text.len(),
include_buffer_count
)
.entered();
SyntaxTree::from_text_with_options(&text, &identity.name, &identity.path, &options)
}
SourceFileKind::LibraryMap => {
Expand Down Expand Up @@ -257,14 +279,40 @@ fn parse_diagnostics(db: &dyn SourceRootDb, file_id: FileId) -> Arc<[SyntaxDiagn
return Arc::from(Vec::<SyntaxDiagnostic>::new());
}

let tree = db.parse_src_for_compilation(file_id);
let _span = tracing::info_span!("slang.parse_diagnostics", ?file_id).entered();
let tree = {
let _span = tracing::info_span!("slang.parse_diagnostics.parse_tree", ?file_id).entered();
db.parse_src_for_compilation(file_id)
};
let root_buffer_id = tree.buffer_id();
let diags = tree
.diagnostics_with_options(&config.slang.warnings)
.into_iter()
.filter(|diag| diag.buffer_id.is_none_or(|buffer_id| buffer_id == root_buffer_id))
.filter_map(|diag| config.apply_rules(DiagnosticSource::Parse, diag))
.collect::<Vec<_>>();
let raw_diagnostics = {
let _span = tracing::info_span!("slang.parse.raw_diagnostics", ?file_id).entered();
tree.diagnostics_with_options(&config.slang.warnings)
};
let raw_diagnostic_count = raw_diagnostics.len();
let mut non_root_buffer_count = 0usize;
let mut ignored_diagnostic_count = 0usize;
let mut diags = Vec::new();

for diag in raw_diagnostics {
if !diag.buffer_id.is_none_or(|buffer_id| buffer_id == root_buffer_id) {
non_root_buffer_count += 1;
continue;
}

match config.apply_rules(DiagnosticSource::Parse, diag) {
Some(diag) => diags.push(diag),
None => ignored_diagnostic_count += 1,
}
}

tracing::info!(
raw_diagnostic_count,
non_root_buffer_count,
ignored_diagnostic_count,
diagnostic_count = diags.len(),
"parse diagnostics complete"
);
Arc::from(diags)
}

Expand Down Expand Up @@ -395,8 +443,21 @@ fn compilation_profile_diagnostics(

let project_config = db.project_config();
let plan = db.compilation_plan_for_profile(Some(profile_id));
let compilation_include_buffers =
compilation_plan::compilation_source_buffers_for_plan(db, &plan);
let compilation_include_buffers = {
let _span = tracing::info_span!("slang.semantic.compilation_buffers").entered();
compilation_plan::compilation_source_buffers_for_plan(db, &plan)
};
let root_count = plan.roots.len();
let top_module_count = plan.top_modules.len();
let include_buffer_count = compilation_include_buffers.len();
let _span = tracing::info_span!(
"slang.compilation_profile_diagnostics",
?profile_id,
root_count,
top_module_count,
include_buffer_count
)
.entered();
let compilation_options = syntax_tree_options_for_profile(
&project_config,
Some(profile_id),
Expand All @@ -405,61 +466,135 @@ fn compilation_profile_diagnostics(
let mut compilation = Compilation::new_with_top_modules(&plan.top_modules);
let mut buffer_file_ids = FxHashMap::default();
let path_file_ids = path_file_ids(db);
for file_id in plan.roots.iter().copied() {
let text = db.file_text(file_id);
let identity = source_file_identity(db, file_id);
let buffer_ids = match db.file_kind(file_id) {
SourceFileKind::SystemVerilog => compilation.add_syntax_tree_from_text(
&text,
&identity.name,
&identity.path,
&compilation_options,
),
SourceFileKind::LibraryMap => compilation.add_library_map_syntax_tree_from_text(
&text,
&identity.name,
&identity.path,
),
SourceFileKind::IncludeHeader | SourceFileKind::ProjectManifest => continue,
};
insert_buffer_file_ids(&mut buffer_file_ids, &path_file_ids, buffer_ids, file_id);
let mut compilation_root_count = 0usize;
let mut compilation_buffer_count = 0usize;
{
let _span = tracing::info_span!("slang.semantic.add_roots", root_count).entered();
for file_id in plan.roots.iter().copied() {
let text = {
let _span =
tracing::info_span!("slang.semantic.add_root.file_text", ?file_id).entered();
db.file_text(file_id)
};
let identity = source_file_identity(db, file_id);
let buffer_ids = match db.file_kind(file_id) {
SourceFileKind::SystemVerilog => {
let include_buffer_count = compilation_options.include_buffers.len();
let _span = tracing::info_span!(
"slang.semantic.add_root.from_text",
?file_id,
bytes = text.len(),
include_buffer_count
)
.entered();
compilation.add_syntax_tree_from_text(
&text,
&identity.name,
&identity.path,
&compilation_options,
)
}
SourceFileKind::LibraryMap => compilation.add_library_map_syntax_tree_from_text(
&text,
&identity.name,
&identity.path,
),
SourceFileKind::IncludeHeader | SourceFileKind::ProjectManifest => continue,
};
compilation_root_count += 1;
compilation_buffer_count += 1 + buffer_ids.source_buffers.len();
insert_buffer_file_ids(&mut buffer_file_ids, &path_file_ids, buffer_ids, file_id);
}
}
tracing::info!(
compilation_root_count,
compilation_buffer_count,
mapped_buffer_count = buffer_file_ids.len(),
"semantic compilation roots added"
);

let mut diagnostics = Vec::new();
if config.parse.enabled {
diagnostics.extend(
compilation
.parse_diagnostics_with_options(&config.slang.warnings)
.into_iter()
.filter_map(|diag| {
let diag_file_id = diag
.buffer_id
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())?;
let diag = config.apply_rules(DiagnosticSource::Parse, diag)?;
Some(CompilationDiagnostic {
file_id: diag_file_id,
source: DiagnosticSource::Parse,
diagnostic: diag,
})
}),
);
}

diagnostics.extend(
compilation
.semantic_diagnostics_with_options(&config.slang.warnings)
.into_iter()
.filter_map(|diag| {
let diag_file_id = diag
let raw_diagnostics = {
let _span = tracing::info_span!("slang.semantic.parse_diagnostics").entered();
compilation.parse_diagnostics_with_options(&config.slang.warnings)
};
let raw_diagnostic_count = raw_diagnostics.len();
let mut unmapped_buffer_count = 0usize;
let mut ignored_diagnostic_count = 0usize;
{
let _span =
tracing::info_span!("slang.semantic.map_parse_diagnostics", raw_diagnostic_count)
.entered();
diagnostics.extend(raw_diagnostics.into_iter().filter_map(|diag| {
let diag_file_id = match diag
.buffer_id
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())?;
let diag = config.apply_rules(DiagnosticSource::Semantic, diag)?;
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())
{
Some(file_id) => file_id,
None => {
unmapped_buffer_count += 1;
return None;
}
};
let diag = match config.apply_rules(DiagnosticSource::Parse, diag) {
Some(diag) => diag,
None => {
ignored_diagnostic_count += 1;
return None;
}
};
Some(CompilationDiagnostic {
file_id: diag_file_id,
source: DiagnosticSource::Semantic,
source: DiagnosticSource::Parse,
diagnostic: diag,
})
}),
}));
}
tracing::info!(
raw_diagnostic_count,
unmapped_buffer_count,
ignored_diagnostic_count,
diagnostic_count = diagnostics.len(),
"compilation parse diagnostics complete"
);
}

let raw_semantic_diagnostics = {
let _span = tracing::info_span!("slang.semantic.raw_diagnostics").entered();
compilation.semantic_diagnostics_with_options(&config.slang.warnings)
};
let raw_semantic_diagnostic_count = raw_semantic_diagnostics.len();
let mut unmapped_semantic_buffer_count = 0usize;
let mut ignored_semantic_diagnostic_count = 0usize;
{
let _span =
tracing::info_span!("slang.semantic.map_diagnostics", raw_semantic_diagnostic_count)
.entered();
diagnostics.extend(raw_semantic_diagnostics.into_iter().filter_map(|diag| {
let diag_file_id =
diag.buffer_id.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied());
let Some(diag_file_id) = diag_file_id else {
unmapped_semantic_buffer_count += 1;
return None;
};
let Some(diag) = config.apply_rules(DiagnosticSource::Semantic, diag) else {
ignored_semantic_diagnostic_count += 1;
return None;
};
Some(CompilationDiagnostic {
file_id: diag_file_id,
source: DiagnosticSource::Semantic,
diagnostic: diag,
})
}));
}
tracing::info!(
raw_semantic_diagnostic_count,
unmapped_semantic_buffer_count,
ignored_semantic_diagnostic_count,
diagnostic_count = diagnostics.len(),
"semantic diagnostics complete"
);

Arc::from(diagnostics)
Expand Down
24 changes: 23 additions & 1 deletion docs/src/content/docs/commands-status-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ description: Vizsla 的命令面板命令、状态栏提示和输出通道。

## 命令面板命令

VS Code 扩展贡献了三个命令:
VS Code 扩展贡献了这些命令:

| 命令 | 作用 |
| --- | --- |
| `Vizsla: Show Language Server Output` | 打开 `Vizsla Language Server` 输出通道。 |
| `Vizsla: Restart Language Server` | 停止并重新启动语言服务器。 |
| `Vizsla: Show Server Version` | 执行服务器 `--version`, 并显示第一行版本输出。 |
| `Vizsla: Profile Diagnostics` | 对工作区或当前 Verilog/SystemVerilog 文件运行一次独立 diagnostics profiling, 并生成 trace、summary 和 flamegraph。 |

## 状态栏

Expand Down Expand Up @@ -39,6 +40,27 @@ VS Code 扩展贡献了三个命令:
- bundled server 查找结果。
- 启动、停止、重启和版本查询结果。

执行 `Vizsla: Profile Diagnostics` 时, 扩展还会打开 `Vizsla Profiling` 输出通道。这里会显示本次 profiling 的目标、产物目录、诊断请求耗时和生成的文件路径。

## 性能分析诊断

执行 `Vizsla: Profile Diagnostics`。扩展会启动一个独立的临时语言服务器进程, 然后根据选择的目标发送一次诊断请求:

- 工作区目标发送 `workspace/diagnostic`, 用于观察项目级诊断路径。
- 当前文件目标发送 `textDocument/diagnostic`, 用于缩小到单文件诊断路径。

请求结束后扩展会关闭临时进程; 这个过程不会重启或影响正在使用的语言服务器。

完成后会生成:

| 文件 | 说明 |
| --- | --- |
| `trace.json` | Chrome/Perfetto/Speedscope 兼容 trace, 也就是 Speedscope 的交互式输入文件。 |
| `summary.json` | 请求耗时、diagnostics 汇总和 top span 汇总。 |
| `trace.folded` | 从 trace 生成的 folded stack。 |
| `flamegraph.svg` | 静态火焰图备用文件。交互式查看会在 VS Code 标签页中用扩展内置的本地 Speedscope viewer 打开 `trace.json`。 |
| `server.log` | 临时语言服务器日志。 |

## 查询服务器版本

你可以从命令面板执行 `Vizsla: Show Server Version`。扩展会解析当前服务器启动配置, 然后执行:
Expand Down
Loading
Loading