1use crate::module::ProgramMetadata;
7use std::collections::HashMap;
8
9pub trait FunctionScope {
15 fn enter_function<'a>(
17 &mut self,
18 name: &str,
19 labels: impl IntoIterator<Item = (&'a str, usize)>,
20 ) -> eyre::Result<()>;
21}
22
23#[derive(Debug)]
30pub struct MetadataLoweringContext {
31 strings: HashMap<String, u32>,
32 functions: HashMap<String, (u32, u32)>,
33 labels: HashMap<String, u32>,
34}
35
36impl MetadataLoweringContext {
37 pub fn new<V, Ty, Info>(metadata: &ProgramMetadata<V, Ty, Info>) -> eyre::Result<Self> {
44 let mut strings = HashMap::new();
45 for (index, text) in metadata.strings.iter().enumerate() {
46 strings.entry(text.clone()).or_insert(u32::try_from(index)?);
47 }
48 let mut functions = HashMap::new();
49 for (index, function) in metadata.functions.iter().enumerate() {
50 let name = metadata
51 .strings
52 .get(function.name as usize)
53 .ok_or_else(|| {
54 eyre::eyre!(
55 "metadata.functions[{index}].name: invalid string index {}",
56 function.name
57 )
58 })?;
59 eyre::ensure!(
60 functions
61 .insert(
62 name.clone(),
63 (u32::try_from(index)?, function.start_address)
64 )
65 .is_none(),
66 "function `@{name}` has duplicate metadata"
67 );
68 }
69 Ok(Self {
70 strings,
71 functions,
72 labels: HashMap::new(),
73 })
74 }
75
76 pub fn function_address(&self, name: &str) -> eyre::Result<u32> {
78 self.functions
79 .get(name)
80 .map(|function| function.1)
81 .ok_or_else(|| eyre::eyre!("undefined function `@{name}`"))
82 }
83
84 pub fn function_index(&self, name: &str) -> eyre::Result<u32> {
86 self.functions
87 .get(name)
88 .map(|function| function.0)
89 .ok_or_else(|| eyre::eyre!("undefined function `@{name}`"))
90 }
91
92 pub fn label_address(&self, name: &str) -> eyre::Result<u32> {
94 self.labels
95 .get(name)
96 .copied()
97 .ok_or_else(|| eyre::eyre!("undefined label `@{name}`"))
98 }
99
100 pub fn string_index(&self, text: &str) -> eyre::Result<u32> {
103 self.strings
104 .get(text)
105 .copied()
106 .ok_or_else(|| eyre::eyre!("string literal `{text}` is missing from metadata.strings"))
107 }
108}
109
110impl FunctionScope for MetadataLoweringContext {
111 fn enter_function<'a>(
112 &mut self,
113 name: &str,
114 labels: impl IntoIterator<Item = (&'a str, usize)>,
115 ) -> eyre::Result<()> {
116 self.labels.clear();
117 let start = self.function_address(name)?;
118 for (label, offset) in labels {
119 let address = start
120 .checked_add(u32::try_from(offset)?)
121 .ok_or_else(|| eyre::eyre!("label address overflow"))?;
122 eyre::ensure!(
123 self.labels.insert(label.to_owned(), address).is_none(),
124 "duplicate label `@{label}` in function `@{name}`"
125 );
126 }
127 Ok(())
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::module::{FunctionInfo, NoInfo, Signature};
135
136 fn metadata() -> ProgramMetadata {
137 ProgramMetadata {
138 strings: vec![
139 "later".into(),
140 "first".into(),
141 "hello".into(),
142 "hello".into(),
143 ],
144 functions: vec![
145 FunctionInfo {
146 name: 0,
147 signature: Signature {
148 params: vec![],
149 ret: vec![],
150 },
151 local_count: 3,
152 start_address: 7,
153 end_address: 9,
154 file: 0,
155 },
156 FunctionInfo {
157 name: 1,
158 signature: Signature {
159 params: vec![],
160 ret: vec![],
161 },
162 local_count: 0,
163 start_address: 0,
164 end_address: 7,
165 file: 0,
166 },
167 ],
168 labels: vec![],
169 constants: vec![],
170 main_function: Some(0),
171 source_symbols: vec![],
172 extra: NoInfo,
173 }
174 }
175
176 #[test]
177 fn lookups_preserve_table_indices_and_function_local_scopes() -> eyre::Result<()> {
178 let metadata = metadata();
179 let mut context = MetadataLoweringContext::new(&metadata)?;
180 assert_eq!(context.function_index("later")?, 0);
181 assert_eq!(context.function_address("later")?, 7);
182 assert_eq!(context.function_index("first")?, 1);
183 assert_eq!(context.function_address("first")?, 0);
184 assert_eq!(context.string_index("hello")?, 2);
185 context.enter_function("first", [("done", 6), ("only_first", 1)])?;
186 assert_eq!(context.label_address("done")?, 6);
187 context.enter_function("later", [("done", 1)])?;
188 assert_eq!(context.label_address("done")?, 8);
189 assert!(context.label_address("only_first").is_err());
190 assert!(context.string_index("missing").is_err());
191 assert_eq!(metadata.strings.len(), 4);
192 Ok(())
193 }
194
195 #[test]
196 fn invalid_symbols_and_address_overflow_return_errors() -> eyre::Result<()> {
197 let mut metadata = metadata();
198 let mut context = MetadataLoweringContext::new(&metadata)?;
199 assert!(context.function_index("missing").is_err());
200 assert!(context.enter_function("missing", []).is_err());
201 assert!(
202 context
203 .enter_function("first", [("same", 0), ("same", 1)])
204 .is_err()
205 );
206 metadata.functions[0].start_address = u32::MAX;
207 let mut context = MetadataLoweringContext::new(&metadata)?;
208 assert!(context.enter_function("later", [("overflow", 1)]).is_err());
209 metadata.functions[0].name = 99;
210 assert!(MetadataLoweringContext::new(&metadata).is_err());
211 metadata.functions[0].name = 1;
212 assert!(MetadataLoweringContext::new(&metadata).is_err());
213 Ok(())
214 }
215
216 #[test]
217 fn map_extra_preserves_all_tables_without_requiring_clone() {
218 struct Extra(String);
219 let original = metadata();
220 let converted = metadata().map_extra(|_| Extra("device".into()));
221 let converted = converted.map_extra(|extra| extra.0.len());
222 assert_eq!(converted.extra, 6);
223 assert_eq!(converted.functions, original.functions);
224 assert_eq!(converted.labels, original.labels);
225 assert_eq!(converted.strings, original.strings);
226 assert_eq!(converted.constants, original.constants);
227 assert_eq!(converted.source_symbols, original.source_symbols);
228 assert_eq!(converted.main_function, original.main_function);
229 }
230}