diff --git a/cranelift/interpreter/Cargo.toml b/cranelift/interpreter/Cargo.toml new file mode 100644 index 0000000000..bdfbd94389 --- /dev/null +++ b/cranelift/interpreter/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cranelift-interpreter" +version = "0.1.0" +authors = ["The Cranelift Project Developers"] +description = "Interpret Cranelift IR" +repository = "https://github.com/bytecodealliance/wasmtime" +documentation = "https://docs.rs/cranelift-interpreter" +categories = ["no-std"] +license = "Apache-2.0 WITH LLVM-exception" +readme = "README.md" +edition = "2018" + +[dependencies] +cranelift-codegen = { path = "../codegen", version = "*", default-features = false } +cranelift-entity = { path = "../entity", version = "*" } +cranelift-reader = { path = "../reader", version = "*" } +hashbrown = { version = "0.7.1", optional = true } +log = { version = "0.4.8", default-features = false } +thiserror = "1.0.15" +walkdir = "2.3.1" +pretty_env_logger = "0.4.0" + +[dev-dependencies] +cranelift-frontend = { path = "../frontend", version = "*" } + +[badges] +maintenance = { status = "experimental" } diff --git a/cranelift/interpreter/LICENSE b/cranelift/interpreter/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/cranelift/interpreter/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/cranelift/interpreter/README.md b/cranelift/interpreter/README.md new file mode 100644 index 0000000000..85389ea0b7 --- /dev/null +++ b/cranelift/interpreter/README.md @@ -0,0 +1,2 @@ +This crate provides an interpreter for Cranelift IR. It is still a work in progress, as many +instructions are unimplemented and various implementation gaps exist. Use at your own risk. diff --git a/cranelift/interpreter/src/environment.rs b/cranelift/interpreter/src/environment.rs new file mode 100644 index 0000000000..8ace55b486 --- /dev/null +++ b/cranelift/interpreter/src/environment.rs @@ -0,0 +1,82 @@ +//! Implements the function environment (e.g. a name-to-function mapping) for interpretation. + +use cranelift_codegen::ir::{FuncRef, Function}; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Environment { + functions: HashMap, + function_name_to_func_ref: HashMap, +} + +impl From for Environment { + fn from(f: Function) -> Self { + let func_ref = FuncRef::from_u32(0); + let mut function_name_to_func_ref = HashMap::new(); + function_name_to_func_ref.insert(f.name.to_string(), func_ref); + let mut functions = HashMap::new(); + functions.insert(func_ref, f); + Self { + functions, + function_name_to_func_ref, + } + } +} + +impl Environment { + /// Add a function by name. + pub fn add(&mut self, name: String, function: Function) { + let func_ref = FuncRef::with_number(self.function_name_to_func_ref.len() as u32) + .expect("a valid function reference"); + self.function_name_to_func_ref.insert(name, func_ref); + self.functions.insert(func_ref, function); + } + + /// Retrieve a reference to a function in the environment by its name. + pub fn index_of(&self, name: &str) -> Option { + self.function_name_to_func_ref.get(name).cloned() + } + + /// Retrieve a function by its function reference. + pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&Function> { + self.functions.get(&func_ref) + } + + /// Retrieve a function by its name. + pub fn get_by_name(&self, name: &str) -> Option<&Function> { + let func_ref = self.index_of(name)?; + self.get_by_func_ref(func_ref) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cranelift_codegen::ir::{ExternalName, Signature}; + use cranelift_codegen::isa::CallConv; + + #[test] + fn addition() { + let mut env = Environment::default(); + let a = "a"; + let f = Function::new(); + + env.add(a.to_string(), f); + assert!(env.get_by_name(a).is_some()); + } + + #[test] + fn nonexistence() { + let env = Environment::default(); + assert!(env.get_by_name("a").is_none()); + } + + #[test] + fn from() { + let name = ExternalName::testcase("test"); + let signature = Signature::new(CallConv::Fast); + let func = Function::with_name_signature(name, signature); + let env: Environment = func.into(); + assert_eq!(env.index_of("%test"), FuncRef::with_number(0)); + } +} diff --git a/cranelift/interpreter/src/frame.rs b/cranelift/interpreter/src/frame.rs new file mode 100644 index 0000000000..751882a327 --- /dev/null +++ b/cranelift/interpreter/src/frame.rs @@ -0,0 +1,128 @@ +//! Implements a call frame (activation record) for the Cranelift interpreter. + +use cranelift_codegen::ir::{Function, Value as ValueRef}; +use cranelift_reader::DataValue; +use std::collections::HashMap; + +/// Holds the mutable elements of an interpretation. At some point I thought about using +/// Cell/RefCell to do field-level mutability, thinking that otherwise I would have to +/// pass around a mutable object (for inst and registers) and an immutable one (for function, +/// could be self)--in the end I decided to do exactly that but perhaps one day that will become +/// untenable. +#[derive(Debug)] +pub struct Frame<'a> { + /// The currently executing function. + pub function: &'a Function, + /// The current mapping of SSA value-references to their actual values. + registers: HashMap, +} + +impl<'a> Frame<'a> { + /// Construct a new [Frame] for a function. This allocates a slot in the hash map for each SSA + /// `Value` (renamed to `ValueRef` here) which should mean that no additional allocations are + /// needed while interpreting the frame. + pub fn new(function: &'a Function) -> Self { + Self { + function, + registers: HashMap::with_capacity(function.dfg.num_values()), + } + } + + /// Construct a new [Frame] with the given `values` assigned to their corresponding slot + /// (from the SSA references in `parameters`) in the [Frame]. + pub fn with_parameters(mut self, parameters: &[ValueRef], values: &[DataValue]) -> Self { + assert_eq!(parameters.len(), values.len()); + for (n, v) in parameters.iter().zip(values) { + self.registers.insert(*n, v.clone()); + } + self + } + + /// Retrieve the actual value associated with an SSA reference. + #[inline] + pub fn get(&self, name: &ValueRef) -> &DataValue { + self.registers + .get(name) + .unwrap_or_else(|| panic!("unknown value: {}", name)) + } + + /// Retrieve multiple SSA references; see `get`. + pub fn get_all(&self, names: &[ValueRef]) -> Vec { + names.iter().map(|r| self.get(r)).cloned().collect() + } + + /// Assign `value` to the SSA reference `name`. + #[inline] + pub fn set(&mut self, name: ValueRef, value: DataValue) -> Option { + self.registers.insert(name, value) + } + + /// Assign to multiple SSA references; see `set`. + pub fn set_all(&mut self, names: &[ValueRef], values: Vec) { + assert_eq!(names.len(), values.len()); + for (n, v) in names.iter().zip(values) { + self.set(*n, v); + } + } + + /// Rename all of the SSA references in `old_names` to those in `new_names`. This will remove + /// any old references that are not in `old_names`. TODO This performs an extra allocation that + /// could be removed if we copied the values in the right order (i.e. when modifying in place, + /// we need to avoid changing a value before it is referenced). + pub fn rename(&mut self, old_names: &[ValueRef], new_names: &[ValueRef]) { + assert_eq!(old_names.len(), new_names.len()); + let mut registers = HashMap::with_capacity(self.registers.len()); + for (on, nn) in old_names.iter().zip(new_names) { + let v = self.registers.get(on).unwrap().clone(); + registers.insert(*nn, v); + } + self.registers = registers; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cranelift_codegen::ir::InstBuilder; + use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; + use cranelift_reader::DataValue; + + /// Build an empty function with a single return. + fn empty_function() -> Function { + let mut func = Function::new(); + let mut context = FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut func, &mut context); + let block = builder.create_block(); + builder.switch_to_block(block); + builder.ins().return_(&[]); + func + } + + #[test] + fn construction() { + let func = empty_function(); + // Construction should not fail. + Frame::new(&func); + } + + #[test] + fn assignment() { + let func = empty_function(); + let mut frame = Frame::new(&func); + + let a = ValueRef::with_number(1).unwrap(); + let fortytwo = DataValue::I32(42); + frame.set(a, fortytwo.clone()); + assert_eq!(frame.get(&a), &fortytwo); + } + + #[test] + #[should_panic] + fn no_existing_value() { + let func = empty_function(); + let frame = Frame::new(&func); + + let a = ValueRef::with_number(1).unwrap(); + frame.get(&a); + } +} diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs new file mode 100644 index 0000000000..61d3133603 --- /dev/null +++ b/cranelift/interpreter/src/interpreter.rs @@ -0,0 +1,365 @@ +//! Cranelift IR interpreter. +//! +//! This module contains the logic for interpreting Cranelift instructions. + +use crate::environment::Environment; +use crate::frame::Frame; +use crate::interpreter::Trap::InvalidType; +use cranelift_codegen::ir::condcodes::IntCC; +use cranelift_codegen::ir::{ + Block, FuncRef, Function, Inst, InstructionData, InstructionData::*, Opcode, Opcode::*, Type, + Value as ValueRef, ValueList, +}; +use cranelift_reader::{DataValue, DataValueCastFailure}; +use log::debug; +use std::ops::{Add, Sub}; +use thiserror::Error; + +/// The valid control flow states. +pub enum ControlFlow { + Continue, + ContinueAt(Block, Vec), + Return(Vec), +} + +impl ControlFlow { + /// For convenience, we can unwrap the [ControlFlow] state assuming that it is a + /// [ControlFlow::Return], panicking otherwise. + pub fn unwrap_return(self) -> Vec { + if let ControlFlow::Return(values) = self { + values + } else { + panic!("expected the control flow to be in the return state") + } + } +} + +/// The ways interpretation can fail. +#[derive(Error, Debug)] +pub enum Trap { + #[error("unknown trap")] + Unknown, + #[error("invalid type for {1}: expected {0}")] + InvalidType(String, ValueRef), + #[error("invalid cast")] + InvalidCast(#[from] DataValueCastFailure), + #[error("the instruction is not implemented (perhaps for the given types): {0}")] + Unsupported(Inst), + #[error("reached an unreachable statement")] + Unreachable, + #[error("invalid control flow: {0}")] + InvalidControlFlow(String), + #[error("invalid function reference: {0}")] + InvalidFunctionReference(FuncRef), + #[error("invalid function name: {0}")] + InvalidFunctionName(String), +} + +/// The Cranelift interpreter; it contains immutable elements such as the function environment and +/// implements the Cranelift IR semantics. +#[derive(Default)] +pub struct Interpreter { + pub env: Environment, +} + +/// Helper for more concise matching. +macro_rules! binary_op { + ( $op:path[$arg1:ident, $arg2:ident]; [ $( $data_value_ty:ident ),* ]; $inst:ident ) => { + match ($arg1, $arg2) { + $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok(DataValue::$data_value_ty($op(a, b))) } )* + _ => Err(Trap::Unsupported($inst)), + } + }; +} + +impl Interpreter { + /// Construct a new [Interpreter] using the given [Environment]. + pub fn new(env: Environment) -> Self { + Self { env } + } + + /// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index]. + pub fn call_by_name( + &self, + func_name: &str, + arguments: &[DataValue], + ) -> Result { + let func_ref = self + .env + .index_of(func_name) + .ok_or_else(|| Trap::InvalidFunctionName(func_name.to_string()))?; + self.call_by_index(func_ref, arguments) + } + + /// Call a function by its index in the [Environment]; this is a proxy for [Interpreter::call]. + pub fn call_by_index( + &self, + func_ref: FuncRef, + arguments: &[DataValue], + ) -> Result { + match self.env.get_by_func_ref(func_ref) { + None => Err(Trap::InvalidFunctionReference(func_ref)), + Some(func) => self.call(func, arguments), + } + } + + /// Interpret a call to a [Function] given its [DataValue] arguments. + fn call(&self, function: &Function, arguments: &[DataValue]) -> Result { + debug!("Call: {}({:?})", function.name, arguments); + let first_block = function + .layout + .blocks() + .next() + .expect("to have a first block"); + let parameters = function.dfg.block_params(first_block); + let mut frame = Frame::new(function).with_parameters(parameters, arguments); + self.block(&mut frame, first_block) + } + + /// Interpret a single [Block] in a [Function]. + fn block(&self, frame: &mut Frame, block: Block) -> Result { + debug!("Block: {}", block); + for inst in frame.function.layout.block_insts(block) { + match self.inst(frame, inst)? { + ControlFlow::Continue => continue, + ControlFlow::ContinueAt(block, old_names) => { + let new_names = frame.function.dfg.block_params(block); + frame.rename(&old_names, new_names); + return self.block(frame, block); + } + ControlFlow::Return(rs) => return Ok(ControlFlow::Return(rs)), + } + } + Err(Trap::Unreachable) + } + + /// Interpret a single [instruction](Inst). This contains a `match`-based dispatch to the + /// implementations. + fn inst(&self, frame: &mut Frame, inst: Inst) -> Result { + use ControlFlow::{Continue, ContinueAt}; + debug!("Inst: {}", &frame.function.dfg.display_inst(inst, None)); + + let data = &frame.function.dfg[inst]; + match data { + Binary { opcode, args } => { + let arg1 = frame.get(&args[0]); + let arg2 = frame.get(&args[1]); + let result = match opcode { + Iadd => binary_op!(Add::add[arg1, arg2]; [I8, I16, I32, I64, F32, F64]; inst), + Isub => binary_op!(Sub::sub[arg1, arg2]; [I8, I16, I32, I64, F32, F64]; inst), + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }?; + frame.set(first_result(frame.function, inst), result); + Ok(Continue) + } + + BinaryImm { opcode, arg, imm } => { + let imm = DataValue::from_integer(*imm, type_of(*arg, frame.function))?; + let arg = frame.get(&arg); + let result = match opcode { + IaddImm => binary_op!(Add::add[arg, imm]; [I8, I16, I32, I64, F32, F64]; inst), + IrsubImm => binary_op!(Sub::sub[arg, imm]; [I8, I16, I32, I64, F32, F64]; inst), + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }?; + frame.set(first_result(frame.function, inst), result); + Ok(Continue) + } + + Branch { + opcode, + args, + destination, + } => match opcode { + Brnz => { + let mut args = value_refs(frame.function, args); + let first = args.remove(0); + match frame.get(&first) { + DataValue::B(false) + | DataValue::I8(0) + | DataValue::I16(0) + | DataValue::I32(0) + | DataValue::I64(0) => Ok(Continue), + DataValue::B(true) + | DataValue::I8(_) + | DataValue::I16(_) + | DataValue::I32(_) + | DataValue::I64(_) => Ok(ContinueAt(*destination, args)), + _ => Err(Trap::InvalidType("boolean or integer".to_string(), args[0])), + } + } + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + InstructionData::Call { args, func_ref, .. } => { + // Find the function to call. + let func_name = function_name_of_func_ref(*func_ref, frame.function); + + // Call function. + let args = frame.get_all(args.as_slice(&frame.function.dfg.value_lists)); + let result = self.call_by_name(&func_name, &args)?; + + // Save results. + if let ControlFlow::Return(returned_values) = result { + let ssa_values = frame.function.dfg.inst_results(inst); + assert_eq!( + ssa_values.len(), + returned_values.len(), + "expected result length ({}) to match SSA values length ({}): {}", + returned_values.len(), + ssa_values.len(), + frame.function.dfg.display_inst(inst, None) + ); + frame.set_all(ssa_values, returned_values); + Ok(Continue) + } else { + Err(Trap::InvalidControlFlow(format!( + "did not return from: {}", + frame.function.dfg.display_inst(inst, None) + ))) + } + } + InstructionData::Jump { + opcode, + destination, + args, + } => match opcode { + Opcode::Fallthrough => { + Ok(ContinueAt(*destination, value_refs(frame.function, args))) + } + Opcode::Jump => Ok(ContinueAt(*destination, value_refs(frame.function, args))), + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + IntCompareImm { + opcode, + arg, + cond, + imm, + } => match opcode { + IcmpImm => { + let arg_value = match *frame.get(arg) { + DataValue::I8(i) => Ok(i as i64), + DataValue::I16(i) => Ok(i as i64), + DataValue::I32(i) => Ok(i as i64), + DataValue::I64(i) => Ok(i), + _ => Err(InvalidType("integer".to_string(), *arg)), + }?; + let imm_value = (*imm).into(); + let result = match cond { + IntCC::UnsignedLessThanOrEqual => arg_value <= imm_value, + IntCC::Equal => arg_value == imm_value, + _ => unimplemented!( + "interpreter does not support condition code yet: {}", + cond + ), + }; + let res = first_result(frame.function, inst); + frame.set(res, DataValue::B(result)); + Ok(Continue) + } + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + MultiAry { opcode, args } => match opcode { + Return => { + let rs: Vec = args + .as_slice(&frame.function.dfg.value_lists) + .iter() + .map(|r| frame.get(r).clone()) + .collect(); + Ok(ControlFlow::Return(rs)) + } + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + NullAry { opcode } => match opcode { + Nop => Ok(Continue), + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + UnaryImm { opcode, imm } => match opcode { + Iconst => { + let res = first_result(frame.function, inst); + let imm_value = DataValue::from_integer(*imm, type_of(res, frame.function))?; + frame.set(res, imm_value); + Ok(Continue) + } + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + UnaryBool { opcode, imm } => match opcode { + Bconst => { + let res = first_result(frame.function, inst); + frame.set(res, DataValue::B(*imm)); + Ok(Continue) + } + _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), + }, + + _ => unimplemented!("interpreter does not support instruction yet: {:?}", data), + } + } +} + +/// Return the first result of an instruction. +/// +/// This helper cushions the interpreter from changes to the [Function] API. +#[inline] +fn first_result(function: &Function, inst: Inst) -> ValueRef { + function.dfg.first_result(inst) +} + +/// Return a list of IR values as a vector. +/// +/// This helper cushions the interpreter from changes to the [Function] API. +#[inline] +fn value_refs(function: &Function, args: &ValueList) -> Vec { + args.as_slice(&function.dfg.value_lists).to_vec() +} + +/// Return the (external) function name of `func_ref` in a local `function`. Note that this may +/// be truncated. +/// +/// This helper cushions the interpreter from changes to the [Function] API. +#[inline] +fn function_name_of_func_ref(func_ref: FuncRef, function: &Function) -> String { + function + .dfg + .ext_funcs + .get(func_ref) + .expect("function to exist") + .name + .to_string() +} + +/// Helper for calculating the type of an IR value. TODO move to Frame? +#[inline] +fn type_of(value: ValueRef, function: &Function) -> Type { + function.dfg.value_type(value) +} + +#[cfg(test)] +mod tests { + use super::*; + use cranelift_reader::parse_functions; + + // Most interpreter tests should use the more ergonomic `test interpret` filetest but this + // unit test serves as a sanity check that the interpreter still works without all of the + // filetest infrastructure. + #[test] + fn sanity() { + let code = "function %test() -> b1 { + block0: + v0 = iconst.i32 40 + v1 = iadd_imm v0, 3 + v2 = irsub_imm v1, 1 + v3 = icmp_imm eq v2, 42 + return v3 + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = Environment::default(); + env.add(func.name.to_string(), func); + let interpreter = Interpreter::new(env); + let result = interpreter + .call_by_name("%test", &[]) + .unwrap() + .unwrap_return(); + + assert_eq!(result, vec![DataValue::B(true)]) + } +} diff --git a/cranelift/interpreter/src/lib.rs b/cranelift/interpreter/src/lib.rs new file mode 100644 index 0000000000..cf100607c6 --- /dev/null +++ b/cranelift/interpreter/src/lib.rs @@ -0,0 +1,7 @@ +//! Cranelift IR interpreter. +//! +//! This module is a project for interpreting Cranelift IR. + +pub mod environment; +pub mod frame; +pub mod interpreter;