1 use std::collections::HashMap;
2 
3 use crate::cdsl::instructions::InstructionPredicate;
4 use crate::cdsl::recipes::{EncodingRecipeBuilder, EncodingRecipeNumber, Recipes, Stack};
5 use crate::cdsl::regs::IsaRegs;
6 use crate::shared::Definitions as SharedDefinitions;
7 
8 /// An helper to create recipes and use them when defining the RISCV encodings.
9 pub(crate) struct RecipeGroup {
10     /// The actualy list of recipes explicitly created in this file.
11     pub recipes: Recipes,
12 
13     /// Provides fast lookup from a name to an encoding recipe.
14     name_to_recipe: HashMap<String, EncodingRecipeNumber>,
15 }
16 
17 impl RecipeGroup {
new() -> Self18     fn new() -> Self {
19         Self {
20             recipes: Recipes::new(),
21             name_to_recipe: HashMap::new(),
22         }
23     }
24 
push(&mut self, builder: EncodingRecipeBuilder)25     fn push(&mut self, builder: EncodingRecipeBuilder) {
26         assert!(
27             self.name_to_recipe.get(&builder.name).is_none(),
28             "riscv recipe '{}' created twice",
29             builder.name
30         );
31         let name = builder.name.clone();
32         let number = self.recipes.push(builder.build());
33         self.name_to_recipe.insert(name, number);
34     }
35 
by_name(&self, name: &str) -> EncodingRecipeNumber36     pub fn by_name(&self, name: &str) -> EncodingRecipeNumber {
37         *self
38             .name_to_recipe
39             .get(name)
40             .unwrap_or_else(|| panic!("unknown riscv recipe name {}", name))
41     }
42 
collect(self) -> Recipes43     pub fn collect(self) -> Recipes {
44         self.recipes
45     }
46 }
47 
define(shared_defs: &SharedDefinitions, regs: &IsaRegs) -> RecipeGroup48 pub(crate) fn define(shared_defs: &SharedDefinitions, regs: &IsaRegs) -> RecipeGroup {
49     let formats = &shared_defs.formats;
50 
51     // Register classes shorthands.
52     let gpr = regs.class_by_name("GPR");
53 
54     // Definitions.
55     let mut recipes = RecipeGroup::new();
56 
57     // R-type 32-bit instructions: These are mostly binary arithmetic instructions.
58     // The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8)
59     recipes.push(
60         EncodingRecipeBuilder::new("R", &formats.binary, 4)
61             .operands_in(vec![gpr, gpr])
62             .operands_out(vec![gpr])
63             .emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
64     );
65 
66     // R-type with an immediate shift amount instead of rs2.
67     recipes.push(
68         EncodingRecipeBuilder::new("Rshamt", &formats.binary_imm64, 4)
69             .operands_in(vec![gpr])
70             .operands_out(vec![gpr])
71             .emit("put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);"),
72     );
73 
74     // R-type encoding of an integer comparison.
75     recipes.push(
76         EncodingRecipeBuilder::new("Ricmp", &formats.int_compare, 4)
77             .operands_in(vec![gpr, gpr])
78             .operands_out(vec![gpr])
79             .emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
80     );
81 
82     recipes.push(
83         EncodingRecipeBuilder::new("Ii", &formats.binary_imm64, 4)
84             .operands_in(vec![gpr])
85             .operands_out(vec![gpr])
86             .inst_predicate(InstructionPredicate::new_is_signed_int(
87                 &*formats.binary_imm64,
88                 "imm",
89                 12,
90                 0,
91             ))
92             .emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"),
93     );
94 
95     // I-type instruction with a hardcoded %x0 rs1.
96     recipes.push(
97         EncodingRecipeBuilder::new("Iz", &formats.unary_imm, 4)
98             .operands_out(vec![gpr])
99             .inst_predicate(InstructionPredicate::new_is_signed_int(
100                 &formats.unary_imm,
101                 "imm",
102                 12,
103                 0,
104             ))
105             .emit("put_i(bits, 0, imm.into(), out_reg0, sink);"),
106     );
107 
108     // I-type encoding of an integer comparison.
109     recipes.push(
110         EncodingRecipeBuilder::new("Iicmp", &formats.int_compare_imm, 4)
111             .operands_in(vec![gpr])
112             .operands_out(vec![gpr])
113             .inst_predicate(InstructionPredicate::new_is_signed_int(
114                 &formats.int_compare_imm,
115                 "imm",
116                 12,
117                 0,
118             ))
119             .emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"),
120     );
121 
122     // I-type encoding for `jalr` as a return instruction. We won't use the immediate offset.  The
123     // variable return values are not encoded.
124     recipes.push(
125         EncodingRecipeBuilder::new("Iret", &formats.multiary, 4).emit(
126             r#"
127                     // Return instructions are always a jalr to %x1.
128                     // The return address is provided as a special-purpose link argument.
129                     put_i(
130                         bits,
131                         1, // rs1 = %x1
132                         0, // no offset.
133                         0, // rd = %x0: no address written.
134                         sink,
135                     );
136                 "#,
137         ),
138     );
139 
140     // I-type encoding for `jalr` as a call_indirect.
141     recipes.push(
142         EncodingRecipeBuilder::new("Icall", &formats.call_indirect, 4)
143             .operands_in(vec![gpr])
144             .emit(
145                 r#"
146                     // call_indirect instructions are jalr with rd=%x1.
147                     put_i(
148                         bits,
149                         in_reg0,
150                         0, // no offset.
151                         1, // rd = %x1: link register.
152                         sink,
153                     );
154                 "#,
155             ),
156     );
157 
158     // Copy of a GPR is implemented as addi x, 0.
159     recipes.push(
160         EncodingRecipeBuilder::new("Icopy", &formats.unary, 4)
161             .operands_in(vec![gpr])
162             .operands_out(vec![gpr])
163             .emit("put_i(bits, in_reg0, 0, out_reg0, sink);"),
164     );
165 
166     // Same for a GPR regmove.
167     recipes.push(
168         EncodingRecipeBuilder::new("Irmov", &formats.reg_move, 4)
169             .operands_in(vec![gpr])
170             .emit("put_i(bits, src, 0, dst, sink);"),
171     );
172 
173     // Same for copy-to-SSA -- GPR regmove.
174     recipes.push(
175         EncodingRecipeBuilder::new("copytossa", &formats.copy_to_ssa, 4)
176             // No operands_in to mention, because a source register is specified directly.
177             .operands_out(vec![gpr])
178             .emit("put_i(bits, src, 0, out_reg0, sink);"),
179     );
180 
181     // U-type instructions have a 20-bit immediate that targets bits 12-31.
182     recipes.push(
183         EncodingRecipeBuilder::new("U", &formats.unary_imm, 4)
184             .operands_out(vec![gpr])
185             .inst_predicate(InstructionPredicate::new_is_signed_int(
186                 &formats.unary_imm,
187                 "imm",
188                 32,
189                 12,
190             ))
191             .emit("put_u(bits, imm.into(), out_reg0, sink);"),
192     );
193 
194     // UJ-type unconditional branch instructions.
195     recipes.push(
196         EncodingRecipeBuilder::new("UJ", &formats.jump, 4)
197             .branch_range((0, 21))
198             .emit(
199                 r#"
200                     let dest = i64::from(func.offsets[destination]);
201                     let disp = dest - i64::from(sink.offset());
202                     put_uj(bits, disp, 0, sink);
203                 "#,
204             ),
205     );
206 
207     recipes.push(EncodingRecipeBuilder::new("UJcall", &formats.call, 4).emit(
208         r#"
209                     sink.reloc_external(func.srclocs[inst],
210                                         Reloc::RiscvCall,
211                                         &func.dfg.ext_funcs[func_ref].name,
212                                         0);
213                     // rd=%x1 is the standard link register.
214                     put_uj(bits, 0, 1, sink);
215                 "#,
216     ));
217 
218     // SB-type branch instructions.
219     recipes.push(
220         EncodingRecipeBuilder::new("SB", &formats.branch_icmp, 4)
221             .operands_in(vec![gpr, gpr])
222             .branch_range((0, 13))
223             .emit(
224                 r#"
225                     let dest = i64::from(func.offsets[destination]);
226                     let disp = dest - i64::from(sink.offset());
227                     put_sb(bits, disp, in_reg0, in_reg1, sink);
228                 "#,
229             ),
230     );
231 
232     // SB-type branch instruction with rs2 fixed to zero.
233     recipes.push(
234         EncodingRecipeBuilder::new("SBzero", &formats.branch, 4)
235             .operands_in(vec![gpr])
236             .branch_range((0, 13))
237             .emit(
238                 r#"
239                     let dest = i64::from(func.offsets[destination]);
240                     let disp = dest - i64::from(sink.offset());
241                     put_sb(bits, disp, in_reg0, 0, sink);
242                 "#,
243             ),
244     );
245 
246     // Spill of a GPR.
247     recipes.push(
248         EncodingRecipeBuilder::new("GPsp", &formats.unary, 4)
249             .operands_in(vec![gpr])
250             .operands_out(vec![Stack::new(gpr)])
251             .emit("unimplemented!();"),
252     );
253 
254     // Fill of a GPR.
255     recipes.push(
256         EncodingRecipeBuilder::new("GPfi", &formats.unary, 4)
257             .operands_in(vec![Stack::new(gpr)])
258             .operands_out(vec![gpr])
259             .emit("unimplemented!();"),
260     );
261 
262     // Stack-slot to same stack-slot copy, which is guaranteed to turn into a no-op.
263     recipes.push(
264         EncodingRecipeBuilder::new("stacknull", &formats.unary, 0)
265             .operands_in(vec![Stack::new(gpr)])
266             .operands_out(vec![Stack::new(gpr)])
267             .emit(""),
268     );
269 
270     // No-op fills, created by late-stage redundant-fill removal.
271     recipes.push(
272         EncodingRecipeBuilder::new("fillnull", &formats.unary, 0)
273             .operands_in(vec![Stack::new(gpr)])
274             .operands_out(vec![gpr])
275             .clobbers_flags(false)
276             .emit(""),
277     );
278 
279     recipes
280 }
281