Skip to content

Commit a08b012

Browse files
committed
Add support for reftypes/stackmaps and Stack constraints, and misc API changes.
The main enhancement in this commit is support for reference types and stackmaps. This requires tracking whether each VReg is a "reference" or "pointer". At certain instructions designated as "safepoints", the regalloc will (i) ensure that all references are in spillslots rather than in registers, and (ii) provide a list of exactly which spillslots have live references at that program point. This can be used by, e.g., a GC to trace and possibly modify pointers. The stackmap of spillslots is precise: it includes all live references, and *only* live references. This commit also brings in some API tweaks as part of the in-progress Cranelift glue. In particular, it makes Allocations and Operands mutually disjoint by using the same bitfield for the type-tag in both and choosing non-overlapping tags. This will allow instructions to carry an Operand for each register slot and then overwrite these in place with Allocations. The `OperandOrAllocation` type does the necessary magic to make this look like an enum, but staying in 32 bits.
1 parent 33ac6cb commit a08b012

File tree

6 files changed

+686
-91
lines changed

6 files changed

+686
-91
lines changed

fuzz/fuzz_targets/ion_checker.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ impl Arbitrary for TestCase {
2121
reducible: false,
2222
block_params: true,
2323
always_local_uses: false,
24+
reftypes: true,
2425
})?,
2526
})
2627
}

src/bitvec.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ impl BitVec {
4848
}
4949
}
5050

51+
pub fn assign(&mut self, other: &Self) {
52+
if other.bits.len() > 0 {
53+
self.ensure_idx(other.bits.len() - 1);
54+
}
55+
for i in 0..other.bits.len() {
56+
self.bits[i] = other.bits[i];
57+
}
58+
for i in other.bits.len()..self.bits.len() {
59+
self.bits[i] = 0;
60+
}
61+
}
62+
5163
#[inline(always)]
5264
pub fn get(&mut self, idx: usize) -> bool {
5365
let word = idx / BITS_PER_WORD;
@@ -59,16 +71,21 @@ impl BitVec {
5971
}
6072
}
6173

62-
pub fn or(&mut self, other: &Self) {
74+
pub fn or(&mut self, other: &Self) -> bool {
6375
if other.bits.is_empty() {
64-
return;
76+
return false;
6577
}
6678
let last_idx = other.bits.len() - 1;
6779
self.ensure_idx(last_idx);
6880

81+
let mut changed = false;
6982
for (self_word, other_word) in self.bits.iter_mut().zip(other.bits.iter()) {
83+
if *other_word & !*self_word != 0 {
84+
changed = true;
85+
}
7086
*self_word |= *other_word;
7187
}
88+
changed
7289
}
7390

7491
pub fn and(&mut self, other: &Self) {
@@ -91,6 +108,29 @@ impl BitVec {
91108
}
92109
}
93110

111+
impl std::cmp::PartialEq for BitVec {
112+
fn eq(&self, other: &Self) -> bool {
113+
let limit = std::cmp::min(self.bits.len(), other.bits.len());
114+
for i in 0..limit {
115+
if self.bits[i] != other.bits[i] {
116+
return false;
117+
}
118+
}
119+
for i in limit..self.bits.len() {
120+
if self.bits[i] != 0 {
121+
return false;
122+
}
123+
}
124+
for i in limit..other.bits.len() {
125+
if other.bits[i] != 0 {
126+
return false;
127+
}
128+
}
129+
true
130+
}
131+
}
132+
impl std::cmp::Eq for BitVec {}
133+
94134
pub struct SetBitsIter<'a> {
95135
words: &'a [u64],
96136
word_idx: usize,

src/checker.rs

Lines changed: 129 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@
6767

6868
use crate::{
6969
Allocation, AllocationKind, Block, Edit, Function, Inst, InstPosition, Operand, OperandKind,
70-
OperandPolicy, OperandPos, Output, ProgPoint, VReg,
70+
OperandPolicy, OperandPos, Output, PReg, ProgPoint, SpillSlot, VReg,
7171
};
7272

73-
use std::collections::{HashMap, VecDeque};
73+
use std::collections::{HashMap, HashSet, VecDeque};
7474
use std::default::Default;
7575
use std::hash::Hash;
7676
use std::result::Result;
@@ -127,6 +127,20 @@ pub enum CheckerError {
127127
alloc: Allocation,
128128
expected_alloc: Allocation,
129129
},
130+
AllocationIsNotStack {
131+
inst: Inst,
132+
op: Operand,
133+
alloc: Allocation,
134+
},
135+
ConflictedValueInStackmap {
136+
inst: Inst,
137+
slot: SpillSlot,
138+
},
139+
NonRefValueInStackmap {
140+
inst: Inst,
141+
slot: SpillSlot,
142+
vreg: VReg,
143+
},
130144
}
131145

132146
/// Abstract state for an allocation.
@@ -162,8 +176,10 @@ impl CheckerValue {
162176
(_, &CheckerValue::Unknown) => *self,
163177
(&CheckerValue::Conflicted, _) => *self,
164178
(_, &CheckerValue::Conflicted) => *other,
165-
(&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2)) if r1 == r2 => {
166-
CheckerValue::Reg(r1, ref1 || ref2)
179+
(&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2))
180+
if r1 == r2 && ref1 == ref2 =>
181+
{
182+
CheckerValue::Reg(r1, ref1)
167183
}
168184
_ => {
169185
log::debug!("{:?} and {:?} meet to Conflicted", self, other);
@@ -192,7 +208,8 @@ impl std::fmt::Display for CheckerValue {
192208
match self {
193209
CheckerValue::Unknown => write!(f, "?"),
194210
CheckerValue::Conflicted => write!(f, "!"),
195-
CheckerValue::Reg(r, _) => write!(f, "{}", r),
211+
CheckerValue::Reg(r, false) => write!(f, "{}", r),
212+
CheckerValue::Reg(r, true) => write!(f, "{}/ref", r),
196213
}
197214
}
198215
}
@@ -305,13 +322,38 @@ impl CheckerState {
305322
self.check_val(inst, *op, *alloc, val, allocs)?;
306323
}
307324
}
325+
&CheckerInst::Safepoint { inst, ref slots } => {
326+
for &slot in slots {
327+
let alloc = Allocation::stack(slot);
328+
let val = self
329+
.allocations
330+
.get(&alloc)
331+
.cloned()
332+
.unwrap_or(Default::default());
333+
debug!(
334+
"checker: checkinst {:?}: safepoint slot {}, checker value {:?}",
335+
checkinst, slot, val
336+
);
337+
338+
match val {
339+
CheckerValue::Unknown => {}
340+
CheckerValue::Conflicted => {
341+
return Err(CheckerError::ConflictedValueInStackmap { inst, slot });
342+
}
343+
CheckerValue::Reg(vreg, false) => {
344+
return Err(CheckerError::NonRefValueInStackmap { inst, slot, vreg });
345+
}
346+
CheckerValue::Reg(_, true) => {}
347+
}
348+
}
349+
}
308350
_ => {}
309351
}
310352
Ok(())
311353
}
312354

313355
/// Update according to instruction.
314-
fn update(&mut self, checkinst: &CheckerInst) {
356+
fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) {
315357
match checkinst {
316358
&CheckerInst::Move { into, from } => {
317359
let val = self
@@ -328,14 +370,19 @@ impl CheckerState {
328370
&CheckerInst::Op {
329371
ref operands,
330372
ref allocs,
373+
ref clobbers,
331374
..
332375
} => {
333376
for (op, alloc) in operands.iter().zip(allocs.iter()) {
334377
if op.kind() != OperandKind::Def {
335378
continue;
336379
}
380+
let reftyped = checker.reftyped_vregs.contains(&op.vreg());
337381
self.allocations
338-
.insert(*alloc, CheckerValue::Reg(op.vreg(), false));
382+
.insert(*alloc, CheckerValue::Reg(op.vreg(), reftyped));
383+
}
384+
for clobber in clobbers {
385+
self.allocations.remove(&Allocation::reg(*clobber));
339386
}
340387
}
341388
&CheckerInst::BlockParams {
@@ -344,8 +391,20 @@ impl CheckerState {
344391
..
345392
} => {
346393
for (vreg, alloc) in vregs.iter().zip(allocs.iter()) {
394+
let reftyped = checker.reftyped_vregs.contains(vreg);
347395
self.allocations
348-
.insert(*alloc, CheckerValue::Reg(*vreg, false));
396+
.insert(*alloc, CheckerValue::Reg(*vreg, reftyped));
397+
}
398+
}
399+
&CheckerInst::Safepoint { ref slots, .. } => {
400+
for (alloc, value) in &mut self.allocations {
401+
if let CheckerValue::Reg(_, true) = *value {
402+
if alloc.is_reg() {
403+
*value = CheckerValue::Conflicted;
404+
} else if alloc.is_stack() && !slots.contains(&alloc.as_stack().unwrap()) {
405+
*value = CheckerValue::Conflicted;
406+
}
407+
}
349408
}
350409
}
351410
}
@@ -365,6 +424,11 @@ impl CheckerState {
365424
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
366425
}
367426
}
427+
OperandPolicy::Stack => {
428+
if alloc.kind() != AllocationKind::Stack {
429+
return Err(CheckerError::AllocationIsNotStack { inst, op, alloc });
430+
}
431+
}
368432
OperandPolicy::FixedReg(preg) => {
369433
if alloc != Allocation::reg(preg) {
370434
return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
@@ -402,6 +466,7 @@ pub(crate) enum CheckerInst {
402466
inst: Inst,
403467
operands: Vec<Operand>,
404468
allocs: Vec<Allocation>,
469+
clobbers: Vec<PReg>,
405470
},
406471

407472
/// The top of a block with blockparams. We define the given vregs
@@ -411,13 +476,18 @@ pub(crate) enum CheckerInst {
411476
vregs: Vec<VReg>,
412477
allocs: Vec<Allocation>,
413478
},
479+
480+
/// A safepoint, with the given SpillSlots specified as containing
481+
/// reftyped values. All other reftyped values become invalid.
482+
Safepoint { inst: Inst, slots: Vec<SpillSlot> },
414483
}
415484

416485
#[derive(Debug)]
417486
pub struct Checker<'a, F: Function> {
418487
f: &'a F,
419488
bb_in: HashMap<Block, CheckerState>,
420489
bb_insts: HashMap<Block, Vec<CheckerInst>>,
490+
reftyped_vregs: HashSet<VReg>,
421491
}
422492

423493
impl<'a, F: Function> Checker<'a, F> {
@@ -428,20 +498,39 @@ impl<'a, F: Function> Checker<'a, F> {
428498
pub fn new(f: &'a F) -> Checker<'a, F> {
429499
let mut bb_in = HashMap::new();
430500
let mut bb_insts = HashMap::new();
501+
let mut reftyped_vregs = HashSet::new();
431502

432503
for block in 0..f.blocks() {
433504
let block = Block::new(block);
434505
bb_in.insert(block, Default::default());
435506
bb_insts.insert(block, vec![]);
436507
}
437508

438-
Checker { f, bb_in, bb_insts }
509+
for &vreg in f.reftype_vregs() {
510+
reftyped_vregs.insert(vreg);
511+
}
512+
513+
Checker {
514+
f,
515+
bb_in,
516+
bb_insts,
517+
reftyped_vregs,
518+
}
439519
}
440520

441521
/// Build the list of checker instructions based on the given func
442522
/// and allocation results.
443523
pub fn prepare(&mut self, out: &Output) {
444524
debug!("checker: out = {:?}", out);
525+
// Preprocess safepoint stack-maps into per-inst vecs.
526+
let mut safepoint_slots: HashMap<Inst, Vec<SpillSlot>> = HashMap::new();
527+
for &(progpoint, slot) in &out.safepoint_slots {
528+
safepoint_slots
529+
.entry(progpoint.inst)
530+
.or_insert_with(|| vec![])
531+
.push(slot);
532+
}
533+
445534
// For each original instruction, create an `Op`.
446535
let mut last_inst = None;
447536
let mut insert_idx = 0;
@@ -454,13 +543,23 @@ impl<'a, F: Function> Checker<'a, F> {
454543
// Any inserted edits before instruction.
455544
self.handle_edits(block, out, &mut insert_idx, ProgPoint::before(inst));
456545

546+
// If this is a safepoint, then check the spillslots at this point.
547+
if self.f.is_safepoint(inst) {
548+
let slots = safepoint_slots.remove(&inst).unwrap_or_else(|| vec![]);
549+
550+
let checkinst = CheckerInst::Safepoint { inst, slots };
551+
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
552+
}
553+
457554
// Instruction itself.
458555
let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
459556
let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
557+
let clobbers: Vec<_> = self.f.inst_clobbers(inst).iter().cloned().collect();
460558
let checkinst = CheckerInst::Op {
461559
inst,
462560
operands,
463561
allocs,
562+
clobbers,
464563
};
465564
debug!("checker: adding inst {:?}", checkinst);
466565
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
@@ -511,7 +610,7 @@ impl<'a, F: Function> Checker<'a, F> {
511610
let mut state = self.bb_in.get(&block).cloned().unwrap();
512611
debug!("analyze: block {} has state {:?}", block.index(), state);
513612
for inst in self.bb_insts.get(&block).unwrap() {
514-
state.update(inst);
613+
state.update(inst, self);
515614
debug!("analyze: inst {:?} -> state {:?}", inst, state);
516615
}
517616

@@ -546,7 +645,7 @@ impl<'a, F: Function> Checker<'a, F> {
546645
debug!("Checker error: {:?}", e);
547646
errors.push(e);
548647
}
549-
state.update(inst);
648+
state.update(inst, self);
550649
if let Err(e) = state.check(InstPosition::After, inst) {
551650
debug!("Checker error: {:?}", e);
552651
errors.push(e);
@@ -575,6 +674,9 @@ impl<'a, F: Function> Checker<'a, F> {
575674
}
576675
debug!(" {{ {} }}", s.join(", "))
577676
}
677+
for vreg in self.f.reftype_vregs() {
678+
debug!(" REF: {}", vreg);
679+
}
578680
for bb in 0..self.f.blocks() {
579681
let bb = Block::new(bb);
580682
debug!("block{}:", bb.index());
@@ -587,8 +689,15 @@ impl<'a, F: Function> Checker<'a, F> {
587689
inst,
588690
ref operands,
589691
ref allocs,
692+
ref clobbers,
590693
} => {
591-
debug!(" inst{}: {:?} ({:?})", inst.index(), operands, allocs);
694+
debug!(
695+
" inst{}: {:?} ({:?}) clobbers:{:?}",
696+
inst.index(),
697+
operands,
698+
allocs,
699+
clobbers
700+
);
592701
}
593702
&CheckerInst::Move { from, into } => {
594703
debug!(" {} -> {}", from, into);
@@ -604,8 +713,15 @@ impl<'a, F: Function> Checker<'a, F> {
604713
}
605714
debug!(" blockparams: {}", args.join(", "));
606715
}
716+
&CheckerInst::Safepoint { ref slots, .. } => {
717+
let mut slotargs = vec![];
718+
for &slot in slots {
719+
slotargs.push(format!("{}", slot));
720+
}
721+
debug!(" safepoint: {}", slotargs.join(", "));
722+
}
607723
}
608-
state.update(inst);
724+
state.update(inst, &self);
609725
print_state(&state);
610726
}
611727
}

0 commit comments

Comments
 (0)