1
1
//! Generate the Cranelift-specific integration of the x64 assembler.
2
2
3
3
use cranelift_assembler_x64_meta:: dsl:: {
4
- Format , Inst , Mutability , Operand , OperandKind , format :: RegClass ,
4
+ Format , Inst , Location , Mutability , Operand , OperandKind , RegClass ,
5
5
} ;
6
6
use cranelift_srcgen:: { Formatter , fmtln} ;
7
7
@@ -89,10 +89,8 @@ pub fn rust_convert_isle_to_assembler(op: &Operand) -> String {
89
89
/// This function panics if the instruction has no operands.
90
90
pub fn generate_macro_inst_fn ( f : & mut Formatter , inst : & Inst ) {
91
91
let struct_name = inst. name ( ) ;
92
- let operands = inst. format . operands . iter ( ) . collect :: < Vec < _ > > ( ) ;
93
- let results = inst
94
- . format
95
- . operands
92
+ let operands = inst. format . operands . iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ;
93
+ let results = operands
96
94
. iter ( )
97
95
. filter ( |o| o. mutability . is_write ( ) )
98
96
. collect :: < Vec < _ > > ( ) ;
@@ -105,6 +103,7 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
105
103
f. add_block (
106
104
& format ! ( "fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs" ) ,
107
105
|f| {
106
+ f. comment ( "Convert ISLE types to assembler types." ) ;
108
107
for op in operands. iter ( ) {
109
108
let loc = op. location ;
110
109
let cvt = rust_convert_isle_to_assembler ( op) ;
@@ -115,60 +114,69 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
115
114
. map ( |o| format ! ( "{}.clone()" , o. location) )
116
115
. collect :: < Vec < _ > > ( ) ;
117
116
let args = args. join ( ", " ) ;
117
+ f. empty_line ( ) ;
118
+
119
+ f. comment ( "Build the instruction." ) ;
118
120
fmtln ! (
119
121
f,
120
122
"let inst = {ASM}::inst::{struct_name}::new({args}).into();"
121
123
) ;
122
124
fmtln ! ( f, "let inst = MInst::External {{ inst }};" ) ;
125
+ f. empty_line ( ) ;
123
126
124
- use cranelift_assembler_x64_meta:: dsl:: Mutability :: * ;
127
+ // When an instruction writes to an operand, Cranelift expects a
128
+ // returned value to use in other instructions: we return this
129
+ // information in the `AssemblerOutputs` struct defined in ISLE
130
+ // (below). The general rule here is that memory stores will create
131
+ // a `SideEffect` whereas for write or read-write registers we will
132
+ // return some form of `Ret*`.
133
+ f. comment ( "Return a type ISLE can work with." ) ;
134
+ let access_reg = |op : & Operand | match op. mutability {
135
+ Mutability :: Read => unreachable ! ( ) ,
136
+ Mutability :: Write => "to_reg()" ,
137
+ Mutability :: ReadWrite => "write.to_reg()" ,
138
+ } ;
139
+ let ty_var_of_reg = |loc : Location | {
140
+ let ty = loc. reg_class ( ) . unwrap ( ) . to_string ( ) ;
141
+ let var = ty. to_lowercase ( ) ;
142
+ ( ty, var)
143
+ } ;
125
144
match results. as_slice ( ) {
126
145
[ ] => fmtln ! ( f, "SideEffectNoResult::Inst(inst)" ) ,
127
- [ one] => match one. mutability {
128
- Read => unreachable ! ( ) ,
129
- Write => match one. location . kind ( ) {
130
- // One write-only register output? Output the
131
- // instruction and that register.
132
- OperandKind :: Reg ( r) => {
133
- let ty = r. reg_class ( ) . unwrap ( ) . to_string ( ) ;
134
- let var = ty. to_lowercase ( ) ;
135
- fmtln ! ( f, "let {var} = {r}.as_ref().to_reg();" ) ;
136
- fmtln ! ( f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}" ) ;
137
- }
138
- _ => unimplemented ! ( ) ,
139
- } ,
140
- ReadWrite => match one. location . kind ( ) {
141
- OperandKind :: Imm ( _) => unreachable ! ( ) ,
142
- // One read/write register output? Output the instruction
143
- // and that register.
144
- OperandKind :: Reg ( r) | OperandKind :: FixedReg ( r) => {
145
- let ty = r. reg_class ( ) . unwrap ( ) . to_string ( ) ;
146
- let var = ty. to_lowercase ( ) ;
147
- fmtln ! ( f, "let {var} = {r}.as_ref().write.to_reg();" , ) ;
148
- fmtln ! ( f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}" ) ;
149
- }
150
- // One read/write memory operand? Output a side effect.
151
- OperandKind :: Mem ( _) => {
152
- fmtln ! ( f, "AssemblerOutputs::SideEffect {{ inst }}" )
153
- }
154
- // One read/write regmem output? We need to output
155
- // everything and it'll internally disambiguate which was
156
- // emitted (e.g. the mem variant or the register variant).
157
- OperandKind :: RegMem ( rm) => {
158
- assert_eq ! ( results. len( ) , 1 ) ;
159
- let ty = rm. reg_class ( ) . unwrap ( ) . to_string ( ) ;
160
- let var = ty. to_lowercase ( ) ;
161
- f. add_block ( & format ! ( "match {rm}" ) , |f| {
162
- f. add_block ( & format ! ( "asm::{ty}Mem::{ty}(reg) => " ) , |f| {
163
- fmtln ! ( f, "let {var} = reg.write.to_reg();" ) ;
164
- fmtln ! ( f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} " ) ;
165
- } ) ;
166
- f. add_block ( & format ! ( "asm::{ty}Mem::Mem(_) => " ) , |f| {
167
- fmtln ! ( f, "AssemblerOutputs::SideEffect {{ inst }} " ) ;
168
- } ) ;
146
+ [ op] => match op. location . kind ( ) {
147
+ OperandKind :: Imm ( _) => unreachable ! ( ) ,
148
+ OperandKind :: Reg ( r) | OperandKind :: FixedReg ( r) => {
149
+ let ( ty, var) = ty_var_of_reg ( r) ;
150
+ fmtln ! ( f, "let {var} = {r}.as_ref().{};" , access_reg( op) ) ;
151
+ fmtln ! ( f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}" ) ;
152
+ }
153
+ OperandKind :: Mem ( _) => {
154
+ fmtln ! ( f, "AssemblerOutputs::SideEffect {{ inst }}" )
155
+ }
156
+ OperandKind :: RegMem ( rm) => {
157
+ let ( ty, var) = ty_var_of_reg ( rm) ;
158
+ f. add_block ( & format ! ( "match {rm}" ) , |f| {
159
+ f. add_block ( & format ! ( "{ASM}::{ty}Mem::{ty}(reg) => " ) , |f| {
160
+ fmtln ! ( f, "let {var} = reg.{};" , access_reg( op) ) ;
161
+ fmtln ! ( f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} " ) ;
162
+ } ) ;
163
+ f. add_block ( & format ! ( "{ASM}::{ty}Mem::Mem(_) => " ) , |f| {
164
+ fmtln ! ( f, "AssemblerOutputs::SideEffect {{ inst }} " ) ;
169
165
} ) ;
170
- }
171
- } ,
166
+ } ) ;
167
+ }
168
+ } ,
169
+ // For now, we assume that if there are two results, they are
170
+ // coming from a register-writing instruction like `mul`. The
171
+ // `match` below can be expanded as needed.
172
+ [ op1, op2] => match ( op1. location . kind ( ) , op2. location . kind ( ) ) {
173
+ ( OperandKind :: FixedReg ( loc1) , OperandKind :: FixedReg ( loc2) ) => {
174
+ fmtln ! ( f, "let one = {loc1}.as_ref().{}.to_reg();" , access_reg( op1) ) ;
175
+ fmtln ! ( f, "let two = {loc2}.as_ref().{}.to_reg();" , access_reg( op2) ) ;
176
+ fmtln ! ( f, "let regs = ValueRegs::two(one, two);" ) ;
177
+ fmtln ! ( f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}" ) ;
178
+ }
179
+ _ => unimplemented ! ( "unhandled results: {results:?}" ) ,
172
180
} ,
173
181
_ => panic ! ( "instruction has more than one result" ) ,
174
182
}
@@ -234,13 +242,16 @@ pub enum IsleConstructor {
234
242
/// a result in memory, however.
235
243
RetMemorySideEffect ,
236
244
237
- /// This constructor produces a `Gpr` value, meaning that it will write the
238
- /// result to a `Gpr` .
245
+ /// This constructor produces a `Gpr` value, meaning that the instruction
246
+ /// will write its result to a single GPR register .
239
247
RetGpr ,
240
248
241
- /// This constructor produces an `Xmm` value, meaning that it will write the
242
- /// result to an `Xmm`.
249
+ /// This is similar to `RetGpr`, but for XMM registers.
243
250
RetXmm ,
251
+
252
+ /// This "special" constructor captures multiple written-to registers (e.g.
253
+ /// `mul`).
254
+ RetValueRegs ,
244
255
}
245
256
246
257
impl IsleConstructor {
@@ -250,6 +261,7 @@ impl IsleConstructor {
250
261
IsleConstructor :: RetMemorySideEffect => "SideEffectNoResult" ,
251
262
IsleConstructor :: RetGpr => "Gpr" ,
252
263
IsleConstructor :: RetXmm => "Xmm" ,
264
+ IsleConstructor :: RetValueRegs => "ValueRegs" ,
253
265
}
254
266
}
255
267
@@ -260,15 +272,15 @@ impl IsleConstructor {
260
272
IsleConstructor :: RetMemorySideEffect => "defer_side_effect" ,
261
273
IsleConstructor :: RetGpr => "emit_ret_gpr" ,
262
274
IsleConstructor :: RetXmm => "emit_ret_xmm" ,
275
+ IsleConstructor :: RetValueRegs => "emit_ret_value_regs" ,
263
276
}
264
277
}
265
278
266
279
/// Returns the suffix used in the ISLE constructor name.
267
280
pub fn suffix ( & self ) -> & ' static str {
268
281
match self {
269
282
IsleConstructor :: RetMemorySideEffect => "_mem" ,
270
- IsleConstructor :: RetGpr => "" ,
271
- IsleConstructor :: RetXmm => "" ,
283
+ IsleConstructor :: RetGpr | IsleConstructor :: RetXmm | IsleConstructor :: RetValueRegs => "" ,
272
284
}
273
285
}
274
286
}
@@ -285,6 +297,7 @@ pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
285
297
IsleConstructor :: RetMemorySideEffect => "Amode" . to_string ( ) ,
286
298
IsleConstructor :: RetGpr => "Gpr" . to_string ( ) ,
287
299
IsleConstructor :: RetXmm => "Xmm" . to_string ( ) ,
300
+ IsleConstructor :: RetValueRegs => "ValueRegs" . to_string ( ) ,
288
301
} ,
289
302
290
303
// everything else is the same as the "raw" variant
@@ -336,6 +349,14 @@ pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
336
349
} ,
337
350
} ,
338
351
} ,
352
+ [ one, two] => {
353
+ // For now, we assume that if there are two results, they are coming
354
+ // from a register-writing instruction like `mul`. This can be
355
+ // expanded as needed.
356
+ assert ! ( matches!( one. location. kind( ) , FixedReg ( _) ) ) ;
357
+ assert ! ( matches!( two. location. kind( ) , FixedReg ( _) ) ) ;
358
+ vec ! [ IsleConstructor :: RetValueRegs ]
359
+ }
339
360
other => panic ! ( "unsupported number of write operands {other:?}" ) ,
340
361
}
341
362
}
@@ -435,8 +456,8 @@ pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
435
456
fmtln ! ( f, " ;; Used for instructions that return an" ) ;
436
457
fmtln ! ( f, " ;; XMM register." ) ;
437
458
fmtln ! ( f, " (RetXmm (inst MInst) (xmm Xmm))" ) ;
438
- fmtln ! ( f, " ;; TODO: eventually add more variants for " ) ;
439
- fmtln ! ( f, " ;; multi-return, XMM, etc.; see " ) ;
459
+ fmtln ! ( f, " ;; Used for multi-return instructions. " ) ;
460
+ fmtln ! ( f, " (RetValueRegs (inst MInst) (regs ValueRegs)) " ) ;
440
461
fmtln ! (
441
462
f,
442
463
" ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
@@ -457,6 +478,16 @@ pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
457
478
fmtln ! ( f, " (let ((_ Unit (emit inst))) xmm))" ) ;
458
479
f. empty_line ( ) ;
459
480
481
+ fmtln ! ( f, ";; Directly emit instructions that return multiple" ) ;
482
+ fmtln ! ( f, ";; registers (e.g. `mul`)." ) ;
483
+ fmtln ! ( f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)" ) ;
484
+ fmtln ! (
485
+ f,
486
+ "(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))"
487
+ ) ;
488
+ fmtln ! ( f, " (let ((_ Unit (emit inst))) regs))" ) ;
489
+ f. empty_line ( ) ;
490
+
460
491
fmtln ! ( f, ";; Pass along the side-effecting instruction" ) ;
461
492
fmtln ! ( f, ";; for later emission." ) ;
462
493
fmtln ! (
0 commit comments