diff options
Diffstat (limited to 'third_party/rust/cranelift-codegen-meta/src/gen_inst.rs')
-rw-r--r-- | third_party/rust/cranelift-codegen-meta/src/gen_inst.rs | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/third_party/rust/cranelift-codegen-meta/src/gen_inst.rs b/third_party/rust/cranelift-codegen-meta/src/gen_inst.rs new file mode 100644 index 0000000000..a2760b34d7 --- /dev/null +++ b/third_party/rust/cranelift-codegen-meta/src/gen_inst.rs @@ -0,0 +1,1184 @@ +//! Generate instruction data (including opcodes, formats, builders, etc.). +use std::fmt; + +use cranelift_codegen_shared::constant_hash; +use cranelift_entity::EntityRef; + +use crate::cdsl::camel_case; +use crate::cdsl::formats::InstructionFormat; +use crate::cdsl::instructions::{AllInstructions, Instruction}; +use crate::cdsl::operands::Operand; +use crate::cdsl::typevar::{TypeSet, TypeVar}; + +use crate::error; +use crate::srcgen::{Formatter, Match}; +use crate::unique_table::{UniqueSeqTable, UniqueTable}; + +// TypeSet indexes are encoded in 8 bits, with `0xff` reserved. +const TYPESET_LIMIT: usize = 0xff; + +/// Generate an instruction format enumeration. +fn gen_formats(formats: &[&InstructionFormat], fmt: &mut Formatter) { + fmt.doc_comment( + r#" + An instruction format + + Every opcode has a corresponding instruction format + which is represented by both the `InstructionFormat` + and the `InstructionData` enums. + "#, + ); + fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug)]"); + fmt.line("pub enum InstructionFormat {"); + fmt.indent(|fmt| { + for format in formats { + fmt.doc_comment(format.to_string()); + fmtln!(fmt, "{},", format.name); + } + }); + fmt.line("}"); + fmt.empty_line(); + + // Emit a From<InstructionData> which also serves to verify that + // InstructionFormat and InstructionData are in sync. + fmt.line("impl<'a> From<&'a InstructionData> for InstructionFormat {"); + fmt.indent(|fmt| { + fmt.line("fn from(inst: &'a InstructionData) -> Self {"); + fmt.indent(|fmt| { + let mut m = Match::new("*inst"); + for format in formats { + m.arm( + format!("InstructionData::{}", format.name), + vec![".."], + format!("Self::{}", format.name), + ); + } + fmt.add_match(m); + }); + fmt.line("}"); + }); + fmt.line("}"); + fmt.empty_line(); +} + +/// Generate the InstructionData enum. +/// +/// Every variant must contain an `opcode` field. The size of `InstructionData` should be kept at +/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a +/// `ValueList` to store the additional information out of line. +fn gen_instruction_data(formats: &[&InstructionFormat], fmt: &mut Formatter) { + fmt.line("#[derive(Clone, Debug)]"); + fmt.line("#[allow(missing_docs)]"); + fmt.line("pub enum InstructionData {"); + fmt.indent(|fmt| { + for format in formats { + fmtln!(fmt, "{} {{", format.name); + fmt.indent(|fmt| { + fmt.line("opcode: Opcode,"); + if format.typevar_operand.is_some() { + if format.has_value_list { + fmt.line("args: ValueList,"); + } else if format.num_value_operands == 1 { + fmt.line("arg: Value,"); + } else { + fmtln!(fmt, "args: [Value; {}],", format.num_value_operands); + } + } + for field in &format.imm_fields { + fmtln!(fmt, "{}: {},", field.member, field.kind.rust_type); + } + }); + fmtln!(fmt, "},"); + } + }); + fmt.line("}"); +} + +fn gen_arguments_method(formats: &[&InstructionFormat], fmt: &mut Formatter, is_mut: bool) { + let (method, mut_, rslice, as_slice) = if is_mut { + ( + "arguments_mut", + "mut ", + "core::slice::from_mut", + "as_mut_slice", + ) + } else { + ("arguments", "", "core::slice::from_ref", "as_slice") + }; + + fmtln!( + fmt, + "pub fn {}<'a>(&'a {}self, pool: &'a {}ir::ValueListPool) -> &{}[Value] {{", + method, + mut_, + mut_, + mut_ + ); + fmt.indent(|fmt| { + let mut m = Match::new("*self"); + for format in formats { + let name = format!("Self::{}", format.name); + + // Formats with a value list put all of their arguments in the list. We don't split + // them up, just return it all as variable arguments. (I expect the distinction to go + // away). + if format.has_value_list { + m.arm( + name, + vec![format!("ref {}args", mut_), "..".to_string()], + format!("args.{}(pool)", as_slice), + ); + continue; + } + + // Fixed args. + let mut fields = Vec::new(); + let arg = if format.num_value_operands == 0 { + format!("&{}[]", mut_) + } else if format.num_value_operands == 1 { + fields.push(format!("ref {}arg", mut_)); + format!("{}(arg)", rslice) + } else { + let arg = format!("args_arity{}", format.num_value_operands); + fields.push(format!("args: ref {}{}", mut_, arg)); + arg + }; + fields.push("..".into()); + + m.arm(name, fields, arg); + } + fmt.add_match(m); + }); + fmtln!(fmt, "}"); +} + +/// Generate the boring parts of the InstructionData implementation. +/// +/// These methods in `impl InstructionData` can be generated automatically from the instruction +/// formats: +/// +/// - `pub fn opcode(&self) -> Opcode` +/// - `pub fn arguments(&self, &pool) -> &[Value]` +/// - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` +/// - `pub fn take_value_list(&mut self) -> Option<ir::ValueList>` +/// - `pub fn put_value_list(&mut self, args: ir::ValueList>` +/// - `pub fn eq(&self, &other: Self, &pool) -> bool` +/// - `pub fn hash<H: Hasher>(&self, state: &mut H, &pool)` +fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter) { + fmt.line("impl InstructionData {"); + fmt.indent(|fmt| { + fmt.doc_comment("Get the opcode of this instruction."); + fmt.line("pub fn opcode(&self) -> Opcode {"); + fmt.indent(|fmt| { + let mut m = Match::new("*self"); + for format in formats { + m.arm(format!("Self::{}", format.name), vec!["opcode", ".."], + "opcode".to_string()); + } + fmt.add_match(m); + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.doc_comment("Get the controlling type variable operand."); + fmt.line("pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> Option<Value> {"); + fmt.indent(|fmt| { + let mut m = Match::new("*self"); + for format in formats { + let name = format!("Self::{}", format.name); + if format.typevar_operand.is_none() { + m.arm(name, vec![".."], "None".to_string()); + } else if format.has_value_list { + // We keep all arguments in a value list. + m.arm(name, vec!["ref args", ".."], format!("args.get({}, pool)", format.typevar_operand.unwrap())); + } else if format.num_value_operands == 1 { + m.arm(name, vec!["arg", ".."], "Some(arg)".to_string()); + } else { + // We have multiple value operands and an array `args`. + // Which `args` index to use? + let args = format!("args_arity{}", format.num_value_operands); + m.arm(name, vec![format!("args: ref {}", args), "..".to_string()], + format!("Some({}[{}])", args, format.typevar_operand.unwrap())); + } + } + fmt.add_match(m); + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.doc_comment("Get the value arguments to this instruction."); + gen_arguments_method(formats, fmt, false); + fmt.empty_line(); + + fmt.doc_comment(r#"Get mutable references to the value arguments to this + instruction."#); + gen_arguments_method(formats, fmt, true); + fmt.empty_line(); + + fmt.doc_comment(r#" + Take out the value list with all the value arguments and return + it. + + This leaves the value list in the instruction empty. Use + `put_value_list` to put the value list back. + "#); + fmt.line("pub fn take_value_list(&mut self) -> Option<ir::ValueList> {"); + fmt.indent(|fmt| { + let mut m = Match::new("*self"); + + for format in formats { + if format.has_value_list { + m.arm(format!("Self::{}", format.name), + vec!["ref mut args", ".."], + "Some(args.take())".to_string()); + } + } + + m.arm_no_fields("_", "None"); + + fmt.add_match(m); + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.doc_comment(r#" + Put back a value list. + + After removing a value list with `take_value_list()`, use this + method to put it back. It is required that this instruction has + a format that accepts a value list, and that the existing value + list is empty. This avoids leaking list pool memory. + "#); + fmt.line("pub fn put_value_list(&mut self, vlist: ir::ValueList) {"); + fmt.indent(|fmt| { + fmt.line("let args = match *self {"); + fmt.indent(|fmt| { + for format in formats { + if format.has_value_list { + fmtln!(fmt, "Self::{} {{ ref mut args, .. }} => args,", format.name); + } + } + fmt.line("_ => panic!(\"No value list: {:?}\", self),"); + }); + fmt.line("};"); + fmt.line("debug_assert!(args.is_empty(), \"Value list already in use\");"); + fmt.line("*args = vlist;"); + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.doc_comment(r#" + Compare two `InstructionData` for equality. + + This operation requires a reference to a `ValueListPool` to + determine if the contents of any `ValueLists` are equal. + "#); + fmt.line("pub fn eq(&self, other: &Self, pool: &ir::ValueListPool) -> bool {"); + fmt.indent(|fmt| { + fmt.line("if ::core::mem::discriminant(self) != ::core::mem::discriminant(other) {"); + fmt.indent(|fmt| { + fmt.line("return false;"); + }); + fmt.line("}"); + + fmt.line("match (self, other) {"); + fmt.indent(|fmt| { + for format in formats { + let name = format!("&Self::{}", format.name); + let mut members = vec!["opcode"]; + + let args_eq = if format.typevar_operand.is_none() { + None + } else if format.has_value_list { + members.push("args"); + Some("args1.as_slice(pool) == args2.as_slice(pool)") + } else if format.num_value_operands == 1 { + members.push("arg"); + Some("arg1 == arg2") + } else { + members.push("args"); + Some("args1 == args2") + }; + + for field in &format.imm_fields { + members.push(field.member); + } + + let pat1 = members.iter().map(|x| format!("{}: ref {}1", x, x)).collect::<Vec<_>>().join(", "); + let pat2 = members.iter().map(|x| format!("{}: ref {}2", x, x)).collect::<Vec<_>>().join(", "); + fmtln!(fmt, "({} {{ {} }}, {} {{ {} }}) => {{", name, pat1, name, pat2); + fmt.indent(|fmt| { + fmt.line("opcode1 == opcode2"); + for field in &format.imm_fields { + fmtln!(fmt, "&& {}1 == {}2", field.member, field.member); + } + if let Some(args_eq) = args_eq { + fmtln!(fmt, "&& {}", args_eq); + } + }); + fmtln!(fmt, "}"); + } + fmt.line("_ => unreachable!()"); + }); + fmt.line("}"); + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.doc_comment(r#" + Hash an `InstructionData`. + + This operation requires a reference to a `ValueListPool` to + hash the contents of any `ValueLists`. + "#); + fmt.line("pub fn hash<H: ::core::hash::Hasher>(&self, state: &mut H, pool: &ir::ValueListPool) {"); + fmt.indent(|fmt| { + fmt.line("match *self {"); + fmt.indent(|fmt| { + for format in formats { + let name = format!("Self::{}", format.name); + let mut members = vec!["opcode"]; + + let args = if format.typevar_operand.is_none() { + "&()" + } else if format.has_value_list { + members.push("ref args"); + "args.as_slice(pool)" + } else if format.num_value_operands == 1 { + members.push("ref arg"); + "arg" + } else { + members.push("ref args"); + "args" + }; + + for field in &format.imm_fields { + members.push(field.member); + } + let members = members.join(", "); + + fmtln!(fmt, "{}{{{}}} => {{", name, members ); // beware the moustaches + fmt.indent(|fmt| { + fmt.line("::core::hash::Hash::hash( &::core::mem::discriminant(self), state);"); + fmt.line("::core::hash::Hash::hash(&opcode, state);"); + for field in &format.imm_fields { + fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", field.member); + } + fmtln!(fmt, "::core::hash::Hash::hash({}, state);", args); + }); + fmtln!(fmt, "}"); + } + }); + fmt.line("}"); + }); + fmt.line("}"); + }); + fmt.line("}"); +} + +fn gen_bool_accessor<T: Fn(&Instruction) -> bool>( + all_inst: &AllInstructions, + get_attr: T, + name: &'static str, + doc: &'static str, + fmt: &mut Formatter, +) { + fmt.doc_comment(doc); + fmtln!(fmt, "pub fn {}(self) -> bool {{", name); + fmt.indent(|fmt| { + let mut m = Match::new("self"); + for inst in all_inst.values() { + if get_attr(inst) { + m.arm_no_fields(format!("Self::{}", inst.camel_name), "true"); + } + } + m.arm_no_fields("_", "false"); + fmt.add_match(m); + }); + fmtln!(fmt, "}"); + fmt.empty_line(); +} + +fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) { + fmt.doc_comment( + r#" + An instruction opcode. + + All instructions from all supported ISAs are present. + "#, + ); + fmt.line("#[repr(u16)]"); + fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]"); + fmt.line( + r#"#[cfg_attr(feature = "enable-peepmatic", derive(serde::Serialize, serde::Deserialize))]"# + ); + + // We explicitly set the discriminant of the first variant to 1, which allows us to take + // advantage of the NonZero optimization, meaning that wrapping enums can use the 0 + // discriminant instead of increasing the size of the whole type, and so the size of + // Option<Opcode> is the same as Opcode's. + fmt.line("pub enum Opcode {"); + fmt.indent(|fmt| { + let mut is_first_opcode = true; + for inst in all_inst.values() { + fmt.doc_comment(format!("`{}`. ({})", inst, inst.format.name)); + + // Document polymorphism. + if let Some(poly) = &inst.polymorphic_info { + if poly.use_typevar_operand { + let op_num = inst.value_opnums[inst.format.typevar_operand.unwrap()]; + fmt.doc_comment(format!( + "Type inferred from `{}`.", + inst.operands_in[op_num].name + )); + } + } + + // Enum variant itself. + if is_first_opcode { + assert!(inst.opcode_number.index() == 0); + // TODO the python crate requires opcode numbers to start from one. + fmtln!(fmt, "{} = 1,", inst.camel_name); + is_first_opcode = false; + } else { + fmtln!(fmt, "{},", inst.camel_name) + } + } + }); + fmt.line("}"); + fmt.empty_line(); + + fmt.line("impl Opcode {"); + fmt.indent(|fmt| { + gen_bool_accessor( + all_inst, + |inst| inst.is_terminator, + "is_terminator", + "True for instructions that terminate the block", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.is_branch, + "is_branch", + "True for all branch or jump instructions.", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.is_indirect_branch, + "is_indirect_branch", + "True for all indirect branch or jump instructions.", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.is_call, + "is_call", + "Is this a call instruction?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.is_return, + "is_return", + "Is this a return instruction?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.is_ghost, + "is_ghost", + "Is this a ghost instruction?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.can_load, + "can_load", + "Can this instruction read from memory?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.can_store, + "can_store", + "Can this instruction write to memory?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.can_trap, + "can_trap", + "Can this instruction cause a trap?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.other_side_effects, + "other_side_effects", + "Does this instruction have other side effects besides can_* flags?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.writes_cpu_flags, + "writes_cpu_flags", + "Does this instruction write to CPU flags?", + fmt, + ); + gen_bool_accessor( + all_inst, + |inst| inst.clobbers_all_regs, + "clobbers_all_regs", + "Should this opcode be considered to clobber all the registers, during regalloc?", + fmt, + ); + }); + fmt.line("}"); + fmt.empty_line(); + + // Generate a private opcode_format table. + fmtln!( + fmt, + "const OPCODE_FORMAT: [InstructionFormat; {}] = [", + all_inst.len() + ); + fmt.indent(|fmt| { + for inst in all_inst.values() { + fmtln!( + fmt, + "InstructionFormat::{}, // {}", + inst.format.name, + inst.name + ); + } + }); + fmtln!(fmt, "];"); + fmt.empty_line(); + + // Generate a private opcode_name function. + fmt.line("fn opcode_name(opc: Opcode) -> &\'static str {"); + fmt.indent(|fmt| { + let mut m = Match::new("opc"); + for inst in all_inst.values() { + m.arm_no_fields( + format!("Opcode::{}", inst.camel_name), + format!("\"{}\"", inst.name), + ); + } + fmt.add_match(m); + }); + fmt.line("}"); + fmt.empty_line(); + + // Generate an opcode hash table for looking up opcodes by name. + let hash_table = constant_hash::generate_table(all_inst.values(), all_inst.len(), |inst| { + constant_hash::simple_hash(&inst.name) + }); + fmtln!( + fmt, + "const OPCODE_HASH_TABLE: [Option<Opcode>; {}] = [", + hash_table.len() + ); + fmt.indent(|fmt| { + for i in hash_table { + match i { + Some(i) => fmtln!(fmt, "Some(Opcode::{}),", i.camel_name), + None => fmtln!(fmt, "None,"), + } + } + }); + fmtln!(fmt, "];"); + fmt.empty_line(); +} + +fn gen_try_from(all_inst: &AllInstructions, fmt: &mut Formatter) { + fmt.line("impl core::convert::TryFrom<u16> for Opcode {"); + fmt.indent(|fmt| { + fmt.line("type Error = ();"); + fmt.line("#[inline]"); + fmt.line("fn try_from(x: u16) -> Result<Self, ()> {"); + fmt.indent(|fmt| { + fmtln!(fmt, "if 0 < x && x <= {} {{", all_inst.len()); + fmt.indent(|fmt| fmt.line("Ok(unsafe { core::mem::transmute(x) })")); + fmt.line("} else {"); + fmt.indent(|fmt| fmt.line("Err(())")); + fmt.line("}"); + }); + fmt.line("}"); + }); + fmt.line("}"); +} + +/// Get the value type constraint for an SSA value operand, where +/// `ctrl_typevar` is the controlling type variable. +/// +/// Each operand constraint is represented as a string, one of: +/// - `Concrete(vt)`, where `vt` is a value type name. +/// - `Free(idx)` where `idx` is an index into `type_sets`. +/// - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. +fn get_constraint<'entries, 'table>( + operand: &'entries Operand, + ctrl_typevar: Option<&TypeVar>, + type_sets: &'table mut UniqueTable<'entries, TypeSet>, +) -> String { + assert!(operand.is_value()); + let type_var = operand.type_var().unwrap(); + + if let Some(typ) = type_var.singleton_type() { + return format!("Concrete({})", typ.rust_name()); + } + + if let Some(free_typevar) = type_var.free_typevar() { + if ctrl_typevar.is_some() && free_typevar != *ctrl_typevar.unwrap() { + assert!(type_var.base.is_none()); + return format!("Free({})", type_sets.add(&type_var.get_raw_typeset())); + } + } + + if let Some(base) = &type_var.base { + assert!(base.type_var == *ctrl_typevar.unwrap()); + return camel_case(base.derived_func.name()); + } + + assert!(type_var == ctrl_typevar.unwrap()); + "Same".into() +} + +fn gen_bitset<'a, T: IntoIterator<Item = &'a u16>>( + iterable: T, + name: &'static str, + field_size: u8, + fmt: &mut Formatter, +) { + let bits = iterable.into_iter().fold(0, |acc, x| { + assert!(x.is_power_of_two()); + assert!(u32::from(*x) < (1 << u32::from(field_size))); + acc | x + }); + fmtln!(fmt, "{}: BitSet::<u{}>({}),", name, field_size, bits); +} + +fn iterable_to_string<I: fmt::Display, T: IntoIterator<Item = I>>(iterable: T) -> String { + let elems = iterable + .into_iter() + .map(|x| x.to_string()) + .collect::<Vec<_>>() + .join(", "); + format!("{{{}}}", elems) +} + +fn typeset_to_string(ts: &TypeSet) -> String { + let mut result = format!("TypeSet(lanes={}", iterable_to_string(&ts.lanes)); + if !ts.ints.is_empty() { + result += &format!(", ints={}", iterable_to_string(&ts.ints)); + } + if !ts.floats.is_empty() { + result += &format!(", floats={}", iterable_to_string(&ts.floats)); + } + if !ts.bools.is_empty() { + result += &format!(", bools={}", iterable_to_string(&ts.bools)); + } + if !ts.specials.is_empty() { + result += &format!(", specials=[{}]", iterable_to_string(&ts.specials)); + } + if !ts.refs.is_empty() { + result += &format!(", refs={}", iterable_to_string(&ts.refs)); + } + result += ")"; + result +} + +/// Generate the table of ValueTypeSets described by type_sets. +pub(crate) fn gen_typesets_table(type_sets: &UniqueTable<TypeSet>, fmt: &mut Formatter) { + if type_sets.len() == 0 { + return; + } + + fmt.comment("Table of value type sets."); + assert!(type_sets.len() <= TYPESET_LIMIT, "Too many type sets!"); + fmtln!( + fmt, + "const TYPE_SETS: [ir::instructions::ValueTypeSet; {}] = [", + type_sets.len() + ); + fmt.indent(|fmt| { + for ts in type_sets.iter() { + fmt.line("ir::instructions::ValueTypeSet {"); + fmt.indent(|fmt| { + fmt.comment(typeset_to_string(ts)); + gen_bitset(&ts.lanes, "lanes", 16, fmt); + gen_bitset(&ts.ints, "ints", 8, fmt); + gen_bitset(&ts.floats, "floats", 8, fmt); + gen_bitset(&ts.bools, "bools", 8, fmt); + gen_bitset(&ts.refs, "refs", 8, fmt); + }); + fmt.line("},"); + } + }); + fmtln!(fmt, "];"); +} + +/// Generate value type constraints for all instructions. +/// - Emit a compact constant table of ValueTypeSet objects. +/// - Emit a compact constant table of OperandConstraint objects. +/// - Emit an opcode-indexed table of instruction constraints. +fn gen_type_constraints(all_inst: &AllInstructions, fmt: &mut Formatter) { + // Table of TypeSet instances. + let mut type_sets = UniqueTable::new(); + + // Table of operand constraint sequences (as tuples). Each operand + // constraint is represented as a string, one of: + // - `Concrete(vt)`, where `vt` is a value type name. + // - `Free(idx)` where `idx` is an index into `type_sets`. + // - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. + let mut operand_seqs = UniqueSeqTable::new(); + + // Preload table with constraints for typical binops. + #[allow(clippy::useless_vec)] + operand_seqs.add(&vec!["Same".to_string(); 3]); + + fmt.comment("Table of opcode constraints."); + fmtln!( + fmt, + "const OPCODE_CONSTRAINTS: [OpcodeConstraints; {}] = [", + all_inst.len() + ); + fmt.indent(|fmt| { + for inst in all_inst.values() { + let (ctrl_typevar, ctrl_typeset) = if let Some(poly) = &inst.polymorphic_info { + let index = type_sets.add(&*poly.ctrl_typevar.get_raw_typeset()); + (Some(&poly.ctrl_typevar), index) + } else { + (None, TYPESET_LIMIT) + }; + + // Collect constraints for the value results, not including `variable_args` results + // which are always special cased. + let mut constraints = Vec::new(); + for &index in &inst.value_results { + constraints.push(get_constraint(&inst.operands_out[index], ctrl_typevar, &mut type_sets)); + } + for &index in &inst.value_opnums { + constraints.push(get_constraint(&inst.operands_in[index], ctrl_typevar, &mut type_sets)); + } + + let constraint_offset = operand_seqs.add(&constraints); + + let fixed_results = inst.value_results.len(); + let fixed_values = inst.value_opnums.len(); + + // Can the controlling type variable be inferred from the designated operand? + let use_typevar_operand = if let Some(poly) = &inst.polymorphic_info { + poly.use_typevar_operand + } else { + false + }; + + // Can the controlling type variable be inferred from the result? + let use_result = fixed_results > 0 && inst.operands_out[inst.value_results[0]].type_var() == ctrl_typevar; + + // Are we required to use the designated operand instead of the result? + let requires_typevar_operand = use_typevar_operand && !use_result; + + fmt.comment( + format!("{}: fixed_results={}, use_typevar_operand={}, requires_typevar_operand={}, fixed_values={}", + inst.camel_name, + fixed_results, + use_typevar_operand, + requires_typevar_operand, + fixed_values) + ); + fmt.comment(format!("Constraints=[{}]", constraints + .iter() + .map(|x| format!("'{}'", x)) + .collect::<Vec<_>>() + .join(", "))); + if let Some(poly) = &inst.polymorphic_info { + fmt.comment(format!("Polymorphic over {}", typeset_to_string(&poly.ctrl_typevar.get_raw_typeset()))); + } + + // Compute the bit field encoding, c.f. instructions.rs. + assert!(fixed_results < 8 && fixed_values < 8, "Bit field encoding too tight"); + let mut flags = fixed_results; // 3 bits + if use_typevar_operand { + flags |= 1<<3; // 4th bit + } + if requires_typevar_operand { + flags |= 1<<4; // 5th bit + } + flags |= fixed_values << 5; // 6th bit and more + + fmt.line("OpcodeConstraints {"); + fmt.indent(|fmt| { + fmtln!(fmt, "flags: {:#04x},", flags); + fmtln!(fmt, "typeset_offset: {},", ctrl_typeset); + fmtln!(fmt, "constraint_offset: {},", constraint_offset); + }); + fmt.line("},"); + } + }); + fmtln!(fmt, "];"); + fmt.empty_line(); + + gen_typesets_table(&type_sets, fmt); + fmt.empty_line(); + + fmt.comment("Table of operand constraint sequences."); + fmtln!( + fmt, + "const OPERAND_CONSTRAINTS: [OperandConstraint; {}] = [", + operand_seqs.len() + ); + fmt.indent(|fmt| { + for constraint in operand_seqs.iter() { + fmtln!(fmt, "OperandConstraint::{},", constraint); + } + }); + fmtln!(fmt, "];"); +} + +/// Emit member initializers for an instruction format. +fn gen_member_inits(format: &InstructionFormat, fmt: &mut Formatter) { + // Immediate operands. + // We have local variables with the same names as the members. + for f in &format.imm_fields { + fmtln!(fmt, "{},", f.member); + } + + // Value operands. + if format.has_value_list { + fmt.line("args,"); + } else if format.num_value_operands == 1 { + fmt.line("arg: arg0,"); + } else if format.num_value_operands > 1 { + let mut args = Vec::new(); + for i in 0..format.num_value_operands { + args.push(format!("arg{}", i)); + } + fmtln!(fmt, "args: [{}],", args.join(", ")); + } +} + +/// Emit a method for creating and inserting an instruction format. +/// +/// All instruction formats take an `opcode` argument and a `ctrl_typevar` argument for deducing +/// the result types. +fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) { + // Construct method arguments. + let mut args = vec![ + "self".to_string(), + "opcode: Opcode".into(), + "ctrl_typevar: Type".into(), + ]; + + // Normal operand arguments. Start with the immediate operands. + for f in &format.imm_fields { + args.push(format!("{}: {}", f.member, f.kind.rust_type)); + } + + // Then the value operands. + if format.has_value_list { + // Take all value arguments as a finished value list. The value lists + // are created by the individual instruction constructors. + args.push("args: ir::ValueList".into()); + } else { + // Take a fixed number of value operands. + for i in 0..format.num_value_operands { + args.push(format!("arg{}: Value", i)); + } + } + + let proto = format!( + "{}({}) -> (Inst, &'f mut ir::DataFlowGraph)", + format.name, + args.join(", ") + ); + + let imms_need_sign_extension = format + .imm_fields + .iter() + .any(|f| f.kind.rust_type == "ir::immediates::Imm64"); + + fmt.doc_comment(format.to_string()); + fmt.line("#[allow(non_snake_case)]"); + fmtln!(fmt, "fn {} {{", proto); + fmt.indent(|fmt| { + // Generate the instruction data. + fmtln!( + fmt, + "let{} data = ir::InstructionData::{} {{", + if imms_need_sign_extension { " mut" } else { "" }, + format.name + ); + fmt.indent(|fmt| { + fmt.line("opcode,"); + gen_member_inits(format, fmt); + }); + fmtln!(fmt, "};"); + + if imms_need_sign_extension { + fmtln!(fmt, "data.sign_extend_immediates(ctrl_typevar);"); + } + + fmt.line("self.build(data, ctrl_typevar)"); + }); + fmtln!(fmt, "}"); +} + +/// Emit a method for generating the instruction `inst`. +/// +/// The method will create and insert an instruction, then return the result values, or the +/// instruction reference itself for instructions that don't have results. +fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Formatter) { + // Construct method arguments. + let mut args = vec![if format.has_value_list { + "mut self" + } else { + "self" + } + .to_string()]; + + let mut args_doc = Vec::new(); + let mut rets_doc = Vec::new(); + + // The controlling type variable will be inferred from the input values if + // possible. Otherwise, it is the first method argument. + if let Some(poly) = &inst.polymorphic_info { + if !poly.use_typevar_operand { + args.push(format!("{}: crate::ir::Type", poly.ctrl_typevar.name)); + args_doc.push(format!( + "- {} (controlling type variable): {}", + poly.ctrl_typevar.name, poly.ctrl_typevar.doc + )); + } + } + + let mut tmpl_types = Vec::new(); + let mut into_args = Vec::new(); + for op in &inst.operands_in { + let t = if op.is_immediate() { + let t = format!("T{}", tmpl_types.len() + 1); + tmpl_types.push(format!("{}: Into<{}>", t, op.kind.rust_type)); + into_args.push(op.name); + t + } else { + op.kind.rust_type.to_string() + }; + args.push(format!("{}: {}", op.name, t)); + args_doc.push(format!( + "- {}: {}", + op.name, + op.doc() + .expect("every instruction's input operand must be documented") + )); + } + + for op in &inst.operands_out { + rets_doc.push(format!( + "- {}: {}", + op.name, + op.doc() + .expect("every instruction's output operand must be documented") + )); + } + + let rtype = match inst.value_results.len() { + 0 => "Inst".into(), + 1 => "Value".into(), + _ => format!("({})", vec!["Value"; inst.value_results.len()].join(", ")), + }; + + let tmpl = if !tmpl_types.is_empty() { + format!("<{}>", tmpl_types.join(", ")) + } else { + "".into() + }; + + let proto = format!( + "{}{}({}) -> {}", + inst.snake_name(), + tmpl, + args.join(", "), + rtype + ); + + fmt.doc_comment(&inst.doc); + if !args_doc.is_empty() { + fmt.line("///"); + fmt.doc_comment("Inputs:"); + fmt.line("///"); + for doc_line in args_doc { + fmt.doc_comment(doc_line); + } + } + if !rets_doc.is_empty() { + fmt.line("///"); + fmt.doc_comment("Outputs:"); + fmt.line("///"); + for doc_line in rets_doc { + fmt.doc_comment(doc_line); + } + } + + fmt.line("#[allow(non_snake_case)]"); + fmtln!(fmt, "fn {} {{", proto); + fmt.indent(|fmt| { + // Convert all of the `Into<>` arguments. + for arg in &into_args { + fmtln!(fmt, "let {} = {}.into();", arg, arg); + } + + // Arguments for instruction constructor. + let first_arg = format!("Opcode::{}", inst.camel_name); + let mut args = vec![first_arg.as_str()]; + if let Some(poly) = &inst.polymorphic_info { + if poly.use_typevar_operand { + // Infer the controlling type variable from the input operands. + let op_num = inst.value_opnums[format.typevar_operand.unwrap()]; + fmtln!( + fmt, + "let ctrl_typevar = self.data_flow_graph().value_type({});", + inst.operands_in[op_num].name + ); + + // The format constructor will resolve the result types from the type var. + args.push("ctrl_typevar"); + } else { + // This was an explicit method argument. + args.push(&poly.ctrl_typevar.name); + } + } else { + // No controlling type variable needed. + args.push("types::INVALID"); + } + + // Now add all of the immediate operands to the constructor arguments. + for &op_num in &inst.imm_opnums { + args.push(inst.operands_in[op_num].name); + } + + // Finally, the value operands. + if format.has_value_list { + // We need to build a value list with all the arguments. + fmt.line("let mut vlist = ir::ValueList::default();"); + args.push("vlist"); + fmt.line("{"); + fmt.indent(|fmt| { + fmt.line("let pool = &mut self.data_flow_graph_mut().value_lists;"); + for op in &inst.operands_in { + if op.is_value() { + fmtln!(fmt, "vlist.push({}, pool);", op.name); + } else if op.is_varargs() { + fmtln!(fmt, "vlist.extend({}.iter().cloned(), pool);", op.name); + } + } + }); + fmt.line("}"); + } else { + // With no value list, we're guaranteed to just have a set of fixed value operands. + for &op_num in &inst.value_opnums { + args.push(inst.operands_in[op_num].name); + } + } + + // Call to the format constructor, + let fcall = format!("self.{}({})", format.name, args.join(", ")); + + if inst.value_results.is_empty() { + fmtln!(fmt, "{}.0", fcall); + return; + } + + fmtln!(fmt, "let (inst, dfg) = {};", fcall); + if inst.value_results.len() == 1 { + fmt.line("dfg.first_result(inst)"); + } else { + fmtln!( + fmt, + "let results = &dfg.inst_results(inst)[0..{}];", + inst.value_results.len() + ); + fmtln!( + fmt, + "({})", + inst.value_results + .iter() + .enumerate() + .map(|(i, _)| format!("results[{}]", i)) + .collect::<Vec<_>>() + .join(", ") + ); + } + }); + fmtln!(fmt, "}") +} + +/// Generate a Builder trait with methods for all instructions. +fn gen_builder( + instructions: &AllInstructions, + formats: &[&InstructionFormat], + fmt: &mut Formatter, +) { + fmt.doc_comment( + r#" + Convenience methods for building instructions. + + The `InstBuilder` trait has one method per instruction opcode for + conveniently constructing the instruction with minimum arguments. + Polymorphic instructions infer their result types from the input + arguments when possible. In some cases, an explicit `ctrl_typevar` + argument is required. + + The opcode methods return the new instruction's result values, or + the `Inst` itself for instructions that don't have any results. + + There is also a method per instruction format. These methods all + return an `Inst`. + "#, + ); + fmt.line("pub trait InstBuilder<'f>: InstBuilderBase<'f> {"); + fmt.indent(|fmt| { + for inst in instructions.values() { + gen_inst_builder(inst, &*inst.format, fmt); + fmt.empty_line(); + } + for (i, format) in formats.iter().enumerate() { + gen_format_constructor(format, fmt); + if i + 1 != formats.len() { + fmt.empty_line(); + } + } + }); + fmt.line("}"); +} + +pub(crate) fn generate( + formats: Vec<&InstructionFormat>, + all_inst: &AllInstructions, + opcode_filename: &str, + inst_builder_filename: &str, + out_dir: &str, +) -> Result<(), error::Error> { + // Opcodes. + let mut fmt = Formatter::new(); + gen_formats(&formats, &mut fmt); + gen_instruction_data(&formats, &mut fmt); + fmt.empty_line(); + gen_instruction_data_impl(&formats, &mut fmt); + fmt.empty_line(); + gen_opcodes(all_inst, &mut fmt); + fmt.empty_line(); + gen_type_constraints(all_inst, &mut fmt); + fmt.empty_line(); + gen_try_from(all_inst, &mut fmt); + fmt.update_file(opcode_filename, out_dir)?; + + // Instruction builder. + let mut fmt = Formatter::new(); + gen_builder(all_inst, &formats, &mut fmt); + fmt.update_file(inst_builder_filename, out_dir)?; + + Ok(()) +} |