Skip to content

Commit a83350e

Browse files
committed
Extract VRegId from a usize
We would like to do type matching on the VRegId. Extracting the VRegID from a usize makes the code a bit easier to understand and refactor. MemBase uses a VReg, and there is also a VReg in Opnd. We should be sharing types between these two, so this is a step in the direction of sharing a type
1 parent 253bfd7 commit a83350e

File tree

4 files changed

+85
-39
lines changed

4 files changed

+85
-39
lines changed

zjit/src/backend/arm64/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ impl Assembler {
390390
}
391391

392392
let mut asm_local = Assembler::new_with_asm(&self);
393-
let live_ranges: Vec<LiveRange> = take(&mut self.live_ranges);
393+
let live_ranges = take(&mut self.live_ranges);
394394
let mut iterator = self.instruction_iterator();
395395
let asm = &mut asm_local;
396396

@@ -1691,7 +1691,7 @@ impl Assembler {
16911691
///
16921692
/// If a, b, and c are all registers.
16931693
fn merge_three_reg_mov(
1694-
live_ranges: &[LiveRange],
1694+
live_ranges: &LiveRanges,
16951695
iterator: &mut InsnIter,
16961696
asm: &mut Assembler,
16971697
left: &Opnd,

zjit/src/backend/lir.rs

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,33 @@ use crate::state::rb_zjit_record_exit_stack;
2121
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
2222
pub struct BlockId(pub usize);
2323

24+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
25+
pub struct VRegId(pub usize);
26+
2427
impl From<BlockId> for usize {
2528
fn from(val: BlockId) -> Self {
2629
val.0
2730
}
2831
}
2932

33+
impl From<VRegId> for usize {
34+
fn from(val: VRegId) -> Self {
35+
val.0
36+
}
37+
}
38+
3039
impl std::fmt::Display for BlockId {
3140
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3241
write!(f, "l{}", self.0)
3342
}
3443
}
3544

45+
impl std::fmt::Display for VRegId {
46+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
47+
write!(f, "v{}", self.0)
48+
}
49+
}
50+
3651
/// Dummy HIR block ID used when creating test or invalid LIR blocks
3752
const DUMMY_HIR_BLOCK_ID: usize = usize::MAX;
3853
/// Dummy RPO index used when creating test or invalid LIR blocks
@@ -131,7 +146,7 @@ pub enum MemBase
131146
/// Register: Every Opnd::Mem should have MemBase::Reg as of emit.
132147
Reg(u8),
133148
/// Virtual register: Lowered to MemBase::Reg or MemBase::Stack in alloc_regs.
134-
VReg(usize),
149+
VReg(VRegId),
135150
/// Stack slot: Lowered to MemBase::Reg in scratch_split.
136151
Stack { stack_idx: usize, num_bits: u8 },
137152
}
@@ -158,7 +173,7 @@ impl fmt::Display for Mem {
158173
write!(f, "[")?;
159174
match self.base {
160175
MemBase::Reg(reg_no) => write!(f, "{}", mem_base_reg(reg_no))?,
161-
MemBase::VReg(idx) => write!(f, "v{idx}")?,
176+
MemBase::VReg(idx) => write!(f, "{idx}")?,
162177
MemBase::Stack { stack_idx, num_bits } if num_bits == 64 => write!(f, "Stack[{stack_idx}]")?,
163178
MemBase::Stack { stack_idx, num_bits } => write!(f, "Stack{num_bits}[{stack_idx}]")?,
164179
}
@@ -196,7 +211,7 @@ pub enum Opnd
196211
Value(VALUE),
197212

198213
/// Virtual register. Lowered to Reg or Mem in Assembler::alloc_regs().
199-
VReg{ idx: usize, num_bits: u8 },
214+
VReg{ idx: VRegId, num_bits: u8 },
200215

201216
// Low-level operands, for lowering
202217
Imm(i64), // Raw signed immediate
@@ -212,8 +227,8 @@ impl fmt::Display for Opnd {
212227
None => write!(f, "None"),
213228
Value(VALUE(value)) if *value < 10 => write!(f, "Value({value:x})"),
214229
Value(VALUE(value)) => write!(f, "Value(0x{value:x})"),
215-
VReg { idx, num_bits } if *num_bits == 64 => write!(f, "v{idx}"),
216-
VReg { idx, num_bits } => write!(f, "VReg{num_bits}(v{idx})"),
230+
VReg { idx, num_bits } if *num_bits == 64 => write!(f, "{idx}"),
231+
VReg { idx, num_bits } => write!(f, "VReg{num_bits}({idx})"),
217232
Imm(value) if value.abs() < 10 => write!(f, "Imm({value:x})"),
218233
Imm(value) => write!(f, "Imm(0x{value:x})"),
219234
UImm(value) if *value < 10 => write!(f, "{value:x}"),
@@ -282,7 +297,7 @@ impl Opnd
282297
}
283298

284299
/// Unwrap the index of a VReg
285-
pub fn vreg_idx(&self) -> usize {
300+
pub fn vreg_idx(&self) -> VRegId {
286301
match self {
287302
Opnd::VReg { idx, .. } => *idx,
288303
_ => unreachable!("trying to unwrap {self:?} into VReg"),
@@ -321,10 +336,10 @@ impl Opnd
321336
pub fn map_index(self, indices: &[usize]) -> Opnd {
322337
match self {
323338
Opnd::VReg { idx, num_bits } => {
324-
Opnd::VReg { idx: indices[idx], num_bits }
339+
Opnd::VReg { idx: VRegId(indices[idx.0]), num_bits }
325340
}
326341
Opnd::Mem(Mem { base: MemBase::VReg(idx), disp, num_bits }) => {
327-
Opnd::Mem(Mem { base: MemBase::VReg(indices[idx]), disp, num_bits })
342+
Opnd::Mem(Mem { base: MemBase::VReg(VRegId(indices[idx.0])), disp, num_bits })
328343
},
329344
_ => self
330345
}
@@ -1355,12 +1370,44 @@ impl LiveRange {
13551370
}
13561371
}
13571372

1373+
/// Type-safe wrapper around Vec<LiveRange> that can be indexed by VRegId
1374+
#[derive(Clone, Debug, Default)]
1375+
pub struct LiveRanges(Vec<LiveRange>);
1376+
1377+
impl std::ops::Index<VRegId> for LiveRanges {
1378+
type Output = LiveRange;
1379+
1380+
fn index(&self, idx: VRegId) -> &Self::Output {
1381+
&self.0[idx.0]
1382+
}
1383+
}
1384+
1385+
impl std::ops::IndexMut<VRegId> for LiveRanges {
1386+
fn index_mut(&mut self, idx: VRegId) -> &mut Self::Output {
1387+
&mut self.0[idx.0]
1388+
}
1389+
}
1390+
1391+
impl std::ops::Deref for LiveRanges {
1392+
type Target = Vec<LiveRange>;
1393+
1394+
fn deref(&self) -> &Self::Target {
1395+
&self.0
1396+
}
1397+
}
1398+
1399+
impl std::ops::DerefMut for LiveRanges {
1400+
fn deref_mut(&mut self) -> &mut Self::Target {
1401+
&mut self.0
1402+
}
1403+
}
1404+
13581405
/// StackState manages which stack slots are used by which VReg
13591406
pub struct StackState {
13601407
/// The maximum number of spilled VRegs at a time
13611408
stack_size: usize,
13621409
/// Map from index at the C stack for spilled VRegs to Some(vreg_idx) if allocated
1363-
stack_slots: Vec<Option<usize>>,
1410+
stack_slots: Vec<Option<VRegId>>,
13641411
/// Copy of Assembler::stack_base_idx. Used for calculating stack slot offsets.
13651412
stack_base_idx: usize,
13661413
}
@@ -1376,7 +1423,7 @@ impl StackState {
13761423
}
13771424

13781425
/// Allocate a stack slot for a given vreg_idx
1379-
fn alloc_stack(&mut self, vreg_idx: usize) -> Opnd {
1426+
fn alloc_stack(&mut self, vreg_idx: VRegId) -> Opnd {
13801427
for stack_idx in 0..self.stack_size {
13811428
if self.stack_slots[stack_idx].is_none() {
13821429
self.stack_slots[stack_idx] = Some(vreg_idx);
@@ -1437,7 +1484,7 @@ struct RegisterPool {
14371484

14381485
/// Some(vreg_idx) if the register at the index in `pool` is used by the VReg.
14391486
/// None if the register is not in use.
1440-
pool: Vec<Option<usize>>,
1487+
pool: Vec<Option<VRegId>>,
14411488

14421489
/// The number of live registers.
14431490
/// Provides a quick way to query `pool.filter(|r| r.is_some()).count()`
@@ -1461,7 +1508,7 @@ impl RegisterPool {
14611508

14621509
/// Mutate the pool to indicate that the register at the index
14631510
/// has been allocated and is live.
1464-
fn alloc_opnd(&mut self, vreg_idx: usize) -> Opnd {
1511+
fn alloc_opnd(&mut self, vreg_idx: VRegId) -> Opnd {
14651512
for (reg_idx, reg) in self.regs.iter().enumerate() {
14661513
if self.pool[reg_idx].is_none() {
14671514
self.pool[reg_idx] = Some(vreg_idx);
@@ -1473,7 +1520,7 @@ impl RegisterPool {
14731520
}
14741521

14751522
/// Allocate a specific register
1476-
fn take_reg(&mut self, reg: &Reg, vreg_idx: usize) -> Opnd {
1523+
fn take_reg(&mut self, reg: &Reg, vreg_idx: VRegId) -> Opnd {
14771524
let reg_idx = self.regs.iter().position(|elem| elem.reg_no == reg.reg_no)
14781525
.unwrap_or_else(|| panic!("Unable to find register: {}", reg.reg_no));
14791526
assert_eq!(self.pool[reg_idx], None, "register already allocated for VReg({:?})", self.pool[reg_idx]);
@@ -1499,7 +1546,7 @@ impl RegisterPool {
14991546
}
15001547

15011548
/// Return a list of (Reg, vreg_idx) tuples for all live registers
1502-
fn live_regs(&self) -> Vec<(Reg, usize)> {
1549+
fn live_regs(&self) -> Vec<(Reg, VRegId)> {
15031550
let mut live_regs = Vec::with_capacity(self.live_regs);
15041551
for (reg_idx, &reg) in self.regs.iter().enumerate() {
15051552
if let Some(vreg_idx) = self.pool[reg_idx] {
@@ -1510,7 +1557,7 @@ impl RegisterPool {
15101557
}
15111558

15121559
/// Return vreg_idx if a given register is already in use
1513-
fn vreg_for(&self, reg: &Reg) -> Option<usize> {
1560+
fn vreg_for(&self, reg: &Reg) -> Option<VRegId> {
15141561
let reg_idx = self.regs.iter().position(|elem| elem.reg_no == reg.reg_no).unwrap();
15151562
self.pool[reg_idx]
15161563
}
@@ -1536,7 +1583,7 @@ pub struct Assembler {
15361583
current_block_id: BlockId,
15371584

15381585
/// Live range for each VReg indexed by its `idx``
1539-
pub(super) live_ranges: Vec<LiveRange>,
1586+
pub(super) live_ranges: LiveRanges,
15401587

15411588
/// Names of labels
15421589
pub(super) label_names: Vec<String>,
@@ -1568,7 +1615,7 @@ impl Assembler
15681615
leaf_ccall_stack_size: None,
15691616
basic_blocks: Vec::default(),
15701617
current_block_id: BlockId(0),
1571-
live_ranges: Vec::default(),
1618+
live_ranges: LiveRanges::default(),
15721619
idx: 0,
15731620
}
15741621
}
@@ -1780,7 +1827,7 @@ impl Assembler
17801827

17811828
/// Build an Opnd::VReg and initialize its LiveRange
17821829
pub(super) fn new_vreg(&mut self, num_bits: u8) -> Opnd {
1783-
let vreg = Opnd::VReg { idx: self.live_ranges.len(), num_bits };
1830+
let vreg = Opnd::VReg { idx: VRegId(self.live_ranges.len()), num_bits };
17841831
self.live_ranges.push(LiveRange { start: None, end: None });
17851832
vreg
17861833
}
@@ -1794,7 +1841,7 @@ impl Assembler
17941841

17951842
// Initialize the live range of the output VReg to insn_idx..=insn_idx
17961843
if let Some(Opnd::VReg { idx, .. }) = insn.out_opnd() {
1797-
assert!(*idx < self.live_ranges.len());
1844+
assert!(idx.0 < self.live_ranges.len());
17981845
assert_eq!(self.live_ranges[*idx], LiveRange { start: None, end: None });
17991846
self.live_ranges[*idx] = LiveRange { start: Some(insn_idx), end: Some(insn_idx) };
18001847
}
@@ -1805,7 +1852,7 @@ impl Assembler
18051852
match *opnd {
18061853
Opnd::VReg { idx, .. } |
18071854
Opnd::Mem(Mem { base: MemBase::VReg(idx), .. }) => {
1808-
assert!(idx < self.live_ranges.len());
1855+
assert!(idx.0 < self.live_ranges.len());
18091856
assert_ne!(self.live_ranges[idx].end, None);
18101857
self.live_ranges[idx].end = Some(self.live_ranges[idx].end().max(insn_idx));
18111858
}
@@ -1894,7 +1941,7 @@ impl Assembler
18941941
let mut vreg_opnd: Vec<Option<Opnd>> = vec![None; self.live_ranges.len()];
18951942

18961943
// List of registers saved before a C call, paired with the VReg index.
1897-
let mut saved_regs: Vec<(Reg, usize)> = vec![];
1944+
let mut saved_regs: Vec<(Reg, VRegId)> = vec![];
18981945

18991946
// Remember the indexes of Insn::FrameSetup to update the stack size later
19001947
let mut frame_setup_idxs: Vec<(BlockId, usize)> = vec![];
@@ -1924,7 +1971,7 @@ impl Assembler
19241971
let new_opnd = pool.alloc_opnd(vreg_idx);
19251972
asm.mov(new_opnd, C_RET_OPND);
19261973
pool.dealloc_opnd(&Opnd::Reg(C_RET_REG));
1927-
vreg_opnd[vreg_idx] = Some(new_opnd);
1974+
vreg_opnd[vreg_idx.0] = Some(new_opnd);
19281975
}
19291976

19301977
true
@@ -1942,8 +1989,8 @@ impl Assembler
19421989
// We're going to check if this is the last instruction that
19431990
// uses this operand. If it is, we can return the allocated
19441991
// register to the pool.
1945-
if live_ranges[idx].end() == index {
1946-
if let Some(opnd) = vreg_opnd[idx] {
1992+
if live_ranges[idx.0].end() == index {
1993+
if let Some(opnd) = vreg_opnd[idx.0] {
19471994
pool.dealloc_opnd(&opnd);
19481995
} else {
19491996
unreachable!("no register allocated for insn {:?}", insn);
@@ -1986,8 +2033,8 @@ impl Assembler
19862033
_ => None,
19872034
};
19882035
if let Some(vreg_idx) = vreg_idx {
1989-
if live_ranges[vreg_idx].end() == index {
1990-
debug!("Allocating a register for VReg({}) at instruction index {} even though it does not live past this index", vreg_idx, index);
2036+
if live_ranges[vreg_idx.0].end() == index {
2037+
debug!("Allocating a register for {vreg_idx} at instruction index {index} even though it does not live past this index");
19912038
}
19922039
// This is going to be the output operand that we will set on the
19932040
// instruction. CCall and LiveReg need to use a specific register.
@@ -2011,8 +2058,8 @@ impl Assembler
20112058
let mut opnd_iter = insn.opnd_iter();
20122059

20132060
if let Some(Opnd::VReg{ idx, .. }) = opnd_iter.next() {
2014-
if live_ranges[*idx].end() == index {
2015-
if let Some(Opnd::Reg(reg)) = vreg_opnd[*idx] {
2061+
if live_ranges[idx.0].end() == index {
2062+
if let Some(Opnd::Reg(reg)) = vreg_opnd[idx.0] {
20162063
out_reg = Some(pool.take_reg(&reg, vreg_idx));
20172064
}
20182065
}
@@ -2031,7 +2078,7 @@ impl Assembler
20312078
// extends beyond the index of the instruction.
20322079
let out = insn.out_opnd_mut().unwrap();
20332080
let out_opnd = out_opnd.with_num_bits(out_num_bits);
2034-
vreg_opnd[out.vreg_idx()] = Some(out_opnd);
2081+
vreg_opnd[out.vreg_idx().0] = Some(out_opnd);
20352082
*out = out_opnd;
20362083
}
20372084

@@ -2040,10 +2087,10 @@ impl Assembler
20402087
while let Some(opnd) = opnd_iter.next() {
20412088
match *opnd {
20422089
Opnd::VReg { idx, num_bits } => {
2043-
*opnd = vreg_opnd[idx].unwrap().with_num_bits(num_bits);
2090+
*opnd = vreg_opnd[idx.0].unwrap().with_num_bits(num_bits);
20442091
},
20452092
Opnd::Mem(Mem { base: MemBase::VReg(idx), disp, num_bits }) => {
2046-
*opnd = match vreg_opnd[idx].unwrap() {
2093+
*opnd = match vreg_opnd[idx.0].unwrap() {
20472094
Opnd::Reg(reg) => Opnd::Mem(Mem { base: MemBase::Reg(reg.reg_no), disp, num_bits }),
20482095
// If the base is spilled, lower it to MemBase::Stack, which scratch_split will lower to MemBase::Reg.
20492096
Opnd::Mem(mem) => Opnd::Mem(Mem { base: pool.stack_state.mem_to_stack_membase(mem), disp, num_bits }),
@@ -2057,8 +2104,8 @@ impl Assembler
20572104
// If we have an output that dies at its definition (it is unused), free up the
20582105
// register
20592106
if let Some(idx) = vreg_idx {
2060-
if live_ranges[idx].end() == index {
2061-
if let Some(opnd) = vreg_opnd[idx] {
2107+
if live_ranges[idx.0].end() == index {
2108+
if let Some(opnd) = vreg_opnd[idx.0] {
20622109
pool.dealloc_opnd(&opnd);
20632110
} else {
20642111
unreachable!("no register allocated for insn {:?}", insn);

zjit/src/backend/tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::backend::lir::*;
33
use crate::cruby::*;
44
use crate::codegen::c_callable;
55
use crate::options::rb_zjit_prepare_options;
6-
use crate::hir;
76

87
#[test]
98
fn test_add() {

zjit/src/backend/x86_64/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ impl Assembler {
140140
{
141141
let mut asm_local = Assembler::new_with_asm(&self);
142142
let asm = &mut asm_local;
143-
let live_ranges: Vec<LiveRange> = take(&mut self.live_ranges);
143+
let live_ranges = take(&mut self.live_ranges);
144144
let mut iterator = self.instruction_iterator();
145145

146146
while let Some((index, mut insn)) = iterator.next(asm) {
@@ -166,9 +166,9 @@ impl Assembler {
166166
// When we split an operand, we can create a new VReg not in `live_ranges`.
167167
// So when we see a VReg with out-of-range index, it's created from splitting
168168
// from the loop above and we know it doesn't outlive the current instruction.
169-
let vreg_outlives_insn = |vreg_idx| {
169+
let vreg_outlives_insn = |vreg_idx: VRegId| {
170170
live_ranges
171-
.get(vreg_idx)
171+
.get(vreg_idx.0)
172172
.is_some_and(|live_range: &LiveRange| live_range.end() > index)
173173
};
174174

0 commit comments

Comments
 (0)