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