split into two matrices
This commit is contained in:
parent
40498bae65
commit
3f0fb4f7d8
|
@ -43,12 +43,27 @@ impl<C: Idx> BitVector<C> {
|
|||
self.data.iter().map(|e| e.count_ones() as usize).sum()
|
||||
}
|
||||
|
||||
/// True if `self` contains the bit `bit`.
|
||||
#[inline]
|
||||
pub fn contains(&self, bit: C) -> bool {
|
||||
let (word, mask) = word_mask(bit);
|
||||
(self.data[word] & mask) != 0
|
||||
}
|
||||
|
||||
/// True if `self` contains all the bits in `other`.
|
||||
///
|
||||
/// The two vectors must have the same length.
|
||||
#[inline]
|
||||
pub fn contains_all(&self, other: &BitVector<C>) -> bool {
|
||||
assert_eq!(self.data.len(), other.data.len());
|
||||
self.data.iter().zip(&other.data).all(|(a, b)| (a & b) == *b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.iter().all(|a| *a == 0)
|
||||
}
|
||||
|
||||
/// Returns true if the bit has changed.
|
||||
#[inline]
|
||||
pub fn insert(&mut self, bit: C) -> bool {
|
||||
|
@ -349,6 +364,10 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
|
|||
self.vector.len()
|
||||
}
|
||||
|
||||
pub fn rows(&self) -> impl Iterator<Item = R> {
|
||||
self.vector.indices()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -522,3 +541,45 @@ fn matrix_iter() {
|
|||
}
|
||||
assert!(iter.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sparse_matrix_iter() {
|
||||
let mut matrix = SparseBitMatrix::new(64, 100);
|
||||
matrix.add(3, 22);
|
||||
matrix.add(3, 75);
|
||||
matrix.add(2, 99);
|
||||
matrix.add(4, 0);
|
||||
matrix.merge(3, 5);
|
||||
|
||||
let expected = [99];
|
||||
let mut iter = expected.iter();
|
||||
for i in matrix.iter(2) {
|
||||
let j = *iter.next().unwrap();
|
||||
assert_eq!(i, j);
|
||||
}
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
let expected = [22, 75];
|
||||
let mut iter = expected.iter();
|
||||
for i in matrix.iter(3) {
|
||||
let j = *iter.next().unwrap();
|
||||
assert_eq!(i, j);
|
||||
}
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
let expected = [0];
|
||||
let mut iter = expected.iter();
|
||||
for i in matrix.iter(4) {
|
||||
let j = *iter.next().unwrap();
|
||||
assert_eq!(i, j);
|
||||
}
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
let expected = [22, 75];
|
||||
let mut iter = expected.iter();
|
||||
for i in matrix.iter(5) {
|
||||
let j = *iter.next().unwrap();
|
||||
assert_eq!(i, j);
|
||||
}
|
||||
assert!(iter.next().is_none());
|
||||
}
|
||||
|
|
|
@ -48,9 +48,6 @@ pub struct RegionInferenceContext<'tcx> {
|
|||
/// from as well as its final inferred value.
|
||||
definitions: IndexVec<RegionVid, RegionDefinition<'tcx>>,
|
||||
|
||||
/// Maps from points/universal-regions to a `RegionElementIndex`.
|
||||
elements: Rc<RegionValueElements>,
|
||||
|
||||
/// The liveness constraints added to each region. For most
|
||||
/// regions, these start out empty and steadily grow, though for
|
||||
/// each universally quantified region R they start out containing
|
||||
|
@ -219,14 +216,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
|
||||
let mut scc_values = RegionValues::new(elements);
|
||||
|
||||
for (region, location_set) in liveness_constraints.iter_enumerated() {
|
||||
for region in liveness_constraints.regions_with_points() {
|
||||
let scc = constraint_sccs.scc(region);
|
||||
scc_values.merge_into(scc, location_set);
|
||||
scc_values.merge_row(scc, region, &liveness_constraints);
|
||||
}
|
||||
|
||||
let mut result = Self {
|
||||
definitions,
|
||||
elements: elements.clone(),
|
||||
liveness_constraints,
|
||||
constraints,
|
||||
constraint_graph,
|
||||
|
@ -273,7 +269,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
}
|
||||
|
||||
// For each universally quantified region X:
|
||||
let elements = self.elements.clone();
|
||||
let universal_regions = self.universal_regions.clone();
|
||||
for variable in universal_regions.universal_regions() {
|
||||
// These should be free-region variables.
|
||||
|
@ -283,9 +278,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
});
|
||||
|
||||
// Add all nodes in the CFG to liveness constraints
|
||||
for point_index in elements.all_point_indices() {
|
||||
self.add_live_element(variable, point_index);
|
||||
}
|
||||
let variable_scc = self.constraint_sccs.scc(variable);
|
||||
self.liveness_constraints.add_all_points(variable);
|
||||
self.scc_values.add_all_points(variable_scc);
|
||||
|
||||
// Add `end(X)` into the set for X.
|
||||
self.add_live_element(variable, variable);
|
||||
|
@ -782,7 +777,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
// now). Therefore, the sup-region outlives the sub-region if,
|
||||
// for each universal region R1 in the sub-region, there
|
||||
// exists some region R2 in the sup-region that outlives R1.
|
||||
let universal_outlives = self.scc_values
|
||||
let universal_outlives = self
|
||||
.scc_values
|
||||
.universal_regions_outlived_by(sub_region_scc)
|
||||
.all(|r1| {
|
||||
self.scc_values
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
|
||||
use rustc::mir::{BasicBlock, Location, Mir};
|
||||
use rustc::ty::RegionVid;
|
||||
use rustc_data_structures::bitvec::{BitVector, SparseBitMatrix};
|
||||
use rustc_data_structures::bitvec::SparseBitMatrix;
|
||||
use rustc_data_structures::indexed_vec::Idx;
|
||||
use rustc_data_structures::indexed_vec::IndexVec;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Maps between the various kinds of elements of a region value to
|
||||
/// the internal indices that w use.
|
||||
|
@ -50,90 +51,65 @@ impl RegionValueElements {
|
|||
|
||||
Self {
|
||||
statements_before_block,
|
||||
num_universal_regions,
|
||||
num_points,
|
||||
num_universal_regions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of element indices that exist.
|
||||
crate fn num_elements(&self) -> usize {
|
||||
self.num_points + self.num_universal_regions
|
||||
fn point_from_location(&self, location: Location) -> PointIndex {
|
||||
let Location {
|
||||
block,
|
||||
statement_index,
|
||||
} = location;
|
||||
let start_index = self.statements_before_block[block];
|
||||
PointIndex::new(start_index + statement_index)
|
||||
}
|
||||
|
||||
/// Converts an element of a region value into a `RegionElementIndex`.
|
||||
crate fn index<T: ToElementIndex>(&self, elem: T) -> RegionElementIndex {
|
||||
elem.to_element_index(self)
|
||||
/// Range coverting all point indices.
|
||||
fn all_points(&self) -> Range<PointIndex> {
|
||||
PointIndex::new(0)..PointIndex::new(self.num_points)
|
||||
}
|
||||
|
||||
/// Iterates over the `RegionElementIndex` for all points in the CFG.
|
||||
crate fn all_point_indices<'a>(&'a self) -> impl Iterator<Item = RegionElementIndex> + 'a {
|
||||
(0..self.num_points).map(move |i| RegionElementIndex::new(i + self.num_universal_regions))
|
||||
}
|
||||
/// Converts a particular `RegionElementIndex` to a location, if
|
||||
/// that is what it represents. Returns `None` otherwise.
|
||||
crate fn to_location(&self, i: PointIndex) -> Location {
|
||||
let point_index = i.index();
|
||||
|
||||
/// Converts a particular `RegionElementIndex` to the `RegionElement` it represents.
|
||||
crate fn to_element(&self, i: RegionElementIndex) -> RegionElement {
|
||||
debug!("to_element(i={:?})", i);
|
||||
// Find the basic block. We have a vector with the
|
||||
// starting index of the statement in each block. Imagine
|
||||
// we have statement #22, and we have a vector like:
|
||||
//
|
||||
// [0, 10, 20]
|
||||
//
|
||||
// In that case, this represents point_index 2 of
|
||||
// basic block BB2. We know this because BB0 accounts for
|
||||
// 0..10, BB1 accounts for 11..20, and BB2 accounts for
|
||||
// 20...
|
||||
//
|
||||
// To compute this, we could do a binary search, but
|
||||
// because I am lazy we instead iterate through to find
|
||||
// the last point where the "first index" (0, 10, or 20)
|
||||
// was less than the statement index (22). In our case, this will
|
||||
// be (BB2, 20).
|
||||
//
|
||||
// Nit: we could do a binary search here but I'm too lazy.
|
||||
let (block, &first_index) = self
|
||||
.statements_before_block
|
||||
.iter_enumerated()
|
||||
.filter(|(_, first_index)| **first_index <= point_index)
|
||||
.last()
|
||||
.unwrap();
|
||||
|
||||
if let Some(r) = self.to_universal_region(i) {
|
||||
RegionElement::UniversalRegion(r)
|
||||
} else {
|
||||
let point_index = i.index() - self.num_universal_regions;
|
||||
|
||||
// Find the basic block. We have a vector with the
|
||||
// starting index of the statement in each block. Imagine
|
||||
// we have statement #22, and we have a vector like:
|
||||
//
|
||||
// [0, 10, 20]
|
||||
//
|
||||
// In that case, this represents point_index 2 of
|
||||
// basic block BB2. We know this because BB0 accounts for
|
||||
// 0..10, BB1 accounts for 11..20, and BB2 accounts for
|
||||
// 20...
|
||||
//
|
||||
// To compute this, we could do a binary search, but
|
||||
// because I am lazy we instead iterate through to find
|
||||
// the last point where the "first index" (0, 10, or 20)
|
||||
// was less than the statement index (22). In our case, this will
|
||||
// be (BB2, 20).
|
||||
//
|
||||
// Nit: we could do a binary search here but I'm too lazy.
|
||||
let (block, &first_index) = self
|
||||
.statements_before_block
|
||||
.iter_enumerated()
|
||||
.filter(|(_, first_index)| **first_index <= point_index)
|
||||
.last()
|
||||
.unwrap();
|
||||
|
||||
RegionElement::Location(Location {
|
||||
block,
|
||||
statement_index: point_index - first_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a particular `RegionElementIndex` to a universal
|
||||
/// region, if that is what it represents. Returns `None`
|
||||
/// otherwise.
|
||||
crate fn to_universal_region(&self, i: RegionElementIndex) -> Option<RegionVid> {
|
||||
if i.index() < self.num_universal_regions {
|
||||
Some(RegionVid::new(i.index()))
|
||||
} else {
|
||||
None
|
||||
Location {
|
||||
block,
|
||||
statement_index: point_index - first_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A newtype for the integers that represent one of the possible
|
||||
/// elements in a region. These are the rows in the `SparseBitMatrix` that
|
||||
/// is used to store the values of all regions. They have the following
|
||||
/// convention:
|
||||
///
|
||||
/// - The first N indices represent free regions (where N = universal_regions.len()).
|
||||
/// - The remainder represent the points in the CFG (see `point_indices` map).
|
||||
///
|
||||
/// You can convert a `RegionElementIndex` into a `RegionElement`
|
||||
/// using the `to_region_elem` method.
|
||||
newtype_index!(RegionElementIndex { DEBUG_FORMAT = "RegionElementIndex({})" });
|
||||
/// A single integer representing a `Location` in the MIR control-flow
|
||||
/// graph. Constructed efficiently from `RegionValueElements`.
|
||||
newtype_index!(PointIndex { DEBUG_FORMAT = "PointIndex({})" });
|
||||
|
||||
/// An individual element in a region value -- the value of a
|
||||
/// particular region variable consists of a set of these elements.
|
||||
|
@ -142,36 +118,9 @@ crate enum RegionElement {
|
|||
/// A point in the control-flow graph.
|
||||
Location(Location),
|
||||
|
||||
/// An in-scope, universally quantified region (e.g., a lifetime parameter).
|
||||
UniversalRegion(RegionVid),
|
||||
}
|
||||
|
||||
crate trait ToElementIndex: Debug + Copy {
|
||||
fn to_element_index(self, elements: &RegionValueElements) -> RegionElementIndex;
|
||||
}
|
||||
|
||||
impl ToElementIndex for Location {
|
||||
fn to_element_index(self, elements: &RegionValueElements) -> RegionElementIndex {
|
||||
let Location {
|
||||
block,
|
||||
statement_index,
|
||||
} = self;
|
||||
let start_index = elements.statements_before_block[block];
|
||||
RegionElementIndex::new(elements.num_universal_regions + start_index + statement_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToElementIndex for RegionVid {
|
||||
fn to_element_index(self, elements: &RegionValueElements) -> RegionElementIndex {
|
||||
assert!(self.index() < elements.num_universal_regions);
|
||||
RegionElementIndex::new(self.index())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToElementIndex for RegionElementIndex {
|
||||
fn to_element_index(self, _elements: &RegionValueElements) -> RegionElementIndex {
|
||||
self
|
||||
}
|
||||
/// A universally quantified region from the root universe (e.g.,
|
||||
/// a lifetime parameter).
|
||||
RootUniversalRegion(RegionVid),
|
||||
}
|
||||
|
||||
/// Stores the values for a set of regions. These are stored in a
|
||||
|
@ -181,7 +130,8 @@ impl ToElementIndex for RegionElementIndex {
|
|||
#[derive(Clone)]
|
||||
crate struct RegionValues<N: Idx> {
|
||||
elements: Rc<RegionValueElements>,
|
||||
matrix: SparseBitMatrix<N, RegionElementIndex>,
|
||||
points: SparseBitMatrix<N, PointIndex>,
|
||||
free_regions: SparseBitMatrix<N, RegionVid>,
|
||||
}
|
||||
|
||||
impl<N: Idx> RegionValues<N> {
|
||||
|
@ -191,7 +141,8 @@ impl<N: Idx> RegionValues<N> {
|
|||
crate fn new(elements: &Rc<RegionValueElements>) -> Self {
|
||||
Self {
|
||||
elements: elements.clone(),
|
||||
matrix: SparseBitMatrix::new(elements.num_elements()),
|
||||
points: SparseBitMatrix::new(elements.num_points),
|
||||
free_regions: SparseBitMatrix::new(elements.num_universal_regions),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,53 +153,83 @@ impl<N: Idx> RegionValues<N> {
|
|||
r: N,
|
||||
elem: impl ToElementIndex,
|
||||
) -> bool {
|
||||
let i = self.elements.index(elem);
|
||||
debug!("add(r={:?}, elem={:?})", r, elem);
|
||||
self.matrix.add(r, i)
|
||||
elem.add_to_row(self, r)
|
||||
}
|
||||
|
||||
/// Adds all the control-flow points to the values for `r`.
|
||||
crate fn add_all_points(&mut self, r: N) {
|
||||
// FIXME OMG so inefficient. We'll fix later.
|
||||
for p in self.elements.all_points() {
|
||||
self.points.add(r, p);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all elements in `r_from` to `r_to` (because e.g. `r_to:
|
||||
/// r_from`).
|
||||
crate fn add_region(&mut self, r_to: N, r_from: N) -> bool {
|
||||
self.matrix.merge(r_from, r_to)
|
||||
self.points.merge(r_from, r_to) | self.free_regions.merge(r_from, r_to)
|
||||
// FIXME universes?
|
||||
}
|
||||
|
||||
/// True if the region `r` contains the given element.
|
||||
crate fn contains(&self, r: N, elem: impl ToElementIndex) -> bool {
|
||||
let i = self.elements.index(elem);
|
||||
self.matrix.contains(r, i)
|
||||
crate fn contains(
|
||||
&self,
|
||||
r: N,
|
||||
elem: impl ToElementIndex,
|
||||
) -> bool {
|
||||
elem.contained_in_row(self, r)
|
||||
}
|
||||
|
||||
/// Iterates through each row and the accompanying bit set.
|
||||
pub fn iter_enumerated<'a>(
|
||||
&'a self
|
||||
) -> impl Iterator<Item = (N, &'a BitVector<RegionElementIndex>)> + 'a {
|
||||
self.matrix.iter_enumerated()
|
||||
/// Iterate through each region that has a value in this set.
|
||||
crate fn regions_with_points<'a>(&'a self) -> impl Iterator<Item = N> {
|
||||
self.points.rows()
|
||||
}
|
||||
|
||||
/// Merge a row, `from`, originating in another `RegionValues` into the `into` row.
|
||||
pub fn merge_into(&mut self, into: N, from: &BitVector<RegionElementIndex>) -> bool {
|
||||
self.matrix.merge_into(into, from)
|
||||
/// `self[to] |= values[from]`, essentially: that is, take all the
|
||||
/// elements for the region `from` from `values` and add them to
|
||||
/// the region `to` in `self`.
|
||||
crate fn merge_row<M: Idx>(&mut self, to: N, from: M, values: &RegionValues<M>) {
|
||||
if let Some(set) = values.points.row(from) {
|
||||
self.points.merge_into(to, set);
|
||||
}
|
||||
|
||||
if let Some(set) = values.free_regions.row(from) {
|
||||
self.free_regions.merge_into(to, set);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
crate fn contains_points(
|
||||
&self,
|
||||
sup_region: N,
|
||||
sub_region: N,
|
||||
) -> bool {
|
||||
// This could be done faster by comparing the bitsets. But I
|
||||
// am lazy.
|
||||
self.element_indices_contained_in(sub_region)
|
||||
.skip_while(|&i| self.elements.to_universal_region(i).is_some())
|
||||
.all(|e| self.contains(sup_region, e))
|
||||
if let Some(sub_row) = self.points.row(sub_region) {
|
||||
if let Some(sup_row) = self.points.row(sup_region) {
|
||||
sup_row.contains_all(sub_row)
|
||||
} else {
|
||||
// sup row is empty, so sub row must be empty
|
||||
sub_row.is_empty()
|
||||
}
|
||||
} else {
|
||||
// sub row is empty, always true
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the value of the region `r`, yielding up element
|
||||
/// indices. You may prefer `universal_regions_outlived_by` or
|
||||
/// `elements_contained_in`.
|
||||
crate fn element_indices_contained_in<'a>(
|
||||
/// Returns the locations contained within a given region `r`.
|
||||
crate fn locations_outlived_by<'a>(
|
||||
&'a self,
|
||||
r: N,
|
||||
) -> impl Iterator<Item = RegionElementIndex> + 'a {
|
||||
self.matrix.iter(r).map(move |i| i)
|
||||
) -> impl Iterator<Item = Location> + 'a {
|
||||
self.points
|
||||
.row(r)
|
||||
.into_iter()
|
||||
.flat_map(move |set| set.iter().map(move |p| self.elements.to_location(p)))
|
||||
}
|
||||
|
||||
/// Returns just the universal regions that are contained in a given region's value.
|
||||
|
@ -256,10 +237,10 @@ impl<N: Idx> RegionValues<N> {
|
|||
&'a self,
|
||||
r: N,
|
||||
) -> impl Iterator<Item = RegionVid> + 'a {
|
||||
self.element_indices_contained_in(r)
|
||||
.map(move |i| self.elements.to_universal_region(i))
|
||||
.take_while(move |v| v.is_some()) // universal regions are a prefix
|
||||
.map(move |v| v.unwrap())
|
||||
self.free_regions
|
||||
.row(r)
|
||||
.into_iter()
|
||||
.flat_map(|set| set.iter())
|
||||
}
|
||||
|
||||
/// Returns all the elements contained in a given region's value.
|
||||
|
@ -267,8 +248,15 @@ impl<N: Idx> RegionValues<N> {
|
|||
&'a self,
|
||||
r: N,
|
||||
) -> impl Iterator<Item = RegionElement> + 'a {
|
||||
self.element_indices_contained_in(r)
|
||||
.map(move |r| self.elements.to_element(r))
|
||||
let points_iter = self
|
||||
.locations_outlived_by(r)
|
||||
.map(RegionElement::Location);
|
||||
|
||||
let free_regions_iter = self
|
||||
.universal_regions_outlived_by(r)
|
||||
.map(RegionElement::RootUniversalRegion);
|
||||
|
||||
points_iter.chain(free_regions_iter)
|
||||
}
|
||||
|
||||
/// Returns a "pretty" string value of the region. Meant for debugging.
|
||||
|
@ -306,7 +294,7 @@ impl<N: Idx> RegionValues<N> {
|
|||
open_location = Some((l, l));
|
||||
}
|
||||
|
||||
RegionElement::UniversalRegion(fr) => {
|
||||
RegionElement::RootUniversalRegion(fr) => {
|
||||
if let Some((location1, location2)) = open_location {
|
||||
push_sep(&mut result);
|
||||
Self::push_location_range(&mut result, location1, location2);
|
||||
|
@ -341,3 +329,55 @@ impl<N: Idx> RegionValues<N> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate trait ToElementIndex: Debug + Copy {
|
||||
fn add_to_row<N: Idx>(
|
||||
self,
|
||||
values: &mut RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool;
|
||||
|
||||
fn contained_in_row<N: Idx>(
|
||||
self,
|
||||
values: &RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
impl ToElementIndex for Location {
|
||||
fn add_to_row<N: Idx>(
|
||||
self,
|
||||
values: &mut RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool {
|
||||
let index = values.elements.point_from_location(self);
|
||||
values.points.add(row, index)
|
||||
}
|
||||
|
||||
fn contained_in_row<N: Idx>(
|
||||
self,
|
||||
values: &RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool {
|
||||
let index = values.elements.point_from_location(self);
|
||||
values.points.contains(row, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToElementIndex for RegionVid {
|
||||
fn add_to_row<N: Idx>(
|
||||
self,
|
||||
values: &mut RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool {
|
||||
values.free_regions.add(row, self)
|
||||
}
|
||||
|
||||
fn contained_in_row<N: Idx>(
|
||||
self,
|
||||
values: &RegionValues<N>,
|
||||
row: N,
|
||||
) -> bool {
|
||||
values.free_regions.contains(row, self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
|
|||
#![feature(core_intrinsics)]
|
||||
#![feature(decl_macro)]
|
||||
#![feature(fs_read_write)]
|
||||
#![feature(in_band_lifetimes)]
|
||||
#![feature(macro_vis_matcher)]
|
||||
#![feature(exhaustive_patterns)]
|
||||
#![feature(range_contains)]
|
||||
|
|
Loading…
Reference in New Issue