From ca277422bb68090f8ac9fc21aa9d00f3ae9177dd Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Mon, 24 Jun 2019 16:46:59 +0200 Subject: [PATCH] [meta] Recipes and encodings descriptions for RiscV; --- .../codegen/meta/src/isa/riscv/encodings.rs | 383 ++++++++++++++++++ cranelift/codegen/meta/src/isa/riscv/mod.rs | 19 +- .../codegen/meta/src/isa/riscv/recipes.rs | 267 ++++++++++++ 3 files changed, 663 insertions(+), 6 deletions(-) create mode 100644 cranelift/codegen/meta/src/isa/riscv/encodings.rs create mode 100644 cranelift/codegen/meta/src/isa/riscv/recipes.rs diff --git a/cranelift/codegen/meta/src/isa/riscv/encodings.rs b/cranelift/codegen/meta/src/isa/riscv/encodings.rs new file mode 100644 index 0000000000..3f1c199bc0 --- /dev/null +++ b/cranelift/codegen/meta/src/isa/riscv/encodings.rs @@ -0,0 +1,383 @@ +use crate::cdsl::ast::{Apply, Expr, Literal, VarPool}; +use crate::cdsl::encodings::{Encoding, EncodingBuilder}; +use crate::cdsl::instructions::{ + BoundInstruction, InstSpec, InstructionPredicateNode, InstructionPredicateRegistry, +}; +use crate::cdsl::recipes::{EncodingRecipeNumber, Recipes}; +use crate::cdsl::settings::SettingGroup; + +use crate::shared::types::Bool::B1; +use crate::shared::types::Int::{I32, I64}; +use crate::shared::Definitions as SharedDefinitions; + +use super::recipes::RecipeGroup; + +fn enc(inst: impl Into, recipe: EncodingRecipeNumber, bits: u16) -> EncodingBuilder { + EncodingBuilder::new(inst.into(), recipe, bits) +} + +pub struct PerCpuModeEncodings<'defs> { + pub inst_pred_reg: InstructionPredicateRegistry, + pub enc32: Vec, + pub enc64: Vec, + recipes: &'defs Recipes, +} + +impl<'defs> PerCpuModeEncodings<'defs> { + fn new(recipes: &'defs Recipes) -> Self { + Self { + inst_pred_reg: InstructionPredicateRegistry::new(), + enc32: Vec::new(), + enc64: Vec::new(), + recipes, + } + } + fn add32(&mut self, encoding: EncodingBuilder) { + self.enc32 + .push(encoding.build(self.recipes, &mut self.inst_pred_reg)); + } + fn add64(&mut self, encoding: EncodingBuilder) { + self.enc64 + .push(encoding.build(self.recipes, &mut self.inst_pred_reg)); + } +} + +// The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit instructions have 11 as +// the two low bits, with bits 6:2 determining the base opcode. +// +// Encbits for the 32-bit recipes are opcode[6:2] | (funct3 << 5) | ... +// The functions below encode the encbits. + +fn load_bits(funct3: u16) -> u16 { + assert!(funct3 <= 0b111); + 0b00000 | (funct3 << 5) +} + +fn store_bits(funct3: u16) -> u16 { + assert!(funct3 <= 0b111); + 0b01000 | (funct3 << 5) +} + +fn branch_bits(funct3: u16) -> u16 { + assert!(funct3 <= 0b111); + 0b11000 | (funct3 << 5) +} + +fn jalr_bits() -> u16 { + // This was previously accepting an argument funct3 of 3 bits and used the following formula: + //0b11001 | (funct3 << 5) + 0b11001 +} + +fn jal_bits() -> u16 { + 0b11011 +} + +fn opimm_bits(funct3: u16, funct7: u16) -> u16 { + assert!(funct3 <= 0b111); + 0b00100 | (funct3 << 5) | (funct7 << 8) +} + +fn opimm32_bits(funct3: u16, funct7: u16) -> u16 { + assert!(funct3 <= 0b111); + 0b00110 | (funct3 << 5) | (funct7 << 8) +} + +fn op_bits(funct3: u16, funct7: u16) -> u16 { + assert!(funct3 <= 0b111); + assert!(funct7 <= 0b1111111); + 0b01100 | (funct3 << 5) | (funct7 << 8) +} + +fn op32_bits(funct3: u16, funct7: u16) -> u16 { + assert!(funct3 <= 0b111); + assert!(funct7 <= 0b1111111); + 0b01110 | (funct3 << 5) | (funct7 << 8) +} + +fn lui_bits() -> u16 { + 0b01101 +} + +pub fn define<'defs>( + shared_defs: &'defs SharedDefinitions, + isa_settings: &SettingGroup, + recipes: &'defs RecipeGroup, +) -> PerCpuModeEncodings<'defs> { + // Instructions shorthands. + let shared = &shared_defs.instructions; + + let band = shared.by_name("band"); + let band_imm = shared.by_name("band_imm"); + let bor = shared.by_name("bor"); + let bor_imm = shared.by_name("bor_imm"); + let br_icmp = shared.by_name("br_icmp"); + let brz = shared.by_name("brz"); + let brnz = shared.by_name("brnz"); + let bxor = shared.by_name("bxor"); + let bxor_imm = shared.by_name("bxor_imm"); + let call = shared.by_name("call"); + let call_indirect = shared.by_name("call_indirect"); + let copy = shared.by_name("copy"); + let fill = shared.by_name("fill"); + let iadd = shared.by_name("iadd"); + let iadd_imm = shared.by_name("iadd_imm"); + let iconst = shared.by_name("iconst"); + let icmp = shared.by_name("icmp"); + let icmp_imm = shared.by_name("icmp_imm"); + let imul = shared.by_name("imul"); + let ishl = shared.by_name("ishl"); + let ishl_imm = shared.by_name("ishl_imm"); + let isub = shared.by_name("isub"); + let jump = shared.by_name("jump"); + let regmove = shared.by_name("regmove"); + let spill = shared.by_name("spill"); + let sshr = shared.by_name("sshr"); + let sshr_imm = shared.by_name("sshr_imm"); + let ushr = shared.by_name("ushr"); + let ushr_imm = shared.by_name("ushr_imm"); + let return_ = shared.by_name("return"); + + // Recipes shorthands, prefixed with r_. + let r_icall = recipes.by_name("Icall"); + let r_icopy = recipes.by_name("Icopy"); + let r_ii = recipes.by_name("Ii"); + let r_iicmp = recipes.by_name("Iicmp"); + let r_iret = recipes.by_name("Iret"); + let r_irmov = recipes.by_name("Irmov"); + let r_iz = recipes.by_name("Iz"); + let r_gp_sp = recipes.by_name("GPsp"); + let r_gp_fi = recipes.by_name("GPfi"); + let r_r = recipes.by_name("R"); + let r_ricmp = recipes.by_name("Ricmp"); + let r_rshamt = recipes.by_name("Rshamt"); + let r_sb = recipes.by_name("SB"); + let r_sb_zero = recipes.by_name("SBzero"); + let r_u = recipes.by_name("U"); + let r_uj = recipes.by_name("UJ"); + let r_uj_call = recipes.by_name("UJcall"); + + // Predicates shorthands. + let use_m = isa_settings.predicate_by_name("use_m"); + + // Definitions. + let mut e = PerCpuModeEncodings::new(&recipes.recipes); + + // Basic arithmetic binary instructions are encoded in an R-type instruction. + for &(inst, inst_imm, f3, f7) in &[ + (iadd, Some(iadd_imm), 0b000, 0b0000000), + (isub, None, 0b000, 0b0100000), + (bxor, Some(bxor_imm), 0b100, 0b0000000), + (bor, Some(bor_imm), 0b110, 0b0000000), + (band, Some(band_imm), 0b111, 0b0000000), + ] { + e.add32(enc(inst.bind(I32), r_r, op_bits(f3, f7))); + e.add64(enc(inst.bind(I64), r_r, op_bits(f3, f7))); + + // Immediate versions for add/xor/or/and. + if let Some(inst_imm) = inst_imm { + e.add32(enc(inst_imm.bind(I32), r_ii, opimm_bits(f3, 0))); + e.add64(enc(inst_imm.bind(I64), r_ii, opimm_bits(f3, 0))); + } + } + + // 32-bit ops in RV64. + e.add64(enc(iadd.bind(I32), r_r, op32_bits(0b000, 0b0000000))); + e.add64(enc(isub.bind(I32), r_r, op32_bits(0b000, 0b0100000))); + // There are no andiw/oriw/xoriw variations. + e.add64(enc(iadd_imm.bind(I32), r_ii, opimm32_bits(0b000, 0))); + + // Use iadd_imm with %x0 to materialize constants. + e.add32(enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0))); + e.add64(enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0))); + e.add64(enc(iconst.bind(I64), r_iz, opimm_bits(0b0, 0))); + + // Dynamic shifts have the same masking semantics as the clif base instructions. + for &(inst, inst_imm, f3, f7) in &[ + (ishl, ishl_imm, 0b1, 0b0), + (ushr, ushr_imm, 0b101, 0b0), + (sshr, sshr_imm, 0b101, 0b100000), + ] { + e.add32(enc(inst.bind(I32).bind(I32), r_r, op_bits(f3, f7))); + e.add64(enc(inst.bind(I64).bind(I64), r_r, op_bits(f3, f7))); + e.add64(enc(inst.bind(I32).bind(I32), r_r, op32_bits(f3, f7))); + // Allow i32 shift amounts in 64-bit shifts. + e.add64(enc(inst.bind(I64).bind(I32), r_r, op_bits(f3, f7))); + e.add64(enc(inst.bind(I32).bind(I64), r_r, op32_bits(f3, f7))); + + // Immediate shifts. + e.add32(enc(inst_imm.bind(I32), r_rshamt, opimm_bits(f3, f7))); + e.add64(enc(inst_imm.bind(I64), r_rshamt, opimm_bits(f3, f7))); + e.add64(enc(inst_imm.bind(I32), r_rshamt, opimm32_bits(f3, f7))); + } + + // Signed and unsigned integer 'less than'. There are no 'w' variants for comparing 32-bit + // numbers in RV64. + { + let mut var_pool = VarPool::new(); + + // Helper that creates an instruction predicate for an instruction in the icmp family. + let mut icmp_instp = |bound_inst: &BoundInstruction, + intcc_field: &'static str| + -> InstructionPredicateNode { + let x = var_pool.create("x"); + let y = var_pool.create("y"); + let cc = + Literal::enumerator_for(shared_defs.operand_kinds.by_name("intcc"), intcc_field); + Apply::new( + bound_inst.clone().into(), + vec![Expr::Literal(cc), Expr::Var(x), Expr::Var(y)], + ) + .inst_predicate(&shared_defs.format_registry, &var_pool) + .unwrap() + }; + + let icmp_i32 = icmp.bind(I32); + let icmp_i64 = icmp.bind(I64); + e.add32( + enc(icmp_i32.clone(), r_ricmp, op_bits(0b010, 0b0000000)) + .inst_predicate(icmp_instp(&icmp_i32, "slt")), + ); + e.add64( + enc(icmp_i64.clone(), r_ricmp, op_bits(0b010, 0b0000000)) + .inst_predicate(icmp_instp(&icmp_i64, "slt")), + ); + + e.add32( + enc(icmp_i32.clone(), r_ricmp, op_bits(0b011, 0b0000000)) + .inst_predicate(icmp_instp(&icmp_i32, "ult")), + ); + e.add64( + enc(icmp_i64.clone(), r_ricmp, op_bits(0b011, 0b0000000)) + .inst_predicate(icmp_instp(&icmp_i64, "ult")), + ); + + // Immediate variants. + let icmp_i32 = icmp_imm.bind(I32); + let icmp_i64 = icmp_imm.bind(I64); + e.add32( + enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b010, 0)) + .inst_predicate(icmp_instp(&icmp_i32, "slt")), + ); + e.add64( + enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b010, 0)) + .inst_predicate(icmp_instp(&icmp_i64, "slt")), + ); + + e.add32( + enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b011, 0)) + .inst_predicate(icmp_instp(&icmp_i32, "ult")), + ); + e.add64( + enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b011, 0)) + .inst_predicate(icmp_instp(&icmp_i64, "ult")), + ); + } + + // Integer constants with the low 12 bits clear are materialized by lui. + e.add32(enc(iconst.bind(I32), r_u, lui_bits())); + e.add64(enc(iconst.bind(I32), r_u, lui_bits())); + e.add64(enc(iconst.bind(I64), r_u, lui_bits())); + + // "M" Standard Extension for Integer Multiplication and Division. + // Gated by the `use_m` flag. + e.add32(enc(imul.bind(I32), r_r, op_bits(0b000, 0b00000001)).isa_predicate(use_m)); + e.add64(enc(imul.bind(I64), r_r, op_bits(0b000, 0b00000001)).isa_predicate(use_m)); + e.add64(enc(imul.bind(I32), r_r, op32_bits(0b000, 0b00000001)).isa_predicate(use_m)); + + // Control flow. + + // Unconditional branches. + e.add32(enc(jump, r_uj, jal_bits())); + e.add64(enc(jump, r_uj, jal_bits())); + e.add32(enc(call, r_uj_call, jal_bits())); + e.add64(enc(call, r_uj_call, jal_bits())); + + // Conditional branches. + { + let mut var_pool = VarPool::new(); + + // Helper that creates an instruction predicate for an instruction in the icmp family. + let mut br_icmp_instp = |bound_inst: &BoundInstruction, + intcc_field: &'static str| + -> InstructionPredicateNode { + let x = var_pool.create("x"); + let y = var_pool.create("y"); + let dest = var_pool.create("dest"); + let args = var_pool.create("args"); + let cc = + Literal::enumerator_for(shared_defs.operand_kinds.by_name("intcc"), intcc_field); + Apply::new( + bound_inst.clone().into(), + vec![ + Expr::Literal(cc), + Expr::Var(x), + Expr::Var(y), + Expr::Var(dest), + Expr::Var(args), + ], + ) + .inst_predicate(&shared_defs.format_registry, &var_pool) + .unwrap() + }; + + let br_icmp_i32 = br_icmp.bind(I32); + let br_icmp_i64 = br_icmp.bind(I64); + for &(cond, f3) in &[ + ("eq", 0b000), + ("ne", 0b001), + ("slt", 0b100), + ("sge", 0b101), + ("ult", 0b110), + ("uge", 0b111), + ] { + e.add32( + enc(br_icmp_i32.clone(), r_sb, branch_bits(f3)) + .inst_predicate(br_icmp_instp(&br_icmp_i32, cond)), + ); + e.add64( + enc(br_icmp_i64.clone(), r_sb, branch_bits(f3)) + .inst_predicate(br_icmp_instp(&br_icmp_i64, cond)), + ); + } + } + + for &(inst, f3) in &[(brz, 0b000), (brnz, 0b001)] { + e.add32(enc(inst.bind(I32), r_sb_zero, branch_bits(f3))); + e.add64(enc(inst.bind(I64), r_sb_zero, branch_bits(f3))); + e.add32(enc(inst.bind(B1), r_sb_zero, branch_bits(f3))); + e.add64(enc(inst.bind(B1), r_sb_zero, branch_bits(f3))); + } + + // Returns are a special case of jalr_bits using %x1 to hold the return address. + // The return address is provided by a special-purpose `link` return value that + // is added by legalize_signature(). + e.add32(enc(return_, r_iret, jalr_bits())); + e.add64(enc(return_, r_iret, jalr_bits())); + e.add32(enc(call_indirect.bind(I32), r_icall, jalr_bits())); + e.add64(enc(call_indirect.bind(I64), r_icall, jalr_bits())); + + // Spill and fill. + e.add32(enc(spill.bind(I32), r_gp_sp, store_bits(0b010))); + e.add64(enc(spill.bind(I32), r_gp_sp, store_bits(0b010))); + e.add64(enc(spill.bind(I64), r_gp_sp, store_bits(0b011))); + e.add32(enc(fill.bind(I32), r_gp_fi, load_bits(0b010))); + e.add64(enc(fill.bind(I32), r_gp_fi, load_bits(0b010))); + e.add64(enc(fill.bind(I64), r_gp_fi, load_bits(0b011))); + + // Register copies. + e.add32(enc(copy.bind(I32), r_icopy, opimm_bits(0b000, 0))); + e.add64(enc(copy.bind(I64), r_icopy, opimm_bits(0b000, 0))); + e.add64(enc(copy.bind(I32), r_icopy, opimm32_bits(0b000, 0))); + + e.add32(enc(regmove.bind(I32), r_irmov, opimm_bits(0b000, 0))); + e.add64(enc(regmove.bind(I64), r_irmov, opimm_bits(0b000, 0))); + e.add64(enc(regmove.bind(I32), r_irmov, opimm32_bits(0b000, 0))); + + e.add32(enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0))); + e.add64(enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0))); + e.add32(enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0))); + e.add64(enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0))); + + e +} diff --git a/cranelift/codegen/meta/src/isa/riscv/mod.rs b/cranelift/codegen/meta/src/isa/riscv/mod.rs index 5bf9c05390..435b38eb34 100644 --- a/cranelift/codegen/meta/src/isa/riscv/mod.rs +++ b/cranelift/codegen/meta/src/isa/riscv/mod.rs @@ -1,7 +1,6 @@ use crate::cdsl::cpu_modes::CpuMode; -use crate::cdsl::instructions::{InstructionGroupBuilder, InstructionPredicateMap}; +use crate::cdsl::instructions::InstructionGroupBuilder; use crate::cdsl::isa::TargetIsa; -use crate::cdsl::recipes::Recipes; use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder}; use crate::cdsl::settings::{PredicateNode, SettingGroup, SettingGroupBuilder}; @@ -9,6 +8,9 @@ use crate::shared::types::Float::{F32, F64}; use crate::shared::types::Int::{I32, I64}; use crate::shared::Definitions as SharedDefinitions; +mod encodings; +mod recipes; + fn define_settings(shared: &SettingGroup) -> SettingGroup { let mut setting = SettingGroupBuilder::new("riscv"); @@ -114,12 +116,17 @@ pub fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa { rv_64.legalize_type(F32, expand); rv_64.legalize_type(F64, expand); + let recipes = recipes::define(shared_defs, ®s); + + let encodings = encodings::define(shared_defs, &settings, &recipes); + rv_32.set_encodings(encodings.enc32); + rv_64.set_encodings(encodings.enc64); + let encodings_predicates = encodings.inst_pred_reg.extract(); + + let recipes = recipes.collect(); + let cpu_modes = vec![rv_32, rv_64]; - let recipes = Recipes::new(); - - let encodings_predicates = InstructionPredicateMap::new(); - TargetIsa::new( "riscv", inst_group, diff --git a/cranelift/codegen/meta/src/isa/riscv/recipes.rs b/cranelift/codegen/meta/src/isa/riscv/recipes.rs new file mode 100644 index 0000000000..8309802b92 --- /dev/null +++ b/cranelift/codegen/meta/src/isa/riscv/recipes.rs @@ -0,0 +1,267 @@ +use std::collections::HashMap; + +use crate::cdsl::formats::FormatRegistry; +use crate::cdsl::instructions::InstructionPredicate; +use crate::cdsl::recipes::{EncodingRecipeBuilder, EncodingRecipeNumber, Recipes, Stack}; +use crate::cdsl::regs::IsaRegs; +use crate::shared::Definitions as SharedDefinitions; + +/// An helper to create recipes and use them when defining the RISCV encodings. +pub struct RecipeGroup<'formats> { + /// Memoized format registry, to pass it to the builders. + formats: &'formats FormatRegistry, + + /// The actualy list of recipes explicitly created in this file. + pub recipes: Recipes, + + /// Provides fast lookup from a name to an encoding recipe. + name_to_recipe: HashMap, +} + +impl<'formats> RecipeGroup<'formats> { + fn new(formats: &'formats FormatRegistry) -> Self { + Self { + formats, + recipes: Recipes::new(), + name_to_recipe: HashMap::new(), + } + } + + fn push(&mut self, builder: EncodingRecipeBuilder) { + assert!( + self.name_to_recipe.get(&builder.name).is_none(), + format!("riscv recipe '{}' created twice", builder.name) + ); + let name = builder.name.clone(); + let number = self.recipes.push(builder.build(self.formats)); + self.name_to_recipe.insert(name, number); + } + + pub fn by_name(&self, name: &str) -> EncodingRecipeNumber { + let number = *self + .name_to_recipe + .get(name) + .expect(&format!("unknown riscv recipe name {}", name)); + number + } + + pub fn collect(self) -> Recipes { + self.recipes + } +} + +pub fn define<'formats>( + shared_defs: &'formats SharedDefinitions, + regs: &IsaRegs, +) -> RecipeGroup<'formats> { + let formats = &shared_defs.format_registry; + + // Format shorthands. + let f_binary = formats.by_name("Binary"); + let f_binary_imm = formats.by_name("BinaryImm"); + let f_branch = formats.by_name("Branch"); + let f_branch_icmp = formats.by_name("BranchIcmp"); + let f_call = formats.by_name("Call"); + let f_call_indirect = formats.by_name("CallIndirect"); + let f_int_compare = formats.by_name("IntCompare"); + let f_int_compare_imm = formats.by_name("IntCompareImm"); + let f_jump = formats.by_name("Jump"); + let f_multiary = formats.by_name("MultiAry"); + let f_regmove = formats.by_name("RegMove"); + let f_unary = formats.by_name("Unary"); + let f_unary_imm = formats.by_name("UnaryImm"); + + // Register classes shorthands. + let gpr = regs.class_by_name("GPR"); + + // Definitions. + let mut recipes = RecipeGroup::new(&shared_defs.format_registry); + + // R-type 32-bit instructions: These are mostly binary arithmetic instructions. + // The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) + recipes.push( + EncodingRecipeBuilder::new("R", f_binary, 4) + .operands_in(vec![gpr, gpr]) + .operands_out(vec![gpr]) + .emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"), + ); + + // R-type with an immediate shift amount instead of rs2. + recipes.push( + EncodingRecipeBuilder::new("Rshamt", f_binary_imm, 4) + .operands_in(vec![gpr]) + .operands_out(vec![gpr]) + .emit("put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);"), + ); + + // R-type encoding of an integer comparison. + recipes.push( + EncodingRecipeBuilder::new("Ricmp", f_int_compare, 4) + .operands_in(vec![gpr, gpr]) + .operands_out(vec![gpr]) + .emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"), + ); + + let format = formats.get(f_binary_imm); + recipes.push( + EncodingRecipeBuilder::new("Ii", f_binary_imm, 4) + .operands_in(vec![gpr]) + .operands_out(vec![gpr]) + .inst_predicate(InstructionPredicate::new_is_signed_int( + format, "imm", 12, 0, + )) + .emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"), + ); + + // I-type instruction with a hardcoded %x0 rs1. + let format = formats.get(f_unary_imm); + recipes.push( + EncodingRecipeBuilder::new("Iz", f_unary_imm, 4) + .operands_out(vec![gpr]) + .inst_predicate(InstructionPredicate::new_is_signed_int( + format, "imm", 12, 0, + )) + .emit("put_i(bits, 0, imm.into(), out_reg0, sink);"), + ); + + // I-type encoding of an integer comparison. + let format = formats.get(f_int_compare_imm); + recipes.push( + EncodingRecipeBuilder::new("Iicmp", f_int_compare_imm, 4) + .operands_in(vec![gpr]) + .operands_out(vec![gpr]) + .inst_predicate(InstructionPredicate::new_is_signed_int( + format, "imm", 12, 0, + )) + .emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"), + ); + + // I-type encoding for `jalr` as a return instruction. We won't use the immediate offset. The + // variable return values are not encoded. + recipes.push(EncodingRecipeBuilder::new("Iret", f_multiary, 4).emit( + r#" + // Return instructions are always a jalr to %x1. + // The return address is provided as a special-purpose link argument. + put_i( + bits, + 1, // rs1 = %x1 + 0, // no offset. + 0, // rd = %x0: no address written. + sink, + ); + "#, + )); + + // I-type encoding for `jalr` as a call_indirect. + recipes.push( + EncodingRecipeBuilder::new("Icall", f_call_indirect, 4) + .operands_in(vec![gpr]) + .emit( + r#" + // call_indirect instructions are jalr with rd=%x1. + put_i( + bits, + in_reg0, + 0, // no offset. + 1, // rd = %x1: link register. + sink, + ); + "#, + ), + ); + + // Copy of a GPR is implemented as addi x, 0. + recipes.push( + EncodingRecipeBuilder::new("Icopy", f_unary, 4) + .operands_in(vec![gpr]) + .operands_out(vec![gpr]) + .emit("put_i(bits, in_reg0, 0, out_reg0, sink);"), + ); + + // Same for a GPR regmove. + recipes.push( + EncodingRecipeBuilder::new("Irmov", f_regmove, 4) + .operands_in(vec![gpr]) + .emit("put_i(bits, src, 0, dst, sink);"), + ); + + // U-type instructions have a 20-bit immediate that targets bits 12-31. + let format = formats.get(f_unary_imm); + recipes.push( + EncodingRecipeBuilder::new("U", f_unary_imm, 4) + .operands_out(vec![gpr]) + .inst_predicate(InstructionPredicate::new_is_signed_int( + format, "imm", 32, 12, + )) + .emit("put_u(bits, imm.into(), out_reg0, sink);"), + ); + + // UJ-type unconditional branch instructions. + recipes.push( + EncodingRecipeBuilder::new("UJ", f_jump, 4) + .branch_range((0, 21)) + .emit( + r#" + let dest = i64::from(func.offsets[destination]); + let disp = dest - i64::from(sink.offset()); + put_uj(bits, disp, 0, sink); + "#, + ), + ); + + recipes.push(EncodingRecipeBuilder::new("UJcall", f_call, 4).emit( + r#" + sink.reloc_external(Reloc::RiscvCall, + &func.dfg.ext_funcs[func_ref].name, + 0); + // rd=%x1 is the standard link register. + put_uj(bits, 0, 1, sink); + "#, + )); + + // SB-type branch instructions. + recipes.push( + EncodingRecipeBuilder::new("SB", f_branch_icmp, 4) + .operands_in(vec![gpr, gpr]) + .branch_range((0, 13)) + .emit( + r#" + let dest = i64::from(func.offsets[destination]); + let disp = dest - i64::from(sink.offset()); + put_sb(bits, disp, in_reg0, in_reg1, sink); + "#, + ), + ); + + // SB-type branch instruction with rs2 fixed to zero. + recipes.push( + EncodingRecipeBuilder::new("SBzero", f_branch, 4) + .operands_in(vec![gpr]) + .branch_range((0, 13)) + .emit( + r#" + let dest = i64::from(func.offsets[destination]); + let disp = dest - i64::from(sink.offset()); + put_sb(bits, disp, in_reg0, 0, sink); + "#, + ), + ); + + // Spill of a GPR. + recipes.push( + EncodingRecipeBuilder::new("GPsp", f_unary, 4) + .operands_in(vec![gpr]) + .operands_out(vec![Stack::new(gpr)]) + .emit("unimplemented!();"), + ); + + // Fill of a GPR. + recipes.push( + EncodingRecipeBuilder::new("GPfi", f_unary, 4) + .operands_in(vec![Stack::new(gpr)]) + .operands_out(vec![gpr]) + .emit("unimplemented!();"), + ); + + recipes +}