diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 3a5f554375..e9ecd9d982 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -56,7 +56,7 @@ impl ControlFlowGraph { // Flips to true when a terminating instruction is seen. So that if additional // instructions occur an error may be returned. for inst in func.layout.ebb_insts(ebb) { - match func[inst] { + match func.dfg[inst] { InstructionData::Branch { ty: _, opcode: _, ref data } => { cfg.add_predecessor(data.destination, (ebb, inst)); } @@ -109,9 +109,9 @@ mod tests { #[test] fn no_predecessors() { let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1 = func.make_ebb(); - let ebb2 = func.make_ebb(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); func.layout.append_ebb(ebb1); func.layout.append_ebb(ebb2); @@ -130,9 +130,9 @@ mod tests { #[test] fn branches_and_jumps() { let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1 = func.make_ebb(); - let ebb2 = func.make_ebb(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); func.layout.append_ebb(ebb1); func.layout.append_ebb(ebb2); diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index ac47a540fe..6f7075ae4d 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,18 +1,14 @@ //! Representation of Cretonne IL functions. -use types::{Type, FunctionName, Signature, VOID}; +use types::{FunctionName, Signature}; use entity_map::EntityRef; -use entities::{Ebb, NO_EBB, Inst, Value, NO_VALUE, ExpandedValue, StackSlot}; -use instructions::*; +use entities::{Ebb, NO_EBB, StackSlot}; +use dfg::DataFlowGraph; use layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Index, IndexMut}; +use std::ops::Index; /// A function. -/// -/// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a -/// container for those objects by implementing both `Index` and `Index`. -/// pub struct Function { /// Name of this function. Mostly used by `.cton` files. pub name: FunctionName, @@ -26,17 +22,8 @@ pub struct Function { /// Stack slots allocated in this function. stack_slots: Vec, - /// Data about all of the instructions in the function. The instructions in this vector is not - /// necessarily in program order. The `Inst` reference indexes into this vector. - instructions: Vec, - - /// Extended basic blocks in the function, not necessarily in program order. The `Ebb` - /// reference indexes into this vector. - extended_basic_blocks: Vec, - - /// Extended value table. Most `Value` references refer directly to their defining instruction. - /// Others index into this table. - extended_values: Vec, + /// Data flow graph containing the primary definition of all instructions, EBBs and values. + pub dfg: DataFlowGraph, /// Layout of EBBs and instructions in the function body. pub layout: Layout, @@ -50,9 +37,7 @@ impl Function { signature: sig, entry_block: NO_EBB, stack_slots: Vec::new(), - instructions: Vec::new(), - extended_basic_blocks: Vec::new(), - extended_values: Vec::new(), + dfg: DataFlowGraph::new(), layout: Layout::new(), } } @@ -83,175 +68,6 @@ impl Function { end: self.stack_slots.len(), } } - - // Instructions. - - /// Create a new instruction. - /// - /// The type of the first result is indicated by `data.ty`. If the instruction produces - /// multiple results, also call `make_inst_results` to allocate value table entries. - pub fn make_inst(&mut self, data: InstructionData) -> Inst { - let inst = Inst::new(self.instructions.len()); - self.instructions.push(data); - inst - } - - /// Create result values for an instruction that produces multiple results. - /// - /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If - /// the instruction may produce more than 1 result, call `make_inst_results` to allocate - /// `Value` table entries for the additional results. - /// - /// The result value types are determined from the instruction's value type constraints and the - /// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic - /// instructions, `ctrl_typevar` is ignored, and `VOID` can be used. - /// - /// The type of the first result value is also set, even if it was already set in the - /// `InstructionData` passed to `make_inst`. If this function is called with a single-result - /// instruction, that is the only effect. - /// - /// Returns the number of results produced by the instruction. - pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { - let constraints = self[inst].opcode().constraints(); - let fixed_results = constraints.fixed_results(); - - // Additional values form a linked list starting from the second result value. Generate - // the list backwards so we don't have to modify value table entries in place. (This - // causes additional result values to be numbered backwards which is not the aestetic - // choice, but since it is only visible in extremely rare instructions with 3+ results, - // we don't care). - let mut head = NO_VALUE; - let mut first_type = Type::default(); - - // TBD: Function call return values for direct and indirect function calls. - - if fixed_results > 0 { - for res_idx in (1..fixed_results).rev() { - head = self.make_value(ValueData::Def { - ty: constraints.result_type(res_idx, ctrl_typevar), - def: inst, - next: head, - }); - } - first_type = constraints.result_type(0, ctrl_typevar); - } - - // Update the second_result pointer in `inst`. - if head != NO_VALUE { - *self[inst] - .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head; - } - *self[inst].first_type_mut() = first_type; - - fixed_results - } - - /// Get the first result of an instruction. - /// - /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. - pub fn first_result(&self, inst: Inst) -> Value { - Value::new_direct(inst) - } - - /// Iterate through all the results of an instruction. - pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { - Values { - func: self, - cur: if self[inst].first_type() == VOID { - NO_VALUE - } else { - Value::new_direct(inst) - }, - } - } - - // Basic blocks - - /// Create a new basic block. - pub fn make_ebb(&mut self) -> Ebb { - let ebb = Ebb::new(self.extended_basic_blocks.len()); - self.extended_basic_blocks.push(EbbData::new()); - ebb - } - - /// Reference the representation of an EBB. - fn ebb(&self, ebb: Ebb) -> &EbbData { - &self.extended_basic_blocks[ebb.index()] - } - - /// Mutably reference the representation of an EBB. - fn ebb_mut(&mut self, ebb: Ebb) -> &mut EbbData { - &mut self.extended_basic_blocks[ebb.index()] - } - - /// Iterate over all the EBBs in order of creation. - pub fn ebbs_numerically(&self) -> NumericalEbbs { - NumericalEbbs { - cur: 0, - limit: self.extended_basic_blocks.len(), - } - } - - /// Append an argument with type `ty` to `ebb`. - pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { - let val = self.make_value(ValueData::Argument { - ty: ty, - ebb: ebb, - next: NO_VALUE, - }); - - let last_arg = self.ebb(ebb).last_arg; - match last_arg.expand() { - // If last_arg = NO_VALUE, we're adding the first EBB argument. - ExpandedValue::None => self.ebb_mut(ebb).first_arg = val, - ExpandedValue::Table(index) => { - // Append to linked list of arguments. - if let ValueData::Argument { ref mut next, .. } = self.extended_values[index] { - *next = val; - } else { - panic!("wrong type of extended value referenced by Ebb::last_arg"); - } - } - ExpandedValue::Direct(_) => panic!("Direct value cannot appear as EBB argument"), - } - self.ebb_mut(ebb).last_arg = val; - - val - } - - /// Iterate through the arguments to an EBB. - pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> { - Values { - func: self, - cur: self.ebb(ebb).first_arg, - } - } - - // Values. - - /// Allocate an extended value entry. - fn make_value(&mut self, data: ValueData) -> Value { - let vref = Value::new_table(self.extended_values.len()); - self.extended_values.push(data); - vref - } - - /// Get the type of a value. - pub fn value_type(&self, v: Value) -> Type { - use entities::ExpandedValue::*; - use self::ValueData::*; - match v.expand() { - Direct(i) => self[i].first_type(), - Table(i) => { - match self.extended_values[i] { - Def { ty, .. } => ty, - Argument { ty, .. } => ty, - } - } - None => panic!("NO_VALUE has no type"), - } - } } impl Debug for Function { @@ -316,197 +132,9 @@ impl Iterator for StackSlotIter { } } -// ====--------------------------------------------------------------------------------------====// -// -// Extended basic block implementation. -// -// ====--------------------------------------------------------------------------------------====// - -/// Contents of an extended basic block. -/// -/// Arguments for an extended basic block are values that dominate everything in the EBB. All -/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must -/// match the function arguments. -#[derive(Debug)] -pub struct EbbData { - /// First argument to this EBB, or `NO_VALUE` if the block has no arguments. - /// - /// The arguments are all ValueData::Argument entries that form a linked list from `first_arg` - /// to `last_arg`. - first_arg: Value, - - /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. - last_arg: Value, -} - -impl EbbData { - fn new() -> EbbData { - EbbData { - first_arg: NO_VALUE, - last_arg: NO_VALUE, - } - } -} - -impl Index for Function { - type Output = EbbData; - - fn index<'a>(&'a self, ebb: Ebb) -> &'a EbbData { - &self.extended_basic_blocks[ebb.index()] - } -} - -impl IndexMut for Function { - fn index_mut<'a>(&'a mut self, ebb: Ebb) -> &'a mut EbbData { - &mut self.extended_basic_blocks[ebb.index()] - } -} - -/// Iterate through all EBBs in a function in numerical order. -/// This order is stable, but has little significance to the semantics of the function. -pub struct NumericalEbbs { - cur: usize, - limit: usize, -} - -impl Iterator for NumericalEbbs { - type Item = Ebb; - - fn next(&mut self) -> Option { - if self.cur < self.limit { - let prev = Ebb::new(self.cur); - self.cur += 1; - Some(prev) - } else { - None - } - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Instruction implementation. -// -// The InstructionData layout is defined in the `instructions` module. -// -// ====--------------------------------------------------------------------------------------====// - -/// Allow immutable access to instructions via function indexing. -impl Index for Function { - type Output = InstructionData; - - fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { - &self.instructions[inst.index()] - } -} - -/// Allow mutable access to instructions via function indexing. -impl IndexMut for Function { - fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData { - &mut self.instructions[inst.index()] - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Value implementation. -// -// ====--------------------------------------------------------------------------------------====// - -// Most values are simply the first value produced by an instruction. -// Other values have an entry in the value table. -#[derive(Debug)] -enum ValueData { - // Value is defined by an instruction, but it is not the first result. - Def { - ty: Type, - def: Inst, - next: Value, // Next result defined by `def`. - }, - - // Value is an EBB argument. - Argument { - ty: Type, - ebb: Ebb, - next: Value, // Next argument to `ebb`. - }, -} - -/// Iterate through a list of related value references, such as: -/// -/// - All results defined by an instruction. -/// - All arguments to an EBB -/// -/// A value iterator borrows a Function reference. -pub struct Values<'a> { - func: &'a Function, - cur: Value, -} - -impl<'a> Iterator for Values<'a> { - type Item = Value; - - fn next(&mut self) -> Option { - let prev = self.cur; - - // Advance self.cur to the next value, or NO_VALUE. - self.cur = match prev.expand() { - ExpandedValue::Direct(inst) => self.func[inst].second_result().unwrap_or_default(), - ExpandedValue::Table(index) => { - match self.func.extended_values[index] { - ValueData::Def { next, .. } => next, - ValueData::Argument { next, .. } => next, - } - } - ExpandedValue::None => return None, - }; - - Some(prev) - } -} - #[cfg(test)] mod tests { use super::*; - use types; - use instructions::*; - - #[test] - fn make_inst() { - let mut func = Function::new(); - - let idata = InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }; - let inst = func.make_inst(idata); - assert_eq!(inst.to_string(), "inst0"); - - // Immutable reference resolution. - let ins = &func[inst]; - assert_eq!(ins.opcode(), Opcode::Iconst); - assert_eq!(ins.first_type(), types::I32); - - // Result iterator. - let mut res = func.inst_results(inst); - assert!(res.next().is_some()); - assert!(res.next().is_none()); - } - - #[test] - fn no_results() { - let mut func = Function::new(); - - let idata = InstructionData::Nullary { - opcode: Opcode::Trap, - ty: types::VOID, - }; - let inst = func.make_inst(idata); - - // Result iterator should be empty. - let mut res = func.inst_results(inst); - assert_eq!(res.next(), None); - } #[test] fn stack_slot() { @@ -520,62 +148,4 @@ mod tests { assert_eq!(func[ss0].size, 4); assert_eq!(func[ss1].size, 8); } - - #[test] - fn ebb() { - let mut func = Function::new(); - - assert_eq!(func.ebbs_numerically().next(), None); - - let ebb = func.make_ebb(); - assert_eq!(ebb.to_string(), "ebb0"); - assert_eq!(func.ebb_args(ebb).next(), None); - func.layout.append_ebb(ebb); - - let arg1 = func.append_ebb_arg(ebb, types::F32); - assert_eq!(arg1.to_string(), "vx0"); - { - let mut args1 = func.ebb_args(ebb); - assert_eq!(args1.next(), Some(arg1)); - assert_eq!(args1.next(), None); - } - let arg2 = func.append_ebb_arg(ebb, types::I16); - assert_eq!(arg2.to_string(), "vx1"); - { - let mut args2 = func.ebb_args(ebb); - assert_eq!(args2.next(), Some(arg1)); - assert_eq!(args2.next(), Some(arg2)); - assert_eq!(args2.next(), None); - } - - // The numerical ebb iterator doesn't capture the function. - let mut ebbs = func.ebbs_numerically(); - assert_eq!(ebbs.next(), Some(ebb)); - assert_eq!(ebbs.next(), None); - - assert_eq!(func.layout.ebb_insts(ebb).next(), None); - - let inst = func.make_inst(InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }); - func.layout.append_inst(inst, ebb); - { - let mut ii = func.layout.ebb_insts(ebb); - assert_eq!(ii.next(), Some(inst)); - assert_eq!(ii.next(), None); - } - - let inst2 = func.make_inst(InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }); - func.layout.append_inst(inst2, ebb); - { - let mut ii = func.layout.ebb_insts(ebb); - assert_eq!(ii.next(), Some(inst)); - assert_eq!(ii.next(), Some(inst2)); - assert_eq!(ii.next(), None); - } - } } diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index a12fb47191..2f45a09cba 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -6,7 +6,7 @@ use instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; use types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Jump { + func.dfg.make_inst(InstructionData::Jump { opcode: Opcode::Jump, ty: types::VOID, data: Box::new(JumpData { @@ -17,7 +17,7 @@ pub fn jump(func: &mut Function, dest: Ebb) -> Inst { } pub fn branch(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Branch { + func.dfg.make_inst(InstructionData::Branch { opcode: Opcode::Brz, ty: types::VOID, data: Box::new(BranchData { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index c43634a636..10181e0f63 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -85,7 +85,7 @@ fn write_preamble(w: &mut Write, func: &Function) -> io::Result { // ====--------------------------------------------------------------------------------------====// pub fn write_arg(w: &mut Write, func: &Function, arg: Value) -> Result { - write!(w, "{}: {}", arg, func.value_type(arg)) + write!(w, "{}: {}", arg, func.dfg.value_type(arg)) } pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { @@ -96,7 +96,7 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // ebb10(vx4: f64, vx5: b1): // - let mut args = func.ebb_args(ebb); + let mut args = func.dfg.ebb_args(ebb); match args.next() { None => return writeln!(w, "{}:", ebb), Some(arg) => { @@ -133,7 +133,7 @@ pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // if it can't be trivially inferred. // fn type_suffix(func: &Function, inst: Inst) -> Option { - let constraints = func[inst].opcode().constraints(); + let constraints = func.dfg[inst].opcode().constraints(); if !constraints.is_polymorphic() { return None; @@ -150,7 +150,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // This polymorphic instruction doesn't support basic type inference. // The controlling type variable is required to be the type of the first result. - let rtype = func.value_type(func.first_result(inst)); + let rtype = func.dfg.value_type(func.dfg.first_result(inst)); assert!(!rtype.is_void(), "Polymorphic instruction must produce a result"); Some(rtype) @@ -161,7 +161,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { // First write out the result values, if any. let mut has_results = false; - for r in func.inst_results(inst) { + for r in func.dfg.inst_results(inst) { if !has_results { has_results = true; try!(write!(w, "{}", r)); @@ -174,7 +174,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { } // Then the opcode, possibly with a '.type' suffix. - let opcode = func[inst].opcode(); + let opcode = func.dfg[inst].opcode(); match type_suffix(func, inst) { Some(suf) => try!(write!(w, "{}.{}", opcode, suf)), @@ -183,7 +183,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { // Then the operands, depending on format. use instructions::InstructionData::*; - match func[inst] { + match func.dfg[inst] { Nullary { .. } => writeln!(w, ""), Unary { arg, .. } => writeln!(w, " {}", arg), UnaryImm { imm, .. } => writeln!(w, " {}", imm), @@ -250,16 +250,16 @@ mod tests { assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n}\n"); - let ebb = f.make_ebb(); + let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); - f.append_ebb_arg(ebb, types::I8); + f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8):\n}\n"); - f.append_ebb_arg(ebb, types::F32.by(4).unwrap()); + f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8, vx1: f32x4):\n}\n"); } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 95eb91dfd2..b79ff16cc3 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -100,7 +100,7 @@ impl Context { // Allocate a new EBB and add a mapping src_ebb -> Ebb. fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { - let ebb = self.function.make_ebb(); + let ebb = self.function.dfg.make_ebb(); self.function.layout.append_ebb(ebb); if self.ebbs.insert(src_ebb, ebb).is_some() { err!(loc, "duplicate EBB: {}", src_ebb) @@ -163,7 +163,7 @@ impl Context { // Rewrite all EBB and value references in the function. fn rewrite_references(&mut self) -> Result<()> { for &(inst, loc) in &self.inst_locs { - match self.function[inst] { + match self.function.dfg[inst] { InstructionData::Nullary { .. } | InstructionData::UnaryImm { .. } | InstructionData::UnaryIeee32 { .. } | @@ -646,7 +646,7 @@ impl<'a> Parser<'a> { // ebb-arg ::= Value(vx) ":" * Type(t) let t = try!(self.match_type("expected EBB argument type")); // Allocate the EBB argument and add the mapping. - let value = ctx.function.append_ebb_arg(ebb, t); + let value = ctx.function.dfg.append_ebb_arg(ebb, t); ctx.add_value(vx, value, &vx_location) } @@ -703,8 +703,8 @@ impl<'a> Parser<'a> { // or function call signature. We also need to create values with the right type for all // the instruction results. let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)); - let inst = ctx.function.make_inst(inst_data); - let num_results = ctx.function.make_inst_results(inst, ctrl_typevar); + let inst = ctx.function.dfg.make_inst(inst_data); + let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); ctx.add_inst_loc(inst, &opcode_loc); @@ -720,7 +720,7 @@ impl<'a> Parser<'a> { // holds a reference to `ctx.function`. self.add_values(&mut ctx.values, results.into_iter(), - ctx.function.inst_results(inst)) + ctx.function.dfg.inst_results(inst)) } // Type inference for polymorphic instructions. @@ -750,7 +750,7 @@ impl<'a> Parser<'a> { // layout of the blocks. let ctrl_src_value = inst_data.typevar_operand() .expect("Constraints <-> Format inconsistency"); - ctx.function.value_type(match ctx.values.get(&ctrl_src_value) { + ctx.function.dfg.value_type(match ctx.values.get(&ctrl_src_value) { Some(&v) => v, None => { return err!(self.loc, @@ -1119,12 +1119,12 @@ mod tests { let mut ebbs = func.layout.ebbs(); let ebb0 = ebbs.next().unwrap(); - assert_eq!(func.ebb_args(ebb0).next(), None); + assert_eq!(func.dfg.ebb_args(ebb0).next(), None); let ebb4 = ebbs.next().unwrap(); - let mut ebb4_args = func.ebb_args(ebb4); + let mut ebb4_args = func.dfg.ebb_args(ebb4); let arg0 = ebb4_args.next().unwrap(); - assert_eq!(func.value_type(arg0), types::I32); + assert_eq!(func.dfg.value_type(arg0), types::I32); assert_eq!(ebb4_args.next(), None); } } diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 0709b358fd..1c7f0e7a19 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -110,14 +110,14 @@ impl CFGPrinter { let inst_data = func.layout .ebb_insts(ebb) .filter(|inst| { - match func[*inst] { + match func.dfg[*inst] { InstructionData::Branch { ty: _, opcode: _, data: _ } => true, InstructionData::Jump { ty: _, opcode: _, data: _ } => true, _ => false, } }) .map(|inst| { - let op = match func[inst] { + let op = match func.dfg[inst] { InstructionData::Branch { ty: _, opcode, ref data } => { Some((opcode, data.destination)) }