//! Generate binary emission code for each ISA. use cranelift_entity::EntityRef; use crate::error; use crate::srcgen::Formatter; use crate::cdsl::recipes::{EncodingRecipe, OperandConstraint, Recipes}; /// Generate code to handle a single recipe. /// /// - Unpack the instruction data, knowing the format. /// - Determine register locations for operands with register constraints. /// - Determine stack slot locations for operands with stack constraints. /// - Call hand-written code for the actual emission. fn gen_recipe(recipe: &EncodingRecipe, fmt: &mut Formatter) { let inst_format = &recipe.format; let num_value_ops = inst_format.num_value_operands; // TODO: Set want_args to true for only MultiAry instructions instead of all formats with value // list. let want_args = inst_format.has_value_list || recipe.operands_in.iter().any(|c| match c { OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true, OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false, }); assert!(!want_args || num_value_ops > 0 || inst_format.has_value_list); let want_outs = recipe.operands_out.iter().any(|c| match c { OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true, OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false, }); let is_regmove = ["RegMove", "RegSpill", "RegFill"].contains(&inst_format.name); // Unpack the instruction data. fmtln!(fmt, "if let InstructionData::{} {{", inst_format.name); fmt.indent(|fmt| { fmt.line("opcode,"); for f in &inst_format.imm_fields { fmtln!(fmt, "{},", f.member); } if want_args { if inst_format.has_value_list || num_value_ops > 1 { fmt.line("ref args,"); } else { fmt.line("arg,"); } } fmt.line(".."); fmt.outdented_line("} = *inst_data {"); // Pass recipe arguments in this order: inputs, imm_fields, outputs. let mut args = String::new(); if want_args && !is_regmove { if inst_format.has_value_list { fmt.line("let args = args.as_slice(&func.dfg.value_lists);"); } else if num_value_ops == 1 { fmt.line("let args = [arg];"); } args += &unwrap_values(&recipe.operands_in, "in", "args", fmt); } for f in &inst_format.imm_fields { args += &format!(", {}", f.member); } // Unwrap interesting output arguments. if want_outs { if recipe.operands_out.len() == 1 { fmt.line("let results = [func.dfg.first_result(inst)];") } else { fmt.line("let results = func.dfg.inst_results(inst);"); } args += &unwrap_values(&recipe.operands_out, "out", "results", fmt); } // Optimization: Only update the register diversion tracker for regmove instructions. if is_regmove { fmt.line("divert.apply(inst_data);") } match &recipe.emit { Some(emit) => { fmt.multi_line(emit); fmt.line("return;"); } None => { fmtln!( fmt, "return recipe_{}(func, inst, sink, bits{});", recipe.name.to_lowercase(), args ); } } }); fmt.line("}"); } /// Emit code that unwraps values living in registers or stack slots. /// /// :param args: Input or output constraints. /// :param prefix: Prefix to be used for the generated local variables. /// :param values: Name of slice containing the values to be unwrapped. /// :returns: Comma separated list of the generated variables fn unwrap_values( args: &[OperandConstraint], prefix: &str, values_slice: &str, fmt: &mut Formatter, ) -> String { let mut varlist = String::new(); for (i, cst) in args.iter().enumerate() { match cst { OperandConstraint::RegClass(_reg_class) => { let v = format!("{}_reg{}", prefix, i); varlist += &format!(", {}", v); fmtln!( fmt, "let {} = divert.reg({}[{}], &func.locations);", v, values_slice, i ); } OperandConstraint::Stack(stack) => { let v = format!("{}_stk{}", prefix, i); varlist += &format!(", {}", v); fmtln!(fmt, "let {} = StackRef::masked(", v); fmt.indent(|fmt| { fmtln!( fmt, "divert.stack({}[{}], &func.locations),", values_slice, i ); fmt.line(format!("{},", stack.stack_base_mask())); fmt.line("&func.stack_slots,"); }); fmt.line(").unwrap();"); } _ => {} } } varlist } fn gen_isa(isa_name: &str, recipes: &Recipes, fmt: &mut Formatter) { fmt.doc_comment(format!( "Emit binary machine code for `inst` for the {} ISA.", isa_name )); if recipes.is_empty() { fmt.line("pub fn emit_inst("); fmt.indent(|fmt| { fmt.line("func: &Function,"); fmt.line("inst: Inst,"); fmt.line("_divert: &mut RegDiversions,"); fmt.line("_sink: &mut CS,"); fmt.line("_isa: &dyn TargetIsa,"); }); fmt.line(") {"); fmt.indent(|fmt| { // No encoding recipes: Emit a stub. fmt.line("bad_encoding(func, inst)"); }); fmt.line("}"); return; } fmt.line("#[allow(unused_variables, unreachable_code)]"); fmt.line("pub fn emit_inst("); fmt.indent(|fmt| { fmt.line("func: &Function,"); fmt.line("inst: Inst,"); fmt.line("divert: &mut RegDiversions,"); fmt.line("sink: &mut CS,"); fmt.line("isa: &dyn TargetIsa,") }); fmt.line(") {"); fmt.indent(|fmt| { fmt.line("let encoding = func.encodings[inst];"); fmt.line("let bits = encoding.bits();"); fmt.line("let inst_data = &func.dfg[inst];"); fmt.line("match encoding.recipe() {"); fmt.indent(|fmt| { for (i, recipe) in recipes.iter() { fmt.comment(format!("Recipe {}", recipe.name)); fmtln!(fmt, "{} => {{", i.index()); fmt.indent(|fmt| { gen_recipe(recipe, fmt); }); fmt.line("}"); } fmt.line("_ => {},"); }); fmt.line("}"); // Allow for unencoded ghost instructions. The verifier will check details. fmt.line("if encoding.is_legal() {"); fmt.indent(|fmt| { fmt.line("bad_encoding(func, inst);"); }); fmt.line("}"); }); fmt.line("}"); } pub(crate) fn generate( isa_name: &str, recipes: &Recipes, binemit_filename: &str, out_dir: &str, ) -> Result<(), error::Error> { let mut fmt = Formatter::new(); gen_isa(isa_name, recipes, &mut fmt); fmt.update_file(binemit_filename, out_dir)?; Ok(()) }