Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/isa/x64/inst/external.rs
1693 views
1
//! Interface with the external assembler crate.
2
3
use super::{
4
Amode, Gpr, Inst, LabelUse, MachBuffer, MachLabel, OperandVisitor, OperandVisitorImpl,
5
SyntheticAmode, VCodeConstant, WritableGpr, WritableXmm, Xmm, args::FromWritableReg,
6
};
7
use crate::{Reg, Writable, ir::TrapCode};
8
use cranelift_assembler_x64 as asm;
9
use regalloc2::{PReg, RegClass};
10
use std::string::String;
11
12
/// Define the types of registers Cranelift will use.
13
#[derive(Clone, Debug)]
14
pub struct CraneliftRegisters;
15
impl asm::Registers for CraneliftRegisters {
16
type ReadGpr = Gpr;
17
type ReadWriteGpr = PairedGpr;
18
type WriteGpr = WritableGpr;
19
type ReadXmm = Xmm;
20
type ReadWriteXmm = PairedXmm;
21
type WriteXmm = WritableXmm;
22
}
23
24
/// Convenience type alias of `asm::inst::Inst` with `R = CraneliftRegisters`
25
/// filled in.
26
pub type AsmInst = asm::inst::Inst<CraneliftRegisters>;
27
28
/// A pair of registers, one for reading and one for writing.
29
///
30
/// Due to how Cranelift's SSA form, we must track the read and write registers
31
/// separately prior to register allocation. Once register allocation is
32
/// complete, we expect the hardware encoding for both `read` and `write` to be
33
/// the same.
34
#[derive(Clone, Copy, Debug, PartialEq)]
35
#[expect(missing_docs, reason = "self-describing variants")]
36
pub struct PairedGpr {
37
pub read: Gpr,
38
pub write: WritableGpr,
39
}
40
41
impl From<WritableGpr> for PairedGpr {
42
fn from(wgpr: WritableGpr) -> Self {
43
let read = wgpr.to_reg();
44
let write = wgpr;
45
Self { read, write }
46
}
47
}
48
49
/// For ABI ergonomics.
50
impl From<WritableGpr> for asm::Gpr<PairedGpr> {
51
fn from(wgpr: WritableGpr) -> Self {
52
asm::Gpr::new(wgpr.into())
53
}
54
}
55
56
// For ABI ergonomics.
57
impl From<Writable<Reg>> for asm::GprMem<PairedGpr, Gpr> {
58
fn from(wgpr: Writable<Reg>) -> Self {
59
assert!(wgpr.to_reg().class() == RegClass::Int);
60
let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();
61
Self::Gpr(wgpr.into())
62
}
63
}
64
65
// For ABI ergonomics.
66
impl From<Reg> for asm::GprMem<Gpr, Gpr> {
67
fn from(gpr: Reg) -> Self {
68
assert!(gpr.class() == RegClass::Int);
69
let gpr = Gpr::unwrap_new(gpr);
70
Self::Gpr(gpr)
71
}
72
}
73
74
// For ABI ergonomics.
75
impl From<Writable<Reg>> for asm::GprMem<Gpr, Gpr> {
76
fn from(wgpr: Writable<Reg>) -> Self {
77
wgpr.to_reg().into()
78
}
79
}
80
81
// For ABI ergonomics.
82
impl From<Writable<Reg>> for asm::Gpr<PairedGpr> {
83
fn from(wgpr: Writable<Reg>) -> Self {
84
assert!(wgpr.to_reg().class() == RegClass::Int);
85
let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();
86
Self::new(wgpr.into())
87
}
88
}
89
90
impl From<Writable<Reg>> for asm::Gpr<WritableGpr> {
91
fn from(wgpr: Writable<Reg>) -> Self {
92
assert!(wgpr.to_reg().class() == RegClass::Int);
93
let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();
94
Self::new(wgpr)
95
}
96
}
97
98
impl asm::AsReg for PairedGpr {
99
fn enc(&self) -> u8 {
100
let PairedGpr { read, write } = self;
101
let read = enc_gpr(read);
102
let write = enc_gpr(&write.to_reg());
103
assert_eq!(read, write);
104
write
105
}
106
107
fn to_string(&self, size: Option<asm::Size>) -> String {
108
if self.read.is_real() {
109
asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()
110
} else {
111
let read = self.read.to_reg();
112
let write = self.write.to_reg().to_reg();
113
format!("(%{write:?} <- %{read:?})")
114
}
115
}
116
117
fn new(_: u8) -> Self {
118
panic!("disallow creation of new assembler registers")
119
}
120
}
121
122
/// A pair of XMM registers, one for reading and one for writing.
123
#[derive(Clone, Copy, Debug, PartialEq)]
124
#[expect(missing_docs, reason = "self-describing variants")]
125
pub struct PairedXmm {
126
pub read: Xmm,
127
pub write: WritableXmm,
128
}
129
130
impl From<WritableXmm> for PairedXmm {
131
fn from(wxmm: WritableXmm) -> Self {
132
let read = wxmm.to_reg();
133
let write = wxmm;
134
Self { read, write }
135
}
136
}
137
138
/// For ABI ergonomics.
139
impl From<WritableXmm> for asm::Xmm<PairedXmm> {
140
fn from(wgpr: WritableXmm) -> Self {
141
asm::Xmm::new(wgpr.into())
142
}
143
}
144
145
// For emission ergonomics.
146
impl From<Writable<Reg>> for asm::Xmm<PairedXmm> {
147
fn from(wxmm: Writable<Reg>) -> Self {
148
assert!(wxmm.to_reg().class() == RegClass::Float);
149
let wxmm = WritableXmm::from_writable_reg(wxmm).unwrap();
150
Self::new(wxmm.into())
151
}
152
}
153
154
// For emission ergonomics.
155
impl From<Reg> for asm::Xmm<Xmm> {
156
fn from(xmm: Reg) -> Self {
157
assert!(xmm.class() == RegClass::Float);
158
let xmm = Xmm::unwrap_new(xmm);
159
Self::new(xmm)
160
}
161
}
162
163
// For emission ergonomics.
164
impl From<Reg> for asm::XmmMem<Xmm, Gpr> {
165
fn from(xmm: Reg) -> Self {
166
assert!(xmm.class() == RegClass::Float);
167
let xmm = Xmm::unwrap_new(xmm);
168
Self::Xmm(xmm)
169
}
170
}
171
172
impl asm::AsReg for PairedXmm {
173
fn enc(&self) -> u8 {
174
let PairedXmm { read, write } = self;
175
let read = enc_xmm(read);
176
let write = enc_xmm(&write.to_reg());
177
assert_eq!(read, write);
178
write
179
}
180
181
fn to_string(&self, size: Option<asm::Size>) -> String {
182
assert!(size.is_none(), "XMM registers do not have size variants");
183
if self.read.is_real() {
184
asm::xmm::enc::to_string(self.enc()).into()
185
} else {
186
let read = self.read.to_reg();
187
let write = self.write.to_reg().to_reg();
188
format!("(%{write:?} <- %{read:?})")
189
}
190
}
191
192
fn new(_: u8) -> Self {
193
panic!("disallow creation of new assembler registers")
194
}
195
}
196
197
/// This bridges the gap between codegen and assembler for general purpose register types.
198
impl asm::AsReg for Gpr {
199
fn enc(&self) -> u8 {
200
enc_gpr(self)
201
}
202
203
fn to_string(&self, size: Option<asm::Size>) -> String {
204
if self.is_real() {
205
asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()
206
} else {
207
format!("%{:?}", self.to_reg())
208
}
209
}
210
211
fn new(_: u8) -> Self {
212
panic!("disallow creation of new assembler registers")
213
}
214
}
215
216
/// This bridges the gap between codegen and assembler for xmm register types.
217
impl asm::AsReg for Xmm {
218
fn enc(&self) -> u8 {
219
enc_xmm(self)
220
}
221
222
fn to_string(&self, size: Option<asm::Size>) -> String {
223
assert!(size.is_none(), "XMM registers do not have size variants");
224
if self.is_real() {
225
asm::xmm::enc::to_string(self.enc()).into()
226
} else {
227
format!("%{:?}", self.to_reg())
228
}
229
}
230
231
fn new(_: u8) -> Self {
232
panic!("disallow creation of new assembler registers")
233
}
234
}
235
236
/// A helper method for extracting the hardware encoding of a general purpose register.
237
#[inline]
238
fn enc_gpr(gpr: &Gpr) -> u8 {
239
if let Some(real) = gpr.to_reg().to_real_reg() {
240
real.hw_enc()
241
} else {
242
unreachable!()
243
}
244
}
245
246
/// A helper method for extracting the hardware encoding of an xmm register.
247
#[inline]
248
fn enc_xmm(xmm: &Xmm) -> u8 {
249
if let Some(real) = xmm.to_reg().to_real_reg() {
250
real.hw_enc()
251
} else {
252
unreachable!()
253
}
254
}
255
256
/// A wrapper to implement the `cranelift-assembler-x64` register allocation trait,
257
/// `RegallocVisitor`, in terms of the trait used in Cranelift,
258
/// `OperandVisitor`.
259
pub(crate) struct RegallocVisitor<'a, T>
260
where
261
T: OperandVisitorImpl,
262
{
263
pub collector: &'a mut T,
264
}
265
266
impl<'a, T: OperandVisitor> asm::RegisterVisitor<CraneliftRegisters> for RegallocVisitor<'a, T> {
267
fn read_gpr(&mut self, reg: &mut Gpr) {
268
self.collector.reg_use(reg);
269
}
270
271
fn read_write_gpr(&mut self, reg: &mut PairedGpr) {
272
let PairedGpr { read, write } = reg;
273
self.collector.reg_use(read);
274
self.collector.reg_reuse_def(write, 0);
275
}
276
277
fn write_gpr(&mut self, reg: &mut WritableGpr) {
278
self.collector.reg_def(reg);
279
}
280
281
fn fixed_read_gpr(&mut self, reg: &mut Gpr, enc: u8) {
282
self.collector
283
.reg_fixed_use(reg, fixed_reg(enc, RegClass::Int));
284
}
285
286
fn fixed_read_write_gpr(&mut self, reg: &mut PairedGpr, enc: u8) {
287
let PairedGpr { read, write } = reg;
288
self.collector
289
.reg_fixed_use(read, fixed_reg(enc, RegClass::Int));
290
self.collector
291
.reg_fixed_def(write, fixed_reg(enc, RegClass::Int));
292
}
293
294
fn fixed_write_gpr(&mut self, reg: &mut WritableGpr, enc: u8) {
295
self.collector
296
.reg_fixed_def(reg, fixed_reg(enc, RegClass::Int));
297
}
298
299
fn read_xmm(&mut self, reg: &mut Xmm) {
300
self.collector.reg_use(reg);
301
}
302
303
fn read_write_xmm(&mut self, reg: &mut PairedXmm) {
304
let PairedXmm { read, write } = reg;
305
self.collector.reg_use(read);
306
self.collector.reg_reuse_def(write, 0);
307
}
308
309
fn write_xmm(&mut self, reg: &mut WritableXmm) {
310
self.collector.reg_def(reg);
311
}
312
313
fn fixed_read_xmm(&mut self, reg: &mut Xmm, enc: u8) {
314
self.collector
315
.reg_fixed_use(reg, fixed_reg(enc, RegClass::Float));
316
}
317
318
fn fixed_read_write_xmm(&mut self, reg: &mut PairedXmm, enc: u8) {
319
let PairedXmm { read, write } = reg;
320
self.collector
321
.reg_fixed_use(read, fixed_reg(enc, RegClass::Float));
322
self.collector
323
.reg_fixed_def(write, fixed_reg(enc, RegClass::Float));
324
}
325
326
fn fixed_write_xmm(&mut self, reg: &mut WritableXmm, enc: u8) {
327
self.collector
328
.reg_fixed_def(reg, fixed_reg(enc, RegClass::Float));
329
}
330
}
331
332
/// A helper for building a fixed register from its hardware encoding.
333
fn fixed_reg(enc: u8, class: RegClass) -> Reg {
334
let preg = PReg::new(usize::from(enc), class);
335
Reg::from_real_reg(preg)
336
}
337
338
impl From<SyntheticAmode> for asm::Amode<Gpr> {
339
fn from(amode: SyntheticAmode) -> asm::Amode<Gpr> {
340
match amode {
341
SyntheticAmode::Real(amode) => amode.into(),
342
SyntheticAmode::IncomingArg { offset } => asm::Amode::ImmReg {
343
base: Gpr::RBP,
344
simm32: asm::AmodeOffsetPlusKnownOffset {
345
simm32: (-i32::try_from(offset).unwrap()).into(),
346
offset: Some(offsets::KEY_INCOMING_ARG),
347
},
348
trap: None,
349
},
350
SyntheticAmode::SlotOffset { simm32 } => asm::Amode::ImmReg {
351
base: Gpr::RSP,
352
simm32: asm::AmodeOffsetPlusKnownOffset {
353
simm32: simm32.into(),
354
offset: Some(offsets::KEY_SLOT_OFFSET),
355
},
356
trap: None,
357
},
358
SyntheticAmode::ConstantOffset(vcode_constant) => asm::Amode::RipRelative {
359
target: asm::DeferredTarget::Constant(asm::Constant(vcode_constant.as_u32())),
360
},
361
}
362
}
363
}
364
365
impl From<Amode> for asm::Amode<Gpr> {
366
fn from(amode: Amode) -> asm::Amode<Gpr> {
367
match amode {
368
Amode::ImmReg {
369
simm32,
370
base,
371
flags,
372
} => asm::Amode::ImmReg {
373
simm32: asm::AmodeOffsetPlusKnownOffset {
374
simm32: simm32.into(),
375
offset: None,
376
},
377
base: Gpr::unwrap_new(base),
378
trap: flags.trap_code().map(Into::into),
379
},
380
Amode::ImmRegRegShift {
381
simm32,
382
base,
383
index,
384
shift,
385
flags,
386
} => asm::Amode::ImmRegRegShift {
387
base,
388
index: asm::NonRspGpr::new(index),
389
scale: asm::Scale::new(shift),
390
simm32: simm32.into(),
391
trap: flags.trap_code().map(Into::into),
392
},
393
Amode::RipRelative { target } => asm::Amode::RipRelative {
394
target: asm::DeferredTarget::Label(asm::Label(target.as_u32())),
395
},
396
}
397
}
398
}
399
400
impl<R: asm::AsReg> From<SyntheticAmode> for asm::XmmMem<R, Gpr> {
401
fn from(amode: SyntheticAmode) -> Self {
402
asm::XmmMem::Mem(amode.into())
403
}
404
}
405
406
impl<R: asm::AsReg> From<SyntheticAmode> for asm::GprMem<R, Gpr> {
407
fn from(amode: SyntheticAmode) -> Self {
408
asm::GprMem::Mem(amode.into())
409
}
410
}
411
412
impl<R: asm::AsReg> From<Amode> for asm::XmmMem<R, Gpr> {
413
fn from(amode: Amode) -> Self {
414
asm::XmmMem::Mem(amode.into())
415
}
416
}
417
418
impl<R: asm::AsReg> From<Amode> for asm::GprMem<R, Gpr> {
419
fn from(amode: Amode) -> Self {
420
asm::GprMem::Mem(amode.into())
421
}
422
}
423
424
/// Keep track of the offset slots to fill in during emission; see
425
/// `KnownOffsetTable`.
426
#[expect(missing_docs, reason = "self-describing keys")]
427
pub mod offsets {
428
pub const KEY_INCOMING_ARG: u8 = 0;
429
pub const KEY_SLOT_OFFSET: u8 = 1;
430
}
431
432
/// Implementor of the [`asm::CodeSink`] trait.
433
pub struct AsmCodeSink<'a> {
434
/// The buffer this is emitting into.
435
pub sink: &'a mut MachBuffer<Inst>,
436
/// The value of `KEY_INCOMING_ARG`.
437
pub incoming_arg_offset: i32,
438
/// The value of `KEY_SLOT_OFFSET`.
439
pub slot_offset: i32,
440
}
441
442
impl asm::CodeSink for AsmCodeSink<'_> {
443
fn put1(&mut self, value: u8) {
444
self.sink.put1(value)
445
}
446
447
fn put2(&mut self, value: u16) {
448
self.sink.put2(value)
449
}
450
451
fn put4(&mut self, value: u32) {
452
self.sink.put4(value)
453
}
454
455
fn put8(&mut self, value: u64) {
456
self.sink.put8(value)
457
}
458
459
fn add_trap(&mut self, code: asm::TrapCode) {
460
self.sink.add_trap(code.into());
461
}
462
463
fn use_target(&mut self, target: asm::DeferredTarget) {
464
let offset = self.sink.cur_offset();
465
match target {
466
asm::DeferredTarget::Label(label) => {
467
self.sink
468
.use_label_at_offset(offset, label.into(), LabelUse::JmpRel32);
469
}
470
asm::DeferredTarget::Constant(constant) => {
471
let label = self.sink.get_label_for_constant(constant.into());
472
self.sink
473
.use_label_at_offset(offset, label, LabelUse::JmpRel32);
474
}
475
asm::DeferredTarget::None => {}
476
}
477
}
478
479
fn known_offset(&self, offset: asm::KnownOffset) -> i32 {
480
match offset {
481
offsets::KEY_INCOMING_ARG => self.incoming_arg_offset,
482
offsets::KEY_SLOT_OFFSET => self.slot_offset,
483
other => panic!("unknown \"known\" offset {other}"),
484
}
485
}
486
}
487
488
impl From<asm::TrapCode> for TrapCode {
489
fn from(value: asm::TrapCode) -> Self {
490
Self::from_raw(value.0)
491
}
492
}
493
494
impl From<TrapCode> for asm::TrapCode {
495
fn from(value: TrapCode) -> Self {
496
Self(value.as_raw())
497
}
498
}
499
500
impl From<asm::Label> for MachLabel {
501
fn from(value: asm::Label) -> Self {
502
Self::from_u32(value.0)
503
}
504
}
505
506
impl From<MachLabel> for asm::Label {
507
fn from(value: MachLabel) -> Self {
508
Self(value.as_u32())
509
}
510
}
511
512
impl From<asm::Constant> for VCodeConstant {
513
fn from(value: asm::Constant) -> Self {
514
Self::from_u32(value.0)
515
}
516
}
517
518
// Include code generated by `cranelift-codegen/meta/src/gen_asm.rs`. This file
519
// contains a `isle_assembler_methods!` macro with Rust implementations of all
520
// the assembler instructions exposed to ISLE.
521
include!(concat!(env!("OUT_DIR"), "/assembler-isle-macro.rs"));
522
pub(crate) use isle_assembler_methods;
523
524
#[cfg(test)]
525
mod tests {
526
use super::PairedGpr;
527
use super::asm::{AsReg, Size};
528
use crate::isa::x64::args::{FromWritableReg, Gpr, WritableGpr, WritableXmm, Xmm};
529
use crate::isa::x64::inst::external::PairedXmm;
530
use crate::{Reg, Writable};
531
use regalloc2::{RegClass, VReg};
532
533
#[test]
534
fn pretty_print_registers() {
535
// For logging, we need to be able to pretty-print the virtual registers
536
// that Cranelift uses before register allocation. This test ensures
537
// that these remain printable using the `AsReg::to_string` interface
538
// (see issue #10631).
539
540
let v200: Reg = VReg::new(200, RegClass::Int).into();
541
let gpr200 = Gpr::new(v200).unwrap();
542
assert_eq!(gpr200.to_string(Some(Size::Quadword)), "%v200");
543
544
let v300: Reg = VReg::new(300, RegClass::Int).into();
545
let wgpr300 = WritableGpr::from_writable_reg(Writable::from_reg(v300)).unwrap();
546
let pair = PairedGpr {
547
read: gpr200,
548
write: wgpr300,
549
};
550
assert_eq!(pair.to_string(Some(Size::Quadword)), "(%v300 <- %v200)");
551
552
let v400: Reg = VReg::new(400, RegClass::Float).into();
553
let xmm400 = Xmm::new(v400).unwrap();
554
assert_eq!(xmm400.to_string(None), "%v400");
555
556
let v500: Reg = VReg::new(500, RegClass::Float).into();
557
let wxmm500 = WritableXmm::from_writable_reg(Writable::from_reg(v500)).unwrap();
558
let pair = PairedXmm {
559
read: xmm400,
560
write: wxmm500,
561
};
562
assert_eq!(pair.to_string(None), "(%v500 <- %v400)");
563
}
564
}
565
566