[meta] Recipes and encodings descriptions for RiscV;
This commit is contained in:
383
cranelift/codegen/meta/src/isa/riscv/encodings.rs
Normal file
383
cranelift/codegen/meta/src/isa/riscv/encodings.rs
Normal file
@@ -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<InstSpec>, recipe: EncodingRecipeNumber, bits: u16) -> EncodingBuilder {
|
||||
EncodingBuilder::new(inst.into(), recipe, bits)
|
||||
}
|
||||
|
||||
pub struct PerCpuModeEncodings<'defs> {
|
||||
pub inst_pred_reg: InstructionPredicateRegistry,
|
||||
pub enc32: Vec<Encoding>,
|
||||
pub enc64: Vec<Encoding>,
|
||||
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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
267
cranelift/codegen/meta/src/isa/riscv/recipes.rs
Normal file
267
cranelift/codegen/meta/src/isa/riscv/recipes.rs
Normal file
@@ -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<String, EncodingRecipeNumber>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user