llvm_mapper/record/
function.rs

1//! Functionality for mapping the `MODULE_CODE_FUNCTION` record.
2
3use std::convert::TryFrom;
4
5use llvm_support::{
6    AlignError, CallingConvention, DllStorageClass, Linkage, MaybeAlign, Type, UnnamedAddr,
7    Visibility,
8};
9use num_enum::TryFromPrimitiveError;
10use thiserror::Error;
11
12use crate::block::attributes::AttributeEntry;
13use crate::map::{CtxMappable, MapCtx};
14use crate::record::StrtabError;
15use crate::unroll::UnrolledRecord;
16
17/// Errors that can occur when mapping a function record.
18#[derive(Debug, Error)]
19pub enum FunctionError {
20    /// The function record is too short to be well-formed.
21    #[error("function record too short: {0} < 10 fields")]
22    TooShort(usize),
23
24    /// The function record is in an old unsupported format.
25    #[error("unsupported function record format (v1)")]
26    V1Unsupported,
27
28    /// Retrieving a string from a string table failed.
29    #[error("error while accessing string table")]
30    Strtab(#[from] StrtabError),
31
32    /// This function has an unknown calling convention.
33    #[error("unknown calling convention")]
34    CallingConvention(#[from] TryFromPrimitiveError<CallingConvention>),
35
36    /// The function has a bad or unknown type.
37    #[error("invalid type table index: {0}")]
38    Type(u64),
39
40    /// The function has an invalid attribute entry ID.
41    #[error("invalid attribute entry ID: {0}")]
42    Attribute(u64),
43
44    /// The function has an invalid alignment.
45    #[error("invalid alignment")]
46    Alignment(#[from] AlignError),
47
48    /// The function has an invalid section table index.
49    #[error("invalid section table index: {0}")]
50    Section(usize),
51
52    /// The function has an invalid visibility.
53    #[error("invalid visibility")]
54    Visibility(#[from] TryFromPrimitiveError<Visibility>),
55
56    /// The function has an invalid GC table index.
57    #[error("invalid GC table index: {0}")]
58    Gc(usize),
59
60    /// The function has an invalid DLL storage class.
61    #[error("invalid storage class")]
62    DllStorageClass(#[from] TryFromPrimitiveError<DllStorageClass>),
63}
64
65/// Models the `MODULE_CODE_FUNCTION` record.
66#[non_exhaustive]
67#[derive(Debug)]
68pub struct Function<'ctx> {
69    /// The function's name.
70    pub name: &'ctx str,
71
72    /// A reference to the function's type in the type table.
73    pub ty: &'ctx Type,
74
75    /// The function's calling convention.
76    pub calling_convention: CallingConvention,
77
78    /// Whether the function is a declaration, or a full definition (with body).
79    pub is_declaration: bool,
80
81    /// The function's linkage.
82    pub linkage: Linkage,
83
84    /// The function's attributes, if it has any.
85    pub attributes: Option<&'ctx AttributeEntry>,
86
87    /// The function's alignment.
88    pub alignment: MaybeAlign,
89
90    /// The function's custom section, if it has one.
91    pub section: Option<&'ctx str>,
92
93    /// The function's visibility.
94    pub visibility: Visibility,
95
96    /// The function's garbage collector, if it has one.
97    pub gc_name: Option<&'ctx str>,
98
99    /// The function's `unnamed_addr` specifier.
100    pub unnamed_addr: UnnamedAddr,
101
102    /// The function's DLL storage class.
103    pub storage_class: DllStorageClass,
104}
105
106impl<'ctx> CtxMappable<'ctx, UnrolledRecord> for Function<'ctx> {
107    type Error = FunctionError;
108
109    fn try_map(record: &UnrolledRecord, ctx: &'ctx MapCtx) -> Result<Self, Self::Error> {
110        let fields = record.fields();
111
112        if !ctx.use_strtab() {
113            return Err(FunctionError::V1Unsupported);
114        }
115
116        // Every function record has at least 10 fields, corresponding to
117        // [strtab_offset, strtab_size, *v1], where v1 has 8 mandatory fields:
118        // [type, callingconv, isproto, linkage, paramattr, alignment, section, visibility, ...]
119        if fields.len() < 10 {
120            return Err(FunctionError::TooShort(fields.len()));
121        }
122
123        let name = ctx.strtab.read_name(record)?;
124        let ty = ctx
125            .type_table
126            .get(fields[2])
127            .ok_or(FunctionError::Type(fields[2]))?;
128        let calling_convention = CallingConvention::try_from(fields[3])?;
129        let is_declaration = fields[4] != 0;
130        let linkage = Linkage::from(fields[5]);
131
132        let attributes = {
133            let paramattr = fields[6];
134            // An ID of 0 is a special sentinel for no attributes,
135            // so any nonzero ID is a 1-based index.
136            if paramattr == 0 {
137                None
138            } else {
139                // NOTE(ww): This is more conservative than LLVM: LLVM treats an
140                // unknown attribute ID as an empty set of attributes,
141                // rather than a hard failure.
142                Some(
143                    ctx.attributes
144                        .get(paramattr - 1)
145                        .ok_or(FunctionError::Attribute(paramattr))?,
146                )
147            }
148        };
149
150        // TODO: Upgrade attributes here? It's what LLVM does.
151
152        let alignment = MaybeAlign::try_from(fields[7] as u8)?;
153
154        let section = match fields[8] as usize {
155            0 => None,
156            idx => Some(
157                ctx.section_table
158                    .get(idx - 1)
159                    .map(AsRef::as_ref)
160                    .ok_or(FunctionError::Section(idx - 1))?,
161            ),
162        };
163
164        let visibility = Visibility::try_from(fields[9])?;
165
166        // From here, all fields are optional and need to be guarded as such.
167
168        let gc_name = fields
169            .get(10)
170            .and_then(|idx| match *idx as usize {
171                0 => None,
172                idx => Some(
173                    ctx.gc_table
174                        .get(idx - 1)
175                        .map(AsRef::as_ref)
176                        .ok_or(FunctionError::Gc(idx - 1)),
177                ),
178            })
179            .transpose()?;
180
181        let unnamed_addr = fields
182            .get(11)
183            .copied()
184            .map(UnnamedAddr::from)
185            .unwrap_or(UnnamedAddr::None);
186
187        // fields[12]: prologuedata
188
189        let storage_class = fields.get(13).map_or_else(
190            || Ok(DllStorageClass::Default),
191            |v| DllStorageClass::try_from(*v),
192        )?;
193
194        // fields[14]: comdat
195        // fields[15]: prefixdata
196        // fields[16]: personalityfn
197        // fields[16]: preemptionspecifier
198
199        Ok(Self {
200            name,
201            ty,
202            calling_convention,
203            is_declaration,
204            linkage,
205            attributes,
206            alignment,
207            section,
208            visibility,
209            gc_name,
210            unnamed_addr,
211            storage_class,
212        })
213    }
214}