rucc_codegen/usage.rs
1//! How much stack each function uses, for `-fstack-usage`.
2//!
3//! The number is [`crate::frame::Frame::usage`] and what it counts is said there. This file is
4//! only somewhere to keep one row per function until the driver, which is the part that knows
5//! what a file is, writes them out beside the object the way gcc does.
6//!
7//! A row keeps the span of the function's name rather than a line and a column, because the
8//! source map that turns one into the other belongs to the driver's session and nothing in this
9//! crate reads it.
10
11use rucc_diag::Span;
12
13/// One function's stack use.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Usage {
16 /// What the source calls it, which is the symbol unless an assembler name renamed it.
17 pub name: String,
18 /// Where its name is written, which is [`rucc_ir::Func::named`].
19 pub named: Span,
20 /// Where its body opens, which is [`rucc_ir::Func::declared`] and what is said instead when a
21 /// function has no name span, as one built by something other than the C lowering has not.
22 pub declared: Span,
23 /// The bytes, as [`crate::frame::Frame::usage`] defines them.
24 pub bytes: u32,
25 /// Whether the function moves the stack pointer while it runs, which a variable length array
26 /// and an `alloca` both do, and which makes `bytes` a floor rather than the whole answer.
27 pub dynamic: bool,
28}
29
30impl Usage {
31 /// What gcc writes in the third column, which is `static` when the number is the whole answer
32 /// and `dynamic` when the function takes more while it runs.
33 ///
34 /// gcc has a third answer, `dynamic,bounded`, for a function that moves the stack pointer by
35 /// an amount it can put a bound on, and on x86-64 the usual one is a function that pushes the
36 /// arguments of a call that passes some on the stack. Such a function here has the room for
37 /// them in its frame from the prologue on, which [`crate::frame::Frame::usage`] counts
38 /// already, so the same bytes come out as `static`. Nothing else here puts a bound on how far
39 /// a frame grows, so this answer is never given.
40 #[must_use]
41 pub fn qualifier(&self) -> &'static str {
42 if self.dynamic { "dynamic" } else { "static" }
43 }
44
45 /// This function's line of a `.su` file, with the newline that ends it.
46 ///
47 /// `file:line:column:function`, a tab, the bytes, a tab and the qualifier, which is what gcc 16
48 /// writes and what a tool reading gcc's files splits on. The line and the column are where
49 /// [`Usage::span`] is, counting from one, and the file is the name the source was opened
50 /// under, so a function in a header is reported against the header, the same as gcc.
51 #[must_use]
52 pub fn line(&self, file: &str, line: u32, column: u32) -> String {
53 format!("{file}:{line}:{column}:{}\t{}\t{}\n", self.name, self.bytes, self.qualifier())
54 }
55
56 /// The span the report points at, which is the name where there is one and the brace where
57 /// there is not.
58 #[must_use]
59 pub fn span(&self) -> Span {
60 if self.named.is_dummy() { self.declared } else { self.named }
61 }
62}
63
64/// Every function a compilation gave a frame, in the order they came through.
65#[derive(Debug, Default, Clone, PartialEq, Eq)]
66pub struct StackUsage {
67 rows: Vec<Usage>,
68}
69
70impl StackUsage {
71 /// Nothing recorded yet.
72 #[must_use]
73 pub fn new() -> Self {
74 Self::default()
75 }
76
77 /// Writes down one function.
78 pub fn record(&mut self, usage: Usage) {
79 self.rows.push(usage);
80 }
81
82 /// The functions, in the order they were compiled, which is the order they are in the file.
83 #[must_use]
84 pub fn rows(&self) -> &[Usage] {
85 &self.rows
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn row(dynamic: bool, named: Span) -> Usage {
94 Usage { name: "f".to_owned(), named, declared: Span::new(10, 11), bytes: 16, dynamic }
95 }
96
97 #[test]
98 fn a_function_that_grows_is_dynamic_and_every_other_one_is_static() {
99 assert_eq!(row(false, Span::new(1, 2)).qualifier(), "static");
100 assert_eq!(row(true, Span::new(1, 2)).qualifier(), "dynamic");
101 }
102
103 #[test]
104 fn a_line_is_the_place_the_name_the_bytes_and_the_qualifier_split_by_tabs() {
105 let line = row(false, Span::new(1, 2)).line("src/a.c", 12, 5);
106 assert_eq!(line, "src/a.c:12:5:f\t16\tstatic\n");
107 let line = row(true, Span::new(1, 2)).line("inc/h.h", 1, 19);
108 assert_eq!(line, "inc/h.h:1:19:f\t16\tdynamic\n");
109 }
110
111 #[test]
112 fn the_report_points_at_the_name_and_falls_back_to_the_brace() {
113 assert_eq!(row(false, Span::new(1, 2)).span(), Span::new(1, 2));
114 assert_eq!(row(false, Span::DUMMY).span(), Span::new(10, 11));
115 }
116}