1use std::collections::HashMap;
4
5use beamr::atom::Atom;
6use beamr::loader::decode::compact::Operand;
7use beamr::loader::{Instruction, Literal, lambda_unique_id, prepare_module};
8use beamr::module::ResolvedImportTarget;
9
10use crate::{EngineError, RuntimeHandle};
11
12pub type ModuleRenameMap = HashMap<Atom, Atom>;
17
18impl RuntimeHandle {
19 pub fn register_module(
29 &self,
30 deployed_name: &str,
31 beam_bytes: &[u8],
32 ) -> Result<(), EngineError> {
33 let mut self_only = HashMap::new();
34 let deployed_atom = self.atom_table.intern(deployed_name);
35 let (module, _) = prepare_module(
36 beam_bytes,
37 &self.atom_table,
38 &self.module_registry,
39 self.native_registry.as_ref(),
40 )
41 .map_err(runtime_error_from_display)?;
42 self_only.insert(module.name, deployed_atom);
43 self.register_prepared_module(module, deployed_name, &self_only)
44 }
45
46 pub fn register_module_with_renames(
54 &self,
55 deployed_name: &str,
56 beam_bytes: &[u8],
57 rename_map: &ModuleRenameMap,
58 ) -> Result<(), EngineError> {
59 let (module, unresolved) = prepare_module(
60 beam_bytes,
61 &self.atom_table,
62 &self.module_registry,
63 self.native_registry.as_ref(),
64 )
65 .map_err(runtime_error_from_display)?;
66 if !unresolved.is_empty() {
67 tracing::warn!(
68 module = deployed_name,
69 count = unresolved.imports().len(),
70 "unresolved BEAM imports after BIF + module registration"
71 );
72 }
73 self.register_prepared_module(module, deployed_name, rename_map)
74 }
75
76 #[must_use]
78 pub fn package_rename_map(
79 &self,
80 original_names: &[&str],
81 deployed_names: &[&str],
82 ) -> ModuleRenameMap {
83 original_names
84 .iter()
85 .zip(deployed_names.iter())
86 .map(|(original, deployed)| {
87 (
88 self.atom_table.intern(original),
89 self.atom_table.intern(deployed),
90 )
91 })
92 .collect()
93 }
94
95 fn register_prepared_module(
96 &self,
97 mut module: beamr::module::Module,
98 deployed_name: &str,
99 rename_map: &ModuleRenameMap,
100 ) -> Result<(), EngineError> {
101 let deployed_atom = self.atom_table.intern(deployed_name);
102 if self.module_registry.lookup_old(deployed_atom).is_some() {
103 return Err(runtime_error(format!(
104 "cannot register deployed module `{deployed_name}` while old code is still retained"
105 )));
106 }
107 rename_module_references(&mut module, deployed_atom, rename_map, &self.atom_table)?;
108 self.module_registry.insert(module);
109 Ok(())
110 }
111
112 #[must_use]
114 pub fn has_registered_module(&self, deployed_name: &str) -> bool {
115 let module = self.atom_table.intern(deployed_name);
116 self.module_registry.lookup(module).is_some()
117 }
118
119 #[must_use]
125 pub fn module_exports_function(&self, deployed_name: &str, function: &str) -> bool {
126 let module_atom = self.atom_table.intern(deployed_name);
127 let Some(module) = self.module_registry.lookup(module_atom) else {
128 return false;
129 };
130 let function_atom = self.atom_table.intern(function);
131 module
132 .exports
133 .keys()
134 .any(|(name, _arity)| *name == function_atom)
135 }
136
137 pub(crate) fn unregister_module(&self, deployed_name: &str) -> Result<(), EngineError> {
139 let module = self.atom_table.intern(deployed_name);
140 if self.module_registry.delete_module(module) {
141 Ok(())
142 } else {
143 Err(runtime_error(format!(
144 "module `{deployed_name}` was not registered"
145 )))
146 }
147 }
148}
149
150fn rename_module_references(
151 module: &mut beamr::module::Module,
152 deployed_atom: Atom,
153 rename_map: &ModuleRenameMap,
154 atom_table: &beamr::atom::AtomTable,
155) -> Result<(), EngineError> {
156 if module.name == deployed_atom && rename_map.len() <= 1 {
157 return Ok(());
158 }
159
160 module.name = deployed_atom;
161 for import in &mut module.resolved_imports {
162 if let Some(&new_atom) = rename_map.get(&import.module) {
163 import.module = new_atom;
164 }
165 rewrite_resolved_import_target(&mut import.target, rename_map);
166 }
167 for instruction in &mut module.code {
168 rewrite_instruction_module_operand(instruction, rename_map);
169 }
170 for literal in &mut module.literals {
171 rewrite_literal_atom(literal, rename_map);
172 }
173 module.constant_pool =
177 beamr::constant_pool::materialise_literals(&module.literals, Some(atom_table))
178 .map_err(runtime_error_from_display)?;
179 for lambda in &mut module.lambdas {
180 lambda.unique_id = lambda_unique_id(
181 atom_table,
182 deployed_atom,
183 lambda.function,
184 lambda.arity,
185 lambda.num_free,
186 )
187 .map_err(runtime_error_from_display)?;
188 }
189
190 Ok(())
191}
192
193fn rewrite_resolved_import_target(target: &mut ResolvedImportTarget, rename_map: &ModuleRenameMap) {
194 match target {
195 ResolvedImportTarget::Code { module, .. }
196 | ResolvedImportTarget::Deferred { module, .. }
197 | ResolvedImportTarget::Unresolved { module, .. } => {
198 if let Some(&new_atom) = rename_map.get(module) {
199 *module = new_atom;
200 }
201 }
202 ResolvedImportTarget::Native(_) | ResolvedImportTarget::Denied { .. } => {}
203 }
204}
205
206fn rewrite_instruction_module_operand(instruction: &mut Instruction, rename_map: &ModuleRenameMap) {
207 if let Instruction::FuncInfo { module, .. } = instruction {
208 rewrite_operand_atom(module, rename_map);
209 }
210}
211
212fn rewrite_operand_atom(operand: &mut Operand, rename_map: &ModuleRenameMap) {
213 match operand {
214 Operand::Atom(Some(atom)) => {
215 if let Some(&new_atom) = rename_map.get(atom) {
216 *atom = new_atom;
217 }
218 }
219 Operand::List(items) => {
220 for item in items {
221 rewrite_operand_atom(item, rename_map);
222 }
223 }
224 Operand::TypedRegister { register, .. } => {
225 rewrite_operand_atom(register, rename_map);
226 }
227 Operand::Literal(_)
228 | Operand::Integer(_)
229 | Operand::Unsigned(_)
230 | Operand::Atom(_)
231 | Operand::X(_)
232 | Operand::Y(_)
233 | Operand::Label(_)
234 | Operand::Character(_)
235 | Operand::FloatRegister(_)
236 | Operand::Allocation(_) => {}
237 }
238}
239
240fn rewrite_literal_atom(literal: &mut Literal, rename_map: &ModuleRenameMap) {
241 match literal {
242 Literal::Atom(atom) => {
243 if let Some(&new_atom) = rename_map.get(atom) {
244 *atom = new_atom;
245 }
246 }
247 Literal::Tuple(items) => {
248 for item in items {
249 rewrite_literal_atom(item, rename_map);
250 }
251 }
252 Literal::List(items, tail) => {
253 for item in items {
254 rewrite_literal_atom(item, rename_map);
255 }
256 rewrite_literal_atom(tail, rename_map);
257 }
258 Literal::Map(entries) => {
259 for (key, value) in entries {
260 rewrite_literal_atom(key, rename_map);
261 rewrite_literal_atom(value, rename_map);
262 }
263 }
264 Literal::Integer(_)
265 | Literal::Float(_)
266 | Literal::BigInteger(_)
267 | Literal::Binary(_)
268 | Literal::Nil
269 | Literal::String(_) => {}
270 Literal::ExportFun {
271 module, function, ..
272 } => {
273 if let Some(&new_atom) = rename_map.get(module) {
274 *module = new_atom;
275 }
276 if let Some(&new_atom) = rename_map.get(function) {
277 *function = new_atom;
278 }
279 }
280 }
281}
282
283fn runtime_error(reason: String) -> EngineError {
284 EngineError::Runtime { reason }
285}
286
287fn runtime_error_from_display(reason: impl std::fmt::Display) -> EngineError {
288 runtime_error(reason.to_string())
289}