From eaac10ec0d7426b53386c053981de02a3aec2fed Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 1 May 2018 10:49:11 -0400 Subject: [PATCH] add `-Znll-facts` switch that dumps facts for new analysis --- src/librustc/session/config.rs | 2 + src/librustc_mir/borrow_check/location.rs | 2 - .../borrow_check/nll/constraint_generation.rs | 187 +++++++++++++---- src/librustc_mir/borrow_check/nll/facts.rs | 194 ++++++++++++++++++ src/librustc_mir/borrow_check/nll/mod.rs | 57 ++++- .../nll/subtype_constraint_generation.rs | 50 ++++- src/librustc_mir/lib.rs | 1 + 7 files changed, 440 insertions(+), 53 deletions(-) create mode 100644 src/librustc_mir/borrow_check/nll/facts.rs diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index dc97c9415670..0beda679e695 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1250,6 +1250,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "choose which RELRO level to use"), nll_subminimal_causes: bool = (false, parse_bool, [UNTRACKED], "when tracking region error causes, accept subminimal results for faster execution."), + nll_facts: bool = (false, parse_bool, [UNTRACKED], + "dump facts from NLL analysis into side files"), disable_nll_user_type_assert: bool = (false, parse_bool, [UNTRACKED], "disable user provided type assertion in NLL"), trans_time_graph: bool = (false, parse_bool, [UNTRACKED], diff --git a/src/librustc_mir/borrow_check/location.rs b/src/librustc_mir/borrow_check/location.rs index e09f0ee9e2a2..1284a5b0e2a8 100644 --- a/src/librustc_mir/borrow_check/location.rs +++ b/src/librustc_mir/borrow_check/location.rs @@ -8,8 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(dead_code)] // TODO -- will be used in a later commit, remove then - use rustc::mir::{BasicBlock, Location, Mir}; use rustc_data_structures::indexed_vec::{Idx, IndexVec}; diff --git a/src/librustc_mir/borrow_check/nll/constraint_generation.rs b/src/librustc_mir/borrow_check/nll/constraint_generation.rs index afaedecdf0ab..d34e9434fbf2 100644 --- a/src/librustc_mir/borrow_check/nll/constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/constraint_generation.rs @@ -8,28 +8,37 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use borrow_check::borrow_set::BorrowSet; +use borrow_check::location::LocationTable; +use borrow_check::nll::facts::AllFacts; use rustc::hir; -use rustc::mir::{BasicBlock, BasicBlockData, Location, Place, Mir, Rvalue}; +use rustc::infer::InferCtxt; +use rustc::mir::visit::TyContext; use rustc::mir::visit::Visitor; use rustc::mir::Place::Projection; -use rustc::mir::{Local, PlaceProjection, ProjectionElem}; -use rustc::mir::visit::TyContext; -use rustc::infer::InferCtxt; -use rustc::ty::{self, CanonicalTy, ClosureSubsts}; -use rustc::ty::subst::Substs; +use rustc::mir::{BasicBlock, BasicBlockData, Location, Mir, Place, Rvalue}; +use rustc::mir::{Local, PlaceProjection, ProjectionElem, Statement, Terminator}; use rustc::ty::fold::TypeFoldable; +use rustc::ty::subst::Substs; +use rustc::ty::{self, CanonicalTy, ClosureSubsts}; +use super::region_infer::{Cause, RegionInferenceContext}; use super::ToRegionVid; -use super::region_infer::{RegionInferenceContext, Cause}; pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>( infcx: &InferCtxt<'cx, 'gcx, 'tcx>, regioncx: &mut RegionInferenceContext<'tcx>, + all_facts: &mut Option, + location_table: &LocationTable, mir: &Mir<'tcx>, + borrow_set: &BorrowSet<'tcx>, ) { let mut cg = ConstraintGeneration { + borrow_set, infcx, regioncx, + location_table, + all_facts, mir, }; @@ -41,8 +50,11 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>( /// 'cg = the duration of the constraint generation process itself. struct ConstraintGeneration<'cg, 'cx: 'cg, 'gcx: 'tcx, 'tcx: 'cx> { infcx: &'cg InferCtxt<'cx, 'gcx, 'tcx>, + all_facts: &'cg mut Option, + location_table: &'cg LocationTable, regioncx: &'cg mut RegionInferenceContext<'tcx>, mir: &'cg Mir<'tcx>, + borrow_set: &'cg BorrowSet<'tcx>, } impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx, 'tcx> { @@ -68,12 +80,14 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx /// call. Make them live at the location where they appear. fn visit_ty(&mut self, ty: &ty::Ty<'tcx>, ty_context: TyContext) { match ty_context { - TyContext::ReturnTy(source_info) | - TyContext::YieldTy(source_info) | - TyContext::LocalDecl { source_info, .. } => { - span_bug!(source_info.span, - "should not be visiting outside of the CFG: {:?}", - ty_context); + TyContext::ReturnTy(source_info) + | TyContext::YieldTy(source_info) + | TyContext::LocalDecl { source_info, .. } => { + span_bug!( + source_info.span, + "should not be visiting outside of the CFG: {:?}", + ty_context + ); } TyContext::Location(location) => { self.add_regular_live_constraint(*ty, location, Cause::LiveOther(location)); @@ -90,25 +104,117 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx self.super_closure_substs(substs); } + fn visit_statement( + &mut self, + block: BasicBlock, + statement: &Statement<'tcx>, + location: Location, + ) { + if let Some(all_facts) = self.all_facts { + all_facts.cfg_edge.push(( + self.location_table.start_index(location), + self.location_table.mid_index(location), + )); + + all_facts.cfg_edge.push(( + self.location_table.mid_index(location), + self.location_table + .start_index(location.successor_within_block()), + )); + } + + self.super_statement(block, statement, location); + } + + fn visit_assign( + &mut self, + block: BasicBlock, + place: &Place<'tcx>, + rvalue: &Rvalue<'tcx>, + location: Location, + ) { + // When we see `X = ...`, then kill borrows of + // `(*X).foo` and so forth. + if let Some(all_facts) = self.all_facts { + if let Place::Local(temp) = place { + if let Some(borrow_indices) = self.borrow_set.local_map.get(temp) { + for &borrow_index in borrow_indices { + let location_index = self.location_table.mid_index(location); + all_facts.killed.push((borrow_index, location_index)); + } + } + } + } + + self.super_assign(block, place, rvalue, location); + } + + fn visit_terminator( + &mut self, + block: BasicBlock, + terminator: &Terminator<'tcx>, + location: Location, + ) { + if let Some(all_facts) = self.all_facts { + all_facts.cfg_edge.push(( + self.location_table.start_index(location), + self.location_table.mid_index(location), + )); + + for successor_block in terminator.successors() { + all_facts.cfg_edge.push(( + self.location_table.mid_index(location), + self.location_table + .start_index(successor_block.start_location()), + )); + } + } + + self.super_terminator(block, terminator, location); + } + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_rvalue(rvalue={:?}, location={:?})", rvalue, location); - // Look for an rvalue like: - // - // & L - // - // where L is the path that is borrowed. In that case, we have - // to add the reborrow constraints (which don't fall out - // naturally from the type-checker). - if let Rvalue::Ref(region, _bk, ref borrowed_lv) = *rvalue { - self.add_reborrow_constraint(location, region, borrowed_lv); + match rvalue { + Rvalue::Ref(region, _borrow_kind, borrowed_place) => { + // In some cases, e.g. when borrowing from an unsafe + // place, we don't bother to create a loan, since + // there are no conditions to validate. + if let Some(all_facts) = self.all_facts { + if let Some(borrow_index) = self.borrow_set.location_map.get(&location) { + let region_vid = region.to_region_vid(); + all_facts.borrow_region.push(( + region_vid, + *borrow_index, + self.location_table.mid_index(location), + )); + } + } + + // Look for an rvalue like: + // + // & L + // + // where L is the path that is borrowed. In that case, we have + // to add the reborrow constraints (which don't fall out + // naturally from the type-checker). + self.add_reborrow_constraint(location, region, borrowed_place); + } + + _ => { } } self.super_rvalue(rvalue, location); } - fn visit_user_assert_ty(&mut self, _c_ty: &CanonicalTy<'tcx>, - _local: &Local, _location: Location) { } + fn visit_user_assert_ty( + &mut self, + _c_ty: &CanonicalTy<'tcx>, + _local: &Local, + _location: Location, + ) { + } } impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { @@ -122,8 +228,7 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { { debug!( "add_regular_live_constraint(live_ty={:?}, location={:?})", - live_ty, - location + live_ty, location ); self.infcx @@ -144,8 +249,10 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { ) { let mut borrowed_place = borrowed_place; - debug!("add_reborrow_constraint({:?}, {:?}, {:?})", - location, borrow_region, borrowed_place); + debug!( + "add_reborrow_constraint({:?}, {:?}, {:?})", + location, borrow_region, borrowed_place + ); while let Projection(box PlaceProjection { base, elem }) = borrowed_place { debug!("add_reborrow_constraint - iteration {:?}", borrowed_place); @@ -165,12 +272,20 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { location.successor_within_block(), ); + if let Some(all_facts) = self.all_facts { + all_facts.outlives.push(( + ref_region.to_region_vid(), + borrow_region.to_region_vid(), + self.location_table.mid_index(location), + )); + } + match mutbl { hir::Mutability::MutImmutable => { // Immutable reference. We don't need the base // to be valid for the entire lifetime of // the borrow. - break + break; } hir::Mutability::MutMutable => { // Mutable reference. We *do* need the base @@ -199,19 +314,19 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { } ty::TyRawPtr(..) => { // deref of raw pointer, guaranteed to be valid - break + break; } ty::TyAdt(def, _) if def.is_box() => { // deref of `Box`, need the base to be valid - propagate } - _ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place) + _ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place), } } - ProjectionElem::Field(..) | - ProjectionElem::Downcast(..) | - ProjectionElem::Index(..) | - ProjectionElem::ConstantIndex { .. } | - ProjectionElem::Subslice { .. } => { + ProjectionElem::Field(..) + | ProjectionElem::Downcast(..) + | ProjectionElem::Index(..) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { // other field access } } diff --git a/src/librustc_mir/borrow_check/nll/facts.rs b/src/librustc_mir/borrow_check/nll/facts.rs new file mode 100644 index 000000000000..2802aa0dff4e --- /dev/null +++ b/src/librustc_mir/borrow_check/nll/facts.rs @@ -0,0 +1,194 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use borrow_check::location::{LocationIndex, LocationTable}; +use dataflow::indexes::BorrowIndex; +use rustc::ty::RegionVid; +use std::error::Error; +use std::fmt::Debug; +use std::fs::{self, File}; +use std::io::Write; +use std::path::Path; + +/// The "facts" which are the basis of the NLL borrow analysis. +#[derive(Default)] +crate struct AllFacts { + // `borrow_region(R, B, P)` -- the region R may refer to data from borrow B + // starting at the point P (this is usually the point *after* a borrow rvalue) + crate borrow_region: Vec<(RegionVid, BorrowIndex, LocationIndex)>, + + // universal_region(R) -- this is a "free region" within fn body + crate universal_region: Vec, + + // `cfg_edge(P,Q)` for each edge P -> Q in the control flow + crate cfg_edge: Vec<(LocationIndex, LocationIndex)>, + + // `killed(B,P)` when some prefix of the path borrowed at B is assigned at point P + crate killed: Vec<(BorrowIndex, LocationIndex)>, + + // `outlives(R1, R2, P)` when we require `R1@P: R2@P` + crate outlives: Vec<(RegionVid, RegionVid, LocationIndex)>, + + // `region_live_at(R, P)` when the region R appears in a live variable at P + crate region_live_at: Vec<(RegionVid, LocationIndex)>, +} + +impl AllFacts { + crate fn write_to_dir( + &self, + dir: impl AsRef, + location_table: &LocationTable, + ) -> Result<(), Box> { + let dir: &Path = dir.as_ref(); + fs::create_dir_all(dir)?; + let wr = FactWriter { location_table, dir }; + macro_rules! write_facts_to_path { + ($wr:ident . write_facts_to_path($this:ident . [ + $($field:ident,)* + ])) => { + $( + $wr.write_facts_to_path( + &$this.$field, + &format!("{}.facts", stringify!($field)) + )?; + )* + } + } + write_facts_to_path! { + wr.write_facts_to_path(self.[ + borrow_region, + universal_region, + cfg_edge, + killed, + outlives, + region_live_at, + ]) + } + Ok(()) + } +} + +struct FactWriter<'w> { + location_table: &'w LocationTable, + dir: &'w Path, +} + +impl<'w> FactWriter<'w> { + fn write_facts_to_path( + &self, + rows: &Vec, + file_name: &str, + ) -> Result<(), Box> + where + T: FactRow, + { + let file = &self.dir.join(file_name); + let mut file = File::create(file)?; + for row in rows { + row.write(&mut file, self.location_table)?; + } + Ok(()) + } +} + +trait FactRow { + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box>; +} + +impl FactRow for RegionVid { + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[self]) + } +} + +impl FactRow for (A, B) +where + A: FactCell, + B: FactCell, +{ + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1]) + } +} + +impl FactRow for (A, B, C) +where + A: FactCell, + B: FactCell, + C: FactCell, +{ + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1, &self.2]) + } +} + +impl FactRow for (A, B, C, D) +where + A: FactCell, + B: FactCell, + C: FactCell, + D: FactCell, +{ + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1, &self.2, &self.3]) + } +} + +fn write_row( + out: &mut dyn Write, + location_table: &LocationTable, + columns: &[&dyn FactCell], +) -> Result<(), Box> { + for (index, c) in columns.iter().enumerate() { + let tail = if index == columns.len() - 1 { + "\n" + } else { + "\t" + }; + write!(out, "{:?}{}", c.to_string(location_table), tail)?; + } + Ok(()) +} + +trait FactCell { + fn to_string(&self, location_table: &LocationTable) -> String; +} + +impl FactCell for A { + default fn to_string(&self, _location_table: &LocationTable) -> String { + format!("{:?}", self) + } +} + +impl FactCell for LocationIndex { + fn to_string(&self, location_table: &LocationTable) -> String { + format!("{:?}", location_table.to_location(*self)) + } +} diff --git a/src/librustc_mir/borrow_check/nll/mod.rs b/src/librustc_mir/borrow_check/nll/mod.rs index 80ad3ecb9f85..0b1729294d84 100644 --- a/src/librustc_mir/borrow_check/nll/mod.rs +++ b/src/librustc_mir/borrow_check/nll/mod.rs @@ -10,32 +10,35 @@ use borrow_check::borrow_set::BorrowSet; use borrow_check::location::LocationTable; +use dataflow::move_paths::MoveData; +use dataflow::FlowAtLocation; +use dataflow::MaybeInitializedPlaces; use rustc::hir::def_id::DefId; -use rustc::mir::{ClosureRegionRequirements, ClosureOutlivesSubject, Mir}; use rustc::infer::InferCtxt; +use rustc::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, Mir}; use rustc::ty::{self, RegionKind, RegionVid}; use rustc::util::nodemap::FxHashMap; use std::collections::BTreeSet; use std::fmt::Debug; use std::io; +use std::path::PathBuf; use transform::MirSource; use util::liveness::{LivenessResults, LocalSet}; -use dataflow::FlowAtLocation; -use dataflow::MaybeInitializedPlaces; -use dataflow::move_paths::MoveData; +use self::mir_util::PassWhere; use util as mir_util; use util::pretty::{self, ALIGN}; -use self::mir_util::PassWhere; mod constraint_generation; pub mod explain_borrow; +mod facts; crate mod region_infer; mod renumber; mod subtype_constraint_generation; crate mod type_check; mod universal_regions; +use self::facts::AllFacts; use self::region_infer::RegionInferenceContext; use self::universal_regions::UniversalRegions; @@ -71,11 +74,11 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>( def_id: DefId, universal_regions: UniversalRegions<'tcx>, mir: &Mir<'tcx>, - _location_table: &LocationTable, + location_table: &LocationTable, param_env: ty::ParamEnv<'gcx>, flow_inits: &mut FlowAtLocation>, move_data: &MoveData<'tcx>, - _borrow_set: &BorrowSet<'tcx>, + borrow_set: &BorrowSet<'tcx>, ) -> ( RegionInferenceContext<'tcx>, Option>, @@ -93,15 +96,47 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>( move_data, ); + let mut all_facts = if infcx.tcx.sess.opts.debugging_opts.nll_facts { + Some(AllFacts::default()) + } else { + None + }; + + if let Some(all_facts) = &mut all_facts { + all_facts + .universal_region + .extend(universal_regions.universal_regions()); + } + // Create the region inference context, taking ownership of the region inference // data that was contained in `infcx`. let var_origins = infcx.take_region_var_origins(); - let mut regioncx = RegionInferenceContext::new(var_origins, universal_regions, mir); - subtype_constraint_generation::generate(&mut regioncx, mir, constraint_sets); + let mut regioncx = + RegionInferenceContext::new(var_origins, universal_regions, mir); + // Generate various constraints. + subtype_constraint_generation::generate( + &mut regioncx, + &mut all_facts, + location_table, + mir, + constraint_sets, + ); + constraint_generation::generate_constraints( + infcx, + &mut regioncx, + &mut all_facts, + location_table, + &mir, + borrow_set, + ); - // Generate non-subtyping constraints. - constraint_generation::generate_constraints(infcx, &mut regioncx, &mir); + // Dump facts if requested. + if let Some(all_facts) = all_facts { + let def_path = infcx.tcx.hir.def_path(def_id); + let dir_path = PathBuf::from("nll-facts").join(def_path.to_filename_friendly_no_crate()); + all_facts.write_to_dir(dir_path, location_table).unwrap(); + } // Solve the region constraints. let closure_region_requirements = regioncx.solve(infcx, &mir, def_id); diff --git a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs index 05b61f25a398..9db19085a396 100644 --- a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs @@ -8,14 +8,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use rustc::mir::{Location, Mir}; +use borrow_check::location::LocationTable; +use borrow_check::nll::facts::AllFacts; use rustc::infer::region_constraints::Constraint; use rustc::infer::region_constraints::RegionConstraintData; use rustc::infer::region_constraints::{Verify, VerifyBound}; +use rustc::mir::{Location, Mir}; use rustc::ty; +use std::iter; use syntax::codemap::Span; -use super::region_infer::{TypeTest, RegionInferenceContext, RegionTest}; +use super::region_infer::{RegionInferenceContext, RegionTest, TypeTest}; use super::type_check::Locations; use super::type_check::MirTypeckRegionConstraints; use super::type_check::OutlivesSet; @@ -27,19 +30,30 @@ use super::type_check::OutlivesSet; /// them into the NLL `RegionInferenceContext`. pub(super) fn generate<'tcx>( regioncx: &mut RegionInferenceContext<'tcx>, + all_facts: &mut Option, + location_table: &LocationTable, mir: &Mir<'tcx>, constraints: &MirTypeckRegionConstraints<'tcx>, ) { - SubtypeConstraintGenerator { regioncx, mir }.generate(constraints); + SubtypeConstraintGenerator { + regioncx, + location_table, + mir, + }.generate(constraints, all_facts); } struct SubtypeConstraintGenerator<'cx, 'tcx: 'cx> { regioncx: &'cx mut RegionInferenceContext<'tcx>, + location_table: &'cx LocationTable, mir: &'cx Mir<'tcx>, } impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> { - fn generate(&mut self, constraints: &MirTypeckRegionConstraints<'tcx>) { + fn generate( + &mut self, + constraints: &MirTypeckRegionConstraints<'tcx>, + all_facts: &mut Option, + ) { let MirTypeckRegionConstraints { liveness_set, outlives_sets, @@ -57,6 +71,17 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> { self.regioncx.add_live_point(region_vid, *location, &cause); } + if let Some(all_facts) = all_facts { + all_facts + .region_live_at + .extend(liveness_set.into_iter().flat_map(|(region, location, _)| { + let r = self.to_region_vid(region); + let p1 = self.location_table.start_index(*location); + let p2 = self.location_table.mid_index(*location); + iter::once((r, p1)).chain(iter::once((r, p2))) + })); + } + for OutlivesSet { locations, data } in outlives_sets { debug!("generate: constraints at: {:#?}", locations); let RegionConstraintData { @@ -88,6 +113,23 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> { // "outlives" (`>=`) whereas the region constraints // talk about `<=`. self.regioncx.add_outlives(span, b_vid, a_vid, at_location); + + // In the new analysis, all outlives relations etc + // "take effect" at the mid point of the statement + // that requires them, so ignore the `at_location`. + if let Some(all_facts) = all_facts { + if let Some(from_location) = locations.from_location() { + all_facts.outlives.push(( + b_vid, + a_vid, + self.location_table.mid_index(from_location), + )); + } else { + for location in self.location_table.all_points() { + all_facts.outlives.push((b_vid, a_vid, location)); + } + } + } } for verify in verifys { diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 95cf3b8ddc6c..2545ba3a94af 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -34,6 +34,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment! #![feature(inclusive_range_methods)] #![feature(crate_visibility_modifier)] #![feature(never_type)] +#![feature(specialization)] #![cfg_attr(stage0, feature(try_trait))] extern crate arena;