sway_core/
metadata.rs

1use crate::{
2    decl_engine::DeclId,
3    language::{ty::TyFunctionDecl, CallPath, Inline, Purity, Trace},
4};
5
6use sway_ir::{Context, MetadataIndex, Metadatum, Value};
7use sway_types::{span::Source, Ident, SourceId, Span, Spanned};
8
9use std::{collections::HashMap, path::PathBuf, sync::Arc};
10
11/// IR metadata needs to be consistent between IR generation (converting [Span]s, etc. to metadata)
12/// and ASM generation (converting the metadata back again).  Here we consolidate all of
13/// `sway-core`s metadata needs into a single place to enable that consistency.
14///
15/// The [MetadataManager] also does its best to reduce redundancy by caching certain common
16/// elements, such as source paths and storage attributes, and to avoid recreating the same
17/// indices repeatedly.
18
19#[derive(Default)]
20pub(crate) struct MetadataManager {
21    // We want to be able to store more than one `Span` per `MetadataIndex`.
22    // E.g., storing the span of the function name, and the whole function declaration.
23    // The spans differ then by the tag property of their `Metadatum::Struct`.
24    // We could cache all such spans in a single `HashMap` where the key would be `(Span, tag)`.
25    // But since the vast majority of stored spans will be tagged with the generic "span" tag,
26    // and only a few elements will have additional spans in their `MetadataIndex`, it is
27    // more efficient to provide two separate caches, one for the spans tagged with "span",
28    // and one for all other spans, tagged with arbitrary tags.
29    /// Holds [Span]s tagged with "span".
30    md_span_cache: HashMap<MetadataIndex, Span>,
31    /// Holds [Span]s tagged with an arbitrary tag.
32    md_tagged_span_cache: HashMap<MetadataIndex, (Span, &'static str)>,
33    md_file_loc_cache: HashMap<MetadataIndex, (Arc<PathBuf>, Source)>,
34    md_purity_cache: HashMap<MetadataIndex, Purity>,
35    md_inline_cache: HashMap<MetadataIndex, Inline>,
36    md_trace_cache: HashMap<MetadataIndex, Trace>,
37    md_test_decl_index_cache: HashMap<MetadataIndex, DeclId<TyFunctionDecl>>,
38
39    span_md_cache: HashMap<Span, MetadataIndex>,
40    tagged_span_md_cache: HashMap<(Span, &'static str), MetadataIndex>,
41    file_loc_md_cache: HashMap<SourceId, MetadataIndex>,
42    purity_md_cache: HashMap<Purity, MetadataIndex>,
43    inline_md_cache: HashMap<Inline, MetadataIndex>,
44    trace_md_cache: HashMap<Trace, MetadataIndex>,
45    test_decl_index_md_cache: HashMap<DeclId<TyFunctionDecl>, MetadataIndex>,
46}
47
48impl MetadataManager {
49    pub(crate) fn md_to_span(
50        &mut self,
51        context: &Context,
52        md_idx: Option<MetadataIndex>,
53    ) -> Option<Span> {
54        Self::for_each_md_idx(context, md_idx, |md_idx| {
55            self.md_span_cache.get(&md_idx).cloned().or_else(|| {
56                md_idx
57                    .get_content(context)
58                    .unwrap_struct("span", 3)
59                    .and_then(|fields| {
60                        // Create a new span and save it in the cache.
61                        let span = self.create_span_from_metadatum_fields(context, fields)?;
62                        self.md_span_cache.insert(md_idx, span.clone());
63                        Some(span)
64                    })
65            })
66        })
67    }
68
69    /// Returns the [Span] tagged with `tag` from the `md_idx`,
70    /// or `None` if such span does not exist.
71    /// If there are more spans tagged with `tag` inside of the
72    /// `md_idx`, the first one will be returned.
73    pub(crate) fn md_to_tagged_span(
74        &mut self,
75        context: &Context,
76        md_idx: Option<MetadataIndex>,
77        tag: &'static str,
78    ) -> Option<Span> {
79        Self::for_each_md_idx(context, md_idx, |md_idx| {
80            let fields = md_idx.get_content(context).unwrap_struct(tag, 3);
81
82            match fields {
83                Some(fields) => self
84                    .md_tagged_span_cache
85                    .get(&md_idx)
86                    .map(|span_and_tag| span_and_tag.0.clone())
87                    .or_else(|| {
88                        // Create a new span and save it in the cache.
89                        let span = self.create_span_from_metadatum_fields(context, fields)?;
90                        self.md_tagged_span_cache
91                            .insert(md_idx, (span.clone(), tag));
92                        Some(span)
93                    }),
94                None => None,
95            }
96        })
97    }
98
99    /// Returns the [Span] pointing to the function name in the function declaration from the `md_idx`,
100    /// or `None` if such span does not exist.
101    pub(crate) fn md_to_fn_name_span(
102        &mut self,
103        context: &Context,
104        md_idx: Option<MetadataIndex>,
105    ) -> Option<Span> {
106        self.md_to_tagged_span(context, md_idx, "fn_name_span")
107    }
108
109    /// Returns the [Span] pointing to the call path in the function call from the `md_idx`,
110    /// or `None` if such span does not exist.
111    pub(crate) fn md_to_fn_call_path_span(
112        &mut self,
113        context: &Context,
114        md_idx: Option<MetadataIndex>,
115    ) -> Option<Span> {
116        self.md_to_tagged_span(context, md_idx, "fn_call_path_span")
117    }
118
119    pub(crate) fn md_to_test_decl_index(
120        &mut self,
121        context: &Context,
122        md_idx: Option<MetadataIndex>,
123    ) -> Option<DeclId<TyFunctionDecl>> {
124        Self::for_each_md_idx(context, md_idx, |md_idx| {
125            self.md_test_decl_index_cache
126                .get(&md_idx)
127                .cloned()
128                .or_else(|| {
129                    // Create a new decl index and save it in the cache
130                    md_idx
131                        .get_content(context)
132                        .unwrap_struct("decl_index", 1)
133                        .and_then(|fields| {
134                            let index = fields[0]
135                                .unwrap_integer()
136                                .map(|index| DeclId::new(index as usize))?;
137                            self.md_test_decl_index_cache.insert(md_idx, index);
138                            Some(index)
139                        })
140                })
141        })
142    }
143
144    pub(crate) fn md_to_purity(
145        &mut self,
146        context: &Context,
147        md_idx: Option<MetadataIndex>,
148    ) -> Purity {
149        // If the purity metadata is not available, we assume the function to
150        // be pure, because in the case of a pure function, we do not store
151        // its purity attribute, to avoid bloating the metadata.
152        Self::for_each_md_idx(context, md_idx, |md_idx| {
153            self.md_purity_cache.get(&md_idx).copied().or_else(|| {
154                // Create a new purity and save it in the cache.
155                md_idx
156                    .get_content(context)
157                    .unwrap_struct("purity", 1)
158                    .and_then(|fields| {
159                        fields[0].unwrap_string().and_then(|purity_str| {
160                            let purity = match purity_str {
161                                "reads" => Some(Purity::Reads),
162                                "writes" => Some(Purity::Writes),
163                                "readswrites" => Some(Purity::ReadsWrites),
164                                _otherwise => panic!("Invalid purity metadata: {purity_str}."),
165                            }?;
166
167                            self.md_purity_cache.insert(md_idx, purity);
168
169                            Some(purity)
170                        })
171                    })
172            })
173        })
174        .unwrap_or(Purity::Pure)
175    }
176
177    /// Gets Inline information from metadata index.
178    /// TODO: We temporarily allow this because we need this
179    /// in the sway-ir inliner, but cannot access it. So the code
180    /// itself has been (modified and) copied there. When we decide
181    /// on the right place for Metadata to be
182    /// (and how it can be accessed form sway-ir), this will be fixed.
183    #[allow(dead_code)]
184    pub(crate) fn md_to_inline(
185        &mut self,
186        context: &Context,
187        md_idx: Option<MetadataIndex>,
188    ) -> Option<Inline> {
189        Self::for_each_md_idx(context, md_idx, |md_idx| {
190            self.md_inline_cache.get(&md_idx).copied().or_else(|| {
191                // Create a new inline and save it in the cache.
192                md_idx
193                    .get_content(context)
194                    .unwrap_struct("inline", 1)
195                    .and_then(|fields| fields[0].unwrap_string())
196                    .and_then(|inline_str| {
197                        let inline = match inline_str {
198                            "always" => Some(Inline::Always),
199                            "never" => Some(Inline::Never),
200                            _otherwise => None,
201                        }?;
202
203                        self.md_inline_cache.insert(md_idx, inline);
204
205                        Some(inline)
206                    })
207            })
208        })
209    }
210
211    #[allow(dead_code)]
212    pub(crate) fn md_to_trace(
213        &mut self,
214        context: &Context,
215        md_idx: Option<MetadataIndex>,
216    ) -> Option<Trace> {
217        Self::for_each_md_idx(context, md_idx, |md_idx| {
218            self.md_trace_cache.get(&md_idx).copied().or_else(|| {
219                // Create a new trace and save it in the cache.
220                md_idx
221                    .get_content(context)
222                    .unwrap_struct("trace", 1)
223                    .and_then(|fields| fields[0].unwrap_string())
224                    .and_then(|trace_str| {
225                        let trace = match trace_str {
226                            "always" => Some(Trace::Always),
227                            "never" => Some(Trace::Never),
228                            _otherwise => None,
229                        }?;
230
231                        self.md_trace_cache.insert(md_idx, trace);
232
233                        Some(trace)
234                    })
235            })
236        })
237    }
238
239    fn md_to_file_location(
240        &mut self,
241        context: &Context,
242        md: &Metadatum,
243    ) -> Option<(Arc<PathBuf>, Source)> {
244        md.unwrap_index().and_then(|md_idx| {
245            self.md_file_loc_cache.get(&md_idx).cloned().or_else(|| {
246                // Create a new file location (path and src) and save it in the cache.
247                md_idx
248                    .get_content(context)
249                    .unwrap_source_id()
250                    .and_then(|source_id| {
251                        let path_buf = context.source_engine.get_path(source_id);
252                        let src = std::fs::read_to_string(&path_buf).ok()?;
253                        let path_and_src = (Arc::new(path_buf), src.as_str().into());
254
255                        self.md_file_loc_cache.insert(md_idx, path_and_src.clone());
256
257                        Some(path_and_src)
258                    })
259            })
260        })
261    }
262
263    pub(crate) fn val_to_span(&mut self, context: &Context, value: Value) -> Option<Span> {
264        self.md_to_span(context, value.get_metadata(context))
265    }
266
267    pub(crate) fn span_to_md(
268        &mut self,
269        context: &mut Context,
270        span: &Span,
271    ) -> Option<MetadataIndex> {
272        self.span_md_cache.get(span).copied().or_else(|| {
273            span.source_id().and_then(|source_id| {
274                let md_idx = self.create_metadata_from_span(context, source_id, span, "span")?;
275                self.span_md_cache.insert(span.clone(), md_idx);
276                Some(md_idx)
277            })
278        })
279    }
280
281    /// Returns [MetadataIndex] with [Metadatum::Struct] tagged with `tag`
282    /// whose content will be the provided `span`.
283    ///
284    /// If the `span` does not have [Span::source_id], `None` is returned.
285    ///
286    /// This [Span] can later be retrieved from the [MetadataIndex] by calling
287    /// [Self::md_to_tagged_span].
288    pub(crate) fn tagged_span_to_md(
289        &mut self,
290        context: &mut Context,
291        span: &Span,
292        tag: &'static str,
293    ) -> Option<MetadataIndex> {
294        let span_and_tag = (span.clone(), tag);
295        self.tagged_span_md_cache
296            .get(&span_and_tag)
297            .copied()
298            .or_else(|| {
299                span.source_id().and_then(|source_id| {
300                    let md_idx = self.create_metadata_from_span(context, source_id, span, tag)?;
301                    self.tagged_span_md_cache.insert(span_and_tag, md_idx);
302                    Some(md_idx)
303                })
304            })
305    }
306
307    /// Returns [MetadataIndex] with [Metadatum::Struct]
308    /// whose content will be the [Span] of the `fn_name` [Ident].
309    ///
310    /// If that span does not have [Span::source_id], `None` is returned.
311    ///
312    /// This [Span] can later be retrieved from the [MetadataIndex] by calling
313    /// [Self::md_to_fn_name_span].
314    pub(crate) fn fn_name_span_to_md(
315        &mut self,
316        context: &mut Context,
317        fn_name: &Ident,
318    ) -> Option<MetadataIndex> {
319        self.tagged_span_to_md(context, &fn_name.span(), "fn_name_span")
320    }
321
322    /// Returns [MetadataIndex] with [Metadatum::Struct]
323    /// whose content will be the [Span] of the `call_path`.
324    ///
325    /// If that span does not have [Span::source_id], `None` is returned.
326    ///
327    /// This [Span] can later be retrieved from the [MetadataIndex] by calling
328    /// [Self::md_to_fn_call_path_span].
329    pub(crate) fn fn_call_path_span_to_md(
330        &mut self,
331        context: &mut Context,
332        call_path: &CallPath,
333    ) -> Option<MetadataIndex> {
334        self.tagged_span_to_md(context, &call_path.span(), "fn_call_path_span")
335    }
336
337    pub(crate) fn test_decl_index_to_md(
338        &mut self,
339        context: &mut Context,
340        decl_index: DeclId<TyFunctionDecl>,
341    ) -> Option<MetadataIndex> {
342        self.test_decl_index_md_cache
343            .get(&decl_index)
344            .copied()
345            .or_else(|| {
346                let md_idx = MetadataIndex::new_struct(
347                    context,
348                    "decl_index",
349                    vec![Metadatum::Integer(decl_index.inner() as u64)],
350                );
351                self.test_decl_index_md_cache.insert(decl_index, md_idx);
352
353                Some(md_idx)
354            })
355    }
356
357    pub(crate) fn purity_to_md(
358        &mut self,
359        context: &mut Context,
360        purity: Purity,
361    ) -> Option<MetadataIndex> {
362        // If the function is pure, we do not store the purity attribute,
363        // to avoid bloating the metadata.
364        (purity != Purity::Pure).then(|| {
365            self.purity_md_cache
366                .get(&purity)
367                .copied()
368                .unwrap_or_else(|| {
369                    // Create new metadatum.
370                    let field = match purity {
371                        Purity::Pure => unreachable!("Already checked for Pure above."),
372                        Purity::Reads => "reads",
373                        Purity::Writes => "writes",
374                        Purity::ReadsWrites => "readswrites",
375                    };
376                    let md_idx = MetadataIndex::new_struct(
377                        context,
378                        "purity",
379                        vec![Metadatum::String(field.to_owned())],
380                    );
381
382                    self.purity_md_cache.insert(purity, md_idx);
383
384                    md_idx
385                })
386        })
387    }
388
389    pub(crate) fn inline_to_md(
390        &mut self,
391        context: &mut Context,
392        inline: Inline,
393    ) -> Option<MetadataIndex> {
394        Some(
395            self.inline_md_cache
396                .get(&inline)
397                .copied()
398                .unwrap_or_else(|| {
399                    // Create new metadatum.
400                    let field = match inline {
401                        Inline::Always => "always",
402                        Inline::Never => "never",
403                    };
404                    let md_idx = MetadataIndex::new_struct(
405                        context,
406                        "inline",
407                        vec![Metadatum::String(field.to_owned())],
408                    );
409
410                    self.inline_md_cache.insert(inline, md_idx);
411
412                    md_idx
413                }),
414        )
415    }
416
417    pub(crate) fn trace_to_md(
418        &mut self,
419        context: &mut Context,
420        trace: Trace,
421    ) -> Option<MetadataIndex> {
422        Some(self.trace_md_cache.get(&trace).copied().unwrap_or_else(|| {
423            // Create new metadatum.
424            let field = match trace {
425                Trace::Always => "always",
426                Trace::Never => "never",
427            };
428            let md_idx = MetadataIndex::new_struct(
429                context,
430                "trace",
431                vec![Metadatum::String(field.to_owned())],
432            );
433
434            self.trace_md_cache.insert(trace, md_idx);
435
436            md_idx
437        }))
438    }
439
440    fn file_location_to_md(
441        &mut self,
442        context: &mut Context,
443        source_id: SourceId,
444    ) -> Option<MetadataIndex> {
445        self.file_loc_md_cache.get(&source_id).copied().or_else(|| {
446            let md_idx = MetadataIndex::new_source_id(context, source_id);
447            self.file_loc_md_cache.insert(source_id, md_idx);
448
449            Some(md_idx)
450        })
451    }
452
453    fn for_each_md_idx<T, F: FnMut(MetadataIndex) -> Option<T>>(
454        context: &Context,
455        md_idx: Option<MetadataIndex>,
456        mut f: F,
457    ) -> Option<T> {
458        // If md_idx is not None and is a list then try them all.
459        md_idx.and_then(|md_idx| {
460            if let Some(md_idcs) = md_idx.get_content(context).unwrap_list() {
461                md_idcs.iter().find_map(|md_idx| f(*md_idx))
462            } else {
463                f(md_idx)
464            }
465        })
466    }
467
468    fn create_span_from_metadatum_fields(
469        &mut self,
470        context: &Context,
471        fields: &[Metadatum],
472    ) -> Option<Span> {
473        let (path, src) = self.md_to_file_location(context, &fields[0])?;
474        let start = fields[1].unwrap_integer()?;
475        let end = fields[2].unwrap_integer()?;
476        let source_engine = context.source_engine();
477        let source_id = source_engine.get_source_id(&path);
478        let span = Span::new(src, start as usize, end as usize, Some(source_id))?;
479        Some(span)
480    }
481
482    fn create_metadata_from_span(
483        &mut self,
484        context: &mut Context,
485        source_id: &SourceId,
486        span: &Span,
487        tag: &'static str,
488    ) -> Option<MetadataIndex> {
489        let file_location_md_idx = self.file_location_to_md(context, *source_id)?;
490        let md_idx = MetadataIndex::new_struct(
491            context,
492            tag,
493            vec![
494                Metadatum::Index(file_location_md_idx),
495                Metadatum::Integer(span.start() as u64),
496                Metadatum::Integer(span.end() as u64),
497            ],
498        );
499        Some(md_idx)
500    }
501}