wasmer_wasm_interface/
interface.rs

1//! The definition of a WASM interface
2
3use crate::interface_matcher::InterfaceMatcher;
4use serde::{Deserialize, Serialize};
5use std::collections::{hash_map::Entry, HashMap, HashSet};
6
7#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
8pub struct Interface {
9    /// The name the interface gave itself
10    pub name: Option<String>,
11    /// Things that the module can import
12    pub imports: HashMap<(String, String), Import>,
13    /// Things that the module must export
14    pub exports: HashMap<String, Export>,
15}
16
17impl Interface {
18    pub fn merge(&self, other: Interface) -> Result<Interface, String> {
19        let mut base = self.clone();
20
21        for (key, val) in other.imports {
22            match base.imports.entry(key) {
23                Entry::Occupied(e) if *e.get() != val => {
24                    let (namespace, name) = e.key();
25                    let original_value = e.get();
26                    return Err(format!("Conflict detected: the import \"{namespace}\" \"{name}\" was found but the definitions were different: {original_value:?} {val:?}"));
27                }
28                Entry::Occupied(_) => {
29                    // it's okay for the imported items to be the same.
30                }
31                Entry::Vacant(e) => {
32                    e.insert(val);
33                }
34            };
35        }
36
37        for (key, val) in other.exports {
38            match base.exports.entry(key) {
39                Entry::Occupied(e) if *e.get() != val => {
40                    let name = e.key();
41                    let original_value = e.get();
42                    return Err(format!("Conflict detected: the key \"{name}\" was found in exports but the definitions were different: {original_value:?} {val:?}"));
43                }
44                Entry::Occupied(_) => {
45                    // it's okay for the exported items to be the same.
46                }
47                Entry::Vacant(e) => {
48                    e.insert(val);
49                }
50            };
51        }
52
53        Ok(base)
54    }
55
56    pub fn create_interface_matcher(&self) -> InterfaceMatcher {
57        let mut namespaces = HashSet::new();
58        let mut namespace_imports: HashMap<String, HashSet<Import>> =
59            HashMap::with_capacity(self.imports.len());
60        let mut exports = HashSet::with_capacity(self.exports.len());
61
62        for (_, import) in self.imports.iter() {
63            match import {
64                Import::Func { namespace, .. } | Import::Global { namespace, .. } => {
65                    if !namespaces.contains(namespace) {
66                        namespaces.insert(namespace.clone());
67                    }
68                    let ni = namespace_imports.entry(namespace.clone()).or_default();
69                    ni.insert(import.clone());
70                }
71            }
72        }
73        for (_, export) in self.exports.iter() {
74            exports.insert(export.clone());
75        }
76        InterfaceMatcher {
77            namespaces,
78            namespace_imports,
79            exports,
80        }
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum Import {
86    Func {
87        namespace: String,
88        name: String,
89        params: Vec<WasmType>,
90        result: Vec<WasmType>,
91    },
92    Global {
93        namespace: String,
94        name: String,
95        var_type: WasmType,
96    },
97}
98
99impl Import {
100    pub fn format_key(ns: &str, name: &str) -> (String, String) {
101        (ns.to_string(), name.to_string())
102    }
103
104    /// Get the key used to look this import up in the Interface's import hashmap
105    pub fn get_key(&self) -> (String, String) {
106        match self {
107            Import::Func {
108                namespace, name, ..
109            }
110            | Import::Global {
111                namespace, name, ..
112            } => Self::format_key(namespace, name),
113        }
114    }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum Export {
119    Func {
120        name: String,
121        params: Vec<WasmType>,
122        result: Vec<WasmType>,
123    },
124    Global {
125        name: String,
126        var_type: WasmType,
127    },
128}
129
130impl Export {
131    pub fn format_key(name: &str) -> String {
132        name.to_string()
133    }
134
135    /// Get the key used to look this export up in the Interface's export hashmap
136    pub fn get_key(&self) -> String {
137        match self {
138            Export::Func { name, .. } | Export::Global { name, .. } => Self::format_key(name),
139        }
140    }
141}
142
143/// Primitive wasm type
144#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
145pub enum WasmType {
146    I32,
147    I64,
148    F32,
149    F64,
150}
151
152impl std::fmt::Display for WasmType {
153    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
154        write!(
155            f,
156            "{}",
157            match self {
158                WasmType::I32 => "i32",
159                WasmType::I64 => "i64",
160                WasmType::F32 => "f32",
161                WasmType::F64 => "f64",
162            }
163        )
164    }
165}
166
167#[cfg(test)]
168mod test {
169    use crate::parser;
170
171    #[test]
172    fn merging_works() {
173        let interface1_src =
174            r#"(interface (func (import "env" "plus_one") (param i32) (result i32)))"#;
175        let interface2_src =
176            r#"(interface (func (import "env" "plus_one") (param i64) (result i64)))"#;
177        let interface3_src =
178            r#"(interface (func (import "env" "times_two") (param i64) (result i64)))"#;
179        let interface4_src =
180            r#"(interface (func (import "env" "times_two") (param i64 i64) (result i64)))"#;
181        let interface5_src = r#"(interface (func (export "empty_bank_account") (param) (result)))"#;
182        let interface6_src =
183            r#"(interface (func (export "empty_bank_account") (param) (result i64)))"#;
184
185        let interface1 = parser::parse_interface(interface1_src).unwrap();
186        let interface2 = parser::parse_interface(interface2_src).unwrap();
187        let interface3 = parser::parse_interface(interface3_src).unwrap();
188        let interface4 = parser::parse_interface(interface4_src).unwrap();
189        let interface5 = parser::parse_interface(interface5_src).unwrap();
190        let interface6 = parser::parse_interface(interface6_src).unwrap();
191
192        assert!(interface1.merge(interface2.clone()).is_err());
193        assert!(interface2.merge(interface1.clone()).is_err());
194        assert!(interface1.merge(interface3.clone()).is_ok());
195        assert!(interface2.merge(interface3.clone()).is_ok());
196        assert!(interface3.merge(interface2).is_ok());
197        assert!(
198            interface1.merge(interface1.clone()).is_ok(),
199            "exact matches are accepted"
200        );
201        assert!(interface3.merge(interface4).is_err());
202        assert!(interface5.merge(interface5.clone()).is_ok());
203        assert!(interface5.merge(interface6).is_err());
204    }
205}