summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cranelift-wasm/src/func_translator.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cranelift-wasm/src/func_translator.rs')
-rw-r--r--third_party/rust/cranelift-wasm/src/func_translator.rs418
1 files changed, 418 insertions, 0 deletions
diff --git a/third_party/rust/cranelift-wasm/src/func_translator.rs b/third_party/rust/cranelift-wasm/src/func_translator.rs
new file mode 100644
index 0000000000..158271ab96
--- /dev/null
+++ b/third_party/rust/cranelift-wasm/src/func_translator.rs
@@ -0,0 +1,418 @@
+//! Stand-alone WebAssembly to Cranelift IR translator.
+//!
+//! This module defines the `FuncTranslator` type which can translate a single WebAssembly
+//! function to Cranelift IR guided by a `FuncEnvironment` which provides information about the
+//! WebAssembly module and the runtime environment.
+
+use crate::code_translator::{bitcast_arguments, translate_operator, wasm_param_types};
+use crate::environ::{FuncEnvironment, ReturnMode, WasmResult};
+use crate::state::FuncTranslationState;
+use crate::translation_utils::get_vmctx_value_label;
+use crate::wasm_unsupported;
+use core::convert::TryInto;
+use cranelift_codegen::entity::EntityRef;
+use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
+use cranelift_codegen::timing;
+use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
+use wasmparser::{self, BinaryReader, FuncValidator, FunctionBody, WasmModuleResources};
+
+/// WebAssembly to Cranelift IR function translator.
+///
+/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided
+/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple
+/// functions which will reduce heap allocation traffic.
+pub struct FuncTranslator {
+ func_ctx: FunctionBuilderContext,
+ state: FuncTranslationState,
+}
+
+impl FuncTranslator {
+ /// Create a new translator.
+ pub fn new() -> Self {
+ Self {
+ func_ctx: FunctionBuilderContext::new(),
+ state: FuncTranslationState::new(),
+ }
+ }
+
+ /// Translate a binary WebAssembly function.
+ ///
+ /// The `code` slice contains the binary WebAssembly *function code* as it appears in the code
+ /// section of a WebAssembly module, not including the initial size of the function code. The
+ /// slice is expected to contain two parts:
+ ///
+ /// - The declaration of *locals*, and
+ /// - The function *body* as an expression.
+ ///
+ /// See [the WebAssembly specification][wasm].
+ ///
+ /// [wasm]: https://webassembly.github.io/spec/core/binary/modules.html#code-section
+ ///
+ /// The Cranelift IR function `func` should be completely empty except for the `func.signature`
+ /// and `func.name` fields. The signature may contain special-purpose arguments which are not
+ /// regarded as WebAssembly local variables. Any signature arguments marked as
+ /// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.
+ ///
+ pub fn translate<FE: FuncEnvironment + ?Sized>(
+ &mut self,
+ validator: &mut FuncValidator<impl WasmModuleResources>,
+ code: &[u8],
+ code_offset: usize,
+ func: &mut ir::Function,
+ environ: &mut FE,
+ ) -> WasmResult<()> {
+ self.translate_body(
+ validator,
+ FunctionBody::new(code_offset, code),
+ func,
+ environ,
+ )
+ }
+
+ /// Translate a binary WebAssembly function from a `FunctionBody`.
+ pub fn translate_body<FE: FuncEnvironment + ?Sized>(
+ &mut self,
+ validator: &mut FuncValidator<impl WasmModuleResources>,
+ body: FunctionBody<'_>,
+ func: &mut ir::Function,
+ environ: &mut FE,
+ ) -> WasmResult<()> {
+ let _tt = timing::wasm_translate_function();
+ let mut reader = body.get_binary_reader();
+ log::debug!(
+ "translate({} bytes, {}{})",
+ reader.bytes_remaining(),
+ func.name,
+ func.signature
+ );
+ debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");
+ debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
+
+ // This clears the `FunctionBuilderContext`.
+ let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
+ builder.set_srcloc(cur_srcloc(&reader));
+ let entry_block = builder.create_block();
+ builder.append_block_params_for_function_params(entry_block);
+ builder.switch_to_block(entry_block); // This also creates values for the arguments.
+ builder.seal_block(entry_block); // Declare all predecessors known.
+
+ // Make sure the entry block is inserted in the layout before we make any callbacks to
+ // `environ`. The callback functions may need to insert things in the entry block.
+ builder.ensure_inserted_block();
+
+ let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);
+
+ // Set up the translation state with a single pushed control block representing the whole
+ // function and its return values.
+ let exit_block = builder.create_block();
+ builder.append_block_params_for_function_returns(exit_block);
+ self.state.initialize(&builder.func.signature, exit_block);
+
+ parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
+ parse_function_body(validator, reader, &mut builder, &mut self.state, environ)?;
+
+ builder.finalize();
+ Ok(())
+ }
+}
+
+/// Declare local variables for the signature parameters that correspond to WebAssembly locals.
+///
+/// Return the number of local variables declared.
+fn declare_wasm_parameters<FE: FuncEnvironment + ?Sized>(
+ builder: &mut FunctionBuilder,
+ entry_block: Block,
+ environ: &FE,
+) -> usize {
+ let sig_len = builder.func.signature.params.len();
+ let mut next_local = 0;
+ for i in 0..sig_len {
+ let param_type = builder.func.signature.params[i];
+ // There may be additional special-purpose parameters in addition to the normal WebAssembly
+ // signature parameters. For example, a `vmctx` pointer.
+ if environ.is_wasm_parameter(&builder.func.signature, i) {
+ // This is a normal WebAssembly signature parameter, so create a local for it.
+ let local = Variable::new(next_local);
+ builder.declare_var(local, param_type.value_type);
+ next_local += 1;
+
+ let param_value = builder.block_params(entry_block)[i];
+ builder.def_var(local, param_value);
+ }
+ if param_type.purpose == ir::ArgumentPurpose::VMContext {
+ let param_value = builder.block_params(entry_block)[i];
+ builder.set_val_label(param_value, get_vmctx_value_label());
+ }
+ }
+
+ next_local
+}
+
+/// Parse the local variable declarations that precede the function body.
+///
+/// Declare local variables, starting from `num_params`.
+fn parse_local_decls<FE: FuncEnvironment + ?Sized>(
+ reader: &mut BinaryReader,
+ builder: &mut FunctionBuilder,
+ num_params: usize,
+ environ: &mut FE,
+ validator: &mut FuncValidator<impl WasmModuleResources>,
+) -> WasmResult<()> {
+ let mut next_local = num_params;
+ let local_count = reader.read_var_u32()?;
+
+ for _ in 0..local_count {
+ builder.set_srcloc(cur_srcloc(reader));
+ let pos = reader.original_position();
+ let count = reader.read_var_u32()?;
+ let ty = reader.read_type()?;
+ validator.define_locals(pos, count, ty)?;
+ declare_locals(builder, count, ty, &mut next_local, environ)?;
+ }
+
+ Ok(())
+}
+
+/// Declare `count` local variables of the same type, starting from `next_local`.
+///
+/// Fail of too many locals are declared in the function, or if the type is not valid for a local.
+fn declare_locals<FE: FuncEnvironment + ?Sized>(
+ builder: &mut FunctionBuilder,
+ count: u32,
+ wasm_type: wasmparser::Type,
+ next_local: &mut usize,
+ environ: &mut FE,
+) -> WasmResult<()> {
+ // All locals are initialized to 0.
+ use wasmparser::Type::*;
+ let zeroval = match wasm_type {
+ I32 => builder.ins().iconst(ir::types::I32, 0),
+ I64 => builder.ins().iconst(ir::types::I64, 0),
+ F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)),
+ F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)),
+ V128 => {
+ let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());
+ builder.ins().vconst(ir::types::I8X16, constant_handle)
+ }
+ ExternRef | FuncRef => {
+ environ.translate_ref_null(builder.cursor(), wasm_type.try_into()?)?
+ }
+ ty => return Err(wasm_unsupported!("unsupported local type {:?}", ty)),
+ };
+
+ let ty = builder.func.dfg.value_type(zeroval);
+ for _ in 0..count {
+ let local = Variable::new(*next_local);
+ builder.declare_var(local, ty);
+ builder.def_var(local, zeroval);
+ builder.set_val_label(zeroval, ValueLabel::new(*next_local));
+ *next_local += 1;
+ }
+ Ok(())
+}
+
+/// Parse the function body in `reader`.
+///
+/// This assumes that the local variable declarations have already been parsed and function
+/// arguments and locals are declared in the builder.
+fn parse_function_body<FE: FuncEnvironment + ?Sized>(
+ validator: &mut FuncValidator<impl WasmModuleResources>,
+ mut reader: BinaryReader,
+ builder: &mut FunctionBuilder,
+ state: &mut FuncTranslationState,
+ environ: &mut FE,
+) -> WasmResult<()> {
+ // The control stack is initialized with a single block representing the whole function.
+ debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
+
+ while !reader.eof() {
+ let pos = reader.original_position();
+ builder.set_srcloc(cur_srcloc(&reader));
+ let op = reader.read_operator()?;
+ validator.op(pos, &op)?;
+ environ.before_translate_operator(&op, builder, state)?;
+ translate_operator(validator, &op, builder, state, environ)?;
+ environ.after_translate_operator(&op, builder, state)?;
+ }
+ let pos = reader.original_position();
+ validator.finish(pos)?;
+
+ // The final `End` operator left us in the exit block where we need to manually add a return
+ // instruction.
+ //
+ // If the exit block is unreachable, it may not have the correct arguments, so we would
+ // generate a return instruction that doesn't match the signature.
+ if state.reachable {
+ debug_assert!(builder.is_pristine());
+ if !builder.is_unreachable() {
+ match environ.return_mode() {
+ ReturnMode::NormalReturns => {
+ let return_types = wasm_param_types(&builder.func.signature.returns, |i| {
+ environ.is_wasm_return(&builder.func.signature, i)
+ });
+ bitcast_arguments(&mut state.stack, &return_types, builder);
+ builder.ins().return_(&state.stack)
+ }
+ ReturnMode::FallthroughReturn => builder.ins().fallthrough_return(&state.stack),
+ };
+ }
+ }
+
+ // Discard any remaining values on the stack. Either we just returned them,
+ // or the end of the function is unreachable.
+ state.stack.clear();
+
+ Ok(())
+}
+
+/// Get the current source location from a reader.
+fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
+ // We record source locations as byte code offsets relative to the beginning of the file.
+ // This will wrap around if byte code is larger than 4 GB.
+ ir::SourceLoc::new(reader.original_position() as u32)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{FuncTranslator, ReturnMode};
+ use crate::environ::DummyEnvironment;
+ use cranelift_codegen::ir::types::I32;
+ use cranelift_codegen::{ir, isa, settings, Context};
+ use log::debug;
+ use target_lexicon::PointerWidth;
+ use wasmparser::{
+ FuncValidator, FunctionBody, Parser, ValidPayload, Validator, ValidatorResources,
+ };
+
+ #[test]
+ fn small1() {
+ // Implicit return.
+ let wasm = wat::parse_str(
+ "
+ (module
+ (func $small2 (param i32) (result i32)
+ (i32.add (get_local 0) (i32.const 1))
+ )
+ )
+ ",
+ )
+ .unwrap();
+
+ let mut trans = FuncTranslator::new();
+ let flags = settings::Flags::new(settings::builder());
+ let runtime = DummyEnvironment::new(
+ isa::TargetFrontendConfig {
+ default_call_conv: isa::CallConv::Fast,
+ pointer_width: PointerWidth::U64,
+ },
+ ReturnMode::NormalReturns,
+ false,
+ );
+
+ let mut ctx = Context::new();
+
+ ctx.func.name = ir::ExternalName::testcase("small1");
+ ctx.func.signature.params.push(ir::AbiParam::new(I32));
+ ctx.func.signature.returns.push(ir::AbiParam::new(I32));
+
+ let (body, mut validator) = extract_func(&wasm);
+ trans
+ .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
+ .unwrap();
+ debug!("{}", ctx.func.display(None));
+ ctx.verify(&flags).unwrap();
+ }
+
+ #[test]
+ fn small2() {
+ // Same as above, but with an explicit return instruction.
+ let wasm = wat::parse_str(
+ "
+ (module
+ (func $small2 (param i32) (result i32)
+ (return (i32.add (get_local 0) (i32.const 1)))
+ )
+ )
+ ",
+ )
+ .unwrap();
+
+ let mut trans = FuncTranslator::new();
+ let flags = settings::Flags::new(settings::builder());
+ let runtime = DummyEnvironment::new(
+ isa::TargetFrontendConfig {
+ default_call_conv: isa::CallConv::Fast,
+ pointer_width: PointerWidth::U64,
+ },
+ ReturnMode::NormalReturns,
+ false,
+ );
+
+ let mut ctx = Context::new();
+
+ ctx.func.name = ir::ExternalName::testcase("small2");
+ ctx.func.signature.params.push(ir::AbiParam::new(I32));
+ ctx.func.signature.returns.push(ir::AbiParam::new(I32));
+
+ let (body, mut validator) = extract_func(&wasm);
+ trans
+ .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
+ .unwrap();
+ debug!("{}", ctx.func.display(None));
+ ctx.verify(&flags).unwrap();
+ }
+
+ #[test]
+ fn infloop() {
+ // An infinite loop, no return instructions.
+ let wasm = wat::parse_str(
+ "
+ (module
+ (func $infloop (result i32)
+ (local i32)
+ (loop (result i32)
+ (i32.add (get_local 0) (i32.const 1))
+ (set_local 0)
+ (br 0)
+ )
+ )
+ )
+ ",
+ )
+ .unwrap();
+
+ let mut trans = FuncTranslator::new();
+ let flags = settings::Flags::new(settings::builder());
+ let runtime = DummyEnvironment::new(
+ isa::TargetFrontendConfig {
+ default_call_conv: isa::CallConv::Fast,
+ pointer_width: PointerWidth::U64,
+ },
+ ReturnMode::NormalReturns,
+ false,
+ );
+
+ let mut ctx = Context::new();
+
+ ctx.func.name = ir::ExternalName::testcase("infloop");
+ ctx.func.signature.returns.push(ir::AbiParam::new(I32));
+
+ let (body, mut validator) = extract_func(&wasm);
+ trans
+ .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
+ .unwrap();
+ debug!("{}", ctx.func.display(None));
+ ctx.verify(&flags).unwrap();
+ }
+
+ fn extract_func(wat: &[u8]) -> (FunctionBody<'_>, FuncValidator<ValidatorResources>) {
+ let mut validator = Validator::new();
+ for payload in Parser::new(0).parse_all(wat) {
+ match validator.payload(&payload.unwrap()).unwrap() {
+ ValidPayload::Func(validator, body) => return (body, validator),
+ _ => {}
+ }
+ }
+ panic!("failed to find function");
+ }
+}