wamr_rust_sdk/
function.rs

1/*
2 * Copyright (C) 2019 Intel Corporation. All rights reserved.
3 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4 */
5
6//! an exported wasm function.
7//! get one via `Function::find_export_func()`
8
9use std::{ffi::CString, marker::PhantomData};
10use wamr_sys::{
11    wasm_exec_env_t, wasm_func_get_param_count, wasm_func_get_result_count,
12    wasm_func_get_result_types, wasm_function_inst_t, wasm_runtime_call_wasm,
13    wasm_runtime_get_exception, wasm_runtime_get_exec_env_singleton,
14    wasm_runtime_get_wasi_exit_code, wasm_runtime_lookup_function,
15    wasm_valkind_enum_WASM_EXTERNREF, wasm_valkind_enum_WASM_F32, wasm_valkind_enum_WASM_F64,
16    wasm_valkind_enum_WASM_FUNCREF, wasm_valkind_enum_WASM_I32, wasm_valkind_enum_WASM_I64,
17    wasm_valkind_enum_WASM_V128,
18};
19
20use crate::{
21    helper::exception_to_string, instance::Instance, value::WasmValue, ExecError, RuntimeError,
22};
23
24pub struct Function<'instance> {
25    function: wasm_function_inst_t,
26    _phantom: PhantomData<Instance<'instance>>,
27}
28
29impl<'instance> Function<'instance> {
30    /// find a function by name
31    ///
32    /// # Error
33    ///
34    /// Return `RuntimeError::FunctionNotFound` if failed.
35    pub fn find_export_func(
36        instance: &'instance Instance<'instance>,
37        name: &str,
38    ) -> Result<Self, RuntimeError> {
39        let name = CString::new(name).expect("CString::new failed");
40        let function =
41            unsafe { wasm_runtime_lookup_function(instance.get_inner_instance(), name.as_ptr()) };
42        match function.is_null() {
43            true => Err(RuntimeError::FunctionNotFound),
44            false => Ok(Function {
45                function,
46                _phantom: PhantomData,
47            }),
48        }
49    }
50
51    #[allow(non_upper_case_globals)]
52    #[allow(non_snake_case)]
53    fn parse_result(
54        &self,
55        instance: &Instance<'instance>,
56        result: Vec<u32>,
57    ) -> Result<Vec<WasmValue>, RuntimeError> {
58        let result_count =
59            unsafe { wasm_func_get_result_count(self.function, instance.get_inner_instance()) };
60        if result_count == 0 {
61            return Ok(vec![WasmValue::Void]);
62        }
63
64        let mut result_types = vec![0u8; result_count as usize];
65        unsafe {
66            wasm_func_get_result_types(
67                self.function,
68                instance.get_inner_instance(),
69                result_types.as_mut_ptr(),
70            );
71        }
72
73        let mut results = Vec::with_capacity(result_types.len());
74        let mut index: usize = 0;
75
76        for result_type in result_types.iter() {
77            match *result_type as u32 {
78                wasm_valkind_enum_WASM_I32
79                | wasm_valkind_enum_WASM_FUNCREF
80                | wasm_valkind_enum_WASM_EXTERNREF => {
81                    results.push(WasmValue::decode_to_i32(&result[index..index + 1]));
82                    index += 1;
83                }
84                wasm_valkind_enum_WASM_I64 => {
85                    results.push(WasmValue::decode_to_i64(&result[index..index + 2]));
86                    index += 2;
87                }
88                wasm_valkind_enum_WASM_F32 => {
89                    results.push(WasmValue::decode_to_f32(&result[index..index + 1]));
90                    index += 1;
91                }
92                wasm_valkind_enum_WASM_F64 => {
93                    results.push(WasmValue::decode_to_f64(&result[index..index + 2]));
94                    index += 2;
95                }
96                wasm_valkind_enum_WASM_V128 => {
97                    results.push(WasmValue::decode_to_v128(&result[index..index + 4]));
98                    index += 4;
99                }
100                _ => return Err(RuntimeError::NotImplemented),
101            }
102        }
103
104        Ok(results)
105    }
106
107    /// execute an export function.
108    /// all parameters need to be wrapped in `WasmValue`
109    ///
110    /// # Error
111    ///
112    /// Return `RuntimeError::ExecutionError` if failed.
113    #[allow(non_upper_case_globals)]
114    pub fn call(
115        &self,
116        instance: &'instance Instance<'instance>,
117        params: &Vec<WasmValue>,
118    ) -> Result<Vec<WasmValue>, RuntimeError> {
119        let param_count =
120            unsafe { wasm_func_get_param_count(self.function, instance.get_inner_instance()) };
121        if param_count > params.len() as u32 {
122            return Err(RuntimeError::ExecutionError(ExecError {
123                message: "invalid parameters".to_string(),
124                exit_code: 0xff,
125            }));
126        }
127
128        // Maintain sufficient allocated space in the vector rather than just declaring its capacity.
129        let result_count =
130            unsafe { wasm_func_get_result_count(self.function, instance.get_inner_instance()) };
131        let capacity = std::cmp::max(param_count, result_count) as usize * 4;
132
133        // Populate the parameters in the sufficiently allocated argv vector
134        let mut argv = Vec::with_capacity(capacity);
135        for p in params {
136            argv.append(&mut p.encode());
137        }
138        argv.resize(capacity, 0);
139
140        let call_result: bool;
141        unsafe {
142            let exec_env: wasm_exec_env_t =
143                wasm_runtime_get_exec_env_singleton(instance.get_inner_instance());
144            call_result =
145                wasm_runtime_call_wasm(exec_env, self.function, param_count, argv.as_mut_ptr());
146        };
147
148        if !call_result {
149            unsafe {
150                let exception_c = wasm_runtime_get_exception(instance.get_inner_instance());
151                let error_info = ExecError {
152                    message: exception_to_string(exception_c),
153                    exit_code: wasm_runtime_get_wasi_exit_code(instance.get_inner_instance()),
154                };
155                return Err(RuntimeError::ExecutionError(error_info));
156            }
157        }
158
159        // there is no out of bounds problem, because we have precalculated the safe vec size
160        self.parse_result(instance, argv)
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::{module::Module, runtime::Runtime, wasi_context::WasiCtxBuilder};
168    use std::{
169        process::{Command, Stdio}, path::Path, path::PathBuf, env, fs,
170    };
171
172    #[test]
173    fn test_func_in_wasm32_unknown() {
174        let runtime = Runtime::new().unwrap();
175
176        // (module
177        //   (func (export "add") (param i64 i32) (result i32 i64)
178        //     (local.get 1)
179        //     (i32.const 32)
180        //     (i32.add)
181        //     (local.get 0)
182        //     (i64.const 64)
183        //     (i64.add)
184        //   )
185        //
186        //   (func (export "multi-result") (result i32 i64 i32)
187        //     (i32.const 1)
188        //     (i64.const 2)
189        //     (i32.const 3)
190        //   )
191        // )
192        let binary = vec![
193            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x02, 0x60, 0x02, 0x7E,
194            0x7F, 0x02, 0x7F, 0x7E, 0x60, 0x00, 0x03, 0x7F, 0x7E, 0x7F, 0x03, 0x03, 0x02, 0x00,
195            0x01, 0x07, 0x16, 0x02, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0C, 0x6D, 0x75, 0x6C,
196            0x74, 0x69, 0x2D, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x00, 0x01, 0x0A, 0x18, 0x02,
197            0x0D, 0x00, 0x20, 0x01, 0x41, 0x20, 0x6A, 0x20, 0x00, 0x42, 0xC0, 0x00, 0x7C, 0x0B,
198            0x08, 0x00, 0x41, 0x01, 0x42, 0x02, 0x41, 0x03, 0x0B,
199        ];
200        let binary = binary.into_iter().map(|c| c as u8).collect::<Vec<u8>>();
201
202        let module = Module::from_vec(&runtime, binary, "");
203        assert!(module.is_ok());
204        let module = module.unwrap();
205
206        let instance = Instance::new(&runtime, &module, 1024);
207        assert!(instance.is_ok());
208        let instance: &Instance = &instance.unwrap();
209
210        //
211        // run add()
212        //
213
214        let function = Function::find_export_func(instance, "add");
215        assert!(function.is_ok());
216        let function = function.unwrap();
217
218        let params: Vec<WasmValue> = vec![WasmValue::I64(10), WasmValue::I32(20)];
219        let call_result = function.call(instance, &params);
220        assert!(call_result.is_ok());
221        assert_eq!(
222            call_result.unwrap(),
223            vec![WasmValue::I32(52), WasmValue::I64(74)]
224        );
225
226        //
227        // run multi-result()
228        //
229
230        let function = Function::find_export_func(instance, "multi-result");
231        assert!(function.is_ok());
232        let function = function.unwrap();
233
234        let params: Vec<WasmValue> = Vec::new();
235        let call_result = function.call(instance, &params);
236        assert!(call_result.is_ok());
237        assert_eq!(
238            call_result.unwrap(),
239            vec![WasmValue::I32(1), WasmValue::I64(2), WasmValue::I32(3)]
240        );
241    }
242
243    #[test]
244    fn test_func_in_wasm32_wasi() {
245        let runtime = Runtime::new().unwrap();
246
247        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
248        d.push("resources/test");
249        d.push("gcd_wasm32_wasi.wasm");
250        let module = Module::from_file(&runtime, d.as_path());
251        assert!(module.is_ok());
252        let mut module = module.unwrap();
253
254        let wasi_ctx = WasiCtxBuilder::new()
255            .set_pre_open_path(vec!["."], vec![])
256            .build();
257        module.set_wasi_context(wasi_ctx);
258
259        let instance = Instance::new(&runtime, &module, 1024 * 64);
260        assert!(instance.is_ok());
261        let instance: &Instance = &instance.unwrap();
262
263        let function = Function::find_export_func(instance, "gcd");
264        assert!(function.is_ok());
265        let function = function.unwrap();
266
267        let params: Vec<WasmValue> = vec![WasmValue::I32(9), WasmValue::I32(27)];
268        let result = function.call(instance, &params);
269        assert_eq!(result.unwrap(), vec![WasmValue::I32(9)]);
270
271        let params: Vec<WasmValue> = vec![WasmValue::I32(0), WasmValue::I32(27)];
272        let result = function.call(instance, &params);
273        assert_eq!(result.unwrap(), vec![WasmValue::I32(27)]);
274    }
275
276    #[test]
277    fn test_func_in_wasm32_wasi_w_args() {
278        let runtime = Runtime::new().unwrap();
279
280        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
281        d.push("resources/test");
282        d.push("wasi-demo-app.wasm");
283        let module = Module::from_file(&runtime, d.as_path());
284        assert!(module.is_ok());
285        let mut module = module.unwrap();
286
287        let wasi_ctx = WasiCtxBuilder::new()
288            .set_pre_open_path(vec!["."], vec![])
289            .set_arguments(vec!["wasi-demo-app.wasm", "echo", "hi"])
290            .build();
291        module.set_wasi_context(wasi_ctx);
292
293        let instance = Instance::new(&runtime, &module, 1024 * 64);
294        assert!(instance.is_ok());
295        let instance: &Instance = &instance.unwrap();
296
297        let function = Function::find_export_func(instance, "_start");
298        assert!(function.is_ok());
299        let function = function.unwrap();
300
301        let result = function.call(instance, &vec![]);
302        assert!(result.is_ok());
303        println!("{:?}", result.unwrap());
304    }
305
306    #[test]
307    fn test_func_in_multi_v128_return() {
308        let runtime = Runtime::new().unwrap();
309
310        // (module
311        // (func (export "multi") (result f64 f32 i32 i64 f64 f32 i32 i64 v128 v128 v128 v128)
312        //     f64.const 22.2222
313        //     f32.const 1.57
314        //     i32.const 42
315        //     i64.const 3523
316        //     f64.const 22.2222
317        //     f32.const 1.57
318        //     i32.const 42
319        //     i64.const 3523
320        //     v128.const i32x4 1 2 3 4
321        //     v128.const f32x4 1 2 3 4
322        //     v128.const i64x2 1 2
323        //     v128.const f64x2 1 2)
324        // )
325        let mut wasm_src = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
326        wasm_src.push("resources/test");
327        wasm_src.push("multiret.wasm");
328
329        // Compiling to AOT
330        let mut aot_dest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
331        aot_dest.push("resources/test");
332        aot_dest.push("multiret.aot");
333
334        // Get the path to wamrc binary
335        let base = match Path::new("target/release").exists() {
336            true => "target/release/build",
337            false => "target/debug/build",
338        };
339        let base_entries = fs::read_dir(base);
340        assert!(base_entries.is_ok());
341        let found = base_entries.unwrap()
342            .filter_map(|entry| entry.ok())
343            .map(|entry| {
344                let path = entry.path();
345                let name = path
346                    .file_name()
347                    .and_then(|s| s.to_str())
348                    .unwrap_or("")
349                    .to_string();
350                (path, name)
351            })
352            .filter_map(|(path, name)| {
353                if name.starts_with("wamr-sys") && path.join("out").join("wamrcbuild").join("bin").join("wamrc").exists() {
354                    Some(path.join("out").join("wamrcbuild").join("bin").join("wamrc"))
355                } else {
356                    None
357                }
358            })
359            .next();
360        assert!(found.is_some());
361        let wamrc_path = found.unwrap();
362
363        let wamrc_output = Command::new(wamrc_path)
364            .arg("--bounds-checks=1")
365            .arg("-o")
366            .arg(aot_dest.clone())
367            .arg(wasm_src.clone())
368            .stderr(Stdio::piped())  
369            .stdout(Stdio::piped())
370            .output()
371            .unwrap();
372        assert!(String::from_utf8_lossy(&wamrc_output.stdout).contains("Compile success"));
373
374        let module = Module::from_file(&runtime, aot_dest.as_path());
375        assert!(module.is_ok());
376        let module = module.unwrap();
377
378        let instance = Instance::new(&runtime, &module, 1024 * 64);
379        assert!(instance.is_ok());
380        let instance: &Instance = &instance.unwrap();
381
382        let function = Function::find_export_func(instance, "multi");
383        assert!(function.is_ok());
384        let function = function.unwrap();
385
386        let wrapped_result = function.call(instance, &vec![]);
387        let unwrapped_result = wrapped_result.unwrap();
388        
389        assert_eq!(unwrapped_result.len(), 12);
390        assert_eq!(
391            unwrapped_result,
392            vec![
393                WasmValue::F64(22.2222),
394                WasmValue::F32(1.57),
395                WasmValue::I32(42),
396                WasmValue::I64(3523),
397                WasmValue::F64(22.2222),
398                WasmValue::F32(1.57),
399                WasmValue::I32(42),
400                WasmValue::I64(3523),
401                WasmValue::V128(316912650112397582603894390785),
402                WasmValue::V128(85735205748011485687928662073142149120),
403                WasmValue::V128(36893488147419103233),
404                WasmValue::V128(85070591730234615870450834276742070272)
405            ]
406        );
407    }
408}