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

User-defined placement-box #18233

Closed
81 changes: 77 additions & 4 deletions src/liballoc/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@

//! A unique pointer type.

use heap;

use core::any::{Any, AnyRefExt};
use core::clone::Clone;
use core::cmp::{PartialEq, PartialOrd, Eq, Ord, Ordering};
use core::default::Default;
use core::fmt;
use core::intrinsics;
use core::kinds::Sized;
use core::mem;
use core::ops::{Drop, Placer, PlacementAgent};
use core::option::Option;
use core::raw::TraitObject;
use core::result::{Ok, Err, Result};
Expand All @@ -36,22 +40,91 @@ use core::result::{Ok, Err, Result};
/// ```
#[lang = "exchange_heap"]
#[experimental = "may be renamed; uncertain about custom allocator design"]
pub static HEAP: () = ();
pub const HEAP: ExchangeHeapSingleton =
ExchangeHeapSingleton { _force_singleton: () };

/// This the singleton type used solely for `boxed::HEAP`.
pub struct ExchangeHeapSingleton { _force_singleton: () }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term exchange heap really shouldn't be used. It's not how dynamic allocation works in Rust. It's wrong to refer to this as HEAP at all. The Box<T> type represents a dynamic allocation from the same allocator as other types like Vec<T> and Rc<T>. Rust doesn't draw any distinction between memory on a "heap" and other memory mappings.


/// A type that represents a uniquely-owned value.
#[lang = "owned_box"]
#[unstable = "custom allocators will add an additional type parameter (with default)"]
pub struct Box<T>(*mut T);
pub struct Box<Sized? T>(*mut T);

/// `IntermediateBox` represents uninitialized backing storage for `Box`.
///
/// FIXME (pnkfelix): Ideally we would just reuse `Box<T>` instead of
/// introducing a separate `IntermediateBox<T>`; but then you hit
/// issues when you e.g. attempt to destructure an instance of `Box`,
/// since it is a lang item and so it gets special handling by the
/// compiler. Easier just to make this parallel type for now.
///
/// FIXME (pnkfelix): Currently the `box` protocol only supports
/// creating instances of sized types. This IntermediateBox is
/// designed to be forward-compatible with a future protocol that
/// supports creating instances of unsized types; that is why the type
/// parameter has the `Sized?` generalization marker, and is also why
/// this carries an explicit size. However, it probably does not need
/// to carry the explicit alignment; that is just a work-around for
/// the fact that the `align_of` intrinsic currently requires the
/// input type to be Sized (which I do not think is strictly
/// necessary).
pub struct IntermediateBox<Sized? T>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is public, we'll be committing to the continued existence of this type, at least. I'm ok with this but cc @aturon. We'll probably want to bikeshed the name :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Of course there's no way to avoid having this be public, at least not without private trait methods at the very least...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular I expect we want to reuse the "agent" term

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Thu, Nov 06, 2014 at 07:48:41AM -0800, Felix S Klock II wrote:

(Of course there's no way to avoid having this be public, at least not without private trait methods at the very least...)

Yes. And I think that's ok.

ptr: *mut u8,
size: uint,
align: uint,
}

impl<T> Placer<T, Box<T>, IntermediateBox<T>> for ExchangeHeapSingleton {
fn make_place(&mut self) -> IntermediateBox<T> {
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();

let p = if size == 0 {
heap::EMPTY as *mut u8
} else {
unsafe {
heap::allocate(size, align)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heap::allocate() can return null on failure, we should check for this and panic

}
};

IntermediateBox { ptr: p, size: size, align: align }
}
}

impl<Sized? T> PlacementAgent<T, Box<T>> for IntermediateBox<T> {
unsafe fn pointer(&self) -> *mut T {
self.ptr as *mut T
}
unsafe fn finalize(self) -> Box<T> {
let p = self.ptr as *mut T;
mem::forget(self);
mem::transmute(p)
}
}

#[unsafe_destructor]
impl<Sized? T> Drop for IntermediateBox<T> {
fn drop(&mut self) {
if self.size > 0 {
unsafe {
heap::deallocate(self.ptr as *mut u8, self.size, self.align)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the cast here? seems unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(agreed, that was probably an artifact of an earlier version of the IntermediateBox structure.)

}
}
}
}

impl<T: Default> Default for Box<T> {
fn default() -> Box<T> { box Default::default() }
fn default() -> Box<T> {
box (HEAP) { let t : T = Default::default(); t }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it not work to just right box (HEAP) Default::default()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the type-inference applied to the desugaring might reject that, but I'll double check.

}
}

#[unstable]
impl<T: Clone> Clone for Box<T> {
/// Returns a copy of the owned box.
#[inline]
fn clone(&self) -> Box<T> { box {(**self).clone()} }
fn clone(&self) -> Box<T> { box (HEAP) {(**self).clone()} }

/// Performs copy-assignment from `source` by reusing the existing allocation.
#[inline]
Expand Down
7 changes: 7 additions & 0 deletions src/liballoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,13 @@ pub mod libc_heap;

// Primitive types using the heaps above

// Need to conditionally define the mod from `boxed.rs` to avoid
// duplicating the lang-items when building in test cfg; but also need
// to allow code to have `use boxed::HEAP;` declaration.
#[cfg(not(test))]
pub mod boxed;
#[cfg(test)]
mod boxed { pub use std::boxed::HEAP; }
pub mod arc;
pub mod rc;

Expand Down Expand Up @@ -126,5 +131,7 @@ pub fn fixme_14344_be_sure_to_link_to_collections() {}
#[doc(hidden)]
mod std {
pub use core::fmt;
pub use core::ops;
pub use core::option;
pub use core::ptr;
}
64 changes: 64 additions & 0 deletions src/libcore/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,3 +901,67 @@ def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12)
def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13)
def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14)
def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15)

/// Interface to user-implementations of `box (<placer_expr>) <value_expr>`.
///
/// `box (P) V` effectively expands into:
/// `{ let b = P.make_place(); let v = V; unsafe { b.pointer() = v; b.finalize() } }`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only question is whether b.pointer() should be evaluated before V or not. Also, we should add a FIXME regarding the matter of temporary lifetimes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched b.pointer() to evaluate before V.

Can you refresh my memory as to which matter of temporary lifetimes you were referring to here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(oh, maybe that was about the "vector should be tied up" note below... which may indeed go away with the revisions to take the placer by value in Placer::make_place(self);)

pub trait Placer<Sized? Data, Owner, Interim: PlacementAgent<Data, Owner>> {
/// Allocates a place for the data to live, returning an
/// intermediate agent to negotiate ownership.
fn make_place(&mut self) -> Interim;
}

/// Some free functions called by expansions of `box <value_expr>` and
/// `box (<place>) <value_expr>`.
pub mod placer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way for us to mark these macro-artifacts as unstable? cc @aturon

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a moot point now that UFCS support has landed.

use kinds::Sized;
use ops::{Placer, PlacementAgent};

/// The first argument, `<place>` in `box (<place>) <value>`, must
/// implement `Placer`.
pub fn parenthesized_input_to_box_must_be_placer<'a, Sized? Data, Owner, A, P>(p: &'a mut P) -> &'a mut P
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...with UFCS, I suspect we don't need this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right; I had thought we would need e.g. impl Trait to assert that the input placer expression had a suitable type, but we can make the assertion via UFCS. The only question is whether the resulting error messages will be usable. We will see about that.

where A:PlacementAgent<Data, Owner>,
P:Placer<Data, Owner, A>+Sized {
p
}

/// Work-around for lack of UFCS: the `box (<place>) <value>`
/// syntax expands into calls to the three free functions below in
/// order to avoid requiring (or expanding into) declarations like
/// `use std::ops::Placer;` and `use std::ops::PlacementAgent;`

/// Calls `p.make_place()` as work-around for lack of UFCS.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have UFCS now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I imagine we can remove make_place

pub fn make_place<Sized? Data, Owner, A, P>(p: &mut P) -> A
where A:PlacementAgent<Data, Owner>,
P:Placer<Data, Owner, A> {
p.make_place()
}

/// Calls `a.pointer()` as work-around for lack of UFCS.
pub unsafe fn pointer<Sized? Data, Owner, A>(a: &A) -> *mut Data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also pointer

where A:PlacementAgent<Data, Owner> {
a.pointer()
}

/// Calls `a.finalize()` as work-around for lack of UFCS.
pub unsafe fn finalize<Sized? Data, Owner, A>(a: A) -> Owner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And finalize

where A:PlacementAgent<Data, Owner> {
a.finalize()
}
}

/// Helper trait for expansion of `box (P) V`.
///
/// We force all implementers to implement `Drop`, since in the majority of
/// cases, leaving out a user-defined drop is a bug for these values.
///
/// See also `Placer`.
pub trait PlacementAgent<Sized? Data, Owner> : Drop {
/// Returns a pointer to the offset in the place where the data lives.
unsafe fn pointer(&self) -> *mut Data;

/// Converts this intermediate agent into owning pointer for the data,
/// forgetting self in the process.
unsafe fn finalize(self) -> Owner;
}
5 changes: 2 additions & 3 deletions src/librustc/middle/cfg/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,15 +461,14 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
self.straightline(expr, pred, [r, l].iter().map(|&e| &**e))
}

ast::ExprBox(Some(ref l), ref r) |
ast::ExprIndex(ref l, ref r) |
ast::ExprBinary(_, ref l, ref r) => { // NB: && and || handled earlier
self.straightline(expr, pred, [l, r].iter().map(|&e| &**e))
}

ast::ExprBox(ref p, ref e) => {
self.straightline(expr, pred, [p, e].iter().map(|&e| &**e))
}

ast::ExprBox(None, ref e) |
ast::ExprAddrOf(_, ref e) |
ast::ExprCast(ref e, _) |
ast::ExprUnary(_, ref e) |
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> {
}

ast::ExprBox(ref place, ref base) => {
self.consume_expr(&**place);
place.as_ref().map(|e|self.consume_expr(&**e));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expr place is not consumed, actually, but rather &mut borrowed, no? Probably this doesn't matter because anything with a place that is not None winds up being macro-expanded away, in which case maybe we can just add a call to span_bug

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was just blindly doing a mechanical transformation here. (But now I think we've further revised the protocol so that make_place takes self by-value ... and of course yes, in principle we should never get here except with place = None, so I will just put the asserting span_bug in.)

self.consume_expr(&**base);
}

Expand Down
3 changes: 2 additions & 1 deletion src/librustc/middle/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {

ExprIndex(ref l, ref r) |
ExprBinary(_, ref l, ref r) |
ExprBox(ref l, ref r) => {
ExprBox(Some(ref l), ref r) => {
let r_succ = self.propagate_through_expr(&**r, succ);
self.propagate_through_expr(&**l, r_succ)
}
Expand All @@ -1197,6 +1197,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
self.propagate_through_expr(&**e1, succ)
}

ExprBox(None, ref e) |
ExprAddrOf(_, ref e) |
ExprCast(ref e, _) |
ExprUnary(_, ref e) |
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/trans/debuginfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3420,7 +3420,7 @@ fn populate_scope_map(cx: &CrateContext,
walk_expr(cx, &**sub_exp, scope_stack, scope_map),

ast::ExprBox(ref place, ref sub_expr) => {
walk_expr(cx, &**place, scope_stack, scope_map);
place.as_ref().map(|e|walk_expr(cx, &**e, scope_stack, scope_map));
walk_expr(cx, &**sub_expr, scope_stack, scope_map);
}

Expand Down
3 changes: 2 additions & 1 deletion src/librustc/middle/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3705,12 +3705,13 @@ pub fn expr_kind(tcx: &ctxt, expr: &ast::Expr) -> ExprKind {

ast::ExprLit(_) | // Note: LitStr is carved out above
ast::ExprUnary(..) |
ast::ExprBox(None, _) |
ast::ExprAddrOf(..) |
ast::ExprBinary(..) => {
RvalueDatumExpr
}

ast::ExprBox(ref place, _) => {
ast::ExprBox(Some(ref place), _) => {
// Special case `Box<T>` for now:
let definition = match tcx.def_map.borrow().find(&place.id) {
Some(&def) => def,
Expand Down
8 changes: 4 additions & 4 deletions src/librustc/middle/typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3723,15 +3723,15 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
let id = expr.id;
match expr.node {
ast::ExprBox(ref place, ref subexpr) => {
check_expr(fcx, &**place);
place.as_ref().map(|e|check_expr(fcx, &**e));
check_expr(fcx, &**subexpr);

let mut checked = false;
match place.node {
place.as_ref().map(|e| match e.node {
ast::ExprPath(ref path) => {
// FIXME(pcwalton): For now we hardcode the two permissible
// places: the exchange heap and the managed heap.
let definition = lookup_def(fcx, path.span, place.id);
let definition = lookup_def(fcx, path.span, e.id);
let def_id = definition.def_id();
let referent_ty = fcx.expr_ty(&**subexpr);
if tcx.lang_items.exchange_heap() == Some(def_id) {
Expand All @@ -3740,7 +3740,7 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
}
}
_ => {}
}
});

if !checked {
span_err!(tcx.sess, expr.span, E0066,
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ pub struct Expr {
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash, Show)]
pub enum Expr_ {
/// First expr is the place; second expr is the value.
ExprBox(P<Expr>, P<Expr>),
ExprBox(Option<P<Expr>>, P<Expr>),
ExprVec(Vec<P<Expr>>),
ExprCall(P<Expr>, Vec<P<Expr>>),
ExprMethodCall(SpannedIdent, Vec<P<Ty>>, Vec<P<Expr>>),
Expand Down
Loading