1
Fork 0

Generate region values directly to reduce memory usage.

Also modify `SparseBitMatrix` so that it does not require knowing the
dimensions in advance, but instead grows on demand.
This commit is contained in:
David Wood 2018-07-09 21:26:20 +01:00 committed by Niko Matsakis
parent bce32b532d
commit 8b94d1605b
9 changed files with 174 additions and 121 deletions

View file

@ -281,10 +281,10 @@ where
}
impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
/// Create a new `rows x columns` matrix, initially empty.
pub fn new(rows: R, _columns: C) -> SparseBitMatrix<R, C> {
SparseBitMatrix {
vector: IndexVec::from_elem_n(SparseBitSet::new(), rows.index()),
/// Create a new empty sparse bit matrix with no rows or columns.
pub fn new() -> Self {
Self {
vector: IndexVec::new(),
}
}
@ -293,6 +293,14 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
///
/// Returns true if this changed the matrix, and false otherwise.
pub fn add(&mut self, row: R, column: C) -> bool {
debug!(
"add(row={:?}, column={:?}, current_len={})",
row,
column,
self.vector.len()
);
self.vector
.ensure_contains_elem(row, || SparseBitSet::new());
self.vector[row].insert(column)
}
@ -301,7 +309,7 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
/// if the matrix represents (transitive) reachability, can
/// `row` reach `column`?
pub fn contains(&self, row: R, column: C) -> bool {
self.vector[row].contains(column)
self.vector.get(row).map_or(false, |r| r.contains(column))
}
/// Add the bits from row `read` to the bits from row `write`,
@ -315,16 +323,27 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
let mut changed = false;
if read != write {
let (bit_set_read, bit_set_write) = self.vector.pick2_mut(read, write);
if self.vector.get(read).is_some() {
self.vector
.ensure_contains_elem(write, || SparseBitSet::new());
let (bit_set_read, bit_set_write) = self.vector.pick2_mut(read, write);
for read_chunk in bit_set_read.chunks() {
changed = changed | bit_set_write.insert_chunk(read_chunk).any();
for read_chunk in bit_set_read.chunks() {
changed = changed | bit_set_write.insert_chunk(read_chunk).any();
}
}
}
changed
}
/// Merge a row, `from`, into the `into` row.
pub fn merge_into(&mut self, into: R, from: &SparseBitSet<C>) -> bool {
self.vector
.ensure_contains_elem(into, || SparseBitSet::new());
self.vector[into].insert_from(from)
}
/// True if `sub` is a subset of `sup`
pub fn is_subset(&self, sub: R, sup: R) -> bool {
sub == sup || {
@ -336,10 +355,20 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
}
}
/// Number of elements in the matrix.
pub fn len(&self) -> usize {
self.vector.len()
}
/// Iterates through all the columns set to true in a given row of
/// the matrix.
pub fn iter<'a>(&'a self, row: R) -> impl Iterator<Item = C> + 'a {
self.vector[row].iter()
self.vector.get(row).into_iter().flat_map(|r| r.iter())
}
/// Iterates through each row and the accompanying bit set.
pub fn iter_enumerated<'a>(&'a self) -> impl Iterator<Item = (R, &'a SparseBitSet<C>)> + 'a {
self.vector.iter_enumerated()
}
}
@ -445,6 +474,15 @@ impl<I: Idx> SparseBitSet<I> {
}
}
/// Insert into bit set from another bit set.
pub fn insert_from(&mut self, from: &SparseBitSet<I>) -> bool {
let mut changed = false;
for read_chunk in from.chunks() {
changed = changed | self.insert_chunk(read_chunk).any();
}
changed
}
pub fn remove_chunk(&mut self, chunk: SparseChunk<I>) -> SparseChunk<I> {
if chunk.bits == 0 {
return chunk;

View file

@ -518,10 +518,28 @@ impl<I: Idx, T> IndexVec<I, T> {
}
impl<I: Idx, T: Clone> IndexVec<I, T> {
/// Grows the index vector so that it contains an entry for
/// `elem`; if that is already true, then has no
/// effect. Otherwise, inserts new values as needed by invoking
/// `fill_value`.
#[inline]
pub fn ensure_contains_elem(&mut self, elem: I, fill_value: impl FnMut() -> T) {
let min_new_len = elem.index() + 1;
if self.len() < min_new_len {
self.raw.resize_with(min_new_len, fill_value);
}
}
#[inline]
pub fn resize(&mut self, new_len: usize, value: T) {
self.raw.resize(new_len, value)
}
#[inline]
pub fn resize_to_elem(&mut self, elem: I, fill_value: impl FnMut() -> T) {
let min_new_len = elem.index() + 1;
self.raw.resize_with(min_new_len, fill_value);
}
}
impl<I: Idx, T: Ord> IndexVec<I, T> {

View file

@ -30,6 +30,7 @@
#![feature(optin_builtin_traits)]
#![feature(macro_vis_matcher)]
#![feature(allow_internal_unstable)]
#![feature(vec_resize_with)]
#![cfg_attr(unix, feature(libc))]
#![cfg_attr(test, feature(test))]

View file

@ -21,7 +21,6 @@ use rustc::mir::{Local, Statement, Terminator};
use rustc::ty::fold::TypeFoldable;
use rustc::ty::subst::Substs;
use rustc::ty::{self, CanonicalTy, ClosureSubsts, GeneratorSubsts};
use std::iter;
pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
@ -30,7 +29,6 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
location_table: &LocationTable,
mir: &Mir<'tcx>,
borrow_set: &BorrowSet<'tcx>,
liveness_set_from_typeck: &[(ty::Region<'tcx>, Location)],
) {
let mut cg = ConstraintGeneration {
borrow_set,
@ -40,8 +38,6 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
all_facts,
};
cg.add_region_liveness_constraints_from_type_check(liveness_set_from_typeck);
for (bb, data) in mir.basic_blocks().iter_enumerated() {
cg.visit_basic_block_data(bb, data);
}
@ -189,42 +185,6 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx
}
impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
/// The MIR type checker generates region liveness constraints
/// that we also have to respect.
fn add_region_liveness_constraints_from_type_check(
&mut self,
liveness_set: &[(ty::Region<'tcx>, Location)],
) {
debug!(
"add_region_liveness_constraints_from_type_check(liveness_set={} items)",
liveness_set.len(),
);
let ConstraintGeneration {
regioncx,
location_table,
all_facts,
..
} = self;
for (region, location) in liveness_set {
debug!("generate: {:#?} is live at {:#?}", region, location);
let region_vid = regioncx.to_region_vid(region);
regioncx.add_live_element(region_vid, *location);
}
if let Some(all_facts) = all_facts {
all_facts
.region_live_at
.extend(liveness_set.into_iter().flat_map(|(region, location)| {
let r = regioncx.to_region_vid(region);
let p1 = location_table.start_index(*location);
let p2 = location_table.mid_index(*location);
iter::once((r, p1)).chain(iter::once((r, p2)))
}));
}
}
/// Some variable with type `live_ty` is "regular live" at
/// `location` -- i.e., it may be used later. This means that all
/// regions appearing in the type `live_ty` must be live at

View file

@ -12,6 +12,7 @@ use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::{LocationIndex, LocationTable};
use borrow_check::nll::facts::AllFactsExt;
use borrow_check::nll::type_check::MirTypeckRegionConstraints;
use borrow_check::nll::region_infer::values::RegionValueElements;
use dataflow::indexes::BorrowIndex;
use dataflow::move_paths::MoveData;
use dataflow::FlowAtLocation;
@ -99,6 +100,8 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
None
};
let elements = &Rc::new(RegionValueElements::new(mir, universal_regions.len()));
// Run the MIR type-checker.
let liveness = &LivenessResults::compute(mir);
let constraint_sets = type_check::type_check(
@ -113,6 +116,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
&mut all_facts,
flow_inits,
move_data,
elements,
);
if let Some(all_facts) = &mut all_facts {
@ -126,7 +130,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
// base constraints generated by the type-check.
let var_origins = infcx.take_region_var_origins();
let MirTypeckRegionConstraints {
liveness_set,
liveness_constraints,
outlives_constraints,
type_tests,
} = constraint_sets;
@ -136,6 +140,8 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
mir,
outlives_constraints,
type_tests,
liveness_constraints,
elements,
);
// Generate various additional constraints.
@ -146,7 +152,6 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
location_table,
&mir,
borrow_set,
&liveness_set,
);
invalidation::generate_invalidates(
infcx,

View file

@ -37,7 +37,7 @@ mod annotation;
mod dump_mir;
mod error_reporting;
mod graphviz;
mod values;
pub mod values;
use self::values::{RegionValueElements, RegionValues};
use super::ToRegionVid;
@ -66,8 +66,8 @@ pub struct RegionInferenceContext<'tcx> {
/// the SCC (see `constraint_sccs`) and for error reporting.
constraint_graph: Rc<ConstraintGraph>,
/// The SCC computed from `constraints` and
/// `constraint_graph`. Used to compute the values of each region.
/// The SCC computed from `constraints` and the constraint graph. Used to compute the values
/// of each region.
constraint_sccs: Rc<Sccs<RegionVid, ConstraintSccIndex>>,
/// The final inferred values of the region variables; we compute
@ -207,15 +207,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
pub(crate) fn new(
var_infos: VarInfos,
universal_regions: UniversalRegions<'tcx>,
mir: &Mir<'tcx>,
_mir: &Mir<'tcx>,
outlives_constraints: ConstraintSet,
type_tests: Vec<TypeTest<'tcx>>,
liveness_constraints: RegionValues<RegionVid>,
elements: &Rc<RegionValueElements>,
) -> Self {
let universal_regions = Rc::new(universal_regions);
let num_region_variables = var_infos.len();
let num_universal_regions = universal_regions.len();
let elements = &Rc::new(RegionValueElements::new(mir, num_universal_regions));
// Create a RegionDefinition for each inference variable.
let definitions: IndexVec<_, _> = var_infos
@ -227,15 +225,20 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let constraint_graph = Rc::new(constraints.graph(definitions.len()));
let constraint_sccs = Rc::new(constraints.compute_sccs(&constraint_graph));
let scc_values = RegionValues::new(elements, constraint_sccs.num_sccs());
let mut scc_values = RegionValues::new(elements);
for (region, location_set) in liveness_constraints.iter_enumerated() {
let scc = constraint_sccs.scc(region);
scc_values.merge_into(scc, location_set);
}
let mut result = Self {
definitions,
elements: elements.clone(),
liveness_constraints: RegionValues::new(elements, num_region_variables),
liveness_constraints,
constraints,
constraint_sccs,
constraint_graph,
constraint_sccs,
scc_values,
type_tests,
universal_regions,
@ -414,7 +417,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
constraints
});
// To propagate constriants, we walk the DAG induced by the
// To propagate constraints, we walk the DAG induced by the
// SCC. For each SCC, we visit its successors and compute
// their values, then we union all those values to get our
// own.

View file

@ -10,7 +10,7 @@
use rustc::mir::{BasicBlock, Location, Mir};
use rustc::ty::RegionVid;
use rustc_data_structures::bitvec::SparseBitMatrix;
use rustc_data_structures::bitvec::{SparseBitMatrix, SparseBitSet};
use rustc_data_structures::indexed_vec::Idx;
use rustc_data_structures::indexed_vec::IndexVec;
use std::fmt::Debug;
@ -55,11 +55,6 @@ impl RegionValueElements {
}
}
/// Total number of element indices that exist.
crate fn num_elements(&self) -> usize {
self.num_points + self.num_universal_regions
}
/// Converts an element of a region value into a `RegionElementIndex`.
crate fn index<T: ToElementIndex>(&self, elem: T) -> RegionElementIndex {
elem.to_element_index(self)
@ -188,18 +183,10 @@ impl<N: Idx> RegionValues<N> {
/// Creates a new set of "region values" that tracks causal information.
/// Each of the regions in num_region_variables will be initialized with an
/// empty set of points and no causal information.
crate fn new(elements: &Rc<RegionValueElements>, num_region_variables: usize) -> Self {
assert!(
elements.num_universal_regions <= num_region_variables,
"universal regions are a subset of the region variables"
);
crate fn new(elements: &Rc<RegionValueElements>) -> Self {
Self {
elements: elements.clone(),
matrix: SparseBitMatrix::new(
N::new(num_region_variables),
RegionElementIndex::new(elements.num_elements()),
),
matrix: SparseBitMatrix::new(),
}
}
@ -227,6 +214,18 @@ impl<N: Idx> RegionValues<N> {
self.matrix.contains(r, i)
}
/// Iterates through each row and the accompanying bit set.
pub fn iter_enumerated<'a>(
&'a self
) -> impl Iterator<Item = (N, &'a SparseBitSet<RegionElementIndex>)> + 'a {
self.matrix.iter_enumerated()
}
/// Merge a row, `from`, originating in another `RegionValues` into the `into` row.
pub fn merge_into(&mut self, into: N, from: &SparseBitSet<RegionElementIndex>) -> bool {
self.matrix.merge_into(into, from)
}
/// True if `sup_region` contains all the CFG points that
/// `sub_region` contains. Ignores universal regions.
crate fn contains_points(&self, sup_region: N, sub_region: N) -> bool {

View file

@ -168,7 +168,18 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
);
cx.tcx().for_each_free_region(&value, |live_region| {
cx.constraints.liveness_set.push((live_region, location));
if let Some(ref mut borrowck_context) = cx.borrowck_context {
let region_vid = borrowck_context.universal_regions.to_region_vid(live_region);
borrowck_context.constraints.liveness_constraints.add_element(region_vid, location);
if let Some(all_facts) = borrowck_context.all_facts {
let start_index = borrowck_context.location_table.start_index(location);
all_facts.region_live_at.push((region_vid, start_index));
let mid_index = borrowck_context.location_table.mid_index(location);
all_facts.region_live_at.push((region_vid, mid_index));
}
}
});
}

View file

@ -16,6 +16,7 @@ use borrow_check::location::LocationTable;
use borrow_check::nll::constraints::{ConstraintSet, OutlivesConstraint};
use borrow_check::nll::facts::AllFacts;
use borrow_check::nll::region_infer::{ClosureRegionRequirementsExt, TypeTest};
use borrow_check::nll::region_infer::values::{RegionValues, RegionValueElements};
use borrow_check::nll::universal_regions::UniversalRegions;
use borrow_check::nll::ToRegionVid;
use dataflow::move_paths::MoveData;
@ -33,8 +34,9 @@ use rustc::mir::*;
use rustc::traits::query::type_op;
use rustc::traits::query::{Fallible, NoSolution};
use rustc::ty::fold::TypeFoldable;
use rustc::ty::{self, ToPolyTraitRef, Ty, TyCtxt, TypeVariants};
use rustc::ty::{self, ToPolyTraitRef, Ty, TyCtxt, TypeVariants, RegionVid};
use std::fmt;
use std::rc::Rc;
use syntax_pos::{Span, DUMMY_SP};
use transform::{MirPass, MirSource};
use util::liveness::LivenessResults;
@ -111,39 +113,55 @@ pub(crate) fn type_check<'gcx, 'tcx>(
all_facts: &mut Option<AllFacts>,
flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
move_data: &MoveData<'tcx>,
elements: &Rc<RegionValueElements>,
) -> MirTypeckRegionConstraints<'tcx> {
let implicit_region_bound = infcx.tcx.mk_region(ty::ReVar(universal_regions.fr_fn_body));
type_check_internal(
infcx,
mir_def_id,
param_env,
mir,
&universal_regions.region_bound_pairs,
Some(implicit_region_bound),
Some(BorrowCheckContext {
let mut constraints = MirTypeckRegionConstraints {
liveness_constraints: RegionValues::new(elements),
outlives_constraints: ConstraintSet::default(),
type_tests: Vec::default(),
};
{
let mut borrowck_context = BorrowCheckContext {
universal_regions,
location_table,
borrow_set,
all_facts,
}),
&mut |cx| {
liveness::generate(cx, mir, liveness, flow_inits, move_data);
constraints: &mut constraints,
};
cx.equate_inputs_and_outputs(mir, mir_def_id, universal_regions);
},
)
type_check_internal(
infcx,
mir_def_id,
param_env,
mir,
&universal_regions.region_bound_pairs,
Some(implicit_region_bound),
Some(&mut borrowck_context),
|cx| {
liveness::generate(cx, mir, liveness, flow_inits, move_data);
cx.equate_inputs_and_outputs(mir, mir_def_id, universal_regions);
},
);
}
constraints
}
fn type_check_internal<'gcx, 'tcx>(
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
fn type_check_internal<'a, 'gcx, 'tcx, F>(
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
mir_def_id: DefId,
param_env: ty::ParamEnv<'gcx>,
mir: &Mir<'tcx>,
region_bound_pairs: &[(ty::Region<'tcx>, GenericKind<'tcx>)],
mir: &'a Mir<'tcx>,
region_bound_pairs: &'a [(ty::Region<'tcx>, GenericKind<'tcx>)],
implicit_region_bound: Option<ty::Region<'tcx>>,
borrowck_context: Option<BorrowCheckContext<'_, 'tcx>>,
extra: &mut dyn FnMut(&mut TypeChecker<'_, 'gcx, 'tcx>),
) -> MirTypeckRegionConstraints<'tcx> {
borrowck_context: Option<&'a mut BorrowCheckContext<'a, 'tcx>>,
mut extra: F,
)
where F: FnMut(&mut TypeChecker<'a, 'gcx, 'tcx>)
{
let mut checker = TypeChecker::new(
infcx,
mir,
@ -165,8 +183,6 @@ fn type_check_internal<'gcx, 'tcx>(
}
extra(&mut checker);
checker.constraints
}
fn mirbug(tcx: TyCtxt, span: Span, msg: &str) {
@ -603,8 +619,7 @@ struct TypeChecker<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
region_bound_pairs: &'a [(ty::Region<'tcx>, GenericKind<'tcx>)],
implicit_region_bound: Option<ty::Region<'tcx>>,
reported_errors: FxHashSet<(Ty<'tcx>, Span)>,
constraints: MirTypeckRegionConstraints<'tcx>,
borrowck_context: Option<BorrowCheckContext<'a, 'tcx>>,
borrowck_context: Option<&'a mut BorrowCheckContext<'a, 'tcx>>,
}
struct BorrowCheckContext<'a, 'tcx: 'a> {
@ -612,11 +627,11 @@ struct BorrowCheckContext<'a, 'tcx: 'a> {
location_table: &'a LocationTable,
all_facts: &'a mut Option<AllFacts>,
borrow_set: &'a BorrowSet<'tcx>,
constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
}
/// A collection of region constraints that must be satisfied for the
/// program to be considered well-typed.
#[derive(Default)]
crate struct MirTypeckRegionConstraints<'tcx> {
/// In general, the type-checker is not responsible for enforcing
/// liveness constraints; this job falls to the region inferencer,
@ -625,7 +640,7 @@ crate struct MirTypeckRegionConstraints<'tcx> {
/// not otherwise appear in the MIR -- in particular, the
/// late-bound regions that it instantiates at call-sites -- and
/// hence it must report on their liveness constraints.
crate liveness_set: Vec<(ty::Region<'tcx>, Location)>,
crate liveness_constraints: RegionValues<RegionVid>,
crate outlives_constraints: ConstraintSet,
@ -717,7 +732,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
param_env: ty::ParamEnv<'gcx>,
region_bound_pairs: &'a [(ty::Region<'tcx>, GenericKind<'tcx>)],
implicit_region_bound: Option<ty::Region<'tcx>>,
borrowck_context: Option<BorrowCheckContext<'a, 'tcx>>,
borrowck_context: Option<&'a mut BorrowCheckContext<'a, 'tcx>>,
) -> Self {
TypeChecker {
infcx,
@ -729,7 +744,6 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
implicit_region_bound,
borrowck_context,
reported_errors: FxHashSet(),
constraints: MirTypeckRegionConstraints::default(),
}
}
@ -767,7 +781,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
locations, data
);
if let Some(borrowck_context) = &mut self.borrowck_context {
if let Some(ref mut borrowck_context) = self.borrowck_context {
constraint_conversion::ConstraintConversion::new(
self.infcx.tcx,
borrowck_context.universal_regions,
@ -776,8 +790,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.implicit_region_bound,
self.param_env,
locations,
&mut self.constraints.outlives_constraints,
&mut self.constraints.type_tests,
&mut borrowck_context.constraints.outlives_constraints,
&mut borrowck_context.constraints.type_tests,
&mut borrowck_context.all_facts,
).convert_all(&data);
}
@ -993,9 +1007,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// output) types in the signature must be live, since
// all the inputs that fed into it were live.
for &late_bound_region in map.values() {
self.constraints
.liveness_set
.push((late_bound_region, term_location));
if let Some(ref mut borrowck_context) = self.borrowck_context {
let region_vid = borrowck_context.universal_regions.to_region_vid(
late_bound_region);
borrowck_context.constraints
.liveness_constraints
.add_element(region_vid, term_location);
}
}
self.check_call_inputs(mir, term, &sig, args, term_location);
@ -1487,9 +1505,10 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
borrow_set,
location_table,
all_facts,
constraints,
..
} = match &mut self.borrowck_context {
Some(borrowck_context) => borrowck_context,
} = match self.borrowck_context {
Some(ref mut borrowck_context) => borrowck_context,
None => return,
};
@ -1531,7 +1550,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
debug!("add_reborrow_constraint - base_ty = {:?}", base_ty);
match base_ty.sty {
ty::TyRef(ref_region, _, mutbl) => {
self.constraints
constraints
.outlives_constraints
.push(OutlivesConstraint {
sup: ref_region.to_region_vid(),
@ -1792,8 +1811,7 @@ impl MirPass for TypeckMir {
let param_env = tcx.param_env(def_id);
tcx.infer_ctxt().enter(|infcx| {
let _ =
type_check_internal(&infcx, def_id, param_env, mir, &[], None, None, &mut |_| ());
type_check_internal(&infcx, def_id, param_env, mir, &[], None, None, |_| ());
// For verification purposes, we just ignore the resulting
// region constraint sets. Not our problem. =)