1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::{core::reader::types::export::ExportDesc, execution::execution_info::ExecutionInfo};
use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec};

pub struct Lut {
    /// function_lut\[local_module_idx\]\[function_local_idx\] = (foreign_module_idx, function_foreign_idx)
    ///
    /// - Module A imports a function "foo". Inside module A, the function has the index "function_local_idx". Module A
    ///   is assigned the index "local_module_idx".
    /// - Module B exports a function "foo". Inside module B, the function has the index "function_foreign_idx". Module
    ///   B is assigned the index "foreign_module_idx".
    function_lut: Vec<Vec<(usize, usize)>>,
}

impl Lut {
    /// Create a new linker lookup-table.
    ///
    /// # Arguments
    /// - `modules`: The modules to link together.
    /// - `module_map`: A map from module name to module index within the `modules` array.
    ///
    /// # Returns
    /// A new linker lookup-table. Can return `None` if there are import directives that cannot be resolved.
    pub fn new(modules: &[ExecutionInfo], module_map: &BTreeMap<String, usize>) -> Option<Self> {
        let mut function_lut = Vec::new();
        for module in modules {
            let module_lut = module
                .store
                .funcs
                .iter()
                .filter_map(|f| f.try_into_imported())
                .map(|import| {
                    Self::manual_lookup(
                        modules,
                        module_map,
                        &import.module_name,
                        &import.function_name,
                    )
                })
                .collect::<Option<Vec<_>>>()?;

            // If there is a missing import/export pair,  we fail the entire
            // operation. Better safe than sorry...

            function_lut.push(module_lut);
        }

        Some(Self { function_lut })
    }

    /// Lookup a function by its module and function index.
    ///
    /// # Arguments
    /// - `module_idx`: The index of the module within the `modules` array passed in [Lut::new].
    /// - `function_idx`: The index of the function within the module. This index is considered in-bounds only if it is
    ///   an index of an imported function.
    ///
    /// # Returns
    /// - `None`, if the indicies are out of bound
    /// - `Some(export_module_idx, export_function_idx)`, where the new indicies are the indicies of the module which
    ///   contains the implementation of the imported function, and the implementation has the returned index within.
    pub fn lookup(&self, module_idx: usize, function_idx: usize) -> Option<(usize, usize)> {
        self.function_lut
            .get(module_idx)?
            .get(function_idx)
            .copied()
    }

    /// Manually lookup a function by its module and function name.
    ///
    /// This function is used to resolve import directives before the [Lut] is created, and can be used to resolve
    /// imports even after the [Lut] is created at the cost of speed.
    ///
    /// # Arguments
    /// - `modules`: The modules to link together.
    /// - `module_map`: A map from module name to module index within the `modules` array.
    /// - `module_name`: The name of the module which imports the function.
    /// - `function_name`: The name of the function to import.
    ///
    /// # Returns
    /// - `None`, if the module or function is not found.
    /// - `Some(export_module_idx, export_function_idx)`, where the new indicies are the indicies of the module which
    ///    contains the implementation of the imported function, and the implementation has the returned index within.
    ///    Note that this function returns the first matching function, if there are multiple functions with the same
    ///    name.
    pub fn manual_lookup(
        modules: &[ExecutionInfo],
        module_map: &BTreeMap<String, usize>,
        module_name: &str,
        function_name: &str,
    ) -> Option<(usize, usize)> {
        let module_idx = module_map.get(module_name)?;
        let module = &modules[*module_idx];

        module
            .store
            .exports
            .iter()
            .filter_map(|export| {
                if export.name == function_name {
                    Some(&export.desc)
                } else {
                    None
                }
            })
            .find_map(|desc| {
                if let ExportDesc::FuncIdx(func_idx) = desc {
                    Some((*module_idx, *func_idx))
                } else {
                    None
                }
            })
    }
}