wasmer_wasix/syscalls/wasix/
call_dynamic.rs

1use super::*;
2use crate::syscalls::*;
3use wasmer::Type;
4use wasmer::ValueType;
5
6macro_rules! write_value {
7    ($memory:expr, $offset:expr, $max:expr, $strict:expr, $value:expr) => {{
8        let bytes = $value.to_le_bytes();
9        if $offset + bytes.len() as u64 <= $max {
10            $memory.write($offset, &bytes)?;
11            $offset += bytes.len() as u64;
12            Ok(true)
13        } else {
14            Ok(!$strict)
15        }
16    }};
17}
18
19fn write_value(
20    memory: &MemoryView,
21    offset: &mut u64,
22    max: u64,
23    strict: bool,
24    value: &Value,
25) -> Result<bool, MemoryAccessError> {
26    match value {
27        Value::I32(value) => write_value!(memory, *offset, max, strict, value),
28        Value::I64(value) => write_value!(memory, *offset, max, strict, value),
29        Value::F32(value) => write_value!(memory, *offset, max, strict, value),
30        Value::F64(value) => write_value!(memory, *offset, max, strict, value),
31        Value::V128(value) => write_value!(memory, *offset, max, strict, value),
32        // ExternRef, FuncRef, and ExceptionRef cannot be represented as byte slices
33        _ => panic!("Cannot write non-scalar value as bytes"),
34    }
35}
36
37macro_rules! read_value {
38    ($memory:expr, $offset:expr, $max:expr, $strict:expr, $ty:ident, $val:ident, $len:expr) => {{
39        if $offset + $len > $max {
40            Ok(if $strict {
41                None
42            } else {
43                Some(Value::$val($ty::default()))
44            })
45        } else {
46            let mut buffer = [0u8; $len];
47            $memory.read($offset, &mut buffer)?;
48            $offset += $len;
49            Ok(Some(Value::$val($ty::from_le_bytes(buffer))))
50        }
51    }};
52}
53
54fn read_value(
55    memory: &MemoryView,
56    offset: &mut u64,
57    max: u64,
58    strict: bool,
59    ty: &Type,
60) -> Result<Option<Value>, MemoryAccessError> {
61    match ty {
62        Type::I32 => read_value!(memory, *offset, max, strict, i32, I32, 4),
63        Type::I64 => read_value!(memory, *offset, max, strict, i64, I64, 8),
64        Type::F32 => read_value!(memory, *offset, max, strict, f32, F32, 4),
65        Type::F64 => read_value!(memory, *offset, max, strict, f64, F64, 8),
66        Type::V128 => read_value!(memory, *offset, max, strict, u128, V128, 16),
67        // ExternRef, FuncRef, and ExceptionRef cannot be represented as byte slices
68        _ => panic!("Cannot read non-scalar value from memory"),
69    }
70}
71
72/// Call a function from the `__indirect_function_table` with parameters and results from memory.
73///
74/// This function can be used to call functions whose types are not known at
75/// compile time of the caller. It is the callers responsibility to ensure
76/// that the passed parameters and results match the signature of the function
77/// beeing called.
78///
79/// ### Format of the values and results buffer
80///
81/// The buffers contain all values sequentially. i32, and f32 are 4 bytes,
82/// i64 and f64 are 8 bytes, v128 is 16 bytes.
83///     
84/// For example if the function takes an i32 and an i64, the values buffer will
85/// be 12 bytes long, with the first 4 bytes being the i32 and the next 8
86/// bytes being the i64.
87///
88/// ### Parameters
89///
90/// * function_id: The indirect function table index of the function to call
91///
92/// * values: Pointer to a sequence of values that will be passed to the function.
93///   The buffer will be interpreted as described above.
94///   If the function does not have any parameters, this can be a nullptr (0).
95///
96/// * results: Pointer to a sequence of values.
97///   If the function does not return a value, this can be a nullptr (0).
98///   The buffer needs to be large enough to hold all return values.
99///
100#[instrument(
101    level = "trace",
102    skip_all,
103    fields(%function_id, values_ptr = values.offset().into(), results_ptr = results.offset().into()),
104    ret
105)]
106#[allow(clippy::result_large_err)]
107pub fn call_dynamic<M: MemorySize>(
108    mut ctx: FunctionEnvMut<'_, WasiEnv>,
109    function_id: u32,
110    values: WasmPtr<u8, M>,
111    values_len: M::Offset,
112    results: WasmPtr<u8, M>,
113    results_len: M::Offset,
114    strict: Bool,
115) -> Result<Errno, RuntimeError> {
116    let (env, mut store) = ctx.data_and_store_mut();
117
118    let strict = matches!(strict, Bool::True);
119
120    let function = wasi_try_ok!(
121        env.inner()
122            .indirect_function_table_lookup(&mut store, function_id)
123            .map_err(Errno::from)
124    );
125
126    let function_type = function.ty(&store);
127
128    let memory = unsafe { env.memory_view(&store) };
129    let mut current_values_offset: u64 = values.offset().into();
130    let max_values_offset = current_values_offset + values_len.into();
131    let mut values_buffer = vec![];
132    for ty in function_type.params() {
133        let Some(value) = wasi_try_mem_ok!(read_value(
134            &memory,
135            &mut current_values_offset,
136            max_values_offset,
137            strict,
138            ty
139        )) else {
140            return Ok(Errno::Inval);
141        };
142        values_buffer.push(value);
143    }
144
145    if strict && current_values_offset != max_values_offset {
146        // If strict is true, we expect to have read all values
147        return Ok(Errno::Inval);
148    }
149
150    let result_values = function
151        .call(&mut store, values_buffer.as_slice())
152        .map_err(crate::flatten_runtime_error)?;
153
154    let memory = unsafe { env.memory_view(&store) };
155    let mut current_results_offset: u64 = results.offset().into();
156    let max_results_offset = current_results_offset + results_len.into();
157    for result_value in result_values {
158        wasi_try_mem_ok!(write_value(
159            &memory,
160            &mut current_results_offset,
161            max_results_offset,
162            strict,
163            &result_value
164        ));
165    }
166
167    if strict && current_results_offset != max_results_offset {
168        // If strict is true, we expect to have written all results
169        return Ok(Errno::Inval);
170    }
171
172    Ok(Errno::Success)
173}