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 core::convert::TryInto;
13use cranelift_codegen::entity::EntityRef;
14use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
15use cranelift_codegen::timing;
16use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
17use wasmparser::{self, BinaryReader, FuncValidator, FunctionBody, WasmModuleResources};
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.
45    ///
46    /// The `code` slice contains the binary WebAssembly *function code* as it appears in the code
47    /// section of a WebAssembly module, not including the initial size of the function code. The
48    /// slice is expected to contain two parts:
49    ///
50    /// - The declaration of *locals*, and
51    /// - The function *body* as an expression.
52    ///
53    /// See [the WebAssembly specification][wasm].
54    ///
55    /// [wasm]: https://webassembly.github.io/spec/core/binary/modules.html#code-section
56    ///
57    /// The Cranelift IR function `func` should be completely empty except for the `func.signature`
58    /// and `func.name` fields. The signature may contain special-purpose arguments which are not
59    /// regarded as WebAssembly local variables. Any signature arguments marked as
60    /// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.
61    ///
62    pub fn translate<FE: FuncEnvironment + ?Sized>(
63        &mut self,
64        validator: &mut FuncValidator<impl WasmModuleResources>,
65        code: &[u8],
66        code_offset: usize,
67        func: &mut ir::Function,
68        environ: &mut FE,
69    ) -> WasmResult<()> {
70        self.translate_body(
71            validator,
72            FunctionBody::new(code_offset, code),
73            func,
74            environ,
75        )
76    }
77
78    /// Translate a binary WebAssembly function from a `FunctionBody`.
79    pub fn translate_body<FE: FuncEnvironment + ?Sized>(
80        &mut self,
81        validator: &mut FuncValidator<impl WasmModuleResources>,
82        body: FunctionBody<'_>,
83        func: &mut ir::Function,
84        environ: &mut FE,
85    ) -> WasmResult<()> {
86        let _tt = timing::wasm_translate_function();
87        let mut reader = body.get_binary_reader();
88        log::trace!(
89            "translate({} bytes, {}{})",
90            reader.bytes_remaining(),
91            func.name,
92            func.signature
93        );
94        debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");
95        debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
96
97        let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
98        builder.set_srcloc(cur_srcloc(&reader));
99        let entry_block = builder.create_block();
100        builder.append_block_params_for_function_params(entry_block);
101        builder.switch_to_block(entry_block);
102        builder.seal_block(entry_block); // Declare all predecessors known.
103
104        // Make sure the entry block is inserted in the layout before we make any callbacks to
105        // `environ`. The callback functions may need to insert things in the entry block.
106        builder.ensure_inserted_block();
107
108        let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);
109
110        // Set up the translation state with a single pushed control block representing the whole
111        // function and its return values.
112        let exit_block = builder.create_block();
113        builder.append_block_params_for_function_returns(exit_block);
114        self.state.initialize(&builder.func.signature, exit_block);
115
116        parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
117        parse_function_body(validator, reader, &mut builder, &mut self.state, environ)?;
118
119        builder.finalize();
120        log::trace!("translated Wasm to CLIF:\n{}", func.display());
121        Ok(())
122    }
123}
124
125/// Declare local variables for the signature parameters that correspond to WebAssembly locals.
126///
127/// Return the number of local variables declared.
128fn declare_wasm_parameters<FE: FuncEnvironment + ?Sized>(
129    builder: &mut FunctionBuilder,
130    entry_block: Block,
131    environ: &FE,
132) -> usize {
133    let sig_len = builder.func.signature.params.len();
134    let mut next_local = 0;
135    for i in 0..sig_len {
136        let param_type = builder.func.signature.params[i];
137        // There may be additional special-purpose parameters in addition to the normal WebAssembly
138        // signature parameters. For example, a `vmctx` pointer.
139        if environ.is_wasm_parameter(&builder.func.signature, i) {
140            // This is a normal WebAssembly signature parameter, so create a local for it.
141            let local = Variable::new(next_local);
142            builder.declare_var(local, param_type.value_type);
143            next_local += 1;
144
145            let param_value = builder.block_params(entry_block)[i];
146            builder.def_var(local, param_value);
147        }
148        if param_type.purpose == ir::ArgumentPurpose::VMContext {
149            let param_value = builder.block_params(entry_block)[i];
150            builder.set_val_label(param_value, get_vmctx_value_label());
151        }
152    }
153
154    next_local
155}
156
157/// Parse the local variable declarations that precede the function body.
158///
159/// Declare local variables, starting from `num_params`.
160fn parse_local_decls<FE: FuncEnvironment + ?Sized>(
161    reader: &mut BinaryReader,
162    builder: &mut FunctionBuilder,
163    num_params: usize,
164    environ: &mut FE,
165    validator: &mut FuncValidator<impl WasmModuleResources>,
166) -> WasmResult<()> {
167    let mut next_local = num_params;
168    let local_count = reader.read_var_u32()?;
169
170    for _ in 0..local_count {
171        builder.set_srcloc(cur_srcloc(reader));
172        let pos = reader.original_position();
173        let count = reader.read_var_u32()?;
174        let ty = reader.read()?;
175        validator.define_locals(pos, count, ty)?;
176        declare_locals(builder, count, ty, &mut next_local, environ)?;
177    }
178
179    environ.after_locals(next_local);
180
181    Ok(())
182}
183
184/// Declare `count` local variables of the same type, starting from `next_local`.
185///
186/// Fail if too many locals are declared in the function, or if the type is not valid for a local.
187fn declare_locals<FE: FuncEnvironment + ?Sized>(
188    builder: &mut FunctionBuilder,
189    count: u32,
190    wasm_type: wasmparser::ValType,
191    next_local: &mut usize,
192    environ: &mut FE,
193) -> WasmResult<()> {
194    // All locals are initialized to 0.
195    use wasmparser::ValType::*;
196    let zeroval = match wasm_type {
197        I32 => builder.ins().iconst(ir::types::I32, 0),
198        I64 => builder.ins().iconst(ir::types::I64, 0),
199        F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)),
200        F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)),
201        V128 => {
202            let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());
203            builder.ins().vconst(ir::types::I8X16, constant_handle)
204        }
205        Ref(wasmparser::RefType {
206            nullable: true,
207            heap_type,
208        }) => environ.translate_ref_null(builder.cursor(), heap_type.try_into()?)?,
209        Ref(wasmparser::RefType {
210            nullable: false, ..
211        }) => unreachable!(),
212    };
213
214    let ty = builder.func.dfg.value_type(zeroval);
215    for _ in 0..count {
216        let local = Variable::new(*next_local);
217        builder.declare_var(local, ty);
218        builder.def_var(local, zeroval);
219        builder.set_val_label(zeroval, ValueLabel::new(*next_local));
220        *next_local += 1;
221    }
222    Ok(())
223}
224
225/// Parse the function body in `reader`.
226///
227/// This assumes that the local variable declarations have already been parsed and function
228/// arguments and locals are declared in the builder.
229fn parse_function_body<FE: FuncEnvironment + ?Sized>(
230    validator: &mut FuncValidator<impl WasmModuleResources>,
231    mut reader: BinaryReader,
232    builder: &mut FunctionBuilder,
233    state: &mut FuncTranslationState,
234    environ: &mut FE,
235) -> WasmResult<()> {
236    // The control stack is initialized with a single block representing the whole function.
237    debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
238
239    environ.before_translate_function(builder, state)?;
240    while !reader.eof() {
241        let pos = reader.original_position();
242        builder.set_srcloc(cur_srcloc(&reader));
243        let op = reader.read_operator()?;
244        validator.op(pos, &op)?;
245        environ.before_translate_operator(&op, builder, state)?;
246        translate_operator(validator, &op, builder, state, environ)?;
247        environ.after_translate_operator(&op, builder, state)?;
248    }
249    environ.after_translate_function(builder, state)?;
250    let pos = reader.original_position();
251    validator.finish(pos)?;
252
253    // The final `End` operator left us in the exit block where we need to manually add a return
254    // instruction.
255    //
256    // If the exit block is unreachable, it may not have the correct arguments, so we would
257    // generate a return instruction that doesn't match the signature.
258    if state.reachable {
259        if !builder.is_unreachable() {
260            bitcast_wasm_returns(environ, &mut state.stack, builder);
261            builder.ins().return_(&state.stack);
262        }
263    }
264
265    // Discard any remaining values on the stack. Either we just returned them,
266    // or the end of the function is unreachable.
267    state.stack.clear();
268
269    Ok(())
270}
271
272/// Get the current source location from a reader.
273fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
274    // We record source locations as byte code offsets relative to the beginning of the file.
275    // This will wrap around if byte code is larger than 4 GB.
276    ir::SourceLoc::new(reader.original_position() as u32)
277}
278
279#[cfg(test)]
280mod tests {
281    use super::FuncTranslator;
282    use crate::environ::DummyEnvironment;
283    use cranelift_codegen::ir::types::I32;
284    use cranelift_codegen::{ir, isa, settings, Context};
285    use log::debug;
286    use target_lexicon::PointerWidth;
287    use wasmparser::{
288        FuncValidator, FunctionBody, Parser, ValidPayload, Validator, ValidatorResources,
289    };
290
291    #[test]
292    fn small1() {
293        // Implicit return.
294        let wasm = wat::parse_str(
295            "
296                (module
297                    (func $small2 (param i32) (result i32)
298                        (i32.add (get_local 0) (i32.const 1))
299                    )
300                )
301            ",
302        )
303        .unwrap();
304
305        let mut trans = FuncTranslator::new();
306        let flags = settings::Flags::new(settings::builder());
307        let runtime = DummyEnvironment::new(
308            isa::TargetFrontendConfig {
309                default_call_conv: isa::CallConv::Fast,
310                pointer_width: PointerWidth::U64,
311            },
312            false,
313        );
314
315        let mut ctx = Context::new();
316
317        ctx.func.name = ir::UserFuncName::testcase("small1");
318        ctx.func.signature.params.push(ir::AbiParam::new(I32));
319        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
320
321        let (body, mut validator) = extract_func(&wasm);
322        trans
323            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
324            .unwrap();
325        debug!("{}", ctx.func.display());
326        ctx.verify(&flags).unwrap();
327    }
328
329    #[test]
330    fn small2() {
331        // Same as above, but with an explicit return instruction.
332        let wasm = wat::parse_str(
333            "
334                (module
335                    (func $small2 (param i32) (result i32)
336                        (return (i32.add (get_local 0) (i32.const 1)))
337                    )
338                )
339            ",
340        )
341        .unwrap();
342
343        let mut trans = FuncTranslator::new();
344        let flags = settings::Flags::new(settings::builder());
345        let runtime = DummyEnvironment::new(
346            isa::TargetFrontendConfig {
347                default_call_conv: isa::CallConv::Fast,
348                pointer_width: PointerWidth::U64,
349            },
350            false,
351        );
352
353        let mut ctx = Context::new();
354
355        ctx.func.name = ir::UserFuncName::testcase("small2");
356        ctx.func.signature.params.push(ir::AbiParam::new(I32));
357        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
358
359        let (body, mut validator) = extract_func(&wasm);
360        trans
361            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
362            .unwrap();
363        debug!("{}", ctx.func.display());
364        ctx.verify(&flags).unwrap();
365    }
366
367    #[test]
368    fn infloop() {
369        // An infinite loop, no return instructions.
370        let wasm = wat::parse_str(
371            "
372                (module
373                    (func $infloop (result i32)
374                        (local i32)
375                        (loop (result i32)
376                            (i32.add (get_local 0) (i32.const 1))
377                            (set_local 0)
378                            (br 0)
379                        )
380                    )
381                )
382            ",
383        )
384        .unwrap();
385
386        let mut trans = FuncTranslator::new();
387        let flags = settings::Flags::new(settings::builder());
388        let runtime = DummyEnvironment::new(
389            isa::TargetFrontendConfig {
390                default_call_conv: isa::CallConv::Fast,
391                pointer_width: PointerWidth::U64,
392            },
393            false,
394        );
395
396        let mut ctx = Context::new();
397
398        ctx.func.name = ir::UserFuncName::testcase("infloop");
399        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
400
401        let (body, mut validator) = extract_func(&wasm);
402        trans
403            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
404            .unwrap();
405        debug!("{}", ctx.func.display());
406        ctx.verify(&flags).unwrap();
407    }
408
409    fn extract_func(wat: &[u8]) -> (FunctionBody<'_>, FuncValidator<ValidatorResources>) {
410        let mut validator = Validator::new();
411        for payload in Parser::new(0).parse_all(wat) {
412            match validator.payload(&payload.unwrap()).unwrap() {
413                ValidPayload::Func(validator, body) => {
414                    let validator = validator.into_validator(Default::default());
415                    return (body, validator);
416                }
417                _ => {}
418            }
419        }
420        panic!("failed to find function");
421    }
422}