Skip to content

Commit cfa15d4

Browse files
committed
implement first pass of memory layout viewer
1 parent db0add1 commit cfa15d4

File tree

9 files changed

+561
-6
lines changed

9 files changed

+561
-6
lines changed

crates/ide/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod interpret_function;
6060
mod view_item_tree;
6161
mod shuffle_crate_graph;
6262
mod fetch_crates;
63+
mod view_memory_layout;
6364

6465
use std::ffi::OsStr;
6566

@@ -74,6 +75,7 @@ use ide_db::{
7475
};
7576
use syntax::SourceFile;
7677
use triomphe::Arc;
78+
use view_memory_layout::{view_memory_layout, RecursiveMemoryLayout};
7779

7880
use crate::navigation_target::{ToNav, TryToNav};
7981

@@ -724,6 +726,10 @@ impl Analysis {
724726
self.with_db(|db| move_item::move_item(db, range, direction))
725727
}
726728

729+
pub fn get_recursive_memory_layout(&self, position: FilePosition) -> Cancellable<Option<RecursiveMemoryLayout>> {
730+
self.with_db(|db| view_memory_layout(db, position))
731+
}
732+
727733
/// Performs an operation on the database that may be canceled.
728734
///
729735
/// rust-analyzer needs to be able to answer semantic questions about the

crates/ide/src/view_memory_layout.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use hir::{Field, HirDisplay, Layout, Semantics, Type};
2+
use ide_db::{
3+
defs::{Definition, IdentClass},
4+
helpers::pick_best_token,
5+
RootDatabase,
6+
};
7+
use syntax::{AstNode, SyntaxKind, SyntaxToken};
8+
9+
use crate::FilePosition;
10+
11+
pub struct MemoryLayoutNode {
12+
pub item_name: String,
13+
pub typename: String,
14+
pub size: u64,
15+
pub alignment: u64,
16+
pub offset: u64,
17+
pub parent_idx: i64,
18+
pub children_start: i64,
19+
pub children_len: u64,
20+
}
21+
22+
pub struct RecursiveMemoryLayout {
23+
pub nodes: Vec<MemoryLayoutNode>,
24+
}
25+
26+
fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Definition> {
27+
for token in sema.descend_into_macros(token) {
28+
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
29+
if let Some(&[x]) = def.as_deref() {
30+
return Some(x);
31+
}
32+
}
33+
None
34+
}
35+
36+
pub(crate) fn view_memory_layout(
37+
db: &RootDatabase,
38+
position: FilePosition,
39+
) -> Option<RecursiveMemoryLayout> {
40+
let sema = Semantics::new(db);
41+
let file = sema.parse(position.file_id);
42+
let token =
43+
pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
44+
SyntaxKind::IDENT => 3,
45+
_ => 0,
46+
})?;
47+
48+
let def = get_definition(&sema, token)?;
49+
50+
let ty = match def {
51+
Definition::Adt(it) => it.ty(db),
52+
Definition::TypeAlias(it) => it.ty(db),
53+
Definition::BuiltinType(it) => it.ty(db),
54+
Definition::SelfType(it) => it.self_ty(db),
55+
Definition::Local(it) => it.ty(db),
56+
_ => return None,
57+
};
58+
59+
enum FieldOrTupleIdx {
60+
Field(Field),
61+
TupleIdx(usize),
62+
}
63+
64+
impl FieldOrTupleIdx {
65+
fn name(&self, db: &RootDatabase) -> String {
66+
match *self {
67+
FieldOrTupleIdx::Field(f) => f
68+
.name(db)
69+
.as_str()
70+
.map(|s| s.to_owned())
71+
.unwrap_or_else(|| format!("{:#?}", f.name(db).as_tuple_index().unwrap())),
72+
FieldOrTupleIdx::TupleIdx(i) => format!(".{i}").to_owned(),
73+
}
74+
}
75+
76+
fn index(&self) -> usize {
77+
match *self {
78+
FieldOrTupleIdx::Field(f) => f.index(),
79+
FieldOrTupleIdx::TupleIdx(i) => i,
80+
}
81+
}
82+
}
83+
84+
fn read_layout(
85+
nodes: &mut Vec<MemoryLayoutNode>,
86+
db: &RootDatabase,
87+
ty: &Type,
88+
layout: &Layout,
89+
parent_idx: usize,
90+
) {
91+
let mut fields = ty
92+
.fields(db)
93+
.into_iter()
94+
.map(|(f, ty)| (FieldOrTupleIdx::Field(f), ty))
95+
.chain(
96+
ty.tuple_fields(db)
97+
.into_iter()
98+
.enumerate()
99+
.map(|(i, ty)| (FieldOrTupleIdx::TupleIdx(i), ty)),
100+
)
101+
.collect::<Vec<_>>();
102+
103+
fields.sort_by_key(|(f, _)| layout.field_offset(f.index()).unwrap_or(u64::MAX));
104+
105+
if fields.len() == 0 {
106+
return;
107+
}
108+
109+
let children_start = nodes.len();
110+
nodes[parent_idx].children_start = children_start as i64;
111+
nodes[parent_idx].children_len = fields.len() as u64;
112+
113+
for (field, child_ty) in fields.iter() {
114+
if let Ok(child_layout) = child_ty.layout(db) {
115+
nodes.push(MemoryLayoutNode {
116+
item_name: field.name(db),
117+
typename: child_ty.display(db).to_string(),
118+
size: child_layout.size(),
119+
alignment: child_layout.align(),
120+
offset: layout.field_offset(field.index()).unwrap_or(0),
121+
parent_idx: parent_idx as i64,
122+
children_start: -1,
123+
children_len: 0,
124+
});
125+
} else {
126+
nodes.push(MemoryLayoutNode {
127+
item_name: field.name(db)
128+
+ format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err())
129+
.as_ref(),
130+
typename: child_ty.display(db).to_string(),
131+
size: 0,
132+
offset: 0,
133+
alignment: 0,
134+
parent_idx: parent_idx as i64,
135+
children_start: -1,
136+
children_len: 0,
137+
});
138+
}
139+
}
140+
141+
for (i, (_, child_ty)) in fields.iter().enumerate() {
142+
if let Ok(child_layout) = child_ty.layout(db) {
143+
read_layout(nodes, db, &child_ty, &child_layout, children_start + i);
144+
}
145+
}
146+
}
147+
148+
ty.layout(db).map(|layout| {
149+
let item_name = match def {
150+
Definition::Local(l) => l.name(db).as_str().unwrap().to_owned(),
151+
_ => "[ROOT]".to_owned(),
152+
};
153+
154+
let typename = ty.display(db).to_string();
155+
156+
let mut nodes = vec![MemoryLayoutNode {
157+
item_name,
158+
typename: typename.clone(),
159+
size: layout.size(),
160+
offset: 0,
161+
alignment: layout.align(),
162+
parent_idx: -1,
163+
children_start: -1,
164+
children_len: 0,
165+
}];
166+
read_layout(&mut nodes, db, &ty, &layout, 0);
167+
168+
RecursiveMemoryLayout { nodes }
169+
})
170+
}

crates/rust-analyzer/src/handlers/request.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,34 @@ pub(crate) fn handle_move_item(
16891689
}
16901690
}
16911691

1692+
pub(crate) fn handle_view_recursive_memory_layout(
1693+
snap: GlobalStateSnapshot,
1694+
params: lsp_ext::ViewRecursiveMemoryLayoutParams,
1695+
) -> Result<Option<lsp_ext::RecursiveMemoryLayout>> {
1696+
let _p = profile::span("view_recursive_memory_layout");
1697+
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1698+
let line_index = snap.file_line_index(file_id)?;
1699+
let offset = from_proto::offset(&line_index, params.position)?;
1700+
1701+
let res = snap.analysis.get_recursive_memory_layout(FilePosition { file_id, offset })?;
1702+
Ok(res.map(|it| lsp_ext::RecursiveMemoryLayout {
1703+
nodes: it
1704+
.nodes
1705+
.iter()
1706+
.map(|n| lsp_ext::MemoryLayoutNode {
1707+
item_name: n.item_name.clone(),
1708+
typename: n.typename.clone(),
1709+
size: n.size,
1710+
offset: n.offset,
1711+
alignment: n.alignment,
1712+
parent_idx: n.parent_idx,
1713+
children_start: n.children_start,
1714+
children_len: n.children_len,
1715+
})
1716+
.collect(),
1717+
}))
1718+
}
1719+
16921720
fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
16931721
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
16941722
}

crates/rust-analyzer/src/lsp_ext.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,40 @@ pub struct ExpandedMacro {
182182
pub expansion: String,
183183
}
184184

185+
pub enum ViewRecursiveMemoryLayout {}
186+
187+
impl Request for ViewRecursiveMemoryLayout {
188+
type Params = ViewRecursiveMemoryLayoutParams;
189+
type Result = Option<RecursiveMemoryLayout>;
190+
const METHOD: &'static str = "rust-analyzer/viewRecursiveMemoryLayout";
191+
}
192+
193+
#[derive(Deserialize, Serialize, Debug)]
194+
#[serde(rename_all = "camelCase")]
195+
pub struct ViewRecursiveMemoryLayoutParams {
196+
pub text_document: TextDocumentIdentifier,
197+
pub position: Position,
198+
}
199+
200+
#[derive(Deserialize, Serialize, Debug)]
201+
#[serde(rename_all = "camelCase")]
202+
pub struct RecursiveMemoryLayout {
203+
pub nodes: Vec<MemoryLayoutNode>,
204+
}
205+
206+
#[derive(Deserialize, Serialize, Debug)]
207+
#[serde(rename_all = "camelCase")]
208+
pub struct MemoryLayoutNode {
209+
pub item_name: String,
210+
pub typename: String,
211+
pub size: u64,
212+
pub offset: u64,
213+
pub alignment: u64,
214+
pub parent_idx: i64,
215+
pub children_start: i64,
216+
pub children_len: u64,
217+
}
218+
185219
pub enum CancelFlycheck {}
186220

187221
impl Notification for CancelFlycheck {

crates/rust-analyzer/src/main_loop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ impl GlobalState {
753753
)
754754
.on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
755755
.on::<lsp_ext::Ssr>(handlers::handle_ssr)
756+
.on::<lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
756757
.finish();
757758
}
758759

editors/code/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@
284284
"command": "rust-analyzer.revealDependency",
285285
"title": "Reveal File",
286286
"category": "rust-analyzer"
287+
},
288+
{
289+
"command": "rust-analyzer.viewMemoryLayout",
290+
"title": "View Memory Layout",
291+
"category": "rust-analyzer"
287292
}
288293
],
289294
"keybindings": [
@@ -2067,6 +2072,10 @@
20672072
{
20682073
"command": "rust-analyzer.openCargoToml",
20692074
"when": "inRustProject"
2075+
},
2076+
{
2077+
"command": "rust-analyzer.viewMemoryLayout",
2078+
"when": "inRustProject"
20702079
}
20712080
],
20722081
"editor/context": [

0 commit comments

Comments
 (0)