Skip to main content

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}