Path: blob/main/crates/cranelift/src/translate/func_translator.rs
3092 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::translation_utils::get_vmctx_value_label;10use cranelift_codegen::entity::EntityRef;11use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};12use cranelift_codegen::timing;13use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};14use wasmparser::{BinaryReader, FuncValidator, FunctionBody, OperatorsReader, WasmModuleResources};15use wasmtime_environ::{TypeConvert, WasmResult};1617/// WebAssembly to Cranelift IR function translator.18///19/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided20/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple21/// functions which will reduce heap allocation traffic.22pub struct FuncTranslator {23func_ctx: FunctionBuilderContext,24}2526impl FuncTranslator {27/// Create a new translator.28pub fn new() -> Self {29Self {30func_ctx: FunctionBuilderContext::new(),31}32}3334/// Returns the underlying `FunctionBuilderContext` that this translator35/// uses.36pub fn context(&mut self) -> &mut FunctionBuilderContext {37&mut self.func_ctx38}3940/// Translate a binary WebAssembly function from a `FunctionBody`.41///42/// See [the WebAssembly specification][wasm].43///44/// [wasm]: https://webassembly.github.io/spec/core/binary/modules.html#code-section45///46/// The Cranelift IR function `func` should be completely empty except for the `func.signature`47/// and `func.name` fields. The signature may contain special-purpose arguments which are not48/// regarded as WebAssembly local variables. Any signature arguments marked as49/// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.50pub fn translate_body(51&mut self,52validator: &mut FuncValidator<impl WasmModuleResources>,53body: FunctionBody<'_>,54func: &mut ir::Function,55environ: &mut FuncEnvironment<'_>,56) -> WasmResult<()> {57let _tt = timing::wasm_translate_function();58let mut reader = body.get_binary_reader();59log::trace!(60"translate({} bytes, {}{})",61reader.bytes_remaining(),62func.name,63func.signature64);65debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");66debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");6768let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);69builder.set_srcloc(cur_srcloc(&reader));70let entry_block = builder.create_block();71builder.append_block_params_for_function_params(entry_block);72builder.switch_to_block(entry_block);73builder.seal_block(entry_block); // Declare all predecessors known.7475environ.create_state_slot(&mut builder);7677// Make sure the entry block is inserted in the layout before we make any callbacks to78// `environ`. The callback functions may need to insert things in the entry block.79builder.ensure_inserted_block();8081let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);8283// Set up the translation state with a single pushed control block representing the whole84// function and its return values.85let exit_block = builder.create_block();86builder.append_block_params_for_function_returns(exit_block);87environ88.stacks89.initialize(&builder.func.signature, exit_block);9091parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;92parse_function_body(validator, reader, &mut builder, environ)?;9394builder.finalize();95log::trace!("translated Wasm to CLIF:\n{}", func.display());96Ok(())97}98}99100/// 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(104builder: &mut FunctionBuilder,105entry_block: Block,106environ: &mut FuncEnvironment<'_>,107) -> usize {108let sig_len = builder.func.signature.params.len();109let mut next_local = 0;110for i in 0..sig_len {111let param_type = builder.func.signature.params[i];112// There may be additional special-purpose parameters in addition to the normal WebAssembly113// signature parameters. For example, a `vmctx` pointer.114if let Some(wasm_type) = environ.clif_param_as_wasm_param(i) {115// This is a normal WebAssembly signature parameter, so create a local for it.116let local = builder.declare_var(param_type.value_type);117debug_assert_eq!(local.index(), next_local);118next_local += 1;119120if environ.param_needs_stack_map(&builder.func.signature, i) {121builder.declare_var_needs_stack_map(local);122}123124let param_value = builder.block_params(entry_block)[i];125builder.def_var(local, param_value);126127environ.add_state_slot_local(builder, wasm_type, Some(param_value));128}129if param_type.purpose == ir::ArgumentPurpose::VMContext {130let param_value = builder.block_params(entry_block)[i];131builder.set_val_label(param_value, get_vmctx_value_label());132}133}134135next_local136}137138/// Parse the local variable declarations that precede the function body.139///140/// Declare local variables, starting from `num_params`.141fn parse_local_decls(142reader: &mut BinaryReader,143builder: &mut FunctionBuilder,144num_params: usize,145environ: &mut FuncEnvironment<'_>,146validator: &mut FuncValidator<impl WasmModuleResources>,147) -> WasmResult<()> {148let mut next_local = num_params;149let local_count = reader.read_var_u32()?;150151for _ in 0..local_count {152builder.set_srcloc(cur_srcloc(reader));153let pos = reader.original_position();154let count = reader.read_var_u32()?;155let ty = reader.read()?;156validator.define_locals(pos, count, ty)?;157declare_locals(builder, count, ty, &mut next_local, environ)?;158}159160Ok(())161}162163/// 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(167builder: &mut FunctionBuilder,168count: u32,169wasm_type: wasmparser::ValType,170next_local: &mut usize,171environ: &mut FuncEnvironment<'_>,172) -> WasmResult<()> {173// All locals are initialized to 0.174use wasmparser::ValType::*;175let (ty, init, needs_stack_map) = match wasm_type {176I32 => (177ir::types::I32,178Some(builder.ins().iconst(ir::types::I32, 0)),179false,180),181I64 => (182ir::types::I64,183Some(builder.ins().iconst(ir::types::I64, 0)),184false,185),186F32 => (187ir::types::F32,188Some(builder.ins().f32const(ir::immediates::Ieee32::with_bits(0))),189false,190),191F64 => (192ir::types::F64,193Some(builder.ins().f64const(ir::immediates::Ieee64::with_bits(0))),194false,195),196V128 => {197let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());198(199ir::types::I8X16,200Some(builder.ins().vconst(ir::types::I8X16, constant_handle)),201false,202)203}204Ref(rt) => {205let hty = environ.convert_heap_type(rt.heap_type())?;206let (ty, needs_stack_map) = environ.reference_type(hty);207let init = if rt.is_nullable() {208Some(environ.translate_ref_null(builder.cursor(), hty)?)209} else {210None211};212(ty, init, needs_stack_map)213}214};215216for _ in 0..count {217let local = builder.declare_var(ty);218debug_assert_eq!(local.index(), *next_local);219if needs_stack_map {220builder.declare_var_needs_stack_map(local);221}222if let Some(init) = init {223builder.def_var(local, init);224builder.set_val_label(init, ValueLabel::new(*next_local));225}226environ.add_state_slot_local(builder, environ.convert_valtype(wasm_type)?, init);227*next_local += 1;228}229Ok(())230}231232/// Parse the function body in `reader`.233///234/// This assumes that the local variable declarations have already been parsed and function235/// arguments and locals are declared in the builder.236fn parse_function_body(237validator: &mut FuncValidator<impl WasmModuleResources>,238reader: BinaryReader,239builder: &mut FunctionBuilder,240environ: &mut FuncEnvironment<'_>,241) -> WasmResult<()> {242// The control stack is initialized with a single block representing the whole function.243debug_assert_eq!(244environ.stacks.control_stack.len(),2451,246"State not initialized"247);248249environ.before_translate_function(builder)?;250251let mut reader = OperatorsReader::new(reader);252let mut operand_types = vec![];253254while !reader.eof() {255let pos = reader.original_position();256builder.set_srcloc(cur_srcloc(&reader.get_binary_reader()));257258let op = reader.read()?;259let operand_types =260validate_op_and_get_operand_types(validator, environ, &mut operand_types, &op, pos)?;261262environ.before_translate_operator(&op, operand_types, builder)?;263translate_operator(validator, &op, operand_types, builder, environ)?;264environ.after_translate_operator(&op, validator, builder)?;265}266267environ.after_translate_function(builder)?;268reader.finish()?;269270// The final `End` operator left us in the exit block where we need to manually add a return271// instruction.272//273// If the exit block is unreachable, it may not have the correct arguments, so we would274// generate a return instruction that doesn't match the signature.275if environ.is_reachable() {276if !builder.is_unreachable() {277let mut returns = core::mem::take(&mut environ.stacks.stack);278environ.handle_before_return(&returns, builder);279bitcast_wasm_returns(&mut returns, builder);280builder.ins().return_(&returns);281}282}283284// Discard any remaining values on the stack. Either we just returned them,285// or the end of the function is unreachable.286environ.stacks.stack.clear();287environ.stacks.stack_shape.clear();288289Ok(())290}291292fn validate_op_and_get_operand_types<'a>(293validator: &mut FuncValidator<impl WasmModuleResources>,294environ: &mut FuncEnvironment<'_>,295operand_types: &'a mut Vec<wasmtime_environ::WasmValType>,296op: &wasmparser::Operator<'_>,297pos: usize,298) -> WasmResult<Option<&'a [wasmtime_environ::WasmValType]>> {299// Get the operand types for this operator.300//301// Note that we don't know if the `op` is valid yet, but only valid ops will302// definitely have arity. However, we also must check the arity before303// validating the op so that the validator has the right state to correctly304// report the arity. Furthermore, even if the op is valid, if it is in305// unreachable code, the op might want to pop more values from the stack306// than actually exist on the stack (which is allowed in unreachable code)307// so even if we can get arity, we are only guaranteed to have operand types308// for ops that are not only valid but also reachable.309let arity = op.operator_arity(&*validator);310operand_types.clear();311let operand_types = arity.and_then(|(operand_arity, _result_arity)| {312for i in (0..operand_arity).rev() {313let i = usize::try_from(i).unwrap();314let ty = validator.get_operand_type(i)??;315let ty = environ.convert_valtype(ty).ok()?;316operand_types.push(ty);317}318Some(&operand_types[..])319});320321validator.op(pos, &op)?;322323Ok(operand_types)324}325326/// Get the current source location from a reader.327fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {328// We record source locations as byte code offsets relative to the beginning of the file.329// This will panic if bytecode is larger than 4 GB.330ir::SourceLoc::new(reader.original_position().try_into().unwrap())331}332333334