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