diff --git a/src/comp/middle/capture.rs b/src/comp/middle/capture.rs new file mode 100644 index 000000000000..0dd1956ff418 --- /dev/null +++ b/src/comp/middle/capture.rs @@ -0,0 +1,132 @@ +import syntax::{ast, ast_util}; +import std::map; + +export capture_mode; +export capture_var; +export capture_map; +export check_capture_clause; +export compute_capture_vars; +export cap_copy; +export cap_move; +export cap_drop; +export cap_ref; + +tag capture_mode { + cap_copy; //< Copy the value into the closure. + cap_move; //< Move the value into the closure. + cap_drop; //< Drop value after creating closure. + cap_ref; //< Reference directly from parent stack frame (block fn). +} + +type capture_var = { + def: ast::def, //< The variable being accessed free. + mode: capture_mode //< How is the variable being accessed. +}; + +type capture_map = map::hashmap; + +// checks the capture clause for a fn_expr() and issues warnings or +// errors for any irregularities which we identify. +fn check_capture_clause(tcx: ty::ctxt, + fn_expr_id: ast::node_id, + fn_proto: ast::proto, + cap_clause: ast::capture_clause) { + let freevars = freevars::get_freevars(tcx, fn_expr_id); + let seen_defs = map::new_int_hash(); + + let check_capture_item = lambda(&&cap_item: @ast::capture_item) { + let cap_def = tcx.def_map.get(cap_item.id); + if !vec::any(*freevars, {|fv| fv.def == cap_def}) { + tcx.sess.span_warn( + cap_item.span, + #fmt("Captured variable '%s' not used in closure", + cap_item.name)); + } + + let cap_def_id = ast_util::def_id_of_def(cap_def).node; + if !seen_defs.insert(cap_def_id, ()) { + tcx.sess.span_err( + cap_item.span, + #fmt("Variable '%s' captured more than once", + cap_item.name)); + } + }; + + let check_not_upvar = lambda(&&cap_item: @ast::capture_item) { + alt tcx.def_map.get(cap_item.id) { + ast::def_upvar(_, _, _) { + tcx.sess.span_err( + cap_item.span, + #fmt("Upvars (like '%s') cannot be moved into a closure", + cap_item.name)); + } + _ {} + } + }; + + let check_block_captures = lambda(v: [@ast::capture_item]) { + if check vec::is_not_empty(v) { + let cap_item0 = vec::head(v); + tcx.sess.span_err( + cap_item0.span, + "Cannot capture values explicitly with a block closure"); + } + }; + + alt fn_proto { + ast::proto_block. { + check_block_captures(cap_clause.copies); + check_block_captures(cap_clause.moves); + } + ast::proto_bare. | ast::proto_shared(_) | ast::proto_send. { + vec::iter(cap_clause.copies, check_capture_item); + vec::iter(cap_clause.moves, check_capture_item); + vec::iter(cap_clause.moves, check_not_upvar); + } + } +} + +fn compute_capture_vars(tcx: ty::ctxt, + fn_expr_id: ast::node_id, + fn_proto: ast::proto, + cap_clause: ast::capture_clause) -> [capture_var] { + let freevars = freevars::get_freevars(tcx, fn_expr_id); + let cap_map = map::new_int_hash(); + + vec::iter(cap_clause.copies) { |cap_item| + let cap_def = tcx.def_map.get(cap_item.id); + let cap_def_id = ast_util::def_id_of_def(cap_def).node; + if vec::any(*freevars, {|fv| fv.def == cap_def}) { + cap_map.insert(cap_def_id, { def:cap_def, mode:cap_copy }); + } + } + + vec::iter(cap_clause.moves) { |cap_item| + let cap_def = tcx.def_map.get(cap_item.id); + let cap_def_id = ast_util::def_id_of_def(cap_def).node; + if vec::any(*freevars, {|fv| fv.def == cap_def}) { + cap_map.insert(cap_def_id, { def:cap_def, mode:cap_move }); + } else { + cap_map.insert(cap_def_id, { def:cap_def, mode:cap_drop }); + } + } + + let implicit_mode = alt fn_proto { + ast::proto_block. { cap_ref } + ast::proto_bare. | ast::proto_shared(_) | ast::proto_send. { cap_copy } + }; + + vec::iter(*freevars) { |fvar| + let fvar_def_id = ast_util::def_id_of_def(fvar.def).node; + alt cap_map.find(fvar_def_id) { + option::some(_) { /* was explicitly named, do nothing */ } + option::none. { + cap_map.insert(fvar_def_id, {def:fvar.def, mode:implicit_mode}); + } + } + } + + let result = []; + cap_map.values { |cap_var| result += [cap_var]; } + ret result; +} diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs index bb68175987e9..aa72cb98ad3e 100644 --- a/src/comp/middle/trans.rs +++ b/src/comp/middle/trans.rs @@ -2566,7 +2566,11 @@ type generic_info = static_tis: [option::t<@tydesc_info>], tydescs: [ValueRef]}; -tag lval_kind { temporary; owned; owned_imm; } +tag lval_kind { + temporary; //< Temporary value passed by value if of immediate type + owned; //< Non-temporary value passed by pointer + owned_imm; //< Non-temporary value passed by value +} type local_var_result = {val: ValueRef, kind: lval_kind}; type lval_result = {bcx: @block_ctxt, val: ValueRef, kind: lval_kind}; tag callee_env { obj_env(ValueRef); null_env; is_closure; } @@ -3550,12 +3554,13 @@ fn trans_expr(bcx: @block_ctxt, e: @ast::expr, dest: dest) -> @block_ctxt { assert op != ast::deref; // lvals are handled above ret trans_unary(bcx, op, x, e.id, dest); } - // NDM captures ast::expr_fn(f, cap_clause) { - ret trans_closure::trans_expr_fn(bcx, f, e.span, e.id, dest); + ret trans_closure::trans_expr_fn( + bcx, f, e.span, e.id, *cap_clause, dest); } ast::expr_bind(f, args) { - ret trans_closure::trans_bind(bcx, f, args, e.id, dest); + ret trans_closure::trans_bind( + bcx, f, args, e.id, dest); } ast::expr_copy(a) { if !expr_is_lval(bcx, a) { diff --git a/src/comp/middle/trans_closure.rs b/src/comp/middle/trans_closure.rs index cc72ef5b3e36..c61c7a4ff930 100644 --- a/src/comp/middle/trans_closure.rs +++ b/src/comp/middle/trans_closure.rs @@ -43,8 +43,10 @@ import trans::{ // }; // }; // -// NB: this is defined in the code in T_closure_ptr and -// closure_ty_to_tuple_ty (below). +// NB: this struct is defined in the code in trans_common::T_closure() +// and mk_closure_ty() below. The former defines the LLVM version and +// the latter the Rust equivalent. It occurs to me that these could +// perhaps be unified, but currently they are not. // // Note that the closure carries a type descriptor that describes // itself. Trippy. This is needed because the precise types of the @@ -64,8 +66,17 @@ import trans::{ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tag environment_value { + // Evaluate expr and store result in env (used for bind). env_expr(@ast::expr); - env_direct(ValueRef, ty::t, bool); + + // Copy the value from this llvm ValueRef into the environment. + env_copy(ValueRef, ty::t, lval_kind); + + // Move the value from this llvm ValueRef into the environment. + env_move(ValueRef, ty::t, lval_kind); + + // Access by reference (used for blocks). + env_ref(ValueRef, ty::t, lval_kind); } // Given a closure ty, emits a corresponding tuple ty @@ -143,8 +154,10 @@ fn store_environment( let bound_tys = []; for bv in bound_values { bound_tys += [alt bv { - env_direct(_, t, _) { t } - env_expr(e) { ty::expr_ty(tcx, e) } + env_copy(_, t, _) { t } + env_move(_, t, _) { t } + env_ref(_, t, _) { t } + env_expr(e) { ty::expr_ty(tcx, e) } }]; } let bound_data_ty = ty::mk_tup(tcx, bound_tys); @@ -239,17 +252,29 @@ fn store_environment( add_clean_temp_mem(bcx, bound.val, bound_tys[i]); temp_cleanups += [bound.val]; } - env_direct(val, ty, is_mem) { - alt ck { - ty::closure_shared. | ty::closure_send. { - let val1 = is_mem ? load_if_immediate(bcx, val, ty) : val; - bcx = trans::copy_val(bcx, INIT, bound.val, val1, ty); - } - ty::closure_block. { - let addr = is_mem ? val : do_spill_noroot(bcx, val); - Store(bcx, addr, bound.val); - } - } + env_copy(val, ty, owned.) { + let val1 = load_if_immediate(bcx, val, ty); + bcx = trans::copy_val(bcx, INIT, bound.val, val1, ty); + } + env_copy(val, ty, owned_imm.) { + bcx = trans::copy_val(bcx, INIT, bound.val, val, ty); + } + env_copy(_, _, temporary.) { + fail "Cannot capture temporary upvar"; + } + env_move(val, ty, kind) { + let src = {bcx:bcx, val:val, kind:kind}; + bcx = move_val(bcx, INIT, bound.val, src, ty); + } + env_ref(val, ty, owned.) { + Store(bcx, val, bound.val); + } + env_ref(val, ty, owned_imm.) { + let addr = do_spill_noroot(bcx, val); + Store(bcx, addr, bound.val); + } + env_ref(_, _, temporary.) { + fail "Cannot capture temporary upvar"; } } } @@ -260,25 +285,38 @@ fn store_environment( // Given a context and a list of upvars, build a closure. This just // collects the upvars and packages them up for store_environment. -fn build_closure(cx: @block_ctxt, - upvars: freevar_info, +fn build_closure(bcx0: @block_ctxt, + cap_vars: [capture::capture_var], ck: ty::closure_kind) -> closure_result { // If we need to, package up the iterator body to call let env_vals = []; - let tcx = bcx_tcx(cx); - // Package up the upvars - vec::iter(*upvars) { |upvar| - let lv = trans_local_var(cx, upvar.def); - let nid = ast_util::def_id_of_def(upvar.def).node; + let bcx = bcx0; + let tcx = bcx_tcx(bcx); + + // Package up the captured upvars + vec::iter(cap_vars) { |cap_var| + let lv = trans_local_var(bcx, cap_var.def); + let nid = ast_util::def_id_of_def(cap_var.def).node; let ty = ty::node_id_to_monotype(tcx, nid); - alt ck { - ty::closure_block. { ty = ty::mk_mut_ptr(tcx, ty); } - ty::closure_send. | ty::closure_shared. {} + alt cap_var.mode { + capture::cap_ref. { + assert ck == ty::closure_block; + ty = ty::mk_mut_ptr(tcx, ty); + env_vals += [env_ref(lv.val, ty, lv.kind)]; + } + capture::cap_copy. { + env_vals += [env_copy(lv.val, ty, lv.kind)]; + } + capture::cap_move. { + env_vals += [env_move(lv.val, ty, lv.kind)]; + } + capture::cap_drop. { + bcx = drop_ty(bcx, lv.val, ty); + } } - env_vals += [env_direct(lv.val, ty, lv.kind == owned)]; } - ret store_environment(cx, copy cx.fcx.lltydescs, env_vals, ck); + ret store_environment(bcx, copy bcx.fcx.lltydescs, env_vals, ck); } // Given an enclosing block context, a new function context, a closure type, @@ -287,7 +325,7 @@ fn build_closure(cx: @block_ctxt, fn load_environment(enclosing_cx: @block_ctxt, fcx: @fn_ctxt, boxed_closure_ty: ty::t, - upvars: freevar_info, + cap_vars: [capture::capture_var], ck: ty::closure_kind) { let bcx = new_raw_block_ctxt(fcx, fcx.llloadenv); @@ -311,23 +349,34 @@ fn load_environment(enclosing_cx: @block_ctxt, // Populate the upvars from the environment. let path = [0, abi::box_rc_field_body, abi::closure_elt_bindings]; - vec::iteri(*upvars) { |i, upvar| - check type_is_tup_like(bcx, boxed_closure_ty); - let upvarptr = - GEP_tup_like(bcx, boxed_closure_ty, llclosure, path + [i as int]); - bcx = upvarptr.bcx; - let llupvarptr = upvarptr.val; - alt ck { - ty::closure_block. { llupvarptr = Load(bcx, llupvarptr); } - ty::closure_send. | ty::closure_shared. { } + let i = 0u; + vec::iter(cap_vars) { |cap_var| + alt cap_var.mode { + capture::cap_drop. { /* ignore */ } + _ { + check type_is_tup_like(bcx, boxed_closure_ty); + let upvarptr = GEP_tup_like( + bcx, boxed_closure_ty, llclosure, path + [i as int]); + bcx = upvarptr.bcx; + let llupvarptr = upvarptr.val; + alt ck { + ty::closure_block. { llupvarptr = Load(bcx, llupvarptr); } + ty::closure_send. | ty::closure_shared. { } + } + let def_id = ast_util::def_id_of_def(cap_var.def); + fcx.llupvars.insert(def_id.node, llupvarptr); + i += 1u; + } } - let def_id = ast_util::def_id_of_def(upvar.def); - fcx.llupvars.insert(def_id.node, llupvarptr); } } -fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span, - id: ast::node_id, dest: dest) -> @block_ctxt { +fn trans_expr_fn(bcx: @block_ctxt, + f: ast::_fn, + sp: span, + id: ast::node_id, + cap_clause: ast::capture_clause, + dest: dest) -> @block_ctxt { if dest == ignore { ret bcx; } let ccx = bcx_ccx(bcx), bcx = bcx; let fty = node_id_type(ccx, id); @@ -339,10 +388,11 @@ fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span, register_fn(ccx, sp, sub_cx.path, "anon fn", [], id); let trans_closure_env = lambda(ck: ty::closure_kind) -> ValueRef { - let upvars = get_freevars(ccx.tcx, id); - let {llbox, box_ty, bcx} = build_closure(bcx, upvars, ck); + let cap_vars = capture::compute_capture_vars( + ccx.tcx, id, f.proto, cap_clause); + let {llbox, box_ty, bcx} = build_closure(bcx, cap_vars, ck); trans_closure(sub_cx, sp, f, llfn, no_self, [], id, {|fcx| - load_environment(bcx, fcx, box_ty, upvars, ck); + load_environment(bcx, fcx, box_ty, cap_vars, ck); }); llbox }; @@ -420,7 +470,7 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, let sp = cx.sp; let llclosurety = T_ptr(type_of(ccx, sp, outgoing_fty)); let src_loc = PointerCast(bcx, cl, llclosurety); - ([env_direct(src_loc, pair_ty, true)], none) + ([env_copy(src_loc, pair_ty, owned)], none) } none. { ([], some(f_res.val)) } }; diff --git a/src/comp/middle/tstate/pre_post_conditions.rs b/src/comp/middle/tstate/pre_post_conditions.rs index 5cfdd0301a6d..862c7867feb2 100644 --- a/src/comp/middle/tstate/pre_post_conditions.rs +++ b/src/comp/middle/tstate/pre_post_conditions.rs @@ -347,6 +347,13 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) { handle_var_def(fcx, rslt, def.def, "upvar"); } + let use_cap_item = lambda(&&cap_item: @capture_item) { + let d = local_node_id_to_local_def_id(fcx, cap_item.id); + option::may(d, { |id| use_var(fcx, id) }); + }; + vec::iter(cap_clause.copies, use_cap_item); + vec::iter(cap_clause.moves, use_cap_item); + vec::iter(cap_clause.moves) { |cap_item| log ("forget_in_postcond: ", cap_item); forget_in_postcond(fcx, e.id, cap_item.id); diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs index 383a4387a5d9..dc3cee75c23b 100644 --- a/src/comp/middle/typeck.rs +++ b/src/comp/middle/typeck.rs @@ -1939,9 +1939,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier, if !arm_non_bot { result_ty = ty::mk_bot(tcx); } write::ty_only_fixup(fcx, id, result_ty); } - ast::expr_fn(f, captures) { // NDM captures - let cx = @{tcx: tcx}; - let fty = ty_of_fn_decl(cx.tcx, m_check_tyvar(fcx), f.decl, + ast::expr_fn(f, captures) { + let fty = ty_of_fn_decl(tcx, m_check_tyvar(fcx), f.decl, f.proto, [], none).ty; write::ty_only_fixup(fcx, id, fty); @@ -1956,6 +1955,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier, if f.proto == ast::proto_block { write::ty_only_fixup(fcx, id, expected); } + + capture::check_capture_clause(tcx, expr.id, f.proto, *captures); } ast::expr_block(b) { // If this is an unchecked block, turn off purity-checking diff --git a/src/comp/rustc.rc b/src/comp/rustc.rc index 9f88b369749a..0ddfb970ed17 100644 --- a/src/comp/rustc.rc +++ b/src/comp/rustc.rc @@ -38,6 +38,7 @@ mod middle { mod shape; mod gc; mod debuginfo; + mod capture; mod tstate { mod ck; diff --git a/src/comp/syntax/ast.rs b/src/comp/syntax/ast.rs index 608796696d6f..4f6b5cf3744f 100644 --- a/src/comp/syntax/ast.rs +++ b/src/comp/syntax/ast.rs @@ -261,6 +261,10 @@ tag expr_ { expr_mac(mac); } +// AST nodes that represent a capture clause, which is used to declare +// variables that are copied or moved explicitly into the closure. In some +// cases, local variables can also be copied implicitly into the closure if +// they are used in the closure body. type capture_item = { id: int, name: ident, // Currently, can only capture a local var. diff --git a/src/test/compile-fail/cap-clause-both-copy-and-move.rs b/src/test/compile-fail/cap-clause-both-copy-and-move.rs new file mode 100644 index 000000000000..0758ad67bd56 --- /dev/null +++ b/src/test/compile-fail/cap-clause-both-copy-and-move.rs @@ -0,0 +1,5 @@ +// error-pattern:error: Variable 'x' captured more than once +fn main() { + let x = 5; + let y = sendfn[move x; copy x]() -> int { x }; +} \ No newline at end of file diff --git a/src/test/compile-fail/cap-clause-double-copy.rs b/src/test/compile-fail/cap-clause-double-copy.rs new file mode 100644 index 000000000000..2e1008bad1c6 --- /dev/null +++ b/src/test/compile-fail/cap-clause-double-copy.rs @@ -0,0 +1,5 @@ +// error-pattern:error: Variable 'x' captured more than once +fn main() { + let x = 5; + let y = sendfn[copy x, x]() -> int { x }; +} diff --git a/src/test/compile-fail/cap-clause-double-move.rs b/src/test/compile-fail/cap-clause-double-move.rs new file mode 100644 index 000000000000..89e1a4a5a51e --- /dev/null +++ b/src/test/compile-fail/cap-clause-double-move.rs @@ -0,0 +1,5 @@ +// error-pattern: error: Variable 'x' captured more than once +fn main() { + let x = 5; + let y = sendfn[move x, x]() -> int { x }; +} diff --git a/src/test/compile-fail/cap-clause-move-upvar.rs b/src/test/compile-fail/cap-clause-move-upvar.rs new file mode 100644 index 000000000000..33b0a67a7e4d --- /dev/null +++ b/src/test/compile-fail/cap-clause-move-upvar.rs @@ -0,0 +1,8 @@ +// error-pattern: error: Upvars (like 'x') cannot be moved into a closure +fn main() { + let x = 5; + let _y = sendfn[move x]() -> int { + let _z = sendfn[move x]() -> int { x }; + 22 + }; +} diff --git a/src/test/compile-fail/cap-clause-use-after-move.rs b/src/test/compile-fail/cap-clause-use-after-move.rs new file mode 100644 index 000000000000..029b05ad2e10 --- /dev/null +++ b/src/test/compile-fail/cap-clause-use-after-move.rs @@ -0,0 +1,7 @@ +// error-pattern:Unsatisfied precondition constraint + +fn main() { + let x = 5; + let _y = sendfn[move x]() { }; + let _z = x; //< error: Unsatisfied precondition constraint +} diff --git a/src/test/run-pass/cap-clause-move.rs b/src/test/run-pass/cap-clause-move.rs new file mode 100644 index 000000000000..96b4b4d3aaa6 --- /dev/null +++ b/src/test/run-pass/cap-clause-move.rs @@ -0,0 +1,17 @@ +// error-pattern: warning: Captured variable 'y' not used in closure +fn main() { + let x = ~1; + let y = ptr::addr_of(*x) as uint; + + let lam_copy = lambda[copy x]() -> uint { ptr::addr_of(*x) as uint }; + let lam_move = lambda[move x]() -> uint { ptr::addr_of(*x) as uint }; + assert lam_copy() != y; + assert lam_move() == y; + + let x = ~2; + let y = ptr::addr_of(*x) as uint; + let snd_copy = sendfn[copy x]() -> uint { ptr::addr_of(*x) as uint }; + let snd_move = sendfn[move x]() -> uint { ptr::addr_of(*x) as uint }; + assert snd_copy() != y; + assert snd_move() == y; +} diff --git a/src/test/run-pass/cap-clause-not-used.rs b/src/test/run-pass/cap-clause-not-used.rs new file mode 100644 index 000000000000..0bb43b9c6f53 --- /dev/null +++ b/src/test/run-pass/cap-clause-not-used.rs @@ -0,0 +1,5 @@ +// error-pattern: warning: Captured variable 'y' not used in closure +fn main() { + let x = 5; + let _y = sendfn[copy x]() { }; +}