Path: blob/main/crates/cranelift/src/translate/func_translator.rs
1692 views
//! Stand-alone WebAssembly to Cranelift IR translator.1//!2//! This module defines the `FuncTranslator` type which can translate a single WebAssembly3//! function to Cranelift IR guided by a `FuncEnvironment` which provides information about the4//! WebAssembly module and the runtime environment.56use crate::func_environ::FuncEnvironment;7use crate::translate::TargetEnvironment;8use crate::translate::code_translator::{bitcast_wasm_returns, translate_operator};9use crate::translate::stack::FuncTranslationStacks;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};1718/// WebAssembly to Cranelift IR function translator.19///20/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided21/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple22/// functions which will reduce heap allocation traffic.23pub struct FuncTranslator {24func_ctx: FunctionBuilderContext,25state: FuncTranslationStacks,26}2728impl FuncTranslator {29/// Create a new translator.30pub fn new() -> Self {31Self {32func_ctx: FunctionBuilderContext::new(),33state: FuncTranslationStacks::new(),34}35}3637/// Returns the underlying `FunctionBuilderContext` that this translator38/// uses.39pub fn context(&mut self) -> &mut FunctionBuilderContext {40&mut self.func_ctx41}4243/// 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-section48///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 not51/// regarded as WebAssembly local variables. Any signature arguments marked as52/// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.53pub fn translate_body(54&mut self,55validator: &mut FuncValidator<impl WasmModuleResources>,56body: FunctionBody<'_>,57func: &mut ir::Function,58environ: &mut FuncEnvironment<'_>,59) -> WasmResult<()> {60let _tt = timing::wasm_translate_function();61let mut reader = body.get_binary_reader();62log::trace!(63"translate({} bytes, {}{})",64reader.bytes_remaining(),65func.name,66func.signature67);68debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");69debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");7071let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);72builder.set_srcloc(cur_srcloc(&reader));73let entry_block = builder.create_block();74builder.append_block_params_for_function_params(entry_block);75builder.switch_to_block(entry_block);76builder.seal_block(entry_block); // Declare all predecessors known.7778// Make sure the entry block is inserted in the layout before we make any callbacks to79// `environ`. The callback functions may need to insert things in the entry block.80builder.ensure_inserted_block();8182let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);8384// Set up the translation state with a single pushed control block representing the whole85// function and its return values.86let exit_block = builder.create_block();87builder.append_block_params_for_function_returns(exit_block);88self.state.initialize(&builder.func.signature, exit_block);8990parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;91parse_function_body(validator, reader, &mut builder, &mut self.state, environ)?;9293builder.finalize();94log::trace!("translated Wasm to CLIF:\n{}", func.display());95Ok(())96}97}9899/// 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(103builder: &mut FunctionBuilder,104entry_block: Block,105environ: &FuncEnvironment<'_>,106) -> usize {107let sig_len = builder.func.signature.params.len();108let mut next_local = 0;109for i in 0..sig_len {110let param_type = builder.func.signature.params[i];111// There may be additional special-purpose parameters in addition to the normal WebAssembly112// signature parameters. For example, a `vmctx` pointer.113if environ.is_wasm_parameter(&builder.func.signature, i) {114// This is a normal WebAssembly signature parameter, so create a local for it.115let local = builder.declare_var(param_type.value_type);116debug_assert_eq!(local.index(), next_local);117next_local += 1;118119if environ.param_needs_stack_map(&builder.func.signature, i) {120builder.declare_var_needs_stack_map(local);121}122123let param_value = builder.block_params(entry_block)[i];124builder.def_var(local, param_value);125}126if param_type.purpose == ir::ArgumentPurpose::VMContext {127let param_value = builder.block_params(entry_block)[i];128builder.set_val_label(param_value, get_vmctx_value_label());129}130}131132next_local133}134135/// Parse the local variable declarations that precede the function body.136///137/// Declare local variables, starting from `num_params`.138fn parse_local_decls(139reader: &mut BinaryReader,140builder: &mut FunctionBuilder,141num_params: usize,142environ: &mut FuncEnvironment<'_>,143validator: &mut FuncValidator<impl WasmModuleResources>,144) -> WasmResult<()> {145let mut next_local = num_params;146let local_count = reader.read_var_u32()?;147148for _ in 0..local_count {149builder.set_srcloc(cur_srcloc(reader));150let pos = reader.original_position();151let count = reader.read_var_u32()?;152let ty = reader.read()?;153validator.define_locals(pos, count, ty)?;154declare_locals(builder, count, ty, &mut next_local, environ)?;155}156157Ok(())158}159160/// Declare `count` local variables of the same type, starting from `next_local`.161///162/// Fail if too many locals are declared in the function, or if the type is not valid for a local.163fn declare_locals(164builder: &mut FunctionBuilder,165count: u32,166wasm_type: wasmparser::ValType,167next_local: &mut usize,168environ: &mut FuncEnvironment<'_>,169) -> WasmResult<()> {170// All locals are initialized to 0.171use wasmparser::ValType::*;172let (ty, init, needs_stack_map) = match wasm_type {173I32 => (174ir::types::I32,175Some(builder.ins().iconst(ir::types::I32, 0)),176false,177),178I64 => (179ir::types::I64,180Some(builder.ins().iconst(ir::types::I64, 0)),181false,182),183F32 => (184ir::types::F32,185Some(builder.ins().f32const(ir::immediates::Ieee32::with_bits(0))),186false,187),188F64 => (189ir::types::F64,190Some(builder.ins().f64const(ir::immediates::Ieee64::with_bits(0))),191false,192),193V128 => {194let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());195(196ir::types::I8X16,197Some(builder.ins().vconst(ir::types::I8X16, constant_handle)),198false,199)200}201Ref(rt) => {202let hty = environ.convert_heap_type(rt.heap_type())?;203let (ty, needs_stack_map) = environ.reference_type(hty);204let init = if rt.is_nullable() {205Some(environ.translate_ref_null(builder.cursor(), hty)?)206} else {207None208};209(ty, init, needs_stack_map)210}211};212213for _ in 0..count {214let local = builder.declare_var(ty);215debug_assert_eq!(local.index(), *next_local);216if needs_stack_map {217builder.declare_var_needs_stack_map(local);218}219if let Some(init) = init {220builder.def_var(local, init);221builder.set_val_label(init, ValueLabel::new(*next_local));222}223*next_local += 1;224}225Ok(())226}227228/// Parse the function body in `reader`.229///230/// This assumes that the local variable declarations have already been parsed and function231/// arguments and locals are declared in the builder.232fn parse_function_body(233validator: &mut FuncValidator<impl WasmModuleResources>,234reader: BinaryReader,235builder: &mut FunctionBuilder,236stack: &mut FuncTranslationStacks,237environ: &mut FuncEnvironment<'_>,238) -> WasmResult<()> {239// The control stack is initialized with a single block representing the whole function.240debug_assert_eq!(stack.control_stack.len(), 1, "State not initialized");241242environ.before_translate_function(builder, stack)?;243244let mut reader = OperatorsReader::new(reader);245let mut operand_types = vec![];246247while !reader.eof() {248let pos = reader.original_position();249builder.set_srcloc(cur_srcloc(&reader.get_binary_reader()));250251let op = reader.read()?;252let operand_types =253validate_op_and_get_operand_types(validator, environ, &mut operand_types, &op, pos)?;254255environ.before_translate_operator(&op, operand_types, builder, stack)?;256translate_operator(validator, &op, operand_types, builder, stack, environ)?;257environ.after_translate_operator(&op, operand_types, builder, stack)?;258}259environ.after_translate_function(builder, stack)?;260reader.finish()?;261262// The final `End` operator left us in the exit block where we need to manually add a return263// instruction.264//265// If the exit block is unreachable, it may not have the correct arguments, so we would266// generate a return instruction that doesn't match the signature.267if stack.reachable {268if !builder.is_unreachable() {269environ.handle_before_return(&stack.stack, builder);270bitcast_wasm_returns(&mut stack.stack, builder);271builder.ins().return_(&stack.stack);272}273}274275// Discard any remaining values on the stack. Either we just returned them,276// or the end of the function is unreachable.277stack.stack.clear();278279Ok(())280}281282fn validate_op_and_get_operand_types<'a>(283validator: &mut FuncValidator<impl WasmModuleResources>,284environ: &mut FuncEnvironment<'_>,285operand_types: &'a mut Vec<wasmtime_environ::WasmValType>,286op: &wasmparser::Operator<'_>,287pos: usize,288) -> WasmResult<Option<&'a [wasmtime_environ::WasmValType]>> {289// Get the operand types for this operator.290//291// Note that we don't know if the `op` is valid yet, but only valid ops will292// definitely have arity. However, we also must check the arity before293// validating the op so that the validator has the right state to correctly294// report the arity. Furthermore, even if the op is valid, if it is in295// unreachable code, the op might want to pop more values from the stack296// than actually exist on the stack (which is allowed in unreachable code)297// so even if we can get arity, we are only guaranteed to have operand types298// for ops that are not only valid but also reachable.299let arity = op.operator_arity(&*validator);300operand_types.clear();301let operand_types = arity.and_then(|(operand_arity, _result_arity)| {302for i in (0..operand_arity).rev() {303let i = usize::try_from(i).unwrap();304let ty = validator.get_operand_type(i)??;305let ty = environ.convert_valtype(ty).ok()?;306operand_types.push(ty);307}308Some(&operand_types[..])309});310311validator.op(pos, &op)?;312313Ok(operand_types)314}315316/// Get the current source location from a reader.317fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {318// We record source locations as byte code offsets relative to the beginning of the file.319// This will panic if bytecode is larger than 4 GB.320ir::SourceLoc::new(reader.original_position().try_into().unwrap())321}322323324