Skip to main content

wasmtime_internal_cranelift/translate/
func_translator.rs

1//! Stand-alone WebAssembly to Cranelift IR translator.
2//!
3//! This module defines the `FuncTranslator` type which can translate a single WebAssembly
4//! function to Cranelift IR guided by a `FuncEnvironment` which provides information about the
5//! WebAssembly module and the runtime environment.
6
7use crate::func_environ::FuncEnvironment;
8use crate::translate::TargetEnvironment;
9use crate::translate::code_translator::{bitcast_wasm_returns, translate_operator};
10use crate::translate::translation_utils::get_vmctx_value_label;
11use cranelift_codegen::entity::EntityRef;
12use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
13use cranelift_codegen::timing;
14use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
15use wasmparser::{BinaryReader, FuncValidator, FunctionBody, OperatorsReader, WasmModuleResources};
16use wasmtime_environ::{TypeConvert, WasmResult};
17
18/// WebAssembly to Cranelift IR function translator.
19///
20/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided
21/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple
22/// functions which will reduce heap allocation traffic.
23pub struct FuncTranslator {
24    func_ctx: FunctionBuilderContext,
25}
26
27impl FuncTranslator {
28    /// Create a new translator.
29    pub fn new() -> Self {
30        Self {
31            func_ctx: FunctionBuilderContext::new(),
32        }
33    }
34
35    /// Returns the underlying `FunctionBuilderContext` that this translator
36    /// uses.
37    pub fn context(&mut self) -> &mut FunctionBuilderContext {
38        &mut self.func_ctx
39    }
40
41    /// Translate a binary WebAssembly function from a `FunctionBody`.
42    ///
43    /// See [the WebAssembly specification][wasm].
44    ///
45    /// [wasm]: https://webassembly.github.io/spec/core/binary/modules.html#code-section
46    ///
47    /// The Cranelift IR function `func` should be completely empty except for the `func.signature`
48    /// and `func.name` fields. The signature may contain special-purpose arguments which are not
49    /// regarded as WebAssembly local variables. Any signature arguments marked as
50    /// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.
51    pub fn translate_body(
52        &mut self,
53        validator: &mut FuncValidator<impl WasmModuleResources>,
54        body: FunctionBody<'_>,
55        func: &mut ir::Function,
56        environ: &mut FuncEnvironment<'_>,
57    ) -> WasmResult<()> {
58        let _tt = timing::wasm_translate_function();
59        let mut reader = body.get_binary_reader();
60        log::trace!(
61            "translate({} bytes, {}{})",
62            reader.bytes_remaining(),
63            func.name,
64            func.signature
65        );
66        debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");
67        debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
68
69        let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
70        builder.set_srcloc(cur_srcloc(&reader));
71        let entry_block = builder.create_block();
72        builder.append_block_params_for_function_params(entry_block);
73        builder.switch_to_block(entry_block);
74        builder.seal_block(entry_block); // Declare all predecessors known.
75
76        environ.create_state_slot(&mut builder);
77
78        // Make sure the entry block is inserted in the layout before we make any callbacks to
79        // `environ`. The callback functions may need to insert things in the entry block.
80        builder.ensure_inserted_block();
81
82        let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);
83
84        // Set up the translation state with a single pushed control block representing the whole
85        // function and its return values.
86        let exit_block = builder.create_block();
87        builder.append_block_params_for_function_returns(exit_block);
88        environ
89            .stacks
90            .initialize(&builder.func.signature, exit_block);
91
92        parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
93        parse_function_body(validator, reader, &mut builder, environ)?;
94
95        builder.finalize();
96        log::trace!("translated Wasm to CLIF:\n{}", func.display());
97        Ok(())
98    }
99}
100
101/// Declare local variables for the signature parameters that correspond to WebAssembly locals.
102///
103/// Return the number of local variables declared.
104fn declare_wasm_parameters(
105    builder: &mut FunctionBuilder,
106    entry_block: Block,
107    environ: &mut FuncEnvironment<'_>,
108) -> usize {
109    let sig_len = builder.func.signature.params.len();
110    let mut next_local = 0;
111    for i in 0..sig_len {
112        let param_type = builder.func.signature.params[i];
113        // There may be additional special-purpose parameters in addition to the normal WebAssembly
114        // signature parameters. For example, a `vmctx` pointer.
115        if let Some(wasm_type) = environ.clif_param_as_wasm_param(i) {
116            // This is a normal WebAssembly signature parameter, so create a local for it.
117            let local = builder.declare_var(param_type.value_type);
118            debug_assert_eq!(local.index(), next_local);
119            next_local += 1;
120
121            if environ.param_needs_stack_map(&builder.func.signature, i) {
122                builder.declare_var_needs_stack_map(local);
123            }
124
125            let param_value = builder.block_params(entry_block)[i];
126            builder.def_var(local, param_value);
127
128            environ.add_state_slot_local(builder, wasm_type, Some(param_value));
129        }
130        if param_type.purpose == ir::ArgumentPurpose::VMContext {
131            let param_value = builder.block_params(entry_block)[i];
132            builder.set_val_label(param_value, get_vmctx_value_label());
133        }
134    }
135
136    next_local
137}
138
139/// Parse the local variable declarations that precede the function body.
140///
141/// Declare local variables, starting from `num_params`.
142fn parse_local_decls(
143    reader: &mut BinaryReader,
144    builder: &mut FunctionBuilder,
145    num_params: usize,
146    environ: &mut FuncEnvironment<'_>,
147    validator: &mut FuncValidator<impl WasmModuleResources>,
148) -> WasmResult<()> {
149    let mut next_local = num_params;
150    let local_count = reader.read_var_u32()?;
151
152    for _ in 0..local_count {
153        builder.set_srcloc(cur_srcloc(reader));
154        let pos = reader.original_position();
155        let count = reader.read_var_u32()?;
156        let ty = reader.read()?;
157        validator.define_locals(pos, count, ty)?;
158        declare_locals(builder, count, ty, &mut next_local, environ)?;
159    }
160
161    Ok(())
162}
163
164/// Declare `count` local variables of the same type, starting from `next_local`.
165///
166/// Fail if too many locals are declared in the function, or if the type is not valid for a local.
167fn declare_locals(
168    builder: &mut FunctionBuilder,
169    count: u32,
170    wasm_type: wasmparser::ValType,
171    next_local: &mut usize,
172    environ: &mut FuncEnvironment<'_>,
173) -> WasmResult<()> {
174    // All locals are initialized to 0.
175    use wasmparser::ValType::*;
176    let (ty, init, needs_stack_map) = match wasm_type {
177        I32 => (
178            ir::types::I32,
179            Some(builder.ins().iconst(ir::types::I32, 0)),
180            false,
181        ),
182        I64 => (
183            ir::types::I64,
184            Some(builder.ins().iconst(ir::types::I64, 0)),
185            false,
186        ),
187        F32 => (
188            ir::types::F32,
189            Some(builder.ins().f32const(ir::immediates::Ieee32::with_bits(0))),
190            false,
191        ),
192        F64 => (
193            ir::types::F64,
194            Some(builder.ins().f64const(ir::immediates::Ieee64::with_bits(0))),
195            false,
196        ),
197        V128 => {
198            let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());
199            (
200                ir::types::I8X16,
201                Some(builder.ins().vconst(ir::types::I8X16, constant_handle)),
202                false,
203            )
204        }
205        Ref(rt) => {
206            let hty = environ.convert_heap_type(rt.heap_type())?;
207            let (ty, needs_stack_map) = environ.reference_type(hty);
208            let init = if rt.is_nullable() {
209                Some(environ.translate_ref_null(builder.cursor(), hty)?)
210            } else {
211                None
212            };
213            (ty, init, needs_stack_map)
214        }
215    };
216
217    for _ in 0..count {
218        let local = builder.declare_var(ty);
219        debug_assert_eq!(local.index(), *next_local);
220        if needs_stack_map {
221            builder.declare_var_needs_stack_map(local);
222        }
223        if let Some(init) = init {
224            builder.def_var(local, init);
225            builder.set_val_label(init, ValueLabel::new(*next_local));
226        }
227        environ.add_state_slot_local(builder, environ.convert_valtype(wasm_type)?, init);
228        *next_local += 1;
229    }
230    Ok(())
231}
232
233/// Parse the function body in `reader`.
234///
235/// This assumes that the local variable declarations have already been parsed and function
236/// arguments and locals are declared in the builder.
237fn parse_function_body(
238    validator: &mut FuncValidator<impl WasmModuleResources>,
239    reader: BinaryReader,
240    builder: &mut FunctionBuilder,
241    environ: &mut FuncEnvironment<'_>,
242) -> WasmResult<()> {
243    // The control stack is initialized with a single block representing the whole function.
244    debug_assert_eq!(
245        environ.stacks.control_stack.len(),
246        1,
247        "State not initialized"
248    );
249
250    environ.before_translate_function(builder)?;
251
252    let mut reader = OperatorsReader::new(reader);
253    let mut operand_types = vec![];
254
255    while !reader.eof() {
256        let pos = reader.original_position();
257        builder.set_srcloc(cur_srcloc(&reader.get_binary_reader()));
258
259        let op = reader.read()?;
260        environ.next_srcloc = cur_srcloc(&reader.get_binary_reader());
261        let operand_types =
262            validate_op_and_get_operand_types(validator, environ, &mut operand_types, &op, pos)?;
263
264        environ.before_translate_operator(&op, operand_types, builder)?;
265        translate_operator(validator, &op, operand_types, builder, environ)?;
266        environ.after_translate_operator(&op, validator, builder)?;
267    }
268
269    environ.after_translate_function(builder)?;
270    reader.finish()?;
271
272    // The final `End` operator left us in the exit block where we need to manually add a return
273    // instruction.
274    //
275    // If the exit block is unreachable, it may not have the correct arguments, so we would
276    // generate a return instruction that doesn't match the signature.
277    if environ.is_reachable() {
278        if !builder.is_unreachable() {
279            let mut returns = core::mem::take(&mut environ.stacks.stack);
280            environ.handle_before_return(&returns, builder);
281            bitcast_wasm_returns(&mut returns, builder);
282            builder.ins().return_(&returns);
283        }
284    }
285
286    // Discard any remaining values on the stack. Either we just returned them,
287    // or the end of the function is unreachable.
288    environ.stacks.stack.clear();
289    environ.stacks.stack_shape.clear();
290
291    Ok(())
292}
293
294fn validate_op_and_get_operand_types<'a>(
295    validator: &mut FuncValidator<impl WasmModuleResources>,
296    environ: &mut FuncEnvironment<'_>,
297    operand_types: &'a mut Vec<wasmtime_environ::WasmValType>,
298    op: &wasmparser::Operator<'_>,
299    pos: usize,
300) -> WasmResult<Option<&'a [wasmtime_environ::WasmValType]>> {
301    // Get the operand types for this operator.
302    //
303    // Note that we don't know if the `op` is valid yet, but only valid ops will
304    // definitely have arity. However, we also must check the arity before
305    // validating the op so that the validator has the right state to correctly
306    // report the arity. Furthermore, even if the op is valid, if it is in
307    // unreachable code, the op might want to pop more values from the stack
308    // than actually exist on the stack (which is allowed in unreachable code)
309    // so even if we can get arity, we are only guaranteed to have operand types
310    // for ops that are not only valid but also reachable.
311    let arity = op.operator_arity(&*validator);
312    operand_types.clear();
313    let operand_types = arity.and_then(|(operand_arity, _result_arity)| {
314        for i in (0..operand_arity).rev() {
315            let i = usize::try_from(i).unwrap();
316            let ty = validator.get_operand_type(i)??;
317            let ty = environ.convert_valtype(ty).ok()?;
318            operand_types.push(ty);
319        }
320        Some(&operand_types[..])
321    });
322
323    validator.op(pos, &op)?;
324
325    Ok(operand_types)
326}
327
328/// Get the current source location from a reader.
329fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
330    // We record source locations as byte code offsets relative to the beginning of the file.
331    // This will panic if bytecode is larger than 4 GB.
332    ir::SourceLoc::new(reader.original_position().try_into().unwrap())
333}