Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow and implement recursive static variables. #26630

Merged
merged 3 commits into from
Jul 25, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/librustc/middle/check_static_recursion.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This compiler pass detects static items that refer to themselves
// This compiler pass detects constants that refer to themselves
// recursively.

use ast_map;
@@ -18,6 +18,7 @@ use util::nodemap::NodeMap;

use syntax::{ast, ast_util};
use syntax::codemap::Span;
use syntax::feature_gate::emit_feature_err;
use syntax::visit::Visitor;
use syntax::visit;

@@ -125,8 +126,27 @@ impl<'a, 'ast: 'a> CheckItemRecursionVisitor<'a, 'ast> {
}
fn with_item_id_pushed<F>(&mut self, id: ast::NodeId, f: F)
where F: Fn(&mut Self) {
if self.idstack.iter().any(|x| *x == id) {
span_err!(self.sess, *self.root_span, E0265, "recursive constant");
if self.idstack.iter().any(|&x| x == id) {
let any_static = self.idstack.iter().any(|&x| {
if let ast_map::NodeItem(item) = self.ast_map.get(x) {
if let ast::ItemStatic(..) = item.node {
true
} else {
false
}
} else {
false
}
});
if any_static {
if !self.sess.features.borrow().static_recursion {
emit_feature_err(&self.sess.parse_sess.span_diagnostic,
"static_recursion",
*self.root_span, "recursive static");
}
} else {
span_err!(self.sess, *self.root_span, E0265, "recursive constant");
}
return;
}
self.idstack.push(id);
52 changes: 20 additions & 32 deletions src/librustc_trans/trans/base.rs
Original file line number Diff line number Diff line change
@@ -2090,7 +2090,7 @@ pub fn trans_item(ccx: &CrateContext, item: &ast::Item) {
let mut v = TransItemVisitor{ ccx: ccx };
v.visit_expr(&**expr);

let g = consts::trans_static(ccx, m, item.id);
let g = consts::trans_static(ccx, m, expr, item.id, &item.attrs);
update_linkage(ccx, g, Some(item.id), OriginalTranslation);
},
ast::ItemForeignMod(ref foreign_mod) => {
@@ -2334,44 +2334,25 @@ pub fn get_item_val(ccx: &CrateContext, id: ast::NodeId) -> ValueRef {
let sym = || exported_name(ccx, id, ty, &i.attrs);

let v = match i.node {
ast::ItemStatic(_, _, ref expr) => {
ast::ItemStatic(..) => {
// If this static came from an external crate, then
// we need to get the symbol from csearch instead of
// using the current crate's name/version
// information in the hash of the symbol
let sym = sym();
debug!("making {}", sym);

// We need the translated value here, because for enums the
// LLVM type is not fully determined by the Rust type.
let empty_substs = ccx.tcx().mk_substs(Substs::trans_empty());
let (v, ty) = consts::const_expr(ccx, &**expr, empty_substs, None);
ccx.static_values().borrow_mut().insert(id, v);
unsafe {
// boolean SSA values are i1, but they have to be stored in i8 slots,
// otherwise some LLVM optimization passes don't work as expected
let llty = if ty.is_bool() {
llvm::LLVMInt8TypeInContext(ccx.llcx())
} else {
llvm::LLVMTypeOf(v)
};

// FIXME(nagisa): probably should be declare_global, because no definition
// is happening here, but we depend on it being defined here from
// const::trans_static. This all logic should be replaced.
let g = declare::define_global(ccx, &sym[..],
Type::from_ref(llty)).unwrap_or_else(||{
ccx.sess().span_fatal(i.span, &format!("symbol `{}` is already defined",
sym))
});

if attr::contains_name(&i.attrs,
"thread_local") {
llvm::set_thread_local(g, true);
}
ccx.item_symbols().borrow_mut().insert(i.id, sym);
g
}
// Create the global before evaluating the initializer;
// this is necessary to allow recursive statics.
let llty = type_of(ccx, ty);
let g = declare::define_global(ccx, &sym[..],
llty).unwrap_or_else(|| {
ccx.sess().span_fatal(i.span, &format!("symbol `{}` is already defined",
sym))
});

ccx.item_symbols().borrow_mut().insert(i.id, sym);
g
}

ast::ItemFn(_, _, _, abi, _, _) => {
@@ -2738,6 +2719,13 @@ pub fn trans_crate(tcx: &ty::ctxt, analysis: ty::CrateAnalysis) -> CrateTranslat
if ccx.sess().opts.debuginfo != NoDebugInfo {
debuginfo::finalize(&ccx);
}
for &(old_g, new_g) in ccx.statics_to_rauw().borrow().iter() {
unsafe {
let bitcast = llvm::LLVMConstPointerCast(new_g, llvm::LLVMTypeOf(old_g));
llvm::LLVMReplaceAllUsesWith(old_g, bitcast);
llvm::LLVMDeleteGlobal(old_g);
}
}
}

// Translate the metadata.
56 changes: 45 additions & 11 deletions src/librustc_trans/trans/consts.rs
Original file line number Diff line number Diff line change
@@ -37,8 +37,9 @@ use middle::subst::Substs;
use middle::ty::{self, Ty};
use util::nodemap::NodeMap;

use std::ffi::{CStr, CString};
use libc::c_uint;
use syntax::{ast, ast_util};
use syntax::{ast, ast_util, attr};
use syntax::parse::token;
use syntax::ptr::P;

@@ -898,37 +899,70 @@ fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
"bad constant expression type in consts::const_expr"),
}
}

pub fn trans_static(ccx: &CrateContext, m: ast::Mutability, id: ast::NodeId) -> ValueRef {
pub fn trans_static(ccx: &CrateContext,
m: ast::Mutability,
expr: &ast::Expr,
id: ast::NodeId,
attrs: &Vec<ast::Attribute>)
-> ValueRef {
unsafe {
let _icx = push_ctxt("trans_static");
let g = base::get_item_val(ccx, id);
// At this point, get_item_val has already translated the
// constant's initializer to determine its LLVM type.
let v = ccx.static_values().borrow().get(&id).unwrap().clone();

let empty_substs = ccx.tcx().mk_substs(Substs::trans_empty());
let (v, _) = const_expr(ccx, expr, empty_substs, None);

// boolean SSA values are i1, but they have to be stored in i8 slots,
// otherwise some LLVM optimization passes don't work as expected
let v = if llvm::LLVMTypeOf(v) == Type::i1(ccx).to_ref() {
llvm::LLVMConstZExt(v, Type::i8(ccx).to_ref())
let mut val_llty = llvm::LLVMTypeOf(v);
let v = if val_llty == Type::i1(ccx).to_ref() {
val_llty = Type::i8(ccx).to_ref();
llvm::LLVMConstZExt(v, val_llty)
} else {
v
};

let ty = ccx.tcx().node_id_to_type(id);
let llty = type_of::type_of(ccx, ty);
let g = if val_llty == llty.to_ref() {
g
} else {
// If we created the global with the wrong type,
// correct the type.
let empty_string = CString::new("").unwrap();
let name_str_ref = CStr::from_ptr(llvm::LLVMGetValueName(g));
let name_string = CString::new(name_str_ref.to_bytes()).unwrap();
llvm::LLVMSetValueName(g, empty_string.as_ptr());
let new_g = llvm::LLVMGetOrInsertGlobal(
ccx.llmod(), name_string.as_ptr(), val_llty);
// To avoid breaking any invariants, we leave around the old
// global for the moment; we'll replace all references to it
// with the new global later. (See base::trans_crate.)
ccx.statics_to_rauw().borrow_mut().push((g, new_g));
new_g
};
llvm::LLVMSetInitializer(g, v);

// As an optimization, all shared statics which do not have interior
// mutability are placed into read-only memory.
if m != ast::MutMutable {
let node_ty = ccx.tcx().node_id_to_type(id);
let tcontents = node_ty.type_contents(ccx.tcx());
let tcontents = ty.type_contents(ccx.tcx());
if !tcontents.interior_unsafe() {
llvm::LLVMSetGlobalConstant(g, True);
llvm::LLVMSetGlobalConstant(g, llvm::True);
}
}

debuginfo::create_global_var_metadata(ccx, id, g);

if attr::contains_name(attrs,
"thread_local") {
llvm::set_thread_local(g, true);
}
g
}
}


fn get_static_val<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, did: ast::DefId,
ty: Ty<'tcx>) -> ValueRef {
if ast_util::is_local(did) { return base::get_item_val(ccx, did.node) }
19 changes: 11 additions & 8 deletions src/librustc_trans/trans/context.rs
Original file line number Diff line number Diff line change
@@ -118,9 +118,6 @@ pub struct LocalCrateContext<'tcx> {
/// Cache of emitted const values
const_values: RefCell<FnvHashMap<(ast::NodeId, &'tcx Substs<'tcx>), ValueRef>>,

/// Cache of emitted static values
static_values: RefCell<NodeMap<ValueRef>>,

/// Cache of external const values
extern_const_values: RefCell<DefIdMap<ValueRef>>,

@@ -129,6 +126,12 @@ pub struct LocalCrateContext<'tcx> {
/// Cache of closure wrappers for bare fn's.
closure_bare_wrapper_cache: RefCell<FnvHashMap<ValueRef, ValueRef>>,

/// List of globals for static variables which need to be passed to the
/// LLVM function ReplaceAllUsesWith (RAUW) when translation is complete.
/// (We have to make sure we don't invalidate any ValueRefs referring
/// to constants.)
statics_to_rauw: RefCell<Vec<(ValueRef, ValueRef)>>,

lltypes: RefCell<FnvHashMap<Ty<'tcx>, Type>>,
llsizingtypes: RefCell<FnvHashMap<Ty<'tcx>, Type>>,
adt_reprs: RefCell<FnvHashMap<Ty<'tcx>, Rc<adt::Repr<'tcx>>>>,
@@ -449,10 +452,10 @@ impl<'tcx> LocalCrateContext<'tcx> {
const_unsized: RefCell::new(FnvHashMap()),
const_globals: RefCell::new(FnvHashMap()),
const_values: RefCell::new(FnvHashMap()),
static_values: RefCell::new(NodeMap()),
extern_const_values: RefCell::new(DefIdMap()),
impl_method_cache: RefCell::new(FnvHashMap()),
closure_bare_wrapper_cache: RefCell::new(FnvHashMap()),
statics_to_rauw: RefCell::new(Vec::new()),
lltypes: RefCell::new(FnvHashMap()),
llsizingtypes: RefCell::new(FnvHashMap()),
adt_reprs: RefCell::new(FnvHashMap()),
@@ -660,10 +663,6 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
&self.local.const_values
}

pub fn static_values<'a>(&'a self) -> &'a RefCell<NodeMap<ValueRef>> {
&self.local.static_values
}

pub fn extern_const_values<'a>(&'a self) -> &'a RefCell<DefIdMap<ValueRef>> {
&self.local.extern_const_values
}
@@ -677,6 +676,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
&self.local.closure_bare_wrapper_cache
}

pub fn statics_to_rauw<'a>(&'a self) -> &'a RefCell<Vec<(ValueRef, ValueRef)>> {
&self.local.statics_to_rauw
}

pub fn lltypes<'a>(&'a self) -> &'a RefCell<FnvHashMap<Ty<'tcx>, Type>> {
&self.local.lltypes
}
42 changes: 12 additions & 30 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
@@ -115,6 +115,7 @@ use syntax::attr::AttrMetaMethods;
use syntax::ast::{self, DefId, Visibility};
use syntax::ast_util::{self, local_def};
use syntax::codemap::{self, Span};
use syntax::feature_gate::emit_feature_err;
use syntax::owned_slice::OwnedSlice;
use syntax::parse::token;
use syntax::print::pprust;
@@ -4009,9 +4010,7 @@ fn check_const_with_ty<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,

/// Checks whether a type can be represented in memory. In particular, it
/// identifies types that contain themselves without indirection through a
/// pointer, which would mean their size is unbounded. This is different from
/// the question of whether a type can be instantiated. See the definition of
/// `check_instantiable`.
/// pointer, which would mean their size is unbounded.
pub fn check_representable(tcx: &ty::ctxt,
sp: Span,
item_id: ast::NodeId,
@@ -4036,31 +4035,19 @@ pub fn check_representable(tcx: &ty::ctxt,
return true
}

/// Checks whether a type can be created without an instance of itself.
/// This is similar but different from the question of whether a type
/// can be represented. For example, the following type:
///
/// enum foo { None, Some(foo) }
///
/// is instantiable but is not representable. Similarly, the type
///
/// enum foo { Some(@foo) }
///
/// is representable, but not instantiable.
/// Checks whether a type can be constructed at runtime without
/// an existing instance of that type.
pub fn check_instantiable(tcx: &ty::ctxt,
sp: Span,
item_id: ast::NodeId)
-> bool {
item_id: ast::NodeId) {
let item_ty = tcx.node_id_to_type(item_id);
if !item_ty.is_instantiable(tcx) {
span_err!(tcx.sess, sp, E0073,
"this type cannot be instantiated without an \
instance of itself");
fileline_help!(tcx.sess, sp, "consider using `Option<{:?}>`",
item_ty);
false
} else {
true
if !item_ty.is_instantiable(tcx) &&
!tcx.sess.features.borrow().static_recursion {
emit_feature_err(&tcx.sess.parse_sess.span_diagnostic,
"static_recursion",
sp,
"this type cannot be instantiated at runtime \
without an instance of itself");
}
}

@@ -4199,11 +4186,6 @@ pub fn check_enum_variants<'a,'tcx>(ccx: &CrateCtxt<'a,'tcx>,
do_check(ccx, vs, id, hint);

check_representable(ccx.tcx, sp, id, "enum");

// Check that it is possible to instantiate this enum:
//
// This *sounds* like the same that as representable, but it's
// not. See def'n of `check_instantiable()` for details.
check_instantiable(ccx.tcx, sp, id);
}

6 changes: 6 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
@@ -160,6 +160,9 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[

// Allows using #[prelude_import] on glob `use` items.
("prelude_import", "1.2.0", Active),

// Allows the definition recursive static items.
("static_recursion", "1.3.0", Active),
];
// (changing above list without updating src/doc/reference.md makes @cmr sad)

@@ -338,6 +341,7 @@ pub struct Features {
/// #![feature] attrs for non-language (library) features
pub declared_lib_features: Vec<(InternedString, Span)>,
pub const_fn: bool,
pub static_recursion: bool
}

impl Features {
@@ -362,6 +366,7 @@ impl Features {
declared_stable_lang_features: Vec::new(),
declared_lib_features: Vec::new(),
const_fn: false,
static_recursion: false
}
}
}
@@ -859,6 +864,7 @@ fn check_crate_inner<F>(cm: &CodeMap, span_handler: &SpanHandler,
declared_stable_lang_features: accepted_features,
declared_lib_features: unknown_features,
const_fn: cx.has_feature("const_fn"),
static_recursion: cx.has_feature("static_recursion")
}
}

5 changes: 2 additions & 3 deletions src/test/compile-fail/const-recursive.rs
Original file line number Diff line number Diff line change
@@ -8,9 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// error-pattern: recursive constant
static a: isize = b;
static b: isize = a;
const a: isize = b; //~ ERROR recursive constant
const b: isize = a; //~ ERROR recursive constant

fn main() {
}
4 changes: 2 additions & 2 deletions src/test/compile-fail/issue-17252.rs
Original file line number Diff line number Diff line change
@@ -8,12 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

static FOO: usize = FOO; //~ ERROR recursive constant
const FOO: usize = FOO; //~ ERROR recursive constant

fn main() {
let _x: [u8; FOO]; // caused stack overflow prior to fix
let _y: usize = 1 + {
static BAR: usize = BAR; //~ ERROR recursive constant
const BAR: usize = BAR; //~ ERROR recursive constant
let _z: [u8; BAR]; // caused stack overflow prior to fix
1
};
3 changes: 2 additions & 1 deletion src/test/compile-fail/issue-3008-2.rs
Original file line number Diff line number Diff line change
@@ -8,10 +8,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(static_recursion)]

enum foo { foo_(bar) }
struct bar { x: bar }
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
//~^^ ERROR this type cannot be instantiated without an instance of itself

fn main() {
}
14 changes: 14 additions & 0 deletions src/test/compile-fail/static-recursion-gate-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2015 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

struct Z(&'static Z);
//~^ ERROR this type cannot be instantiated

pub fn main() {}
16 changes: 16 additions & 0 deletions src/test/compile-fail/static-recursion-gate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2015 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

static mut S: *const u8 = unsafe { &S as *const *const u8 as *const u8 };
//~^ ERROR recursive static

pub fn main() {
unsafe { assert_eq!(S, *(S as *const *const u8)); }
}
2 changes: 1 addition & 1 deletion src/test/compile-fail/type-recursive.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// error-pattern:this type cannot be instantiated
// error-pattern:illegal recursive struct type
struct t1 {
foo: isize,
foolish: t1
Original file line number Diff line number Diff line change
@@ -8,15 +8,17 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(static_recursion)]

// test that autoderef of a type like this does not
// cause compiler to loop. Note that no instances
// of such a type could ever be constructed.
struct S { //~ ERROR this type cannot be instantiated

struct S {
x: X,
to_str: (),
}

struct X(Box<S>); //~ ERROR this type cannot be instantiated
struct X(Box<S>);

fn main() {}
Original file line number Diff line number Diff line change
@@ -8,28 +8,25 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(static_recursion)]

// test that autoderef of a type like this does not
// cause compiler to loop. Note that no instances
// of such a type could ever be constructed.

struct t(Box<t>); //~ ERROR this type cannot be instantiated
struct T(Box<T>);

trait to_str_2 {
fn my_to_string() -> String;
trait ToStr2 {
fn my_to_string(&self) -> String;
}

// I use an impl here because it will cause
// the compiler to attempt autoderef and then
// try to resolve the method.
impl to_str_2 for t {
fn my_to_string() -> String { "t".to_string() }
impl ToStr2 for T {
fn my_to_string(&self) -> String { "t".to_string() }
}

fn new_t(x: t) {
#[allow(dead_code)]
fn new_t(x: T) {
x.my_to_string();
// (there used to be an error emitted right here as well. It was
// spurious, at best; if `t` did exist as a type, it clearly would
// have an impl of the `to_str_2` trait.)
}

fn main() {
47 changes: 47 additions & 0 deletions src/test/run-pass/static-recursive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2015 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(static_recursion)]

static mut S: *const u8 = unsafe { &S as *const *const u8 as *const u8 };

struct StaticDoubleLinked {
prev: &'static StaticDoubleLinked,
next: &'static StaticDoubleLinked,
data: i32,
head: bool
}

static L1: StaticDoubleLinked = StaticDoubleLinked{prev: &L3, next: &L2, data: 1, head: true};
static L2: StaticDoubleLinked = StaticDoubleLinked{prev: &L1, next: &L3, data: 2, head: false};
static L3: StaticDoubleLinked = StaticDoubleLinked{prev: &L2, next: &L1, data: 3, head: false};


pub fn main() {
unsafe { assert_eq!(S, *(S as *const *const u8)); }

let mut test_vec = Vec::new();
let mut cur = &L1;
loop {
test_vec.push(cur.data);
cur = cur.next;
if cur.head { break }
}
assert_eq!(&test_vec, &[1,2,3]);

let mut test_vec = Vec::new();
let mut cur = &L1;
loop {
cur = cur.prev;
test_vec.push(cur.data);
if cur.head { break }
}
assert_eq!(&test_vec, &[3,2,1]);
}