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